Program Design

Game Dynamics

To store game blocks we used unsigned integer variables. Each variable has 16 bits, so we split the variable up into a 4X4 grid, with the least significant 4 bits representing the top row of the "block space". A "1" represents a block; a "0" means empty space. In this fashion we stored 7 blocks, each with 4 possible rotations. Therefore the blocks are stored in a 7X4 unsigned int array. Here are the blocks we used in the game.


All of these block designs are original. Really!! We were just inspired.

To store the state of the game board we used an unsigned integer for each board row. However, we could not use the full 16 bits for the board because our CheckOverlap routine needed space to work with and would crash if the "block space" was ever to move outside the game board. Therefore the game board needs some "walls" to keep the block space the game space. To do this we reserved the 3 most significant and 3 least significant bits of the top 16 rows of the game board and locked them to be "1" (representing a taken space) and the start of the game. The two bottom rows of the board, board[0] and board[1], are locked walls.

To integrate the block space and the board space we used lots of shifts, and's and or's. To orient the block space in relation to the game board four variables are used, current_x, current_y, current_block and current_rotation. Current_x stores the current x-coordinate position of the top left corner of the block space in reference to the game board. Current_y is the current y-coordinate position. Current_block refers to the current active block and current_rotation is used to call the correct array value.

One of the most important functions in the game is void CheckOverlap(unsigned char Movement). It checks the current block position and according to the variable Movement calculates the next desired position. It then runs a loop to check each of the 4 rows of the game board to check for overlaps. If there is an overlap, a 1 is returned, otherwise a 0 is returned. This function is used for all block movement and to check if the game is over. If a freshly called block is overlapping, the game is over.

Text & Graphics

The Seiko G321D LCD was surprisingly easy to work with, thanks to the SED1330F controller. To write a command or data to the LCD, it is written to the data port (Port B) and then a command is written to the control port (Port A) telling the LCD to read the data port. Once the primitive functions (WriteCommand and WriteData) were written and the correct command definitions mapped, working with the LCD just involved address calculations and function calls.

Our initialization function configured the LCD memory to map the first 1000 bytes of memory to the character space using 8X8 characters, giving us 40 lines of 25 characters each. To get a character address we use:

Address = 40 * Line + Character Offset

The next 8K of memory is the graphics space. Since the only graphics ever drawn for the game involve either blocks, we wrote the void DrawBlock(unsigned char Action) routine which allows us to either draw or undraw a block given the current_x, current_y and current_block and current_rotation parameters. Since the blocks are 12 pixels high and the game screen is on the right side of the screen, the address is obtained using:

Address = 1000 + 12*40*(17 - current_y) + 20 + current_x

Using a for loop and a bit-shifting algorithm it was easy to draw any block we wanted. To update the entire game screen (after a line clear or a multiplayer penalty), void UpdateScreen(void) is used. It redraws the entire game screen for top to bottom.

Timer / Interrupt Usage

With the exception of a few game reset and ending sequences the game is run off interrupts. On the Mega163 we had 3 timers to work with.

Timer0 is used to sample the Genesis gamepad on Port C at about 30Hz. Even with a 2-cycle debounce the gamepad was still too fast so we instead opted to freeze the gamepad after a user presses a button, until it is released. Button detection was easy; we simply scanned Port C for zeroes. This is what the buttons do in each part of the game:

Intro
Button A - Select mode
Start - Start the game

Game
Left / Right / Down - Left / Right / Quick Drop
Start - Pause
A - Scroll through music
B - Rotate counterclockwise
C - Rotate clockwise

Game Over
Start - Reset the game

Timer 1 is used to time dropping blocks. We calculated the amount of timer 1 ticks for each drop at different game levels (running at the slowest prescalar) and wrote them to an array. When the level increases we reset timer 1 and load a new value into OCR1A. At Level 1 it takes 1.5 seconds per drop, at Level 10 it drops 4 times a second! Neither of us managed to go beyond Level 8. Incidentally it is possible to beat the game in single player mode; you need to score 50000 points. Good luck!

Timer 2 is a random number generator. We set OCR2 to 6, turned off the interrupt and ran it as fast as possible. When we need a new block or a penalty is inflicted, we sample timer 2.

Multiplayer

To play multiplayer simply connect two game boards through the UART using a null modem cable. If "2 Player" is selected in the Intro screen a handshake routine is run to ensure that both games start simultaneously. A "w" is sent through the UART and waits (this requires nested interrupts). The routine assumes that once the UART is activated the game is ready to go. If the other board is receives the "w" that means it is also ready to play. It replies with a "r" and starts both games. If the other board wasn't ready yet then no reply is received. However, when the second board becomes ready and sends the "w" it wakes up the first board, and replies with a "r" of its own. That way both games start together.

When a player clears multiple lines the opponent is punished by having the same number of lines cleared minus one inserted at the bottom of the board. Those blocks have a space inserted at random (using timer 2) into them. This makes the gameplay much more exciting.

Statistical tracking and other fun stuff

During the game statistics are kept on score, total lines cleared, line clear types (singles, doubles, etc.) and blocks dropped. The score-lines cleared ratio is an indicator of your skill and patience in the Russian Bloc Game, since it is much harder to clear multiple lines than single lines. The high score is kept in EEPROM so that it would not be lost if the system is reset. It can also be saved across chip reprogramming if one chooses to "Preserve EEPROM" in the compiler options window in CodeVisionAVR.  At the end of the game all your statistics are displayed, along with such insightful comments as "Your Score Sucks" and "Your Ratio: Crappy".  It's just more incentive to do well; you don't have to put up with our insults.

Music

Each musical score is stored using parallel arrays. The first vector contains note references, they are used for a lookup table which contains the note frequencies translated to timer 1 ticks (see music program comments). The second vector contains note lengths, each value corresponds to one timer 0 overflow (32.5 milliseconds). The timer 0 overflow ISR checks if the current note is over and plays the next note if it is. Timer 1 is dedicated to the sine wave. Notes span the G3 (G below Middle C) to Eb6 (E flat two octaves above Middle C scale). For the high notes timer 1 becomes speed-critical so we rewrote the ISR using assembly.

The music was transcribed by ear and metronome.  Tons of fun!  Current musical selection:

To communicate with the game board the external interrupt on Pin D.2 is enabled to trigger on the rising edge. When the game board wants a new song it sets Port D pins 4 to 7 to the correct code and sends a pulse on Pin D.2, which triggers the ISR on the sound board.  During gameplay the current musical selection is displayed at the bottom right side of the LCD.

p.s. We realise that all of these musical selections (with the exception of the erstwhile USSR's national anthem) are probably under copyright, which means that we could possibly get into serious trouble for using them without due permission from the creators/owners of said musical pieces. In lieu of this, we would like to present the following defense: