Saturday, December 27, 2008

(0) Comments

Paddle Battle

welcome to infomix.blogspot.com

What this tutorial covers

* The creation of a Pong clone
* Simulating intelligence with random logic
* Bouncing a circle back and forth between two digital sticks

Introduction

Before we get to the code for this project I'd like to discuss what's going on in the game. Right now would be a good time to get the .zip file for this tutorial and try the game out. You'll notice that when you run the program you get a whole lot of Pong. Your paddle is on the bottom and the AI's paddle is at the top. If you hit the space bar, the ball will start moving and you and the computer will be given the opportunity to display your wit, reflexes, and ability with the arrow keys.

The scores are shown in the top right of the screen and are colored green just like everything else (thought we could simulate the good old days). When the ball makes contact with a paddle, it is reflected at an angle according to the part of the paddle that it hit. Some Pong clones have a bit more complicated system but I like this one, it gives you total control over where the ball will go.

One last thing to note is that the paddles can't go beyond the sides of the screen and obviously can't move up or down.
Getting started

If you're coming from the old introduction and Tetris tutorials, you should download the new introduction tutorial code, Click Here. The only big difference is the ClearScreen() function. All it does is clears the screen by drawing a black rectangle over everything using the SDL_FillRect() function.

The first thing we need to do is start a new project called "Paddle Battle" and copy in "Main.cpp" and "Defines.h" from the Introduction tutorial. Also remember to copy "SDL.dll", "SDL_ttf.dll", and "ARIAL.TTF" into you project directory and set Project->Paddle Battle Properties->C/C++->Code Generation->Runtime Library to Multi-threaded DLL (/MD). You'll also need the bitmap for this tutorial, which can be found in the downloadable source code.

Since you can win and lose in Pong, just like in Tetris, we'll just copy the GameWon() and GameLost() functions from the previous tutorial. We'll also need to copy in HandleWinLoseInput(). Do that now, and don't forget to get the prototypes as well as the implementations.
The Code
Defines.h

As usual, we'll start our project by setting up our external files. Our game window will now be 800x600 and our caption definately needs to change so replace the previous code with the following:

#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define WINDOW_CAPTION "Paddle Battle"

Now let's define the locations of our game objects within the bitmap file. Add the following to "Defines.h":

// Location of images within bitmap
#define PADDLE_BITMAP_X 0
#define PADDLE_BITMAP_Y 0
#define BALL_BITMAP_X 100
#define BALL_BITMAP_Y 0

Although the paddles move left and right, they'll never move up and down. For this reason, we can safely consider their distances from the top of the screen to be constant. It will be helpful to have these values for detecting collisions with the ball so we'll define them here. We should also define the dimensions of the ball and paddles. Add the following to "Defines.h":

// Locations of paddles in the game
#define COMPUTER_Y 30
#define PLAYER_Y 550

// Dimensions of a paddle
#define PADDLE_WIDTH 100
#define PADDLE_HEIGHT 20

// Diameter of the ball
#define BALL_DIAMETER 20

The two paddles in the game will move at a constant speed. Although we'll have them both move at the same speed (making the computer move faster is cheap!), we'll have two values in case we want to change one of them later.

The speed at which the ball moves vertically will be constant. We'll determine its horizontal speed according to where it hits a paddle. If it hits the edge of a paddle (located 50 pixels from the paddle's center) we'll set its horizontal speed to 10. If it hits the center of the paddle, we'll set its horizontal speed to 0. Note that we can divide the distance from the paddle's center by 5 to get the speed we want. We'll define this value here in case we decide to change it later. Add the following to "Defines.h":

// Paddle speeds
#define PLAYER_SPEED 10
#define COMPUTER_SPEED 10

// Ball speeds
#define BALL_SPEED_MODIFIER 5 // divide location on paddle by this
#define BALL_SPEED_Y 10 // max speed of ball along y axis

The last thing we need to specify here is the locations on the screen where we will display the scores. Add the following to "Defines.h":

// Location of output text
#define COMPUTER_SCORE_X 10
#define COMPUTER_SCORE_Y 10
#define PLAYER_SCORE_X 150
#define PLAYER_SCORE_Y 10
Enums.h

Our enumerations file will be very small for this project. It just has the Direction enumeration from the Falling Blocks tutorial, with UP added to it. Add a file to your project named "Enums.h" and add the following lines of code to it:

#pragma once

enum Direction
{
LEFT,
RIGHT,
UP,
DOWN
};
Includes

The includes for our project will be the same as in the Falling Blocks tutorial only without #include and #include "cBlock.h". Your includes list should look like the following:

#include // We'll use the STL stack to store our function pointers
#include "SDL.h" // Main SDL header
#include "SDL_TTF.h" // True Type Font header
#include "math.h" // We'll be using abs()
#include "time.h" // For seeding the random generator
#include "Defines.h" // Our defines header
#include "Enums.h" // Our enums header

Although we could create some classes to represent our game objects, we can easily get away with just using a single struct. There is only one ball and two paddles, and they each only need to store their speeds and locations. We really don't need to treat them as separate entities. We can just have one struct, which we'll name Entity, that stores the locations and speeds of our game objects.

There are two different locations that we need to store however. First, we need to store the location of the object within the game area. Second, we need to store the location of the object's image within our bitmap. Notice that both of these locations happen to be values that we pass as SDL_Rect structures to SDL functions (SDL_BlitSurface() and SDL_LoadBMP()). Instead of storing the x, y, width and height values separately and creating SDL_Rect structures with these values every time we want to display them, it would be very convenient to always store them as SDL_Rect's. For this reason, our struct will contain two SDL_Rect variables.

Add the following code to "Main.cpp":

// Struct that represents an entity in our game
struct Entity
{
SDL_Rect screen_location; // location on screen
SDL_Rect bitmap_location; // location of image in bitmap

int x_speed;
int y_speed;
};
Global Variables

For global data, all we need is three Entity variables and two integers to keep track of the scores. Add the following to the global data section in "Main.cpp":

Entity g_Computer; // The computer's paddle
Entity g_Player; // The player's paddle
Entity g_Ball; // The game ball
int g_ComputerScore; // AI's score
int g_PlayerScore; // Player's score
Function Prototypes

There are two types of collisions that can happen in our game. The ball or one of the paddles can hit the side of the screen, or the ball can hit one of the paddles. Because our ball and paddles are of the same type, we don't need to do any function overloading here. Our CheckWallCollisions() function will take the entity to check and its direction, and our CheckBallCollisions() function will take a reference to one of our paddles (whichever one the ball is moving towards).

Note that we'll pass our game objects to our functions by reference. If you don't understand this concept, see my Pointers Tutorial.

Add the following to "Main.cpp":

bool CheckWallCollisions(Entity& entity, Direction dir);
bool CheckBallCollisions(Entity& paddle);

The ball and the computer's paddle are never controlled by the player. This means we need to program their behavior, so we should prototype some functions to handle this. For the ball, we'll have a function that determines if the ball has hit one of the paddles or if it has passed one of the paddles. We'll also have a function that handles actually moving the ball. We'll just have one function for the computer's paddle. Add the following to "Main.cpp":

void HandleBall();
void MoveBall();
void HandleAI();

When the ball passes one of the paddles, we need to increase the score of whoever has scored and see if they have scored enough to win. We also need to reset the ball to the center of the screen. We'll have two functions that handle this, one for each paddle. Add the following to "Main.cpp":

void HandlePlayerScore();
void HandleComputerScore();

In the last two tutorials we had a function called DrawBackground(). There is no background in Pong so we can erase that function from our project.
Init()

The first thing we need to add to Init() is a call to srand(). If you remember from the last tutorial, this function gives our random number generator a different value to work with. This prevents rand() from always giving us the same pattern of numbers. Add the following line to Init():

srand( time(0) );

Notice that we seed our generator with the current time.

Now we need to initialize the data in our entities. This is really straight forward but kinda long so feel free to just copy-paste this code. Add the following to Init():

// Initialize the screen locations of two paddles and the ball
g_Computer.screen_location.x = (WINDOW_WIDTH / 2) - (PADDLE_WIDTH / 2); // center screen
g_Computer.screen_location.y = COMPUTER_Y;
g_Computer.screen_location.w = PADDLE_WIDTH;
g_Computer.screen_location.h = PADDLE_HEIGHT;

g_Player.screen_location.x = (WINDOW_WIDTH / 2) - (PADDLE_WIDTH / 2); // center screen
g_Player.screen_location.y = PLAYER_Y;
g_Player.screen_location.w = PADDLE_WIDTH;
g_Player.screen_location.h = PADDLE_HEIGHT;

g_Ball.screen_location.x = (WINDOW_WIDTH / 2) - (BALL_DIAMETER / 2); // center screen
g_Ball.screen_location.y = (WINDOW_HEIGHT / 2) - (BALL_DIAMETER / 2); // center screen
g_Ball.screen_location.w = BALL_DIAMETER;
g_Ball.screen_location.h = BALL_DIAMETER;

// Initialize the image location rects
g_Computer.bitmap_location.x = PADDLE_BITMAP_X;
g_Computer.bitmap_location.y = PADDLE_BITMAP_Y;
g_Computer.bitmap_location.w = PADDLE_WIDTH;
g_Computer.bitmap_location.h = PADDLE_HEIGHT;

g_Player.bitmap_location.x = PADDLE_BITMAP_X;
g_Player.bitmap_location.y = PADDLE_BITMAP_Y;
g_Player.bitmap_location.w = PADDLE_WIDTH;
g_Player.bitmap_location.h = PADDLE_HEIGHT;

g_Ball.bitmap_location.x = BALL_BITMAP_X;
g_Ball.bitmap_location.y = BALL_BITMAP_Y;
g_Ball.bitmap_location.w = BALL_DIAMETER;
g_Ball.bitmap_location.h = BALL_DIAMETER;

// Initialize speeds
g_Computer.x_speed = COMPUTER_SPEED;
g_Player.x_speed = PLAYER_SPEED;
g_Ball.x_speed = 0;
g_Ball.y_speed = 0;

// Set scores to zero
g_ComputerScore = 0;
g_PlayerScore = 0;

Now we need to load the proper bitmap. We just need to use SDL_LoadBMP() for this as usual but there is one new thing we'll be doing here. Because our game objects aren't perfect squares, we're going to end up with parts of our surfaces that we want to be transparent. To do this, we'll choose a color that we want to be transparent and tell SDL to never draw that color when we draw something from our bitmap. By telling SDL to never draw a certain color, we effectively make it an invisible color.

We accomplish this by calling SDL_SetColorKey(), which takes the surface that we're setting a transparent color for and the color that we want to be transparent. We need to pass this color in as a color structure, which we'll get with a call to SDL_MapRGB(). This function takes the color format (bits per color) and the numerical values of the red, green, and blue components of the color. The SDL_Surface structure stores the color format of the bitmap, so we'll use the format of our bitmap.

Add the following to Init():

// Set our transparent color (magenta)
SDL_SetColorKey( g_Bitmap, SDL_SRCCOLORKEY,
SDL_MapRGB(g_Bitmap->format, 255, 0, 255) );

Note that (255, 0, 255) specifies magenta. I've never seen this color used in a game, so it's always the color I choose to be transparent. Also notice the SDL_SRCCOLORKEY parameter. API functions always take parameters like this and a lot of the times you really don't need to worry about what they're for. This is one of those parameters. When you get to bigger APIs like OpenGL or DirectX, you'll waste a lot of time if you hopelessly try to memorize what all of these parameters are for.
Menu(), Exit(), GameWon(), and GameLost()

Aside from Game(), our state functions don't really change (we'll get to Game() in a sec). The only thing we need to do is change the location of the text we display because we changed the size of our window. Replace the existing lines of text output code in the four state functions with the following:

void Menu()
{
...

DisplayText("Start (G)ame", 350, 250, 12, 255, 255, 255, 0, 0, 0);
DisplayText("(Q)uit Game", 350, 270, 12, 255, 255, 255, 0, 0, 0);

....

}

void Exit()
{
...

DisplayText("Quit Game (Y or N)?", 350, 260, 12, 255, 255, 255, 0, 0, 0);

....

}

void GameWon()
{
...

DisplayText("You Win!!!", 350, 250, 12, 255, 255, 255, 0, 0, 0);
DisplayText("Quit Game (Y or N)?", 350, 270, 12, 255, 255, 255, 0, 0, 0);

....

}

void GameLost()
{
...

DisplayText("You Lose.", 350, 250, 12, 255, 255, 255, 0, 0, 0);
DisplayText("Quit Game (Y or N)?", 350, 270, 12, 255, 255, 255, 0, 0, 0);

....

}
Game()

At the beginning of each frame of our game, we need to call HandleBall() and HandleAI(). This will take care of most of our game's functionality. Add the following to Game(), just below the call to HandleGameInput():

HandleBall();
HandleAI();

Since the previous two functions, along with HandleGameInput(), handle most of our game, all we need to do now is draw everything. Because we stored the bitmap and screen locations of our objects in the Entity structure, we no longer have to build SDL_Rect structures to display our game objects. Aside from that, everything here should be fairly familiar to you by now. Remove the call to DrawBackground() and add the following to Game(), just below the call to ClearScreen():

// Draw the two paddles and the ball
SDL_BlitSurface(g_Bitmap, &g_Computer.bitmap_location, g_Window,
&g_Computer.screen_location);
SDL_BlitSurface(g_Bitmap, &g_Player.bitmap_location, g_Window,
&g_Player.screen_location);
SDL_BlitSurface(g_Bitmap, &g_Ball.bitmap_location, g_Window,
&g_Ball.screen_location);

// Output the computer and player scores
char buffer[256];

string c_score = "Computer Score: ";
itoa(g_ComputerScore, buffer, 10);
c_score.append(buffer);

string p_score = "Player Score: ";
itoa(g_PlayerScore, buffer, 10);
p_score.append(buffer);

DisplayText(c_score, COMPUTER_SCORE_X, COMPUTER_SCORE_Y, 12, 66, 239, 16, 0, 0, 0);
DisplayText(p_score, PLAYER_SCORE_X, PLAYER_SCORE_Y, 12, 66, 239, 16, 0, 0, 0);
Collision Detection

Let's start with CheckWallCollisions() because it's really easy. It takes a reference to an Entity object and the direction the object is moving. All we need to do is check to see if the object is going to hit the left or right wall and return true or false accordingly. Add the following to "Main.cpp":

bool CheckWallCollisions(Entity& entity, Direction dir)
{
int temp_x; // stores the location of the entity after moving

// Get the location of the entity after it moves
switch (dir)
{
case LEFT:
{
temp_x = entity.screen_location.x - entity.x_speed;
} break;
case RIGHT:
{
// Notice that we have to add the entity's width to get its
// right(direction) coordinate
temp_x = (entity.screen_location.x + entity.screen_location.w) +
entity.x_speed;
} break;
}

if ( (temp_x <= 0) || (temp_x >= WINDOW_WIDTH) )
{
return true;
}

return false;
}

The function starts with a switch statement that determines the location of the object after it moves. It then checks that location with the sides of the game area. That's it!

CheckBallCollisions() is slightly more complicated. It takes a reference to the paddle that the ball is moving towards so the first thing we need to do is determine which paddle it is. Since we specified the Y locations of our paddles in "Defines.h", and because our paddles never move up or down, we can determine which paddle was passed to the function by checking its Y value against the defined values.

To see if there was a collision, we'll check to see if part of the ball is inside the given paddle. To understand why we would need to check to see if the ball is inside the paddle, imagine the paddle holding still and the ball moving towards it from the side. If the ball hits the side of the paddle, and our function only checks to see if the ball has hit the top of the paddle, the ball will pass right through our paddle. Considering the best way to win pong is by hitting the ball with the side of your paddle, I'd say we should be checking for more than just the top of the paddle hitting the ball.

Note that there is one problem that arises from checking to see if part of the ball is inside of a paddle. If the ball is near the bottom of the paddle when it hits it, it will be reflected backwards. The only problem is that our function will immediately detect another collision (because the ball is inside the paddle) and once again reflect the ball in the other direction. To avoid this, we just check to see if the ball is moving towards the paddle. If it isn't, we shouldn't be checking for collisions anyways.

If you have trouble understanding the code for this function, try drawing the game screen with a coordinate system. This should help you understand when to use the bottom of the ball (ball_y + ball_height) and when to use the top (ball_y). Note that all of the variables at the top of the function are just there to clean the code up a bit.

Add the following to "Main.cpp":

// Check to see if the ball is going to hit a paddle
bool CheckBallCollisions(Entity& paddle)
{
// Temporary values to keep things tidy
int ball_x = g_Ball.screen_location.x;
int ball_y = g_Ball.screen_location.y;
int ball_width = g_Ball.screen_location.w;
int ball_height = g_Ball.screen_location.h;
int ball_speed = g_Ball.y_speed;

int paddle_x = paddle.screen_location.x;
int paddle_y = paddle.screen_location.y;
int paddle_width = paddle.screen_location.w;
int paddle_height = paddle.screen_location.h;

// Get which paddle we're checking against
if ( paddle.screen_location.y == PLAYER_Y)
{
// Check to see if ball is in Y range of the player's paddle.
// We check its speed to see if it's even moving towards the player's paddle.
if ( (ball_speed > 0) && (ball_y + ball_height >= paddle_y) &&
(ball_y + ball_height <= paddle_y + paddle_height) ) // side hit
{
// If ball is in the X range of the paddle, return true.
if ( (ball_x <= paddle_x + paddle_width) && (ball_x + ball_width
>= paddle_x) )
{
return true;
}
}
}
else
{
// Check to see if ball is in Y range of the computer's paddle.
// We check its speed to see if it's even moving towards the computer's paddle.
if ( (ball_speed < 0) && (ball_y >= paddle_y) && (ball_y <= paddle_y +
paddle_height) )
{
// If ball is in the X range of the paddle, return true.
if ( (ball_x <= paddle_x + paddle_width) && (ball_x + ball_width >=
paddle_x) )
{
return true;
}
}
}

return false;
}
HandleBall()

HandleBall() heavily relies on MoveBall(), CheckBallCollisions(), HandlePlayerScore(), and HandleComputerScore(). The first thing we'll do is make a call to MoveBall(). We'll then check for collisions with the paddles using CheckBallCollisions(). If there is a collision, we determine what part of the paddle the ball hit and we change its speed accordingly. Finally, we check to see if the ball has moved passed one of the paddles. When this happens, we call the appropriate Handle...Score() function.

As previously discussed, when the ball hits a paddle we reverse its vertical speed. To determine its new horizontal speed, we divide the distance from the center of the paddle to the center of the ball by BALL_SPEED_MODIFIER. If the ball hits the center of the paddle, its horizontal speed will be 0. If the ball hits the edge of the paddle, its speed will be 10 (unless we change the size of the paddle or the value of BALL_SPEED_MODIFIER).

You'll notice that I once again use temporary variables to clean up the code here. I find this a great way to make the code more readable. It also saves me from having really long lines of code. Add the following to "Main.cpp":

void HandleBall()
{
// Start by moving the ball
MoveBall();

if ( CheckBallCollisions(g_Player) )
{
// Get center location of paddle and ball
int paddle_center = g_Player.screen_location.x +
g_Player.screen_location.w / 2;
int ball_center = g_Ball.screen_location.x +
g_Ball.screen_location.w / 2;

// Find the location on the paddle that the ball hit
int paddle_location = ball_center - paddle_center;

// Increase X speed according to distance from center of paddle.
g_Ball.x_speed = paddle_location / BALL_SPEED_MODIFIER;
g_Ball.y_speed = -g_Ball.y_speed;
}

if ( CheckBallCollisions(g_Computer) )
{
// Get center location of paddle
int paddle_center = g_Computer.screen_location.x +
g_Computer.screen_location.w / 2;
int ball_center = g_Ball.screen_location.x +
g_Ball.screen_location.w / 2;

// Find the location on the paddle that the ball hit
int paddle_location = ball_center - paddle_center;

// Increase X speed according to distance from center of paddle.
g_Ball.x_speed = paddle_location / BALL_SPEED_MODIFIER;
g_Ball.y_speed = -g_Ball.y_speed;
}

// Check to see if someone has scored
if (g_Ball.screen_location.y < 0)
{
HandlePlayerScore();
}
if (g_Ball.screen_location.y + g_Ball.screen_location.h > WINDOW_HEIGHT)
{
HandleComputerScore();
}
}

I'm assuming the code here is fairly self explanatory. I apologize if this seems like a bit of a code dump but I think it's pretty straight-forward (email me and let me know if it's not).
MoveBall()

Every frame we call HandleBall(), which in turn calls MoveBall(). MoveBall() simply moves the ball according to its current speed and checks to see if it hits a wall. If the ball does hit a wall, we just reverse its horizontal speed. Notice that since MoveBall() is constantly called, we just have to change the speed of the ball and wait for MoveBall() to get called again and actually move it.

The actual code is very simple. We start by adding the speed of the ball to its location. This makes the ball actually move. We then check to see if the ball has run into one of the walls. If it has, we negate its horizontal speed. Add the following code to "Main.cpp":

void MoveBall()
{
// Add the current speed of the ball to its location to move it
g_Ball.screen_location.x += g_Ball.x_speed;
g_Ball.screen_location.y += g_Ball.y_speed;

// If the ball is moving left, we see if it hits the wall. If does,
// we change its direction. We do the same thing if it's moving right.
if ( ( (g_Ball.x_speed < 0) && CheckWallCollisions(g_Ball, LEFT) ) ||
( (g_Ball.x_speed > 0) && CheckWallCollisions(g_Ball, RIGHT) ) )
{
g_Ball.x_speed = -g_Ball.x_speed;
}
}
HandleAI()

HandleAI() is probably the most complicated function in this tutorial. Before we discuss the code, we need to decide on what our AI will do. There are all kinds of ways to handle this. Initially I tried having the computer's paddle chase the ball by moving it until its center was over the ball. The problem with this was that the ball always got hit straight forward. It certainly didn't look like a very cunning AI.

To get around this, I decided to select a random number between 1 and 3. 1 represented the left of the computer's paddle, 2 represented the center, and 3 represented the right side. This way the computer made random decisions as to which part of its paddle to hit the ball with. This method turned out to make the computer pretty unpredictable, which I consider to be a good thing. This is the method I'll use for this tutorial.

The only problem now is figuring out when we should have the computer make a decision. Every time HandleAI() gets called we'll move the computer's paddle closer to the ball. If every frame we randomly decide on which part of the paddle to use, the computer's paddle will move around erratically because it will be constantly changing its mind.

To get around this, we'll record the speed of the ball every time this function finishes. At the start of the function, we'll check to see if the ball has changed speeds. If the speed changes, we know that the ball has changed direction and we should make a new decision. Otherwise we'll just keep moving the computer's paddle toward the ball until the appropriate part of the paddle is in the ball's path or the ball changes direction again.

One last thing to note is that if the ball's X location is near the computer paddle's center X location, the computer paddle will jerk back and forth. This is because it will first notice that its center is over the ball so it'll stop moving. Because the ball is still moving, the computer will notice that it is no longer over the ball so it will move again. If it passes the ball, it will notice that it is once again not over the ball so it will move back. This looks pretty bad in the game so we'll only move the computer's paddle if it is a certain distance from the ball.

Add the following to "Main.cpp":

// Move the computer's paddle and change its direction if necessary
void HandleAI()
{
// The first time this function gets called, we record the current speed
// of the ball and decide on which part of the paddle to hit the ball with.
static int last_speed = g_Ball.x_speed;
static int decision = rand() % 3 + 1;

// Keep the code tidy
int computer_x;
int ball_center = g_Ball.screen_location.x + g_Ball.screen_location.w / 2;

// See if ball has changed direction
if (last_speed != g_Ball.x_speed)
{
// Make a new decision if the ball changed direction.
// 1 == left side, 2 == right side, 3 = center
decision = rand() % 3 + 1;

last_speed = g_Ball.x_speed;
}

// Determine part of paddle to hit ball with according to decision
switch (decision)
{
case 1:
{
computer_x = g_Computer.screen_location.x;
} break;

case 2:
{
computer_x = g_Computer.screen_location.x +
g_Computer.screen_location.w;
} break;

case 3:
{
computer_x = g_Computer.screen_location.x +
g_Computer.screen_location.w / 2;
} break;
}

// See if ball is near computer's center. Prevents
// computer from rapidly moving back and forth.
if ( abs(computer_x - ball_center) < 10 )
{
return;
}

// Ball is to the left of the paddle
if (computer_x > ball_center)
{
// Make sure the paddle hasn't hit a side of the screen
if ( !CheckWallCollisions(g_Computer, LEFT) )
{
g_Computer.screen_location.x -= COMPUTER_SPEED;
}
}
// Ball is to the right of the paddle
else if (computer_x < ball_center)
{
// Make sure the paddle hasn't hit a side of the screen
if ( !CheckWallCollisions(g_Computer, RIGHT) )
{
g_Computer.screen_location.x += COMPUTER_SPEED;
}
}
}
HandlePlayerScore() and HandleComputerScore()

These two functions could probably be put into one, but what the heck! We start by increasing the player or computer's global score variable. We then reset the ball's location to the center of the screen. Note that we first place the ball's top-left corner at the center of the screen, and then we move it back by half of its radius. This places the center of the ball in the center of the screen.

To finish up, we just check to see if the player or computer has reached 10 points. If so, we reset the global score variables (in case the player decides to start a new game) and place the appropriate state function on the stack.

Add the following to "Main.cpp":

// Increase the player's score, reset ball, and see if player has won.
void HandlePlayerScore()
{
// Increase score
g_PlayerScore++;

// Reset ball
g_Ball.x_speed = 0;
g_Ball.y_speed = 0;
g_Ball.screen_location.x = (WINDOW_WIDTH / 2) - (BALL_DIAMETER / 2);
g_Ball.screen_location.y = (WINDOW_HEIGHT / 2) - (BALL_DIAMETER / 2);

// Check to see if player has won
if (g_PlayerScore == 10)
{
g_ComputerScore = 0;
g_PlayerScore = 0;

while (!g_StateStack.empty())
{
g_StateStack.pop();
}

StateStruct win;
win.StatePointer = GameWon;
g_StateStack.push(win);
}
}

// Increase computer's score, reset ball, and see it if computer has won.
void HandleComputerScore()
{
// Increase score
g_ComputerScore++;

// Reset ball
g_Ball.x_speed = 0;
g_Ball.y_speed = 0;
g_Ball.screen_location.x = (WINDOW_WIDTH / 2) - (BALL_DIAMETER / 2);
g_Ball.screen_location.y = (WINDOW_HEIGHT / 2) - (BALL_DIAMETER / 2);

// See if computer has won
if (g_ComputerScore == 10)
{
g_ComputerScore = 0;
g_PlayerScore = 0;
while (!g_StateStack.empty())
{
g_StateStack.pop();
}

StateStruct lose;
lose.StatePointer = GameLost;
g_StateStack.push(lose);
}
}
Conclusion

Well that's it for our Pong clone. If you try programming some of these old arcade games on your own, you'll find that you become much faster at it the more you do. Your code should also become a lot cleaner. Although you obviously become more advanced as you try more advanced projects, never count out the easy stuff as a good way to hone your skills. If you're impatient like me, you're probably thinking right now about making some large scale RPG or strategy game. I can assure you though that you'll get there a lot quicker by working through gradually harder projects than you will if you try to rush ahead.

0 Responses to "Paddle Battle"

Post a Comment