For my final project, I created a clone of the popular game 2048 in C using Raylib. Raylib is a graphics library that provides functionality for rendering and input. I was initially going to make a 2d platformer shooter, but I quickly realized that it was beyond the scope of what I could accomplish with CS50's final project. So, I instead chose to implement a game that already exists in C as opposed to Javascript because it both incorporated the learnings from the first 5 weeks of CS50 as well as posed some interesting programming challenges of its own.
The game is fairly simple. You use the arrow keys to shift the number tiles in a given direction. When 2 tiles with the same number collide, they merge into a single tile with double their original number. Each time you merge tiles, your score increases. When no moves can be made, the game ends.
The entirety of the game's logic is contained within a single "main.c" file. For a game as simple as 2048, I felt that splitting the code up into multiple files would only serve to add unnecessary complexity. Raylib's library file is contained in the "lib" folder, and its header files in the "include" folder. The "res" folder contains the game's icon as well as the font the game uses. I created a "make.bat" file to compile the game using the MSVC compiler, a "run.bat" file to run the game from the command line, and an "export.bat" file to generate a folder for exporting the game.
The most essential component of 2048 is shifting the tiles. Every time an arrow key is pressed, each tile needs to move as much as it possibly can without overlapping or passing tiles that it shouldn't. Getting this mechanic right was the hardest part of making the game. As per usual in programming, the edge cases caused the bulk of the bugs. Properly accounting for the tiles ahead moving or merging proved difficult, but I eventually came up with a solution. For each tile, the tiles ahead in a given direction would be iterated over, keeping track of the next nonzero tile and the number of tiles ahead that will merge. Throughout this process, the number of squares that a tile would move and whether or not it would merge was calculated. This information was vital for animating the movement of the tiles.
Animation was another tricky mechanic to implement. I had initially stored the board as a 2d array of ints, but I later decided to store it as a 2d array of structs containg the number as well as a pointer to an animation struct which itself contained the movement in the x and y directions and an interpolation value. I stored the animations as pointers because I wanted them to be nullable since the tiles didn't always have an animation.
Though it was slightly cumbersome, I wrote a different set of instructions for each arrow key input. I did this primarily because a significant amount of the code behaved differently depending on the direction that coulnd't be easily parameterized. This was mostly because the tiles could move in two directions, but only one at a time.
One component of the game that was nontrivial to implement in C was UI. I had to create my own callback system using function pointers. Though I only ended up with two buttons, they both needed to call a function that reset the board state, so they needed a pointer to each state variable. This prompted me to package the board's state into a single struct.
Aside from the actual code, I had a lot difficulty with compiling my project. Perhaps the biggest pain-point of C and vestige of its age is how nonstandardized and nontrivial it is to create a build system for a project, especially if the project uses external libraries. I initially used GCC, which required a ".a" file for Raylib and a bunch of compiler flags. When I ended up wanting to use the RemedyBG debugger, I ran into issues because it required a ".pdb" file, which GCC apparently couldn't generate. I had to switch to using MSVC, which required a ".lib" file instead of a ".a" file and had its own set of compiler flags that I had to figure out.