Sol's Graphics for Beginners

35 - Lives, Time and Game Over

The new game mechanic also requires us to add lives to the game, and when lives are over, we can show the game over screen.

Add a new global variable (gp.h):

// Lives left
extern int gLives;

..and main.cpp:

// Lives left
int gLives;

Three tries sounds fair. In changestate(), when we're leaving STATE_STAGESELECT:

case STATE_STAGESELECT:
    imgui_close();
    gLives = 2;
    break;

Let's show the lives as well. In game.cpp, rendergame() has a comment saying "draw status strings" - change the code after that to look like this:

char statusstring[80];
  sprintf(statusstring, "'%s', time limit:%ds", gLevelName, gLevelTime / 1000);
  drawstring(5, 5, statusstring);

  sprintf(statusstring, "Score:%d", gScore);
  drawstring(5, 22, statusstring);
  sprintf(statusstring, "Lives:%d", gLives);
  drawstring(5, 39, statusstring);

  int secondsleft = gLevelTime - (gLastTick - gLevelStartTick);
  if (secondsleft < 0)
    secondsleft = 0;
  sprintf(statusstring, &quot;Time:%3.1f&quot;, secondsleft / 1000.0f);
  drawstring(5, 56, statusstring);

If you compile and run, you'll find that the time seems to run out rather fast.. it's because the above code considers gLevelTime to be milliseconds, instead of seconds.

Let's fix this by changing the time everywhere to be in milliseconds. Go to reset() in main.cpp, and find where the level file is loaded, and more importantly where gLevelTime is set. Find the fclose(f) following this and add the following line after that:

gLevelTime *= 1000;

Then go to changestate() in main.cpp and change the STATE_PAUSEMENU level time calculation to:

gLevelTime -= (gLastTick - gLevelStartTick);

After this the player can't cheat anymore by using the pause menu.

Now the scoring is messed up, so let's fix that. In game.cpp, gamephysics(), LEVEL_END tile collision increases the player's score by secondsleft * 25. Let's change this to..

case LEVEL_END:
      {
        int secondsleft = gLevelTime - (gLastTick - gLevelStartTick);
        if (secondsleft < 0)
          secondsleft = 0;
        gScore += 100 + secondsleft / 40;
        gNextState = STATE_ENDLEVEL;
      }
      break;

The same is (rather awkwardly) also calculated when showing the bonus. Near the end of the rendergame() in game.cpp, change code to look like this:

int seconds = (gLastTick - gLevelStartTick);
      int bonus = (int)(gLevelTime - seconds) / 40;
      if (bonus < 0)
        bonus = 0;
      sprintf(temp, "Time:%0.2fs Bonus:%d", seconds / 1000.0f, bonus);
      drawstring(160, 140, temp);

Next we'll do a brutal change: if player runs out of time, we'll simply drop the player off the board. Add the next two lines to the start of the physics loop in gamephysics() in game.cpp:

if (gLevelTime - (gLastTick - gLevelStartTick) < 0)
    gNextState = STATE_FALLOFF;

Let's show the player the correct message when time runs out as well. In rendergame(), where the "Fall off!" is being printed, change the code to:

if (gGameState == STATE_FALLOFF)
  {
    if (gLevelTime - (gLastTick - gLevelStartTick) < 0)
    {
      drawstring(200, 140, "Out of time!");
    }
    else
    {
      drawstring(200, 140, "Fall off!");
    }
  }

Next we'll reduce lives and move to gameover state if we're out of lives. changestate() function in main.cpp, add this case block when leaving the falloff state:

case STATE_FALLOFF:
    if (gLives == 0)
      gNextState = STATE_GAMEOVER;
    else
      gLives--;
    break;

Next we'll add the end-of-stage and game over states. Still in changestate(), remove the STATE_ENDSTAGE and STATE_GAMEOVER blocks from the things to do when entering a state switch block.

We'll be lazy about this and just overlay the last two bits over the game. In render() add the two states to the rendergame() case list:

case STATE_GAMEOVER:
  case STATE_ENDSTAGE:
  case STATE_ENTRY:
  case STATE_READY:
  case STATE_INGAME:
  case STATE_FALLOFF:
  case STATE_ENDLEVEL:
    rendergame();
    break;

Add the following printouts near the end of rendergame() in game.cpp (before gScreen is unlocked)

if (gGameState == STATE_GAMEOVER)
  {
    drawstring(160, 100, &quot;Game over&quot;);
    drawstring(160, 140, &quot;Better luck next time!&quot;);
    drawstring(160, 200, &quot;Hit space for main menu&quot;);
  }

  if (gGameState == STATE_ENDSTAGE)
  {
    drawstring(160, 100, &quot;Congratulations!&quot;);
    drawstring(160, 120, &quot;You solved the stage!&quot;);
    if (gScore > gCurrentStage->mHiscore)
      drawstring(160, 140, &quot;New high score!&quot;);
    drawstring(160, 200, &quot;Hit space to continue&quot;);
  }

Finally, main.cpp main function needs a change. The SDLK_RETURN / SDLK_SPACE key up event changes to:

case SDLK_RETURN:
    case SDLK_SPACE:
      if (gGameState == STATE_SPLASH)
      {
        gNextState = STATE_MAINMENU;
      }
      if (gGameState == STATE_ENDLEVEL)
      {
        if (gCurrentLevel == 4)
        {
          gNextState = STATE_ENDSTAGE;
        }
        else
        {
          gNextState = STATE_ENTRY;
        }
      }
      if (gGameState == STATE_GAMEOVER)
      {
        gNextState = STATE_MAINMENU;
      }
      if (gGameState == STATE_ENDSTAGE)
      {
        gNextState = STATE_STAGESELECT;
      }
      break;

After this you can compile and run the game, and you'll get the end of level and game over screens.

Before moving on, let's make two more changes. First, when the player dies, it's rather unfair that he keeps the points he received from collecting coins, so let's take them away. Secondly we'll need to record the new high score when player solves a stage.

The first is relatively easy. changestate() in main.cpp, STATE_FALLOFF block changes to:

case STATE_FALLOFF:
    if (gLives == 0)
    {
      gNextState = STATE_GAMEOVER;
    }
    else
    {
      gLives--;
      gScore -= gCollectiblesTaken * 10;
    }
    break;

The second thing is easy to add in the same function. Add the following case when leaving a state:

case STATE_ENDSTAGE:
    if (gCurrentStage->mHiscore < gScore)
      gCurrentStage->mHiscore = gScore;
    break;

The high scores are not persistent between game runs, however, but let's look at that next.

Let's give the player a chance to continue later via 36. Saving and Restoring..

This is really the end, I never continued after this, and because I had not finished the whole block of chapters I had planned, I never published any of the chapters in this block.

Having problems? Improvement ideas? Just want to discuss this tutorial? Try the forums!

Any comments etc. can be emailed to me.