Our program consists of the following features:
The menu screen acts as the introduction and configuration page for the program. The screen first displays the title "SUPER BREAKOUT" for a couple seconds. Then the text "PLAYERS:" is displayed allowing the first player to choose between one and two player modes. If two player mode is chosen, additional text is displayed that allows the second player to choose competitive or cooperative mode. If player 1 switches back to single player mode in this menu, this additional text allowing selection of modes disappears.
In order to account for the two inputs (one from each player), we alternate sampling of the accelerometer voltages on ports A.0 and A.1. This alternating scheme is necessary because the A/D converter on the Mega32 can only perform one conversion at a time. This switching is done by alternating the ADMUX register bits to read either port A.0 or A.1.
After a certain amount of time, the game exits from the menu screen and stores the configuration obtained from the user. At this time, the program begins the game.
The brick matrix is a representation of the mapping of bricks on the screen. The location of the "brick" within the matrix corresponds to its position on the screen. The brick matrix was a 4x14 matrix which corresponded to a maximum amount of bricks displayed in 4 rows and 14 columns (56 total bricks). In order to map the indices in the brick matrix "brick_pos[x][y]" to a coordinate on the screen, we used a method of multiplication followed by a translation. The on screen x position was mapped as follows: x = y-index << 3 + 4. The on screen y position was similarly mapped as follows: y = x-index << 3 + 14. The values in the brick matrix indicated the type of brick in that location (0-blank, 1-regular, 2-2nd ball, 3-lengthener, 4-point doubler, 5-unbreakable, 6-extra life). A matrix called "bricks" held the illustrations of the possible bricks.
In order to determine which brick was hit, we needed a mapping from the screen back into the brick matrix. We used a method of truncation and translation for the mapping. We took the x position of the ball (x(1,2)) and mapped it to the brick matrix as follows: y = (x(1,2)-4)>>3. The y position (y(1,2)) of the ball was similarly mapped as follows: x = (y(1,2)-14)>>3. Therefore the lookup was brick_pos[x][y]. When there is a successful mapping, we have determined that a brick is hit. If the brick is not an unbreakable brick, we replace the value in the brick matrix with that corresponding to a blank. This blank is then drawn onto the screen "erasing" the corresponding brick. If the brick is of type unbreakable, we keep the unbreakable value in the brick matrix and redraw this unbreakable brick. Using a variable "brick_count," we track the number of breakable bricks remaining. When we hit a breakable brick, we decrement brick_count until its value is equal to zero at which time we end the level and progress to the next level. . When the level is cleared, we turn off the interrupts and begin drawing the preset brick matrix for the next level. The regular brick and the unbreakable brick positions are preset; however, the "special powerup bricks" are given random locations and random types.
Updating position of ball and paddles
We track the position of the ball and paddles by storing its x position and y position as 16-bit integers. These 16-bit integers are used in order to utilize a binary-fixed point method. Thus, we are able to represent fractional frames.
For the ball, we update its x and y position variables by adding its 16-bit binary fixed-point velocity. The corresponding mathematical expression is x1_pos(x2_pos for ball 2) = x1_pos + v1x (* t). We however use a time value of 1 to simplify the calculations. The y position is found using a similar expression. In order to show movement of the ball on the screen, we first erase the ball at the previous location by drawing a black dot. We then draw the ball at its new position with a white dot.
Whenever we hit one of the boundaries, bricks, or paddles, we change the value of v1x or v1y appropriately (v2x and v2y for ball 2). We use an algorithm to determine whether the brick is hit from the side in which case we negate the ball's x velocity (v1x or v2x). Else, if the ball is hit from the top or bottom, we negate its y velocity (v1y or v2y). After you hit a brick, you check the ball's x position to determine if it corresponds to the x value of the side of one of the bricks. If so, we negate its x velocity leaving the y velocity. Else, we negate its y velocity, leaving its x velocity. We provide the user with some control over the direction of the ball by changing the ball's x velocity by varying amounts depending on the location of contact of the ball in relation to the paddle. When the ball hits the paddle, we negate its y velocity. We then calculate its new x velocity by using the distance between the ball and the center of the paddle. The further away the ball is from the center, the more its x velocity will be changed. If the ball hits the right side of the center point of the paddle, we add a positive value to the x velocity. If the ball hits the left side, we subtract a certain amount from the x velocity. These values also depend on the distance from the center point. We also implemented a maximum velocity that could be obtained by the ball so the ball would not go so fast that the user could never hit it.
The position of the paddles is updated by just changing its x velocity and thus x position. The use of accelerometers allowed the user to change the velocity of the paddle corresponding to the amount the accelerometers are tilted. We had 10 different degrees of change giving the user precise control over the position of the paddle. We set the paddles' maximum velocity at 1 pixel per frame. The y-position of the paddle(s) is preset at the start of game play and is never changed. This y-position is determined by the mode selected at the start menu. In cooperative mode, we set paddle 1 to be slightly above the bottom boundary. We set paddle 2 to be slightly above paddle 1. We did this so that we could distinguish which player corresponded to which paddle in game play. In competitive mode, we set player 2's paddle to be at the top of the screen. We also had boundary checks to ensure that the paddles were never drawn off screen.
In one player mode and two player cooperative, we implemented numerous powerups. These powerups facilitated game play for the players by giving them added bonuses as they progressed through the levels. The powerups we implemented includes a 2nd ball powerup, a paddle lengthener, a point doubler, and an extra life powerup. These powerups could be accessed by breaking "special bricks." We had a certain brick type correspond to each of the powerups such that the user would know which powerup would come out of the brick when it is hit (see powerups page).
Upon breaking a "special brick" a powerup would appear and drop down from the broken brick. The players would have to "catch" these powerups by being directly under them when they fall down and touching a point on the powerup with a point on the paddle. Upon catching the powerup, the corresponding player(s) would be rewarded appropriately. If the players miss a powerup, it just disappears upon touching the bottom of the screen.
Since the powerup image appears once the brick is hit, it is possible that there are objects in the powerup's path as it drops down. In the case that bricks are in its path, we draw the powerup over the brick, but then when the powerup image passes the location where the brick was located, the program redraws this brick. This gives the illusion that the special powerup passes over the brick.
2nd ball powerup
When the corresponding brick is hit, a second ball appears and bounces around. This ball allows the user to break more bricks at a quicker rate. However, for some players, the second ball may confuse the player and actually be detrimental as they stall to determine which ball to go for. The player(s) will not lose a life if an extra ball hits the bottom boundary as long as one ball is remaining. When an extra ball hits the bottom, it simply deactivates. There can only be at maximum two balls on the screen. The user will not be able to obtain more than two balls by hitting multiple 2nd ball powerup bricks.
When the paddle lengthener brick is hit, an illustration of a powerup appears and begins to traverse down the screen at 1/4 pixel per frame. If the user successfully catches this powerup, we increase the length of their paddle. This increase in length not only helps out the player by making it easier to catch the ball, but it also provides more control over the direction the ball bounces off the paddle. If the ball hits the lengthened paddle at the right most point, it will change the velocity by a significant amount more than if the paddle was not lengthened. A boundary check was necessary to ensure that when the user picks up this power up, his paddle did not extend beyond the confines of the screen. If this check is not performed, the paddle would be allowed to extend past the screen and corrupt system memory. As a result, if the player is positioned at the extreme right of the screen and "catches" a paddle lengthener, his paddle lengthened, however it is not drawn off screen. As the player moves away from the boundary, his paddle would be lengthened appropriately. If the player "catches" this paddle lengthener power anywhere else on the screen, the length of the player's paddle would be increased immediately since it poses no risk of corrupting memory. In two player cooperative mode, if both players are under the lengthener powerup when it falls, both players will receive the powerup bonus.
Point doubler and extra life
If the player catches the point doubler, the players total points are doubled. If the player catches the extra life powerup, the total lives are incremented by one.
We implemented an audible tone that would alert the user of several events. The sound was used as a feedback feature to the user to signify unique events such as contact with player's paddle, contact with bricks, successfully clearing a level, and losing a life. A short beep (about 0.5 s) would be generated to alert the user that the player's paddle has made contact with the ball in action, or that the ball in action has struck a brick and "broken" it. Another unique set of tones would be generated in the event that a player successfully clears a level to indicate completion of that level and progress forward. A low pitch tone would be generated if the current active ball goes past the player's paddle and touches the bottom boundary, indicating the ball has been deactivated and a possible loss of a life. We used a variable "tone" to set the frequency of our square wave. A value of about 6 produced a 1KHz square wave. This 1KHz frequency is well within the audible range of the human ear (20Hz - 20KHz). We varied "tone" from 5-8 depending on the desired tone of the sound played. This unique audible feedback system could serve as an aid to those who have certain visual impairments and would aid in their game play.
We instantiate numerous vectors and variables of different types (int, char, etc.) in global memory. Because of this, we had to ensure that there was no overlap in memory between the variables. Thus, we hardcoded memory locations for certain variables. The variables were mapped in sequential memory locations spanning from 0xC4 to 0x72B. One motivation in hardcoding memory locations was because we needed to obtain certain bytes out of certain variables. For example, the x position of the ball on the screen was obtained from the top 8 bits of the 16-bit binary fixed-point representation of the x position. The starting address of a variable was calculated by adding the starting location of the previous variable to its size. For example, if an integer variable started at 0xC4, the next available memory location is 0xC6 (0xC4 + 2 bytes).
Handling of lives and points
A point tally is implemented in one player mode and two player cooperative mode. We increment this point total value every time a brick is hit. The points actually correspond to respective bricks. Some bricks with powerups are worth more points than other bricks. There is also a point doubler powerup that will multiply this point total by two. Catching the other powerups is also worth a certain amount of points (1000 points). Thus, even if you have already obtained such features such as a paddle lengthener, there is still a benefit to catching this powerup.
We implement lives in every mode of play. In one player mode, the life total just corresponds to the total number of lives of player 1 which starts off at a value of 10. When the life total reaches 0, the game is over for player 1. In two player cooperative mode, the life total (starting at 10) corresponds to a value shared by both players. When this life total reaches 0, the game is over for both players. Extra life powerups can be obtained through the levels that increment your life count to help progression through the levels. In competitive mode, each player has a separate life count starting at 3 lives. The goal in competitive mode is to outlive your opponent. When one player's lives reduces to 0, the other player will win.
Modes: 1 player, 2 player, competitive, cooperative, knockout - Win/loss conditions
1 player and 2 player cooperative
In one player and two player cooperative modes, the levels are complete when all the breakable bricks have been broken. After a level is completed, the program exits the while and if (LineCount == 231) loops, turns off interrupts, creates a new brick matrix, draws the matrix to the screen, then turns the interrupts back on and returns into the while and if (LineCount == 231) loops to continue real time game play. The win condition is satisfied for these two modes when all 10 levels are completed. The player(s) starts off with 10 lives. If there is only one ball on the screen, once that ball hits the bottom boundary the player(s) loses a life and the current level restarts. If there are two balls on the screen, one ball hitting the bottom boundary just causes that ball to be deactivated. The player(s) will not lose a life until all balls have been deactivated. As long as the player(s) still have lives remaining, the current level will continue to restart every time the ball(s) is deactivated. The loss condition occurs for these two modes if the life count displayed at the top left of the screen reaches 0.
2 player competitive
In two player competitive mode, the goal is to outlive your opponent. Both players start off with 3 lives and a player will lose a life if the ball hits the boundary behind their paddle. The balls in this mode will not become deactivated and will continue to bounce off the bottom/top boundaries as long as both players have lives remaining. Once one player's life count reaches 0, that player loses and their opponent wins. In this mode, there is a randomly generated row of bricks in the middle of the screen. This row consists of both unbreakable and regular bricks (special powerup bricks are not included in this mode). If the players break all the regular bricks on the screen, the program exits the while and if (LineCount == 231) loops, turns off interrupts, creates a new brick matrix row, draws the row to the screen, then turns the interrupts back on and returns into the while and if (LineCount == 231) loops to continue real time game play.
2 player knockout
In two player knockout mode, the goal is to break all of the opponent's bricks. Each player has a row of bricks behind their paddle. Each player has two goals: to break all of the opponent's bricks, and to protect their own bricks. The program also displays a brick count for each of the players referring to the number of bricks they have remaining. The player that breaks all of his opponent's bricks and still has some of his/her own bricks remaining is the winner.
We soldered together a standalone board in efforts to create a gaming system that people can take home and enjoy on their home television. The board still requires the STK500 in order to program, but once programming is complete, the board can be detached and still implement the features of the program.
Powering through STK500
Another modification to the board was soldering wires onto the board that allowed us to power the standalone board through the STK500 when connected. We found that there was actually a voltage drop when we connected the 5V terminal of the STK500 cable to our standalone board. The voltage actually input into our standalone board was around 3.6V. This was not ok for game play since some parts of our design required a 5V reference such as the A/D converter. However, for just programming the chip, this technique seemed to work. For playing the game from just our standalone board, it was important for it to have its own 5V power connection. Be careful to not plug in the standalone board when it is hooked up to the STK500 in this manner. This causes a large voltage feedback to the STK500. Near the end of our final project work, we found out that this scheme was not the safest and removed this feature by rewiring our standalone board so that it could no longer be powered through the STK500.
The television was connected to the chip through a resistor network (a video DAC) attached to the output of port D.5 and D.6. These two outputs determined the sync and intensity levels. The video DAC was used to make the levels conform to NTSC standards. We soldered the video DAC directly onto the standalone board so that the user could plug the television video cable directly onto the board.
One unique feature of the Super Breakout game is the ability to move the paddles through accelerometers. The game features two player modes and thus uses two accelerometers for the 2 users. The voltage output from the accelerometers are fed into the A/D converter of the Mega32 chip and the corresponding digital value is read by our program to determine the amount of tilt the user is applying. We soldered the surface mount accelerometers onto SOIC to DIP adapters so that we could connect wires to the adapter boards. We connected the accelerometers (mounted on the adapter boards) to the standalone board through long wires so that the players could have a good deal of mobility when playing the game. There are three connections between the accelerometers and the standalone board: Vcc, Gnd, and output from the accelerometer. We also soldered on plugs to the standalone board so that the accelerometers could be unplugged if desired.
We used Professor Bruce Land's code for video generation.
Things we tried that did not work
One of the things we tried early on was having many powerups in each level. Before we created the preset levels, we randomly generated each level by randomly generating the 4x14 matrix with all the different types of bricks that we had. Each brick had an equal probability of occurring. Because of this, we had numerous special powerup bricks. We found that if many powerups appeared simultaneously on the screen, the program had glitches because the calculations became too intensive to be done within a single frame. Thus, in our preset levels, we reduced the number of special powerup bricks to a maximum of 3 or 4 per level.