Before jumping straight into coding the game, we first discussed how the game works, how a match unfolds, and our ambitions for the project.
These simple diagrams help us get a clear understanding of what we are dealing with.
For the model, we need a grid, which will serve as the game board. This board will be represented using integers: `0` for an empty cell and a sufficiently large number for default wall obstacles (here, `100`).
These values are defined in an enum in `model.h`. Players will be represented by positive values, while their light walls will be represented by their negative counterparts (-1 for Player 1’s wall, 1 for Player 1’s bike, -2 for Player 2’s walls, etc.).
In the model, we also need to store the positions of the players to avoid scanning the grid at every loop iteration to find them.
We also need to keep track of the number of players (why limit ourselves to just two?), their alive/dead status, different scores, and the number of players still in the game, to avoid iterating through the alive/dead state array each time.
Once all of this is listed and gathered, we must initialize the model, destroy it, move a player (i.e., check if they can move or if they collide), check if the game is over, and determine the winner, if any (or handle ties).
We need a game grid, with two options: a default grid or a user-selected grid.
Thus, we allow users to create their own maps with custom obstacles.
We also need a way to load these maps and upscale them if necessary, in case the provided map is too small.
We read the map file, and if anything goes wrong, we attempt to generate the default grid. If the grid is too small, we upscale it.
### # # ###
Yes, this is actually a map.
We provide four example maps in the `./maps/` directory.
Since the number of players is variable (more than two, even infinite!!!), we cannot manually position them.
We need a way to position them fairly and equitably.
To achieve this, we draw a circle at the center of the grid with a small margin to avoid being too close to the walls. We then distribute the players around this circle using trigonometric functions, dividing the circle by the number of players.
However, another issue arises: what if the chosen initial position is a wall in the map (a user could be tricky)?
To solve this, we perform a breadth-first search (BFS) to find the closest free cell. This requires additional data structures:
We also need a list of directions for the players. At initialization, before user input, the bikes will move toward the center (relative to the players' positions).
Here is a more complete diagram of a game loop:
According to the specifications, we want to switch between ncurses and SDL interfaces at launch—why not both simultaneously?
Thus, we need a generic way to represent our views.
For this generic view, we need the ability to update the screen, track changes, capture user directions, initialize and destroy resources, display the winner, and show menus and actions.
The view must also store the grid size.
We simply create a structure with function pointers for the necessary operations and the relevant data to be accessed by the controller.
For SDL, a window and a renderer are initialized to draw the game interface.
The game grid is represented using filled rectangles, each with a unique color corresponding to game elements. These rectangles act as enlarged "pixels" to depict the game elements.
Player movements are captured through SDL_Event, translating user interactions into game actions.
The Ncurses interface is text-based, using special characters to represent the game board and players.
This method is simple but highly effective for terminal-based play.
Each screen update only modifies changed cells to optimize performance.
We wrote the game loop as described in the diagram, and it worked immediately.
We added a fixed duration for each loop iteration to prevent games from ending in a nanosecond.
Menu navigation was implemented with a list of possible actions.
We wanted to allow interface selection via command-line options:
The controller initializes with the appropriate view(s).
We implemented various AI behaviors for the game.
More details : HERE
We added a multiplayer network mode.
A server structure manages sockets, connection limits, and connected clients.
Clients communicate by sending and receiving structured data packets.
We used Figma for diagrams, Google Docs for notes, and GitHub for version control.
We encountered minor solvable issues, such as memory leaks in SDL menu buttons.
Starting early allowed us to implement all our ideas and address challenges effectively.