The video code was based on Bruce Land's TV generation code. The Timer1 interrupt is used to read a line of the 128x100 pixel video buffer and write the it to the TV screen. Since the sync pulse timing is critical, the main program loop is stalled by an idle sleep command which makes the entry into the Timer1 interrupt service routine (ISR) uniform. It is critical that the code in the main program loop finishes executing before the ISR is triggered.
One of the most challenging parts of the software was staying within the limits of the hardware when it came to graphics. Our finished game has up to twenty-five enemy ships, the player, and other status indicators that are updated as necessary. In order to draw everything to screen each frame, we had to write fast graphics routines. The ships and text are drawn as 3x5 pixel bitmaps.
We could not use the original 3x5 character drawing routine (video_smallchar) that Bruce Land wrote for the TV generation code for most of the game, since it requires that characters be aligned to every fourth pixel. The screen buffer is stored as a vector of 1600 bytes, dedicating 1 bit to each pixel (1 for white, 0 for black). First we tried writing a routine similar to the one that prints unaligned 5x7 characters. We were barely able to draw 10 ships before we saw corruption since it made too many function calls, which introduced additional delays. We decided to write a new routine from scratch. Since unaligned characters may cross byte boundaries, it was necessary to read two bytes from the screen, mask the appropriate bits, and then write the new data back. The resulting routine (video_smallchar_ua) is fast enough to handle the graphics updates.
To erase areas of the screen, at first we tried drawing blank characters over the areas to be updated. This caused some corruption of the video signal since it practically doubled the number of calls to video_smallchar_ua and the instructions did not finish executing before the Timer1 ISR was triggered. We instead tried using a minimalist approach by drawing black vertical lines. The direct screen buffer manipulation of video_vertline_black was fast enough to erase parts of the screen to do updates and it allowed us to add more graphics.
The awesome logo on the menu screen was meticulously hand-coded in hex from a drawing and stored in flash memory. The draw_logo routine reads the raster and quickly writes it to the proper location in the screen buffer. erase_logo blanks the same area on screen by writing directly to the vector.
The game controllers on PORTB and PORTC are polled every execution of the main program loop (i.e., once per frame). A simple release-debounce state machine reads the input from each button on the controllers and provides button release signals to the rest of the code. The behavior of the individual buttons depends on the game state.
Controller 1 is designated as the main controller. The up/down buttons on the directional pad manipulate the menu options. The B button displays the high scores list. To select the game mode and begin, the C button is used. In the single player game, left/right move the player's ship, B fires, and C pauses/unpauses the game. In pause mode, holding B while pressing and releasing C causes the game to do a soft reset. At the high scores entry screen, up and down change the character and left and right switch between the three initials. Controller 2 is active in versus mode. Left/right move the ship and B fires.
The bulk of the game code is contained in the main program loop. The game state machine, button debounce, and animations are all handled here. A picture of our gameflow follows.
The game starts off by displaying the menu and allowing the player to choose
a game mode. The state machine goes to the appropriate initialization state as
appropriate. Each frame, the single player game state performs many tasks:
-enemy ships are scrolled
-the player's input is read
-the player's ship is moved
-enemy bullets' positions are updated/created
-the player's bullet is updated/created
-an enemy ship is selected to break off at random
-collisions with bullets are checked
-the score is updated
-lives are updated
-invulnerability status is updated
The versus mode reads input and updates ships, bullets, and lives for both players.
When the player's ship is destroyed, an explosion animation is played while the game continues. The new ship slides into position and then invulnerability is granted. The animation is handled by using a timer to count frames and update the sequence accordingly. An animation for the game over screen is handled similarly.
The five highest scores and associated initials are stored in EEPROM so the values remain even after the power is turned off. When the player dies, the score is checked against each of the scores in the list, starting from the highest. If the new score exceeds the stored one, the other scores are shifted down the list and the player is given an opportunity to enter initials.
One of the challenging parts of the code was getting the ship that breaks off from the formation in single player mode to not wipe out the graphics of ships it flies over. We were able to solve this problem by being careful about the order in which we drew the array of ships and the kamikaze ship. Another tricky part of the code was getting the collision of enemy bullets with the player accurate down to the last pixel. We tried using video_set to check the status of a pixel but this did not work well with our code. We instead defined bounding boxes for each ship and check if bullets occupy the space in the box, which counts as a collision. This proved to be faster and more reliable than using video_set. Finally, the sheer mass of the code made it difficult to keep track of everything. The source file is over 3000 lines long. We did not run into problems with using too mush of program memory but did come close (~92% of flash memory used).