Building the Space Out 4 Game


The final version of the Space Out game that you create in this book is called Space Out 4, and it completes the game by including a high score list that is stored to a file on disk. You've learned enough about high scores and file I/O in this hour to handle the task of adding a high score list to the game. So, let's get started!

Writing the Game Code

The Space Out 4 game requires only one new global variable, which is the array of integers that stores the high score list. Following is the _iHiScores variable as it appears in the SpaceOut.h header file:

 int _iHiScores[5]; 

This variable is pretty straightforward in that it stores away five integers that represent the top five scores for the game. The scores are arranged in order of most to least.

Several new functions are required to successfully update, read, and write the high score list in the Space Out 4 game:

 void UpdateHiScores(); BOOL ReadHiScores(); BOOL WriteHiScores(); 

The UpdateHiScores() function is used to determine if a new score has made it into the high score list. If so, the function makes sure to insert the score in the correct position and slide the lower scores down a notch to make room for the new score. The ReadHiScores() and WriteHiScores() functions use the _iHiScores array as the basis for reading and writing the high score list from and to the HiScores.dat file. You learn how each of these functions work a little later in the hour, but for now it's important to see how they are used in the context of the game.

The GameStart() function is responsible for initializing the game, which now includes reading the high score list. The only change to this function is the new call to the ReadHiScores() function:

 ReadHiScores(); 

This call makes sure that the high score list is properly initialized before the game attempts to draw the scores later in the GamePaint() function.

The GameEnd() function cleans up resources used by the game, and serves as a great place to write the high score list to a file. A simple addition to this function is all that is required to write the high score list:

 WriteHiScores(); 

Of course, the high score list wouldn't be of much use if the game didn't display it for all to see. This takes place in the GamePaint() function, which is shown in Listing 24.1.

Listing 24.1 The GamePaint() Function Draws the High Score List if the Game Is in Demo Mode
 1: void GamePaint(HDC hDC)  2: {  3:   // Draw the background  4:   _pBackground->Draw(hDC);  5:  6:   // Draw the desert bitmap  7:   _pDesertBitmap->Draw(hDC, 0, 371);  8:  9:   // Draw the sprites 10:   _pGame->DrawSprites(hDC); 11: 12:   if (_bDemo) 13:   { 14:     // Draw the splash screen image 15:     _pSplashBitmap->Draw(hDC, 142, 20, TRUE); 16: 17:     // Draw the hi scores 18:     TCHAR szText[64]; 19:     RECT  rect = { 275, 230, 325, 250}; 20:     SetBkMode(hDC, TRANSPARENT); 21:     SetTextColor(hDC, RGB(255, 255, 255)); 22:     for (int i = 0; i < 5; i++) 23:     { 24:       wsprintf(szText, "%d", _iHiScores[i]); 25:       DrawText(hDC, szText, -1, &rect, DT_SINGLELINE  DT_CENTER  26:         DT_VCENTER); 27:       rect.top += 20; 28:       rect.bottom += 20; 29:     } 30:   } 31:   else 32:   { 33:     // Draw the score 34:     TCHAR szText[64]; 35:     RECT  rect = { 460, 0, 510, 30 }; 36:     wsprintf(szText, "%d", _iScore); 37:     SetBkMode(hDC, TRANSPARENT); 38:     SetTextColor(hDC, RGB(255, 255, 255)); 39:     DrawText(hDC, szText, -1, &rect, DT_SINGLELINE  DT_RIGHT  40:        DT_VCENTER); 41: 42:     // Draw the number of remaining lives (cars) 43:     for (int i = 0; i < _iNumLives; i++) 44:       _pSmCarBitmap->Draw(hDC, 520 + (_pSmCarBitmap->GetWidth() * i), 45:         10, TRUE); 46: 47:     // Draw the game over message, if necessary 48:     if (_bGameOver) 49:       _pGameOverBitmap->Draw(hDC, 170, 100, TRUE); 50:   } 51: } 

The high score list is drawn just after the splash screen image by looping through each score and drawing it a little below the previous score (lines 17 “29). Notice that the high score list is only drawn if the game is in demo mode (line 12), which makes sense when you consider that the high score list is something you want to see in between games .

The last game function impacted by the high score list is the SpriteCollision() function, which is important because it detects when a game ends. Listing 24.2 contains the code for the SpriteCollision() function.

Listing 24.2 The SpriteCollision() Function Updates the High Score List Whenever a Game Ends
 1: BOOL SpriteCollision(Sprite* pSpriteHitter, Sprite* pSpriteHittee)  2: {  3:   // See if a player missile and an alien have collided  4:   Bitmap* pHitter = pSpriteHitter->GetBitmap();  5:   Bitmap* pHittee = pSpriteHittee->GetBitmap();  6:   if ((pHitter == _pMissileBitmap && (pHittee == _pBlobboBitmap   7:     pHittee == _pJellyBitmap  pHittee == _pTimmyBitmap))   8:     (pHittee == _pMissileBitmap && (pHitter == _pBlobboBitmap   9:     pHitter == _pJellyBitmap  pHitter == _pTimmyBitmap))) 10:   { 11:     // Play the small explosion sound 12:     PlaySound((LPCSTR)IDW_LGEXPLODE, _hInstance, SND_ASYNC  13:       SND_RESOURCE); 14: 15:     // Kill both sprites 16:     pSpriteHitter->Kill(); 17:     pSpriteHittee->Kill(); 18: 19:     // Create a large explosion sprite at the alien's position 20:     RECT rcBounds = { 0, 0, 600, 450 }; 21:     RECT rcPos; 22:     if (pHitter == _pMissileBitmap) 23:       rcPos = pSpriteHittee->GetPosition(); 24:     else 25:       rcPos = pSpriteHitter->GetPosition(); 26:     Sprite* pSprite = new Sprite(_pLgExplosionBitmap, rcBounds); 27:     pSprite->SetNumFrames(8, TRUE); 28:     pSprite->SetPosition(rcPos.left, rcPos.top); 29:     _pGame->AddSprite(pSprite); 30: 31:     // Update the score 32:     _iScore += 25; 33:     _iDifficulty = max(80 - (_iScore / 20), 20); 34:   } 35: 36:   // See if an alien missile has collided with the car 37:   if ((pHitter == _pCarBitmap && (pHittee == _pBMissileBitmap  38:     pHittee == _pJMissileBitmap  pHittee == _pTMissileBitmap))  39:     (pHittee == _pCarBitmap && (pHitter == _pBMissileBitmap  40:     pHitter == _pJMissileBitmap  pHitter == _pTMissileBitmap))) 41:   { 42:     // Play the large explosion sound 43:     PlaySound((LPCSTR)IDW_LGEXPLODE, _hInstance, SND_ASYNC  44:       SND_RESOURCE); 45: 46:     // Kill the missile sprite 47:     if (pHitter == _pCarBitmap) 48:       pSpriteHittee->Kill(); 49:     else 50:       pSpriteHitter->Kill(); 51: 52:     // Create a large explosion sprite at the car's position 53:     RECT rcBounds = { 0, 0, 600, 480 }; 54:     RECT rcPos; 55:     if (pHitter == _pCarBitmap) 56:       rcPos = pSpriteHitter->GetPosition(); 57:     else 58:       rcPos = pSpriteHittee->GetPosition(); 59:     Sprite* pSprite = new Sprite(_pLgExplosionBitmap, rcBounds); 60:     pSprite->SetNumFrames(8, TRUE); 61:     pSprite->SetPosition(rcPos.left, rcPos.top); 62:     _pGame->AddSprite(pSprite); 63: 64:     // Move the car back to the start 65:     _pCarSprite->SetPosition(300, 405); 66: 67:     // See if the game is over 68:     if (--_iNumLives == 0) 69:     { 70:       // Play the game over sound 71:       PlaySound((LPCSTR)IDW_GAMEOVER, _hInstance, SND_ASYNC  72:         SND_RESOURCE); 73:       _bGameOver = TRUE; 74:       _bGameOverDelay = 150; 75: 76:       // Update the hi scores 77:       UpdateHiScores(); 78:     } 79:   } 80: 81:   return FALSE; 82: } 

When a game ends, the SpriteCollision() function plays a game over sound and sets the game over delay, which you already know about (lines 71 “74). However, what you don't already know about is the line of code that calls the UpdateHiScores() function to give the game a chance to insert the new score in the high score list (line 77). You don't have to worry about whether the score is high enough to make the high score list because this is determined by the UpdateHiScores() function, which is shown in Listing 24.3.

Listing 24.3 The UpdateHiScores() Function Checks to See if a High Score Should Be Added to the High Score List, and Adds It if Necessary
 1: void UpdateHiScores()  2: {  3:   // See if the current score made the hi score list  4:   int i;  5:   for (i = 0; i < 5; i++)  6:   {  7:     if (_iScore > _iHiScores[i])  8:       break;  9:   } 10: 11:   // Insert the current score into the hi score list 12:   if (i < 5) 13:   { 14:     for (int j = 4; j > i; j--) 15:     { 16:       _iHiScores[j] = _iHiScores[j - 1]; 17:     } 18:     _iHiScores[i] = _iScore; 19:   } 20: } 

Although the code for the UpdateHiScores() function looks a little tricky at first, it really isn't too bad. The first loop checks to see if the score is higher than any of the existing high scores (lines 5 “9). If so, the second loop is entered, which handles the task of inserting the score in the correct position in the list, as well as sliding down the lower scores in the list (lines 12 “19). You might notice that the list is looped through in reverse, which allows it to easily move scores down the list to make room for the new score (lines 14 “17). After a space has been made, the new score is placed in the high score list (line 18).

The high scores are written to a data file in the WriteHiScores() function, which is shown in Listing 24.4.

Listing 24.4 The WriteHiScores() Function Writes the High Score List to the File HiScores.dat
 1: BOOL WriteHiScores()  2: {  3:   // Create the hi score file (HiScores.dat) for writing  4:   HANDLE hFile = CreateFile(TEXT("HiScores.dat"), GENERIC_WRITE, 0, NULL,  5:     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);  6:   if (hFile == INVALID_HANDLE_VALUE)  7:     // The hi score file couldn't be created, so bail  8:     return FALSE;  9: 10:   // Write the scores 11:   for (int i = 0; i < 5; i++) 12:   { 13:     // Format each score for writing 14:     CHAR cData[6]; 15:     wsprintf(cData, "%05d", _iHiScores[i]); 16: 17:     // Write the score 18:     DWORD dwBytesWritten; 19:     if (!WriteFile(hFile, &cData, 5, &dwBytesWritten, NULL)) 20:     { 21:       // Something went wrong, so close the file handle 22:       CloseHandle(hFile); 23:       return FALSE; 24:     } 25:   } 26: 27:   // Close the file 28:   return CloseHandle(hFile); 29: } 

Most of this code should look familiar to you because it is very similar to the code you saw earlier in the hour when discussing how to create a new file and write a score using the CreateFile() and WriteFile() functions. A new HiScores.dat file is first created with a call to the CreateFile() function (lines 4 “8); even if the file already exists, a new one is created to replace it. After the file is created, the function prepares to write to the file by looping through the high scores (line 11). Each score must be formatted into a five-digit character string before it can be written (lines 14 and 15). The score is then written to the file with a call to the WriteFile() function (line 19). If an error occurs during the write, the file handle is closed with a call to CloseHandle() (line 22). If all goes well and the data is successfully written, the function finishes by closing the file with a call to CloseHandle() (line 28).

Not surprisingly, the ReadHiScores() function works similarly to WriteHiScores() , except everything happens in the reverse. Listing 24.5 contains the code for the ReadHiScores() function.

Listing 24.5 The ReadHiScores() Function Reads the High Score List from the File HiScores.dat
 1: BOOL ReadHiScores()  2: {  3:   // Open the hi score file (HiScores.dat)  4:   HANDLE hFile = CreateFile(TEXT("HiScores.dat"), GENERIC_READ, 0, NULL,  5:     OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);  6:   if (hFile == INVALID_HANDLE_VALUE)  7:   {  8:     // The hi score file doesn't exist, so initialize the scores to 0  9:     for (int i = 0; i < 5; i++) 10:       _iHiScores[i] = 0; 11:     return FALSE; 12:   } 13: 14:   // Read the scores 15:   for (int i = 0; i < 5; i++) 16:   { 17:     // Read the score 18:     char  cData[6]; 19:     DWORD dwBytesRead; 20:     if (!ReadFile(hFile, &cData, 5, &dwBytesRead, NULL)) 21:     { 22:       // Something went wrong, so close the file handle 23:       CloseHandle(hFile); 24:       return FALSE; 25:     } 26: 27:     // Extract each integer score from the score data 28:     _iHiScores[i] = atoi(cData); 29:   } 30: 31:   // Close the file 32:   return CloseHandle(hFile); 33: } 

The ReadHiScores() function reads the high score list from the HiScores.dat file. The CreateFile() function is again used to obtain a file handle, but this time an existing file is opened, as opposed to creating a new file (lines 4 and 5). If there is an error reading the file, such as if the file doesn't exist, the high score array is simply filled with scores of (lines 9 and 10). This code is very important because the first time you play the game, there won't be a high score file.

If the file is opened okay, the function starts a loop to read each score from the file (line 15). Each score is then read by calling the ReadFile() function (line 20). Simply reading the scores from the file isn't sufficient to place the score in the high score list because the scores are read as five-digit character strings. Each score must be converted to an integer number before you can add it to the high score array (line 28). After reading and converting all the scores, the ReadFile() function finishes by calling the CloseHandle() function to close the file (line 32).

Testing the Finished Product

Similar to a few of its predecessors, the Space Out 4 game is quite simple to test. In fact, it's also a fun test to perform because you need to play the game a few times and build up some scores on the high score list. Figure 24.2 shows the high score list in the game upon playing the game for the first time, which means that the list is filled with scores of .

Figure 24.2. Prior to playing the Space Out 4 game for the first time, the high score list is full of zero scores.

graphics/24fig02.jpg

Keep in mind that in this figure the game still tried to read the high scores from a file, but the file didn't exist, so the scores were zeroed out. After you play a few games, the list will start looking better as it fills out with new high scores. Whenever you exit the game, the high score list gets stored to a file. Upon restarting the game, the high score list is restored by reading the scores from the same file. Figure 24.3 shows a high score list that has just been restored from a file.

Figure 24.3. The high score list was read from a file, which causes it to persist between games.

graphics/24fig03.jpg

The high score list really is a neat enhancement to the Space Out game because it allows you to keep track of the best games you've played , potentially over a long period of time. Keep in mind that you can easily clear out the high score list by simply deleting the HiScores.dat 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