objects should be a pointer array. You were copying the data into the array, so the original objects were untouched.
Here is the refactored code. It is annotated:
#define bool int
#define false 0
#define true 1
typedef struct {
int x;
float y;
float speed;
bool isSwordActive;
bool facingRight;
} humanoid;
void
game()
{
humanoid player = { 12, 12, 0, false, false };
humanoid zombie = { 226, 12, 0, false, false };
// ORIGINAL
#if 0
humanoid objects[1] = { zombie };
// FIXED
#else
humanoid *objects[2] = { &zombie, &player };
#endif
// ORIGINAL
#if 0
while (1) {
// This works
zombie.y += zombie.speed;
zombie.speed += 0.125;
// This does not work
for (int i = 0; i < sizeof(objects) / sizeof(objects[0]); i++) {
objects[i].y += objects[i].speed;
objects[i].speed += 0.125;
}
}
#endif
// BETTER
#if 0
while (1) {
// This works
zombie.y += zombie.speed;
zombie.speed += 0.125;
// This does not work
for (int i = 0; i < sizeof(objects) / sizeof(objects[0]); i++) {
objects[i]->y += objects[i]->speed;
objects[i]->speed += 0.125;
}
}
#endif
// BEST
#if 1
while (1) {
// This works
zombie.y += zombie.speed;
zombie.speed += 0.125;
// This does not work
for (int i = 0; i < sizeof(objects) / sizeof(objects[0]); i++) {
humanoid *obj = objects[i];
obj->y += obj->speed;
obj->speed += 0.125;
}
}
#endif
}
In the above code, I've used cpp conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
This works, thank you! Pointers are always screwing me over. If you don't mind, could you explain why the best solution is better than the better solution? Is it performance-wise? Wouldn't performance slow down a bit if there are too many objects and making new variables for each object? Thank you! –
JuanR4140
We're not creating/copying the contents of the array element (e.g. 20 bytes). We're just calculating the address of the array element. This must be done anyway, so no extra code.
This is a fundamental difference and I think this is still a point of confusion for you.
If we compile without optimization, the "BETTER" solution would reevaluate objects[i] three times. Before it can "use" object[i], the value there must be fetched and placed into a CPU register. That is, it would do:
regA = &objects[0];
regA += i * sizeof(humanoid);
regA = *regA;
regA->something
Again, this is repeated 3 times:
regA = &objects[0];
regA += i * sizeof(humanoid);
regA = *regA;
regA->something
regA = &objects[0];
regA += i * sizeof(humanoid);
regA = *regA;
regA->something
regA = &objects[0];
regA += i * sizeof(humanoid);
regA = *regA;
regA->something
If we compile with optimization, the generated code would be similar to the "BEST" solution. That is, the compiler realizes that objects[i] is invariant within the given loop iteration, so it only needs to calculate it once:
regA = &objects[0];
regA += i * sizeof(humanoid);
regA = *regA;
regA->something
regA->something
regA->something
Note that the compiler must always put the calculated address value into a register before it can use it. The compiler will assume that obj should be in a register. So, no extra code generated.
In the "BEST" version, I "told" the compiler how to generate the efficient code. And, it would generate the efficient code without optimization.
And, doing obj->something in several different places is simpler to read [by "humanoid" programmers ;-)] than objects[i]->something in multiple places.
This would show its value even more with a 2D array.