Falling Blocks Part 3: Includes, Global Data, and Prototypes
Includes
Because this project is more advanced than the introduction tutorial, we're definately going to need to include some more files. The most obvious are "cBlock.h" and "Enums.h". Note that we don't really need to include "cSquare.h", because it is already included in "cBlock.h". Add the following to "Main.cpp":
#include "Enums.h" // Our enums header
#include "cBlock.h" // Contains the class that represents a game block
When we start our game, we'll randomly select a block type to be the focus block, and once again randomly select a block type to be the next block in line. Every time our focus block changes, we need to select another block type to be the next block in line. We've already enumerated our block types, so we'll use rand() to get a random integer that we'll compare with our BlockType enumerations.
The one problem with rand() is that it doesn't really return a random number. It actually just returns a pattern of numbers that seem to be randomly generated. The function will actually always use the same pattern of number, which will be a very obvious problem to the player. Seeing the same pattern of blocks every game will really ruin the experience.
What we need to do is "seed" our random generator with another number. This basically means that we'll give rand() a different number to use when generating a pattern of numbers. The function that seeds rand() is called srand() and it takes a numerical value as a parameter. One of the most common numbers to seed rand() with is the current time, so we'll call the time() function to get the time. To use time(), we need to include "time.h".
Another file we need is "math.h". This file contains tons of math functions and you should generally assume that you'll be using it in your game projects. Add the following to "Main.cpp":
#include "time.h" // We use time(), located in "time.h", to seed our random generator
#include "math.h" // We'll be using the abs() function located in "math.h"
When the focus block can no longer move, we add its squares to our game area. We'll need to have instant access to the squares in the game area, so it seems like an array would be a good choice. If you want to access an element of an array, you just give the array the index you want to access and you get the element right away (array[4], for example).
One problem with arrays though is that you can't resize an array once you've declared it. Because there's no way to tell how many squares we'll have in our game area at any given time, we need a data structure that can hold a variable amount of data. I've decided to use the STL vector for this reason. The vector structure can change in size, and we can access its elements just like an array. We also should include the string class here. Add the following to "Main.cpp":
#include <vector> // An STL vector will store the game squares
#include <string> // Used for any string functionality we require
Note that the STL vector has no relation to the type of vector you would encounter in math or physics. If you'd like to learn more about the vector structure, see my STL Tutorial.
Global Data
We'll be using all of the global data from the previous tutorial, as well as the data required for this specific project. As already discussed, we'll have two blocks in our game. The focus block will be the block the player actually controls. The other block will be the block that is next in line to be the focus block. We want to have a pointer to this block so we can draw it in the bottom of the screen. Add the following to "Main.cpp":
cBlock* g_FocusBlock = NULL; // The block the player is controlling
cBlock* g_NextBlock = NULL; // The next block to be the focus block
We just discussed the vector of squares in our game, so we can safely add that now. We'll also want data to keep track of the player's current score, as well as the current level. Each time the level changes, the focus block will speed up. We'll have a a global integer to keep track of the focus block's speed. Add the following to "Main.cpp":
vector<cSquare*> g_OldSquares; // The squares that no longer form the focus block
int g_Score = 0; // Players current score
int g_Level = 1; // Current level player is on
int g_FocusBlockSpeed = INITIAL_SPEED; // Speed of the focus block
That's it for our global variables!
Function Prototypes
In the introduction tutorial, we had three functions to represent the three states of our program. For the rest of our projects, we'll need to add two more states. One state will display a victory message and the other will display a game over message. Both states will give the player the option to either quit the program or go back to the main menu. Add the following right below void Exit(); in "Main.cpp":
void Win();
void Lose();
We'll also need a function to handle the input for these two states. We could have two different functions, but all we need to do is check to see if the player has pressed 'n' or 'y' so there wouldn't be much point. Add the following right below the other input functions in "Main.cpp":
void HandleWinLoseInput();
Now we need some functions to handle our game. Collision detection is always necessary in games, so we'll start with that. The two things we can do with our objects are move them around and rotate them. Our objects can either collide with each other, or with the sides of the game area.
Because we'll be moving individual squares as well as entire blocks, we need functions that take both types of objects. Instead of writing functions with different names, we'll take advantage of function overloading. One set of functions will take pointers to squares and the other set will take pointers to blocks.
Only blocks can rotate so we only need one function to handle collisions when the player rotates a block.
Add the following to "Main.cpp":
bool CheckEntityCollisions(cSquare* square, Direction dir);
bool CheckWallCollisions(cSquare* square, Direction dir);
bool CheckEntityCollisions(cBlock* block, Direction dir);
bool CheckWallCollisions(cBlock* block, Direction dir);
bool CheckRotationCollisions(cBlock* block);
Each time the player advances to the next level, we need to check to see if the game has been won.
Each time the focus block is changed and reset to the top of the game area, we need to see if it can still move. If it can't, we know that the game is over because the squares have built up to high.
We'll have functions that check for and handle these two events. Add the following to "Main.cpp":
void CheckWin();
void CheckLoss();
When the focus block either reaches the bottom of the game area or is blocked by another square, we need to change the focus block and check to see if a line has been completed. We'll have three functions handle this process. Add the following to "Main.cpp":
void HandleBottomCollision();
void ChangeFocusBlock();
int CheckCompletedLines();
We are now ready to begin writing our game specific code!
Falling Blocks Part 4: Init(), Shutdown(), and State Functions