Basic Game Programming Part 2


So here’s a video of the finished product for today. If you haven’t read the last post, you should do that and try the exercises. Better yet, watch the finished product and try to create it without reading the excercises:

We’ll go through all the changes I made, but we’ll start with the most important one. Last week I gave the suggestion “Feature 6: Figure out where to clear the screen to not be so cluttered.” This is a crucial concept in drawing the graphics when making a game.

This toy example gives us the fundamental idea, but if you actually try it in C++, you’ll find it somewhat difficult. I used Linux, so I could use system(“clear”) to clear the terminal screen. You’ll need different commands depending on the operating system. Fighting with drawing things in the proper place will give you a huge appreciation for tools that aid in doing this (we’ll talk about one next week called “ncurses”).

If you watch the video, I programmed it to give the illusion that certain things were static on the screen and don’t get redrawn. In reality, on every iteration of the game loop, I cleared the entire screen and redrew the whole screen with the updated information. This makes it look like “Your Health:” stays the same while the number clicks down as you lose health.

You obviously don’t have to do it this way, but experience has taught me you can get some nasty display bugs if you don’t fully clear and redraw. As I said before, this merely amounted to adding a simple system(“clear”) at the beginning of the Draw() function. I also added a few lines of information.

void Draw() {
  system("clear");
  cout << "Your Health: " << PLYRHLTH << "\n";
  cout << "Enemy Health: " << ENMYHLTH << "\n";
  cout << "a. Attack \n";
  cout << "b. Run \n";
  cout << PLYRDMG;
  cout << ENMYDMG;
}

The other major change was to rescope the variables (you might have noticed they changed names and are all caps, my convention for global variables). Since I wanted many of the functions to be able to access and change the PlayerHealth, EnemyHealth, and so on, I made them all global. This also cleaned up having to pass all of them to each function and allowed me to refactor Update() into its own function.

At the top of the program I list them and initialize them. PLYRDMG is the message “You hit for 2 damage” and so on (see Draw() and compare to the video). It is initialized to be blank so everything writes in the same place:

int PLYRHLTH = 10;
int ENMYHLTH = 10;
string PLYRDMG = "\n";
string ENMYDMG = "\n";

The only thing that changed in the Update() function is an added if statement in a few places so that half the time you miss and half the time the enemy misses. I also had to come up with a way tell the game loop whether the player chose to “Run” and exit the game. This was easy before, because we could write if (PlayerInput == “b”) { break; } to break out of the game loop.

Now the function isn’t in the game loop, so calling “break;” doesn’t work. The way around this was something people came up with in Operating Systems programming a long time ago. I made Update() return an integer rather than be a void. This way I could always return 0 if the player wanted it to keep going. Otherwise it returns 1 and I know they want to break out of the game. This why in C and C++ main() is an int rather than a void. Returning 0 tells the OS that everything went well in the execution of the program.

int Update(string playerinput) {
  if (playerinput == "a") {
    if (rand() % 2 == 0) {
      int r1 = rand() % 4;
      ENMYHLTH -= r1;
      stringstream ss;
      ss << "You hit for " << r1 << " damage.\n";
      PLYRDMG = ss.str();
    } else {
      PLYRDMG = "You miss.\n";
    }
  }

  else if (playerinput == "b") {
    return 1;
  }

  else {
    PLYRDMG = "This is not a valid option.\n";
  }

  //Enemy attacks.
  if (rand() %2 == 0) {
    int r2 = rand() % 4;
    PLYRHLTH -= r2;
    stringstream ss;
    ss << "Enemy hits for " << r2 << " damage.\n";
    ENMYDMG = ss.str();
  } else {
    ENMYDMG = "Enemy Misses!\n";
  }

  return 0;
}

Lastly, I added a “Start Screen” to get into the game. I return to this when the game ends and ask if the player wants to play again. This was done simply by putting the whole game in a second while loop. As long as the player keeps entering “y” for “do you want to play” I keep the boolean variable WantToPlay = true. Otherwise I switch it to false and the game ends. This is also something a prebuilt engine will do for you in a much more elegant way.

Here’s the whole thing:

#include <iostream>
#include <sstream>
#include <stdlib.h>

using namespace std;

int PLYRHLTH = 10;
int ENMYHLTH = 10;
string PLYRDMG = "\n";
string ENMYDMG = "\n";

void ResetVariables() {
  PLYRHLTH = 10;
  ENMYHLTH = 10;
  PLYRDMG = "\n";
  ENMYDMG = "\n";
}

void Draw() {
  system("clear");
  cout << "Your Health: " << PLYRHLTH << "\n";
  cout << "Enemy Health: " << ENMYHLTH << "\n";
  cout << "a. Attack \n";
  cout << "b. Run \n";
  cout << PLYRDMG;
  cout << ENMYDMG;
}

string Input() {
  string tmp;
  cin >> tmp;
  return tmp;
}

int Update(string playerinput) {
  if (playerinput == "a") {
    if (rand() % 2 == 0) {
      int r1 = rand() % 4;
      ENMYHLTH -= r1;
      stringstream ss;
      ss << "You hit for " << r1 << " damage.\n";
      PLYRDMG = ss.str();
    } else {
      PLYRDMG = "You miss.\n";
    }
  }

  else if (playerinput == "b") {
    return 1;
  }

  else {
    PLYRDMG = "This is not a valid option.\n";
  }

  //Enemy attacks.
  if (rand() %2 == 0) {
    int r2 = rand() % 4;
    PLYRHLTH -= r2;
    stringstream ss;
    ss << "Enemy hits for " << r2 << " damage.\n";
    ENMYDMG = ss.str();
  } else {
    ENMYDMG = "Enemy Misses!\n";
  }

  return 0;
}

int main() {
  int State;
  string PlayerInput;
  bool WantToPlay = true;

  while(WantToPlay) {

    //Reset the Initial Information
    ResetVariables();
    srand(time(NULL));
    system("clear");
    cout << "An enemy approaches. Do you fight (y/n)?\n";
    PlayerInput = Input();
    if (PlayerInput == "n") {
      WantToPlay = false;
    }
    Draw();

    //Game Loop
    while(true) {

      PlayerInput = Input();
      State = Update(PlayerInput);

      if (State == 1)
        break;

      //Break out of the game looop if won or lost.
      if (ENMYHLTH <= 0) {
        system("clear");
        cout << "You Win.\n";
        break;
      }
      else if (PLYRHLTH <= 0) {
        system("clear");
        cout << "You Lose.\n";
        break;
      }

      Draw();
    }

    cout << "Game Over.\n";
    cout << "Want to play again (y/n)?\n";
    PlayerInput = Input();
    if (PlayerInput == "n") {
      WantToPlay = false;
    }
  }

  return 0;
}

Next time we’ll work on improving the graphics by using outside tools and getting something moving around the screen so it feels more game-like.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s