Array index as Variable (or, avoiding off-by-one errors)

Started by deadsuperhero, Sun 08/11/2020 21:54:12

Previous topic - Next topic

deadsuperhero

(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. 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:

Code: ags
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?
The fediverse needs great indie game developers! Find me there!

Khris

You can use an int variable as array index, yes. An enum will also work:

Code: ags
enum TaskIndex {
  eTaskBecomePirate, eTaskKillLeChuck
};


You can now do
Code: ags
  task[eTaskBecomePirate].complete = true;


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

But a function like that is pretty pointless.

deadsuperhero

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

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


Or something to that effect.
The fediverse needs great indie game developers! Find me there!

Khris

You can keep an array of ints in a parent instance that stores the indices of the child objects.

deadsuperhero

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?
The fediverse needs great indie game developers! Find me there!

Khris

Say you have:
Code: ags
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
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
void Parent::SetAllTasksComplete() {
  for (int i = 0; i < this.childrenCount; i++) {
    task[this.childrenIDs[i]].complete = true;
  }
}


SMF spam blocked by CleanTalk