Building the Game


In previous hours, you've seen how the game engine that you've built thus far has made it possible to create interesting programs with a relatively small amount of code. You're now going to see the true value of the game engine as you assemble the code for the Brainiac game. The next couple of sections explore the code development for the Brainiac game, which is surprisingly straightforward considering that you're creating a fully functioning game. The complete source code for the Brainiac game, not to mention all of the examples throughout the book, is located on the accompanying CD-ROM.

Writing the Game Code

The code for the Brainiac game begins with the Brainiac.h header file, which is shown in Listing 8.1.

Listing 8.1 The Brainiac.h Header File Declares Global Variables That Are Used to Manage the Game
 1: #pragma once  2:  3: //-----------------------------------------------------------------  4: // Include Files  5: //-----------------------------------------------------------------  6: #include <windows.h>  7: #include "Resource.h"  8: #include "GameEngine.h"  9: #include "Bitmap.h" 10: 11: //----------------------------------------------------------------- 12: // Global Variables 13: //----------------------------------------------------------------- 14: HINSTANCE   _hInstance; 15: GameEngine* _pGame; 16: Bitmap*     _pTiles[9]; 17: BOOL        _bTileStates[4][4]; 18: int         _iTiles[4][4]; 19: POINT       _ptTile1, _ptTile2; 20: int         _iMatches, _iTries; 

This code is where the design work you carried out earlier in this hour directly plays out. More specifically , the pieces of information that were mentioned as critical components of the game are now realized as global variables. The _pTiles variable stores away the tile images (line 16). You might be wondering why the array contains nine bitmaps when there are only eight different tile images for matching. The reason is because there is an extra tile image to represent the backs of the tiles when they are hidden.

graphics/book.gif

You might have noticed in the code that a data type called POINT is being used for the tile selections. This is a standard Win32 data type that stores two pieces of information: an X value and a Y value. The POINT data structure is quite handy and can be used in any situation in which you need to store a point, coordinate, or other numeric data consisting of two integer parts . The X and Y values in the structure are named x and y .


The tile states are stored in a two-dimensional array of Boolean values named _bTileStates (line 17). The idea behind this variable is that a value of TRUE for a tile indicates that it has already been matched, whereas FALSE means that it is still hidden and unmatched. The bitmap associated with each tile is stored in the _iTiles array (line 18), which might seem strange because it doesn't actually reference Bitmap objects. This is because you already have the Bitmap objects available in the _pTiles array, so all the _iTiles array has to do is reference each tile bitmap as an index into the _pTiles array.

The two tile selections are stored in the _ptTile1 and _ptTile2 variables (line 19). These variables are important because they keep track of the user 's tile selections, and are therefore used to determine when a successful match takes place. The last two variables, _iMatches and _iTries , are used to keep track of the number of matches made and the number of match attempts, respectively (line 20). The _iMatches variable will be used a little later in the code to determine if the game is over.

The GameInitialize() function for the Brainiac game is similar to what you've seen in previous examples. In fact, the only change to this function from earlier examples is that the frame rate is set to 1 . This means that the GameCycle() function is only called once per second, which is painfully slow for most games . However, the frame rate theoretically could be zero for Brainiac because there is nothing to update on a regular basis. This is because you know when game updates are necessarywhen the user clicks a tile. Therefore, because it's not a big deal having a high frame rate, you might as well minimize its effect on the game.

The GameStart() and GameEnd() functions are responsible for initializing and cleaning up the game data, as shown in Listing 8.2.

Listing 8.2 The GameStart() Function Creates and Loads the Tile Bitmaps, as well as Initializes Game State Member Variables, Whereas the GameEnd() Function Cleans Up the Bitmaps
 1: void GameStart(HWND hWindow)  2: {  3:   // Seed the random number generator  4:   srand(GetTickCount());  5:  6:   // Create and load the tile bitmaps  7:   HDC hDC = GetDC(hWindow);  8:   _pTiles[0] = new Bitmap(hDC, IDB_TILEBLANK, _hInstance);  9:   _pTiles[1] = new Bitmap(hDC, IDB_TILE1, _hInstance); 10:   _pTiles[2] = new Bitmap(hDC, IDB_TILE2, _hInstance); 11:   _pTiles[3] = new Bitmap(hDC, IDB_TILE3, _hInstance); 12:   _pTiles[4] = new Bitmap(hDC, IDB_TILE4, _hInstance); 13:   _pTiles[5] = new Bitmap(hDC, IDB_TILE5, _hInstance); 14:   _pTiles[6] = new Bitmap(hDC, IDB_TILE6, _hInstance); 15:   _pTiles[7] = new Bitmap(hDC, IDB_TILE7, _hInstance); 16:   _pTiles[8] = new Bitmap(hDC, IDB_TILE8, _hInstance); 17: 18:   // Clear the tile states and images 19:   for (int i = 0; i < 4; i++) 20:     for (int j = 0; j < 4; j++) 21:     { 22:       _bTileStates[i][j] = FALSE; 23:       _iTiles[i][j] = 0; 24:     } 25: 26:   // Initialize the tile images randomly 27:   for (int i = 0; i < 2; i++) 28:     for (int j = 1; j < 9; j++) 29:     { 30:       int x = rand() % 4; 31:       int y = rand() % 4; 32:       while (_iTiles[x][y] != 0) 33:       { 34:         x = rand() % 4; 35:         y = rand() % 4; 36:       } 37:       _iTiles[x][y] = j; 38:     } 39: 40:   // Initialize the tile selections and match/try count 41:   _ptTile1.x = _ptTile1.y = -1; 42:   _ptTile2.x = _ptTile2.y = -1; 43:   _iMatches = _iTries = 0; 44: } 45: 46: void GameEnd() 47: { 48:   // Cleanup the tile bitmaps 49:   for (int i = 0; i < 9; i++) 50:     delete _pTiles[i]; 51: 52:   // Cleanup the game engine 53:   delete _pGame; 54: } 
graphics/book.gif

If you happen to get a "multiple initialization" compiler error while compiling the Brainiac program, you can easily fix it by removing the int variable declaration in line 27 of the code. This error stems from the fact that some compilers don't fully support the standard C++ approach of declaring loop initializer variables local to the loop. So, the int variable i is mistakenly interpreted as being declared twice. Just remove int from line 27 and everything will work fine.


The GameStart() function contains a fair amount of code because all the variables in the game must be carefully initialized for the game to play properly. The function begins by seeding the random number generator (line 4), which is necessary because you'll be randomly placing tiles in a moment. The tile bitmaps are then loaded into the _pTiles array (lines 816). From there, the real work of initializing the game data begins to take place.

Because a value of FALSE indicates that a tile has not yet been matched, it's necessary to initialize each element in the _bTileStates array to FALSE (line 22). Similarly, the elements in the _iTiles array are initialized to (line 23), but for a different reason. When the tiles are randomly placed in the _iTiles array in a moment, it's important to have the elements initialized to so that you can tell which tile spaces are available for tile placement. The actual tile placement occurs in lines 2738, and definitely isn't as complicated as it looks. What's happening is that a tile space is randomly selected and tested to see if it has been filled with a tile (line 32). If so, the code continues to select random tiles until an empty one is found. When an empty space is found, the tile is set (line 37).

The last few steps in the GameStart() function involve setting the _ptTile1 and _ptTile2 variables to -1 (lines 41 and 42). Because these variables are POINT structures, each of them contains an X and Y value. So, you're actually initializing two pieces of information for each of the tile selection variables. The value of -1 comes into play because it's a good way to indicate that a selection hasn't been made. In other words, you can tell that the user hasn't made the first tile selection by simply looking at the values of _ptTile1.x and _ptTile1.y and seeing if they are set to -1 . The _iMatches and _iTries variables are simply initialized to , which makes sense given their purpose (line 43).

The GameEnd() is responsible for cleaning up the tile bitmaps (lines 49 and 50), as well as the game engine (line 53).

The Brainiac game screen is painted in the GamePaint() function, which is shown in Listing 8.3.

Listing 8.3 The GamePaint() Function Draws the Tiles for the Game According to the Tile States Stored in the Game State Member Variables
 1: void GamePaint(HDC hDC)  2: {  3:   // Draw the tiles  4:   int iTileWidth = _pTiles[0]->GetWidth();  5:   int iTileHeight = _pTiles[0]->GetHeight();  6:   for (int i = 0; i < 4; i++)  7:     for (int j = 0; j < 4; j++)  8:       if (_bTileStates[i][j]  ((i == _ptTile1.x) && (j == _ptTile1.y))   9:         ((i == _ptTile2.x) && (j == _ptTile2.y))) 10:         _pTiles[_iTiles[i][j]]->Draw(hDC, i * iTileWidth, j * iTileHeight, 11:           TRUE); 12:       else 13:         _pTiles[0]->Draw(hDC, i * iTileWidth, j * iTileHeight, TRUE); 14: } 

The job of the GamePaint() function is to draw the 4x4 grid of tile bitmaps on the game screen. This is primarily accomplished by examining each tile state and either drawing its associated tile image if it is already matched (lines 10 and 11) or drawing a generic tile image if it isn't matched (line 13). One interesting thing to note in this code is that you have to look beyond the tile state when drawing the tiles because it's important to draw the two selected tiles properly even though they might not be a match. This test is performed in lines 8 and 9 where the tile selection values are taken into account along with the tile state.

Although GamePaint() is important in providing the visuals for the Brainiac game, the bulk of the game logic is in the MouseButtonDown() function, which is shown in Listing 8.4.

Listing 8.4 The MouseButtonDown() Function Carries Out the Majority of the Game Logic for the Brainiac Game
 1: void MouseButtonDown(int x, int y, BOOL bLeft)  2: {  3:   // Determine which tile was clicked  4:   int iTileX = x / _pTiles[0]->GetWidth();  5:   int iTileY = y / _pTiles[0]->GetHeight();  6:  7:   // Make sure the tile hasn't already been matched  8:   if (!_bTileStates[iTileX][iTileY])  9:   { 10:     // See if this is the first tile selected 11:     if (_ptTile1.x == -1) 12:     { 13:       // Set the first tile selection 14:       _ptTile1.x = iTileX; 15:       _ptTile1.y = iTileY; 16:     } 17:     else if ((iTileX != _ptTile1.x)  (iTileY != _ptTile1.y)) 18:     { 19:       if (_ptTile2.x == -1) 20:       { 21:         // Increase the number of tries 22:         _iTries++; 23: 24:         // Set the second tile selection 25:         _ptTile2.x = iTileX; 26:         _ptTile2.y = iTileY; 27: 28:         // See if it's a match 29:         if (_iTiles[_ptTile1.x][_ptTile1.y] == 30:           _iTiles[_ptTile2.x][_ptTile2.y]) 31:         { 32:           // Set the tile state to indicate the match 33:           _bTileStates[_ptTile1.x][_ptTile1.y] = TRUE; 34:           _bTileStates[_ptTile2.x][_ptTile2.y] = TRUE; 35: 36:           // Clear the tile selections 37:           _ptTile1.x = _ptTile1.y = _ptTile2.x = _ptTile2.y = -1; 38: 39:           // Update the match count and check for winner 40:           if (++_iMatches == 8) 41:           { 42:             TCHAR szText[64]; 43:             wsprintf(szText, "You won in %d tries.", _iTries); 44:             MessageBox(_pGame->GetWindow(), szText, TEXT("Brainiac"), 45:               MB_OK); 46:           } 47:         } 48:       } 49:       else 50:       { 51:         // Clear the tile selections 52:         _ptTile1.x = _ptTile1.y = _ptTile2.x = _ptTile2.y = -1; 53:       } 54:     } 55: 56:     // Force a repaint to update the tile 57:     InvalidateRect(_pGame->GetWindow(), NULL, FALSE); 58:   } 59: } 

The first step in the MouseButtonDown() function is to determine which tile was clicked. This determination takes place in lines 4 and 5, and involves using the mouse cursor position provided in the x and y arguments passed into the function. When you know which tile was clicked, you can quickly check and see if it has already been matched (line 8); if so, there is no reason to do anything else because it doesn't make sense to play a tile that has already been matched. Assuming that the tile hasn't already been matched, the next test is to see if this is the first tile selected (line 11); if so, all you have to do is store the tile position in _ptTile1 . If not, you have to move on and make sure that it isn't the first tile being reselected, which wouldn't make much sense (line 17). If you pass that test, you have to check and see if this is indeed the second tile selection (line 19); if so, it's time to go to work and see if there is a match. If this isn't the second tile selection, you know it must be the third click in an unsuccessful match, which means that the tile selections need to be cleared so that the selection process can be repeated (lines 4751).

Getting back to the second tile selection, when you know that the user has just selected the second tile, you can safely increment the number of tries (line 22), as well as store away the tile selection (lines 25 and 26). You can then check for a match by comparing the two tile selections to each other (line 29). If there is a match, the tile states are modified for each tile (lines 32 and 33), and the tile selections are cleared (line 36). The match count is then incremented and checked to see if the game is over (line 39). If the game is over, a window is displayed that notifies you of winning, as well as displays how many tries it took (lines 4143).

The last step in the MouseButtonDown() function is to force a repaint of the game screen with a call to InvalidateRect() (line 56). This line is extremely important because it results in the user's action (the tile selection with a mouse click) being visually carried out on the screen.

Tweaking Your Compiler for the Brainiac Program

The Brainiac program example, and all the remaining programs in the book for that matter, rely on two standard libraries that aren't automatically linked into a Win32 program. These libraries are called winmm.lib and msimg32.lib, and they are included with all Windows compilers. Before you attempt to compile any of the remaining programs in the book, including Brainiac, make sure that you change the link settings for the program so that the winmm.lib and msimg32.lib library files are linked into the final executable. Refer to the documentation for your specific compiler for how this is done, or follow these steps if you're using Microsoft Visual Studio:

  1. Open the project in Visual Studio (Visual C++).

  2. Right-click on the project's folder in Solution Explorer, and click Properties in the pop-up menu.

  3. Click the Linker folder in the left pane of the Properties window, and then click Input.

  4. Click next to Additional Dependencies in the right pane, and type winmm.lib followed by a space followed by msimg32.lib .

  5. Click the OK button to accept the changes to the project.

After completing these steps, you can safely compile a program and know that the winmm.lib and msimg32.lib libraries are being successfully linked into the executable program file.



Sams Teach Yourself Game Programming in 24 Hours
Sams Teach Yourself Game Programming in 24 Hours
ISBN: 067232461X
EAN: 2147483647
Year: 2002
Pages: 271

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net