Moving the invaders as one
Something that @Krangogram has alluded to in their answer is that the individual invaders don't actually move by themselves, so one approach to efficiently move the invaders is to treat them as a single invader formation and the move the formation as a whole. You could define this similar to:
class Invader {
// x,y offset within the formation. Each invader should have a unique offset from
// (0,0), the top left, to (FORMATION_WIDTH-1,FORMATION_HEIGHT-1), the bottom right.
int offsetX, offsetY;
// Whether this invader is alive.
bool isAlive;
};
class InvaderFormation {
// The invaders within the formation, includes dead invaders
std::vector<Invader *> invaders;
// The location of the top left invader in the formation.
int x, y;
};
Since every invader's location is defined as an offset from the formation as a whole, you can move every invader at the same time with invaderFormation->x += DELTA or invaderFormation->y += DELTA.
Checking for the edge
As for how to determine whether the invaders can continue moving in a direction or if they've reached the edge, with 50 invaders you're probably safe to enumerate them all without running into any performance issues. But maybe in the future the screen will be bigger/scroll and there'll be a million invaders, so let's look how you can optimize this check.
The first thing to notice about this problem is that the only invaders you actually care about are the ones on the sides of the formation. The ones in the middle will never touch an edge as long as there's an invader on the side of the formation. But wait, you don't even need to figure out a way to track and enumerate those on the sides, because another thing to notice about this problem is that it doesn't matter which invader is on the side, all that matters is there's at least one.
So you could define your invader formation closer to something like this:
class Invader {
int offsetY;
bool isAlive;
// The column of the formation this invader is in. The invader's offset within the
// formation can now be defined as (column.offsetX, offsetY). It's fine to keep offsetX
// in this class too though.
InvaderFormationColumn* column;
};
class InvaderFormationColumn {
// offsetX is also in the index into InvaderFormation::columns
int offsetX;
// How many invaders are still alive in this column
int aliveCount;
};
class InvaderFormation {
// All of the columns...
std::vector<InvaderFormationColumn *> columns;
// ...And more specifically, shortcuts to the leftmost and rightmost columns
// that have at least 1 living invader
InvaderFormationColumn* leftmostColumn;
InvaderFormationColumn* rightmostColumn;
int x, y;
};
Now you can know if the invader formation has reached the edge with a single check:
invaderFormation->x + invaderFormation->leftmostColumn->offsetX == LEFT_EDGE or
invaderFormation->x + invaderFormation->rightmostColumn->offsetX == RIGHT_EDGE.
But you'll need to update these leftmostColumn and rightmostColumn pointers whenever an invader dies in order to keep them up-to-date:
// When an invader is killed:
invader->isAlive = false;
invader->column->aliveCount--;
// Assuming there is still at least 1 living invader in the formation:
if (invader->column->aliveCount == 0) {
// All invaders are dead in this column, so check if the formation needs to point to
// a new leftmost/rightmost column
if (invader->column->offsetX == invaderFormation->leftmostColumn->offsetX) {
// Look for a new leftmost column, starting from the current one
// You can stop at the rightmost column because you know there's no invaders still
// alive in any columns past that.
for (int i = invaderFormation->leftmostColumn->offsetX; i <= invaderFormation->rightmostColumn->offsetX; i++) {
InvaderFormationColumn* column = invaderFormation->columns[i];
if (column->aliveCount > 0) {
invaderFormation->leftmostColumn = column;
break;
}
}
}
// And similarly for the rightmost column moving towards the leftmost column
}