My first big programming assignment was to make a game in assembly in two weeks. The challenges of making an assembly game are pretty daunting: you basically need to build everything from the ground up. You have access to memory, registers, and trap commands for simple graphics and audio functions.
The game has collision detection, vector graphics, music, sound effects, scoring, and physics-based movement. You can find the source on GitHub here.
One of the core issues I had to wrap my mind around was memory management. Before, I had been reliant on dynamically allocating memory, but in assembly there is no such thing. I allocated memory segments for every possible asteroid, using an array of flags to determine if they were visible or not. This project definitely showed me the power of parallel arrays and gave me ideas on how I should structure my code.
The project also exposed me directly to the importance of optimization. On our powerful machines, we take for granted the luxuries of a fast CPU. However, the EASy68k emulator is rather poor on performance - to the point where just displaying a few bitmap images at high framerates is impressive. I discovered that the bottleneck in performance came through trap codes - special instructions that allow the emulator to do things like draw pixels or play sounds. While they had no impact on the clock cycle counter, they would quickly eat up time every frame. I designed my game to minimize trap codes, and I’m able to draw dozens of asteroids on screen with minimal performance loss.
Run Length Encoding
Speaking of optimization, one of the requirements of the project was a big obstacle to getting my game to run smoothly. The requirement was to draw the game over a bitmap background. Typically, one would draw the background (a process which would take 2-3 seconds), and then draw the game sprites on top of the background, redrawing the background where needed. This is very slow and challenging given that the vector images are transparent. My goal was to be able to optimize the drawing so that I could redraw everything each frame. I achieved this through run length encoding (and careful selection of background image) - instead of drawing a pixel individually I would keep track of how many pixels of the same color appeared in a row. I was able to reduce the number of trap code calls from over 300,000 to less than 1,000.
An unexpected challenge for me was being able to rotate the player ship. On any other platform it’s a simple process: call the
cos() functions and apply the proper trigonometric formulas. However, there are no math functions in assembly, so I had to resort to my own functions. I ended up using a lookup table, where I had written a separate C program to output a binary file containing the values of sin and cos from 0 degrees to 359 degrees. My math functions just offset into those tables whenever they needs a value of sin or cos.
This project has been incredibly enlightening as I continue to practice game programming. Diving into low level code and making something interactive with it has proved to me that it’s possible to do a lot with limited resources. I’m looking forward to working more in C and C++ in the future.