Adventure Game Studio

AGS Support => Advanced Technical Forum => Topic started by: deadsuperhero on Sun 08/11/2020 21:54:12

Title: Array index as Variable (or, avoiding off-by-one errors)
Post by: deadsuperhero on Sun 08/11/2020 21:54:12
(forgive me, I didn't quite know what to call this, as far as descriptive thread titles go)

Hey all,

I've been scratching my head at some of the challenges in building a quest system (https://www.adventuregamestudio.co.uk/forums/index.php?topic=58265.0). Before I dive back into that monstrosity, I wanted to ask a simpler question that would clean up a lot of my code.

TL;DR - Structs and Arrays are fun and very, very powerful. However, creating a new instance of a struct object that lives inside of an array forces me to do somewhat gross stuff like this:

task[1].complete

Ostensibly, it's fine as-is. If I wanted to manually make calls like that every time a task gets marked as Completed, it would work. But it's ugly, kind of goes against the concept of "Don't Repeat Yourself", and makes it difficult to reference in generic functions. It can also potentially cause off-by-one errors if, say, I somehow create tasks in a non-linear order./

So, let's say every task object has a unique identifier, like an ID. Maybe this task has an ID of 4, but it was created with an array index of [3].
How might I go about referencing this without causing an off-by-one error? Is it possible to refer to a task by a related variable, rather than the index itself? Otherwise, is there some magic index variable I can use like task[task.array_number].complete?
Title: Re: Array index as Variable (or, avoiding off-by-one errors)
Post by: Khris on Sun 08/11/2020 22:59:41
You can use an int variable as array index, yes. An enum will also work:

Code (ags) Select
enum TaskIndex {
  eTaskBecomePirate, eTaskKillLeChuck
};


You can now do
Code (ags) Select
  task[eTaskBecomePirate].complete = true;

The question is, how would generic functions look like? A simple example would be
void MarkTaskComplete(TaskIndex index) {
  task[index].complete = true;
}

But a function like that is pretty pointless.
Title: Re: Array index as Variable (or, avoiding off-by-one errors)
Post by: deadsuperhero on Sun 08/11/2020 23:05:22
Hmm, that's pretty interesting. I didn't really know about enum!

I guess I've been trying to just figure out how to keep track of child objects belonging to a parent object, and manipulate their state in an external function. It seemed like setting the state of the child objects based on the altered state of the parent would be a solution to another problem I was having, and that doing some kind of generic function could serve as a catch-all.

In other words (pseudocode incoming)

Code (ags) Select

if (parent_object.state = completed) {
for each child_object {
  child_object.state = completed;
  }
}


Or something to that effect.
Title: Re: Array index as Variable (or, avoiding off-by-one errors)
Post by: Khris on Sun 08/11/2020 23:16:29
You can keep an array of ints in a parent instance that stores the indices of the child objects.
Title: Re: Array index as Variable (or, avoiding off-by-one errors)
Post by: deadsuperhero on Sun 08/11/2020 23:18:37
Quote from: Khris on Sun 08/11/2020 23:16:29
You can keep an array of ints in a parent instance that stores the indices of the child objects.

Yeah, I had that thought too the other day, but kind of drew a blank on how to do that.
Seems like basically I'd be creating an entirely new array and then somehow assigning the whole thing to a Struct?
Title: Re: Array index as Variable (or, avoiding off-by-one errors)
Post by: Khris on Mon 09/11/2020 00:15:14
Say you have:
Code (ags) Select
struct Parent {
  int ID;
  int childrenIDs[10];
  int childrenCount;
};


Using an array of fixed size to keep track of instances is best done by keeping a count variable (initialized to 0).
When you need a "new" instance, you use the count as index, then simply increase it by one.

You can now write a function that adds a task:
Code (ags) Select
void Parent::AddTask(String description) {
  task[task_count].description = description;
  task[task_count].parentID = this.ID;
  // add task ID to parent's children array
  this.childrenIDs[this.childrenCount] = task_count;
  this.childrenCount++;
  task_count++;
}


To iterate over a parent's tasks:
Code (ags) Select
void Parent::SetAllTasksComplete() {
  for (int i = 0; i < this.childrenCount; i++) {
    task[this.childrenIDs[i]].complete = true;
  }
}