SGADE Tutorial – Boulder Bombers Advance

by Mark T. Price
Chief Scientist, Sudden Presence

 
 
 
 Previous Chapter Index Next Chapter 

Chapter 6 – Actual Gameplay

In this chapter, we will add the interactions necessary to turn the project into an actual game. We'll put in collision detection between the bombs and canyon rocks, make the rocks fall when there's nothing below & refill the canyon when it's empty, add score keeping and display, and finally add the start, pause and end-game screens. We've got a lot to do, so let's get started.

What you need for this chapter
1. The project created in Chapter 5
2. An updated tile bitmap containing the original canyon tiles plus text characters
3. The bitmap containing large characters for the titles

Bomb / Rock Interactions

The first thing we want to add to our project is to fix the add handling to check when a bomb hits a rock in the canyon and do something about it. Unfortunately, there is no magic to this as the GBA doesn't have any special Sprite/Tile collision checking built in. Luckily it really isn't that hard to calculate.

Add the following configuration #defines to the beginning of the bbadvance.c file. These defines are used to identify the edges of the canyon.

  #define CANYON_TOP       11        // y offset of top canyon row
#define CANYON_COLUMNS 30 // number of columns in canyon array
#define CANYON_ROWS 9 // number of rows in canyon array

In order to track the current state of the canyon rocks, we're going to need to add a couple of variables to hold the information and some code to initialize them.

Add declarations for canyon tracking variables to the Globals section

  u16 g_canyon[CANYON_COLUMNS*CANYON_ROWS];
int g_rocksLeft;

Insert the following new routine at the beginning of the Initialization section. This routine starts by initializing the tracking array and the count of rocks in the canyon and finishes by displaying the rocks on the screen.

  void fillCanyon()
{
// construct rock array
{
int xx, yy;
g_rocksLeft = 0;
for(yy = 0; yy < CANYON_ROWS; ++yy)
for(xx = 0; xx < CANYON_COLUMNS; ++xx)
{
if((g_canyon[yy*CANYON_COLUMNS+xx] = canyonData[(yy+CANYON_TOP) * CANYON_COLUMNS+xx]) < 4)
++g_rocksLeft;
}
}
// load the tile indexes into the background
SoBkgWriteBlock(0, 0, 0, 30, 20, 30, (u16*)&canyonData[0]);
}

Finally, modify the setupPlayfield to replace its existing call to write the canyon data to the screen with a call to the new fillCanyon function.

      // load the tile indexes into the background
SoBkgWriteBlock(0, 0, 0, 30, 20, 30, (u16*)&canyonData[0]);
// construct rock array & load on-screen image
fillCanyon();

Now that we're got a place to track the rocks, we'll add the code to do the tracking. The biggest part of this is the collision handling.

Add the checkHit function below before the update function. This function determines if a bomb's tip has hit anything in the canyon. It starts by converting the bomb's position to a position in the canyon and then checking the contents of that position in the canyon tracking array. If a hit occurs, the rock is removed.

  bool checkHit(int iBomb)
{
int xx = (g_bombs[iBomb].vPosition.m_X>>4) + 3; // x-position of bomb tip in pixels
int yy = (g_bombs[iBomb].vPosition.m_Y>>4) + 6; // y-position of bomb tip in pixels

// adjust bomb x position to canyon column number
xx /= 8;
if(xx < 0 || xx >= CANYON_COLUMNS)
{
// bomb went off left or right of screen
killBomb(iBomb);
return false;
}

// adjust bomb y position to canyon row number
yy = (yy / 8) - CANYON_TOP;
if(yy < 0)
return false; // not down to canyon top yet
if(yy >= 10)
{
// bomb went past bottom of canyon
killBomb(iBomb);
return false;
}

u16 val;
val = g_canyon[yy*CANYON_COLUMNS+xx];

// check if it's a rock...
if(!val)
return false; // empty
if(val >= 4)
{
// it hit bedrock -- it's done
killBomb(iBomb);
return false;
}

// it hit a canyon rock!

g_canyon[yy*CANYON_COLUMNS+xx] = 0; // remove the rock
--g_rocksLeft;

// erase the rock on screen
val = 0;
SoBkgWrite(0, xx, yy+CANYON_TOP, 1, &val);

return true;
}

Next, we modify the update function to call the checkHit function during the bomb update cycle. Add the following lines after the bomb position is updated and before the acceleration is applied.

          // check for hits
checkHit(ii);
if(!g_bombs[ii].bActive)
continue;

Finally, remove the old out-of-range test from the bomb update cycle of the update function.

          // check if bomb went off screen
if((g_bombs[ii].vPosition.m_X < 0) ||
(g_bombs[ii].vPosition.m_X >= (SO_SCREEN_WIDTH<<4)) ||
(g_bombs[ii].vPosition.m_Y >= (SO_SCREEN_HEIGHT<<4)))
{
killBomb(ii);
continue;
}

Compile and test the program.

When you drop bombs on the canyon, the rocks now disappear. Unfortunately each bomb will take out an entire column of rocks, the rocks never fall if the rock below is destroyed, and the canyon will not refill when the last rock is destroyed. Our next set of changes will address these problems.

The first set limit the number of rocks that a bomb can destroy.

Add the following configuration #define to the beginning of the bbadvance.c file.

  #define BOMB_NORMAL_POWER       4    // number of rocks destroyed by a 'normal' bomb

And add this field to the bombInfo structure.

      int       iHits;

Modify the dropBomb function to initialize the new iHits field.

      g_bombs[ii].iHits      = 0;

And add these lines to the bottom of the checkHits function, just before the line that reads 'return true'.

      if(++(g_bombs[iBomb].iHits) >= BOMB_NORMAL_POWER)
{
killBomb(iBomb);
return false;
}

The next set of changes enables unsupported rocks to fall.

Modify the "int ii;" declaration at the beginning of the update function to add a definition of a new loop index variable 'jj'.

      int ii, jj;

Add the following code block to the update function, just after the increment of cntFrame and animFrame. This code causes a column of rocks to cascade downwards if a rock at the bottom of the column is removed.

      // drop rocks that have nothing under them (once in 8 passes)
if(!(cntFrame&7))
{
bool bMoveIt; // keeps rocks from going right to the bottom
u16 *cp1, *cp2;

for(ii = 0; ii < CANYON_COLUMNS; ++ii)
{
cp1 = &g_canyon[ii];
cp2 = cp1+CANYON_COLUMNS;
bMoveIt = true;
// look at blocks in column
for(jj = 0; jj < CANYON_ROWS-1; ++jj, cp1=cp2, cp2+=CANYON_COLUMNS) {
// something (rock) at CP1, nothing below it at CP2?
if(*cp1 && !*cp2 && bMoveIt)
{
*cp2 = *cp1;
SoBkgWrite(0, ii, jj+CANYON_TOP+1, 1, cp2); // draw rock in new position

*cp1 = 0;
SoBkgWrite(0, ii, jj+CANYON_TOP, 1, cp1); // erase rock from old position

bMoveIt = false;
}
else if(!bMoveIt)
bMoveIt = true;
}
}
}

The next changes refills the canyon after the last rock is removed.

Insert the following code in the update function inside of the block that takes care of players going off of the screen.

              if(g_rocksLeft == 0)
{
// start new screen if no rocks remain
fillCanyon();
}

Compile and test the program.

Score Keeping

The next thing required for actual gameplay is scorekeeping along with a way to display the score. While we're at it, we'll also add code to limit and track the number of bombs. These changes are going to require a feature of the SGADE that we haven't yet used: tile-based character fonts. Fonts are used to convert string data to tile data, and make it much easier to display text.

The first step in supporting tile-based character fonts is to provide the graphics for the characters. I've done this by expanding our tile set to include the numbers 0-9 and all 26 English letters.

Put the updated bbtiles.bmp file in the data subdirectory of the BBAdvance project, overwriting the one that is there already.

From a command prompt in the BBAdvance/data directory, run the SoConverter.exe tool from the SGADE distribution to convert the bitmap to a tileset.

  % SoConverter.exe -file bbtiles.bmp -converter SoTileSet
  Invalid value for the -converter argument
  Please wait while converting....

  Done

  Press almost any key to continue...

The next step is to provide a mapping of character values to tile numbers. This is the job of the SoBkgFont class. As there is currently no converter to create SoBkgFont objects, we'll write one by hand.

Create and edit a file called 'bbtilesFonts.dat' in the data subdirectory of the BBAdvance project. Enter the following code and save the file.

  /*!
Hand-generated source file
*/
// The maps contains only characters between '0' and 'Z'
//! tile font character map
const u16 bbtilesFontMap[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9,10, // '0' to '9'
0, 0, 0, 0, 0, 0, 0, // ':' to '@' (unused)
11,12,13,14,15,16,17,18,19,20, // 'A' to 'J'
21,22,23,24,25,26,27,28,29,30, // 'K' to 'T'
31,32,33,34,35,36 // 'U' to 'Z'
};
const SoBkgFont bbtilesYellowFont = {
'0', 43, 10, (u16*)&bbtilesFontMap[0]
};

In this code, the bbtilesFontMap array contains the mapping from text character to tile. It only contains entries for the subrange of valid characters for the 'font'. All characters outside the valid range and all characters whose entry is equal to 0 are written as tile 0, which is normally transparent. The bbtilesYellowFont specifies the first valid character ('0') the number of characters (43) the base tile index (10) and the tile mapping array.

The tricky thing to remember when setting up font structures is that the base index is one less than the first valid character in the tile set. This is because only non-zero entries will be remapped when writing to the screen.

It is also useful to note that by making the base tile index part of the font structure, the SGADE allows you to use the same map array for multiple fonts.

Add a new #include to the constant definitions section of bbadvance.c to incorporate bbtilesFonts.dat.

  #include "bbtilesFonts.dat"

With the supporting data in place, we can now start on the numerous changes necessary to track and draw the remaining bombs and score displays. First, we'll need to add some new constant definitions and member variables.

Add these defines just after the PLAYER_OFFSET_* defines.

  #define PLAYER_BOMBS_START      3       // number of bombs to start with
#define PLAYER_BOMBS_SHOW 6 // max number of bombs to show
#define PLAYER_BOMBS_SHOWROW 3 // max number of bombs in one row

#define PLAYER_BOMB_FREESCORE 1000 // score for 1st free bomb
#define PLAYER_BOMB_FREEINCR 2000 // score increment for nth free bomb

And add this define after the BOMB_NORMAL_POWER define.

  #define BOMB_NORMAL_IMAGE       4       // the index of the bomb image in the tileset

Finally, extend the playerInfo structure to include these bomb and score tracking variables.

      int       iBombsLeft;   // number of bombs remaining
u32 iScore;
u32 iFreeBombScore;
bool bDroppedBomb; // dropped at least one bomb on this pass

Since we're going to be dynamically displaying the remaining bombs, modify the canyonData array in the constants section replacing the second line which contains static bomb images with this one, containing all zeros.

      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

Add the shell / HUD display section right before the initialization section. The two functions in this new section manage the display of remaining bombs and score respectively.

  // ***************************************************************************
// *** SHELL
// ***************************************************************************

void showHudBombs(int iPlayer)
{
int ii, xx, yy;
u16 image;

// Show normal bombs - - - - - - - - - - - - - - - - - - - - - - - -

if(iPlayer == 0)
xx = 1;
else
xx = 26;
yy = 1;
image = BOMB_NORMAL_IMAGE + iPlayer;

for(ii = 1; ii <= PLAYER_BOMBS_SHOW; ++ii, ++xx)
{
if(ii > g_player[iPlayer].iBombsLeft)
image = 0; // draw blanks...

SoBkgWrite(0, xx, yy, 1, &image);

if((ii % PLAYER_BOMBS_SHOWROW) == 0)
{
xx -= PLAYER_BOMBS_SHOWROW;
--yy;
}
}
}

// display the score on screen
void showHudScore(int iPlayer)
{
char szScorePad[8+1];
char *szScoreNoPad;
szScoreNoPad = ditoa(g_player[iPlayer].iScore, &szScorePad[0], sizeof(szScorePad), 10);

SoBkgSetFont(0, &bbtilesYellowFont);

if(iPlayer == 0)
SoBkgPrintAt(0, 6, 1, &szScoreNoPad[0]);

else
SoBkgPrintAt(0, 16, 1, szScorePad);

// check for & give extra bombs
if(g_player[iPlayer].iScore >= g_player[iPlayer].iFreeBombScore)
{
g_player[iPlayer].iFreeBombScore += PLAYER_BOMB_FREEINCR;
g_player[iPlayer].iBombsLeft++;
showHudBombs(iPlayer);
}
}

The showHudScore function includes calls to three new SGADE functions: ditoa, SoBkgSetFont and SoBkgPrintAt.

ditoa -- converts an integer value to an equivalent string representation, part of the SGADE debugging library.
SoBkgSetFont -- called to set the current font mapping for a background
SoBkgPrintAt -- prints a string at a specified position on the background

The next set of changes manages the initialization of the score and bombs as well as the update of bombs when the are removed.

Change the name of the existing killBomb() function to 'hideBomb' to more accurately reflect what it does.

  void hideBomb(int iBomb)

And add a new killBomb() function after the hideBomb() function. This new function tracks the remaining bombs left and updates the HUD to show the available bombs.

  // remove bomb (in-play)
void killBomb(int iBomb)
{
if(g_bombs[iBomb].iHits > 0)
{
++g_player[g_bombs[iBomb].iPlayer].iBombsLeft;
showHudBombs(g_bombs[iBomb].iPlayer);
}
hideBomb(iBomb);
}

Add these lines to the player initialization section of setupSprites.

          g_player[ii].iBombsLeft = PLAYER_BOMBS_START;
g_player[ii].iScore = 0;
g_player[ii].iFreeBombScore = PLAYER_BOMB_FREESCORE;
g_player[ii].bDroppedBomb = false;

And change the killBomb() call in the bomb initialization section to call the renamed hideBomb() instead.

          hideBomb(ii);

Next, we add code to track the number of bombs we've dropped and to update the score.

Insert the following code at the start of the dropBomb() function, just after the declaration of ii. This ensures that the player can't drop a bomb if they don't have one.

      // make sure player has a bomb to drop
if(g_player[iPlayer].iBombsLeft == 0)
return;

And insert this code at the end of the dropBomb() function. This updates the available bombs and tracks the fact that a bomb was dropped this pass.

      g_player[iPlayer].iBombsLeft--;
showHudBombs(iPlayer);
g_player[iPlayer].bDroppedBomb = true;

Next, add this code to the checkHit() function right before the code that removes a hit rock from the canyon. This updates and displays the players score.

      // update score
g_player[g_bombs[iBomb].iPlayer].iScore += val*4;
showHudScore(g_bombs[iBomb].iPlayer);

The final set of changes in this block manage taking away a bomb if the player fails to attempt a drop on a pass and ensuring that the score & free bombs are always displayed.

In the update() function, add this code in the block that handles the player going off of the screen, just before the test on g_rocksLeft.

              if(!g_player[ii].bDroppedBomb && g_player[ii].iBombsLeft)
{
g_player[ii].iBombsLeft--;
showHudBombs(ii);
}

And add this code just after the fillCanyon() call inside of the g_rocksLeft block.

                  showHudScore(0);
showHudScore(1);
showHudBombs(0);
showHudBombs(1);

Next, at the end of the block that handles the player going off of the screen, reset the dropped bomb flag by adding this line.

              g_player[ii].bDroppedBomb = false;

Finally, add this code to AgbMain() just before the for(;;) loop. This ensures that the score and free bombs will be correctly displayed at the start of the game.

      showHudScore(0);
showHudScore(1);
showHudBombs(0);
showHudBombs(1);

Compile and test the program.

Game States

The game is getting closer to becoming playable, but it still has problems. There is no start screen, the game cannot be paused, and when the player runs out of bombs the game does not end. These will all be addressed in the next set of changes.

Our new game screens are going to require identifying text to let the player know what's going on. We could use the character font that we added in the last section, but it lacks authority. We really want big characters. We could use multiple-tiles per character, but there would be no help for us in the SGADE library. Instead, we're going to do our characters using 16x16 sprites. This has the added advantage that we can apply character-by-character special effects (vibrating, etc) later if we so choose.

Obviously, new characters means more graphics, this time in a sprite animation.

Put the spritefont.bmp file in the data subdirectory of the BBAdvance project. The new font graphic can be seen on the right edge of this page.

From a command prompt in the BBAdvance/data directory, run the SoConverter.exe tool from the SGADE distribution to convert the sprite font to a sprite animation. Since this file was created using the same palette as the existing sprites we won't have to regenerate the palette.

  % SoConverter.exe -file spritefont.bmp -converter SoSpriteAnimation
  Invalid value for the -converter argument
  Please wait while converting....

  Done

  Press almost any key to continue...

Since we built the sprite animation from a bitmap, we'll once again have to do the frame-count conversion hack. Edit the generated spritefontSpriteAnimation.dat file and change it from a single frame 16x416 pixel animation to a 26 frame 16x16 pixel animation and save the result.

  const SoSpriteAnimation spritefontSpriteAnimation = { false, 16, 16, 26,
    (u8*) spritefontSpriteAnimationData };
        

Open the bbadvance.c file in your editor and add a new #include to the constant definitions section to incorporate spritefontSpriteAnimation.dat.

  #include "spritefontSpriteAnimation.dat"

Add a variable to hold the sprite handle to the Globals section.

  u32 g_iTextBaseSpr;

And finally, add code to load the sprite animation to the setupSprites function, just before the call to SoSpriteManagerEnableSprites();

      // load text sprite data

      g_iTextBaseSpr = SoSpriteMemManagerLoad(&spritefontSpriteAnimation);

With the sprite font data in place, we now must add code to use it. We'll do this by inserting a stripped-down version of the string printing code from SoBkg which has been modified to use sprite animation frames instead of tile indices.

Add the following code to the end of the new shell section. We've seen all of the SGADE functions used in this code in previous parts of the tutorial, so we won't detail them again here. It should be noted that this routine only supports the display of a single sprite string at any one time. Printing a new string will erase the previous one.

  // display a text string using sprites for letters
#define SPRITETEXT_LETTERSPACING 14
#define SPRITETEXT_BLANKSPACING 8
#define SPRITETEXT_MAXCHARS 18

void showSpriteText(u16 xpos, u16 ypos, const char *string)
{
static SoSprite *letters[SPRITETEXT_MAXCHARS] = { 0 };

int charNum = 0;
int letterSize = SoSpriteAnimationGetNumIndicesPerFrame(&spritefontSpriteAnimation);

// Loop through a_String
while(*string != '\0')
{
if(charNum >= SPRITETEXT_MAXCHARS)
break;

// conver the character to sprite anim position
u16 charCode = *string;
if(charCode >= 'A' && charCode <= 'Z')
{
charCode -= 'A';

if(letters[charNum] == NULL)
{
letters[charNum] = SoSpriteManagerRequestSprite();
SoSpriteCopyPropertiesFromAnimation(letters[charNum], &spritefontSpriteAnimation);
}

SoSpriteSetSizeDoubleEnable(letters[charNum], false);
SoSpriteSetRotationAndScaleEnable(letters[charNum], false);
SoSpriteSetAnimationIndex(letters[charNum], g_iTextBaseSpr + charCode*letterSize);
SoSpriteSetTranslate(letters[charNum], xpos, ypos);

++charNum;
}

if(*string == ' ')
xpos += SPRITETEXT_BLANKSPACING;
else
xpos += SPRITETEXT_LETTERSPACING;

++string;
}

// clear unused sprites
while(charNum < SPRITETEXT_MAXCHARS && letters[charNum] != NULL)
{
SoSpriteDisable(letters[charNum]);
++charNum;
}
}

That's it for our big character support. There's only one thing left to do, and that's the implementation of the various game screens.

When we add a start screen, pause screen and game over screen, what we're really adding are multiple new game states. The different states affect what is displayed, how input is handled and how the game is updated from frame to frame. Also, changes from one state to may require additional work to be done, such as hiding the player & bomb sprites when going into pause state.

Add #defines for the new game states at the beginning of the configuration defines section.

  #define GAME_STATE_START    0
#define GAME_STATE_PAUSED 1
#define GAME_STATE_RUN 2
#define GAME_STATE_GAMEOVER 3

Add two variables to the globals section, one to track the current game state and another to manage the Game Over timeout

  int g_iGameState;
u16 g_iGameoverCountdown;

We have two functions to add to the initialization section, the first is used to hide the game sprites when going into pause or game-over mode. Add it before the hideBomb() function. This code makes use of SoSpriteSetTranslate to move the sprite off of the bottom right edge of the screen. This makes redisplaying the sprites automatic when their positions are reset the next time update() is called.

  // hide all player / bomb sprites
void hideGameSprites(void)
{
int ii;

for(ii = 0; ii < MAX_PLAYERS; ++ii)
{
SoSpriteSetTranslate(g_player[ii].sprite,
SO_SCREEN_WIDTH, SO_SCREEN_HEIGHT);
}
for(ii = 0; ii < MAX_BOMBS; ++ii)
{
SoSpriteSetTranslate(g_bombs[ii].sprite,
SO_SCREEN_WIDTH, SO_SCREEN_HEIGHT);
}
}

The next function initializes a new game by setting default values for the players, bombs and canyon. Part of this work is currently being done in the setupSprites() function. Since we don't need this code in both places, remove the non-sprite player setup code from setupSprites().

          g_player[ii].iDirection = startDirection[ii];
g_player[ii].vPosition = startPosition[ii];
g_player[ii].iHeading = PLAYER_HEADING_LEVEL;
g_player[ii].iType = ii;
g_player[ii].iBombsLeft = PLAYER_BOMBS_START;
g_player[ii].iScore = 0;
g_player[ii].iFreeBombScore = PLAYER_BOMB_FREESCORE;
g_player[ii].bDroppedBomb = false;

Add the initGame() function to the end of the initialization section. The player initialization lines are the same as the ones you removed above.

  void initGame(void)
{
int ii;

fillCanyon();

for(ii = 0; ii < MAX_BOMBS; ++ii)
{
hideBomb(ii);
}

for(ii = 0; ii < MAX_PLAYERS; ++ii)
{
g_player[ii].iDirection = startDirection[ii];
g_player[ii].vPosition = startPosition[ii];
g_player[ii].iHeading = PLAYER_HEADING_LEVEL;
g_player[ii].iType = ii;
g_player[ii].iBombsLeft = PLAYER_BOMBS_START;
g_player[ii].iScore = 0;
g_player[ii].iFreeBombScore = PLAYER_BOMB_FREESCORE;
g_player[ii].bDroppedBomb = false;

showHudBombs(ii);
showHudScore(ii);
}
}

Next we add code to handle state changes. First we add the function that manages the actual operations necessary to do a switch and then all of the calls into this function.

Add the following function just before the control section. As you can see, this function makes use of the sprite text routine we added earlier.

  // ***************************************************************************
// *** GAME STATE
// ***************************************************************************

void setGameState(iState)
{
SO_ASSERT(g_iGameState != iState, "Already in desired state!");

SoBkgSetFont(0, &bbtilesYellowFont);
showSpriteText(0, 0, ""); // clear any existing sprite text

switch(iState)
{
case GAME_STATE_START:
showSpriteText(18, 32, "BOULDER BOMBERS");
SoBkgPrintAt(0, 10, 7, "PRESS START");
break;
case GAME_STATE_PAUSED:
showSpriteText(78, 32, "PAUSED");
SoBkgPrintAt(0, 9, 7, "PRESS ANY KEY");
break;
case GAME_STATE_RUN:
SoBkgFillBlock(0, 0, 4, 30, 6, 0); // erase title/pause text
if(g_iGameState == GAME_STATE_START)
initGame();
break;
case GAME_STATE_GAMEOVER:
showSpriteText(60, 32, "GAME OVER");
g_iGameoverCountdown = 3 * 60;
break;
}
SoSpriteManagerUpdate(); // display the sprite text

g_iGameState = iState;
}

Change the name of the handleInput() function to handleRunInput().

  void handleRunInput(void)

And modify the handleRunInput() function to switch from the run state to the pause state by adding this code just after the SoKeysUpdate() call.

      if(SoKeysPressed(SO_KEY_SELECT))
{
setGameState(GAME_STATE_PAUSED);
}

Modify the update() function to switch from the run state to the game over state by adding this code in the block that handles the player going off of the screen, just before the test on g_rocksLeft.

              if(ii == 0 && g_player[ii].iBombsLeft == 0)
{
setGameState(GAME_STATE_GAMEOVER);
return;
}

The rest of the state change calls are made from new input handling functions for each of the other states.

Add the input handling functions for each of the other game states before the existing handleRunInput() function. All of the new states have fairly simple input requirements, mostly only consisting of switching into another state.

  void handleStartInput(void)
{
SoKeysUpdate();

if(SoKeysPressed(SO_KEY_START))
{
setGameState(GAME_STATE_RUN);
}
}

void handlePausedInput(void)
{
SoKeysUpdate();

if(SoKeysPressed(SO_KEY_ANY))
{
setGameState(GAME_STATE_RUN);
}
}

void handleGameoverInput(void)
{
SoKeysUpdate();

if(SoKeysPressed(SO_KEY_ANY) || (g_iGameoverCountdown-- == 0))
{
setGameState(GAME_STATE_START);
}
}

Finally, we modify our AgbMain() function to initialize the game and handle state changes.

Replace the startup calls to display remaining bombs and score with code to initialize the game state.

      showHudScore(0);
showHudScore(1);
showHudBombs(0);
showHudBombs(1);
g_iGameState = -1;
setGameState(GAME_STATE_START);

Modify the main loop to replace the handleInput() and update() calls with a switch to call the state appropriate functions.

          handleInput();
update();
switch(g_iGameState)
{
case GAME_STATE_START:
handleStartInput();
break;

case GAME_STATE_PAUSED:
handlePausedInput();
break;

case GAME_STATE_RUN:
handleRunInput();
if(g_iGameState == GAME_STATE_RUN) // may have paused
update();
break;

case GAME_STATE_GAMEOVER:
handleGameoverInput();
break;
}

That's all! Save the file.

Compile your updated project by running ‘make’ from a command shell in the BBAdvance directory. Once it’s ready, try it out. You will get screens like the ones below. Pressing the Start button begins the game. Once the game is running, pressing Select pauses the game. When you miss with your last bomb, the game over screen will show for a half second before switching back to the title screen.

click here to download the solution to this chapter

 Previous Chapter Index Next Chapter