4. Software Design

 

The software design is the main component of this project.  Two separate software codes, one for the PS/2 mouse and one for video generation, were used. 

 

4.1 Mouse Software

 

For the mouse microcontroller, we used code developed by Chee Ming Chaw and Elaine Siu for a Minesweeper Program for 476 in 2003.  This supplied us with a basic way to interface with the mouse and get the following three bytes of information:

 

Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

Byte 1

Y overflow

X overflow

Y sign bit

X sign bit

Always 1

Middle Btn

Right Btn

Left Btn

Byte 2

X Movement

Byte 3

Y Movement

 

The useful pieces of information are the x movement, y movement, x sign, y sign, and left button.  We mask the left button bit form the first byte and output it on PORTD.2, which is connected to PORTD.2 on the video micro.  The x movement that we get in byte 2 is actually the amount of change that has occurred since we last polled the mouse.  It is an unsigned value, with the sign coming from bit 4 of byte 1.  The same goes for the y movement from byte 3.  The only difference for the y movement is that we have to invert it because our video screen has line 1 at the top.  We then restrict the movement of the mouse such that it cannot go outside of the game area, and cannot go as far down as the cities (we don't want to friendly fire!)  We add the movement of the mouse to the current position of the mouse, and output the x and y coordinates on PORTB and PORTC, respectively.  These ports are connected to the same ports on the other MCU. 

 

The resolution from the mouse is four pixels per millimeter, which may seem to be a bit overly sensitive for precise applications, is great for our program, because a premium is placed not on accuracy (the explosions are 5 pixels large), but on speed.  We sample the mouse every 200 line times (~78.5 Hz).

 

4.2 Initial Video Design

 

The initial software template that was used for the video generation code was that of ECE 476 Laboratory 4: Lunar Lander.  This code contained small and large alphanumeric character bitmaps that were utilized as well as tasks that sent the bitmaps to the television.  Some modifications were made to these existing tasks to incorporate the bitmaps that we designed for the cities and explosions.  The function video_puts() was modified to generate video signals from bitmaps stored directly in flash rather than ROM.  This modified function is referred to as video_putsf().

 

The video generation software is divided into segments that generate the initial menu, clear the screen, update the cursor, as well as generate cities, missiles, and interceptors.  It is not possible to do many video intensive updates in a single frame which means that some tasks have to be further broken-down and divided executed over multiple frames. 

 

An initialize routine is called in main() to set-up the MCU for NTSC video generation as well as to set the initial values of some of the game parameters.  Subsequently, the main() task enters an infinite loop where the processor goes into ‘sleep’ mode until the raster on the television reaches line 231.  At this point the MCU starts executing the video generation code.  It begins by erasing the mouse cursor on the previous frame and drawing the new cursor based on the output of mouse MCU.  Also, the bounds for the mouse position are checked to ensure that the cursor is not drawn outside of our active window. 

 

Following the updated cursor, a switch-case statement is used to create a state-machine structure that executes one of the following states:  PRNT_INTRO, GET_INTRO, PRNT_DIF, GET_DIF, GAME_INIT, RUN_GAME.

 

PRNT_INTRO: This state sends the game menu to the television.  Additionally it sets the city status (cstatus) and interceptor status (istatus) arrays to their initial values indicating that all cities are alive and all interceptors are dead.  The game state variable, gamestate, is updated to GET_INTRO.

 

GET_INTRO: This state is repeatedly executed until the user makes a valid right mouse click.

 

PRNT_DIF:  After the player’s mouse click, this state is executed and displays the difficult menu.  The three difficulty levels are North Korea, Pakistan, and USSR.

 

GET_DIF:  Similar to GET_INTRO, this state waits for a mouse click and checks the location of the cursor to determine which difficulty level was chosen.

 

GAME_INIT:  This state clears the screen by clearing three screen lines in each iteration until all of the active screen has been cleared.

 

RUN_GAME:  This is the most complicated state because all of the actual game video generation occurs in this state.  Within RUN_GAME, another state machine determines the state of the game in progress.  This state machine is necessary to subdivide the tasks of generating images for the city, interceptor, and missile.  The following sections explain each function in detail.  See Appendix for images of game in play.

 

PRNT_GAMEOVER: The end of the game is detected within RUN_GAME when all of the city status bits, cstatus[], have been set to zero.  The gamestate is updated to PRNT_GAMEOVER which checks for a change in the high score, sends the message “BOOM” to the screen and calls GET_GAMEOVER.

 

GET_GAMEOVER: This state generates a large mushroom cloud that occupies most of the active screen.  This image remains until the user clicks the left mouse button which prompts the gamestate to change to RESTART.

 

 

RESTART:  Similar to GAME_INIT.  The state clears the active screen and returns to the main menu.

 

PAUSE:  Upon a right-mouse click during game play, the gamestate is updated to PAUSE.  This state freezes the current screen and allows the user to return to the main menu by clicking on the “E” that appears in the upper left corner of the screen.

 

Game Menu and Paused Game

 

4.3 City Generation

 

The city generation scheme draws the five cities at the bottom of the screen according to the status, cstatus[], of each city.  The variable cstatus[] is an array of size citycount which is set to five cities.  The array is indexed by a city index, c_index.  No more than once city is drawn in any own frame which means that the c_index is updated in each frame to keep track of which city should be drawn in the next frame.  If there is a change to the status of a particular city in the execution of the frame due to a missile impact, c_index is set so that the city will be updated immediately in the next frame.  The set of possible city states includes healthy (2), wounded (1), and dead (0).  These states correspond to different images in the city bitmap array that is stored in flash.  A special function, video_putcity, is used to send the city bitmap to the television based on the desired x-coordinate of the left corner of the bitmap image.  An array of x-coordinates for the cities is stored in the variable cx[] which is indexed using c_index.  The y-coordinate of each city is the same and corresponds to the bottom of the active screen.

 

4.4 Interceptor Generation

 

Similar to the city generation scheme, the interceptor generation scheme uses an array to track the status of interceptors, an interceptor index to map into the array and a counter to keep track of the number of interceptors fired.  The interceptor status array, istatus[], is  of size max_intercept which is set to five.  The status array is initialized to zero for each index value.  When the game begins, the interceptor state is called periodically to check for a mouse click indicating the player's desire to launch an interceptor missile.  If the total launched interceptor count, icount, is less than max_intercepts, then an interceptor is generated.  All interceptors begin at the same location at the bottom middle of the active screen.   

 

For the purposes of calculating interceptor trajectories, we have separated the active part of the display screen into a grid.  The active screen is 128x78 pixels in size, which has been separated into 42x26 target boxes with each box occupying a 3x3 pixel area.

 

The interceptor generation scheme determines which box the target is located in and automatically resets the target location as the center of that box.  This targeting approximation is acceptable because the explosion size exceeds the size of the target box, therefore destroying all missiles within range.  Once an interceptor is generated, the corresponding istatus[] bit is set to 7 which indicates the missile is alive.  The states 5-6 indicated partial detonation and the states 1-4 indicate full detonation.  These states are necessary to display the evolution of the explosion over multiple frames.  Finally, state zero indicates a dead interceptor.

 

If no mouse click is asserted during the execution of the interceptor task, no new interceptors are generated.  However, one existing interceptor is always updated based on the present i_index during the execution of the interceptor task.  The (x,y) coordinate of each interceptor is tracked in the variables ixp[] and iyp[] which are indexed by i_index.  The position is updated by adding the appropriate sine or cosine value from flash to these variables.  The cosine and sine tables are indexed based on the number of the x-box and y-box representing the target box.

 

4.5 Missile Generation

 

In designing the missile generation scheme, we first agreed on certain parameters.  We decided that the missile launch location and the launch angle would be random. Additionally, the y-velocity would be constant according to difficulty level, with only variations in x-velocity determining the incoming angle.  Finally, the number of missiles on the screen is determined only by the initial level selection made.  We have a time2gnd[] array that tracks the time remaining until each of the generated missiles reaches the bottom of the screen.  Regardless of whether the missile has been destroyed in transit, this timer is updated until the maximum value is reached at which point a replacement missile can be generated.  However, when the missile is destroyed, another counter also begins and continues updating until it has reached ~6.7sec.  If this counter reaches the maximum value before the time2gnd[] counter has completed, a replacement missile is generated.   This system of multiple missile counters is necessary to minimize penalizing the player for shooting the missile down while maintaining the pace of the game.

 

We accomplished all of these things in our program by making extensive use of the Mega32's flash memory. The program determines the angle at which the missile will be shot from by taking the current number in fastcount, a variable that can be any of the values 0-19 and is incremented once every line of video generation.  This number is divided by 2, which results in temp, a number from 0-9.  The missile is shot at an angle of 45-10*x.  To determine the start position, we use an array in flash with the minimum start location that would result in the missile landing on the screen for a missile with the given angle, as well as an array with the distance above that minimum that the missile could launch from and still hit the ground on screen.  The remainder of division of a second random number between 0 and 249 by the value in the second array is added to the value in the first array, which gives us a semi-random location for the missile that still guarantees that the missile will not fly off screen.

 

In order to continue to draw the missiles we made another two flash arrays that are indexed by the difficulty level that the user has selected.  One is the y increment, which depends only on the level, while the other is the x increment, which depends on both the level and angle.  By pre-calculating all of these values we saved a great deal of processing time and RAM.  Each time we redraw a missile (once per 8 frames) an appropriate increment is added to both the x and y position of the missile.  Using fixed point addition we can change the position by as little as 1/256th of a pixel.

 

The missile's status can change before it hits the ground because of three different conditions: It hits a city, it hits a missile (in flight or exploding), or it hits another missile as it is exploding.  In either of the later two cases the missile will explode and regenerate after 50 more cycles. The city collision detection is done by checking the status of the pixel that the missile is moving into and checking if the missile is within the 8x16 box of any of the cities.  In order to check for a collision with an interceptor, the program checks if there is an interceptor within a certain number of x and y coordinates of the missile.  This number is determined by the state of the interceptor (flying, partially exploded, or fully exploded).  The number is slightly larger than the drawn object to take into account shock waves created by the explosion, and to make the game actually playable.  The same detection is done for all other missiles, except we only change the status of the current missile if the other missile is exploding so missiles can't blow each other up by simply running into one another.  If a missile hits an interceptor the score is increased by 10 times a numerical representation of the difficulty level (it's difficult to multiply anything by a dead Soviet state).  If a missile is destroyed by another missile, the score is increased by twice as much, thus rewarding the user for chain reactions and for using those rouge bastards' own weapons against them.

 

4.6 Lessons Learned

 

The main lesson learned was to utilize flash memory whenever possible to reduce the number of unnecessary arithmetic calculations, particularly those involving multiplication and division, in the main program.  By pre-calculating a set of possible trajectories for the missile and interceptor, and placing the associated sine and cosine tables into flash allowed us to simplify the game design.  However, since the sine and cosine values are fractions of an integer it was necessary to use floating point flash which introduced complications in the game when the floating point flash value was added to the current x and y position of the missile or interceptor.  This problem was circumvented by scaling the sine and cosine tables by 256 and truncating the value to be a character.  Additionally there are two versions of each x and y position variable, one long and one normal.  When the position is to be updated using the scaled sine or cosine values, the value is added to the long version of the position which is subsequently right shifted by eight bits and stored in the normal x and y position variables.  The normal variables are then used as the coordinates for video generation.  This scheme has the advantage of using shifts rather than multiplication or division which improves the execution speed of the code.

 

A painful lesson learned, after a few hours of testing, is that it is a poor idea to assign ixp[] the value that you meant to assign to iyp[].  This creates interceptors that rain down from the sky!

 

We also decided against plotting the path that each missile had traveled because it introduced unnecessary complications when crossing paths had to be cleared or a local interceptor detonation cleared part of the path.  Furthermore, with large numbers of missiles and interceptors, the screen became cluttered with missile lines.  We feel that the current implementation of the missiles and interceptors as single pixel points makes the game more visually appealing.

 

Also, we have learned that it is unwise to purchase RCA cables from Radio Shack and that asking for a single 9-V battery makes retailers sad.