Now it is time to return your attention to this chapter's game project, the Hangman game. The Hangman game is based on a children's game in which the player attempts to guess a secret word. If the player guesses the word before making six incorrect guesses, the player wins. Otherwise, he loses.
You will create the Hangman game by following the same five basic development steps that you followed when completing previous chapter projects.
The Hangman game will be played within a single window. Therefore, it will be made up of one form and the 25 controls listed in Table 8.1.
Control Type | Control Name | Description |
---|---|---|
Panel1 | pnlLetters | Groups TextBox controls that will display letters of a guessed word |
TextBox1 | txtLetter1 | Displays the first letter in the game's secret word |
TextBox2 | txtLetter2 | Displays the second letter in the game's secret word |
TextBox3 | txtLetter3 | Displays the third letter in the game's secret word |
TextBox4 | txtLetter4 | Displays the fourth letter in the game's secret word |
TextBox5 | txtLetter5 | Displays the fifth letter in the game's secret word |
TextBox6 | txtLetter6 | Displays the sixth letter in the game's secret word |
TextBox7 | txtLetter7 | Displays the seventh letter in the game's secret word |
TextBox8 | txtLetter8 | Displays the eighth letter in the game's secret word |
TextBox9 | txtLetter9 | Displays the ninth letter in the game's secret word |
TextBox10 | txtLetter10 | Displays the tenth letter In the game's secret word |
TextBox11 | txtMisses | Displays the number of misses that the player has made |
TextBox12 | txtGuesses | Displays the letters that the player has already guessed (both right and wrong) |
TextBox13 | txtGamesWon | Displays the number of games that the player has won |
TextBox14 | txtGamesLost | Displays the number of games that the player has lost |
GroupBox1 | grpStats | Contains Label and TextBox controls that display game statistics |
Label1 | lblInput | Identifies the TextBox control where the player enters a letter guess |
Label2 | lblNoMissed | Identifies the TextBox control that displays the number of misses that the player has made |
Label3 | lblGuessed | Identifies the TextBox control that displays the letters that the player has already guessed (both right and wrong) |
Label4 | lblGamesWon | Identifies the TextBox control that displays the number of games that the player has won |
Label5 | lblGamesLost | Identifies the TextBox control that displays the number of games that the player has lost |
Button1 | btnSubmit | Executes code that checks to see if the player has guessed a letter in the secret word |
PictureBox1 | pbcHangmanBmp | Displays a graphic that shows how many letter guesses the player has missed |
ImageList1 | imlHangmanBmps | Stores an indexed collection of the Hangman images that are displayed in the PictureBox control |
The first step in creating the Hangman game is to open Visual C++ and create a new project, as outlined here:
If you have not already done so, start Visual C++ 2005 Express and then click on File, New Project. The New Project dialog box will appear.
Select Windows Application template.
Type Hangman as the name of your new application in the Name field located at the bottom of the New Project window.
Click on OK to close the New Project dialog box.
Visual C++ then creates a new project for you and displays a new form upon which you will design the game's user interface.
Now let's begin work on laying out the game's user interface by adding controls to the form and moving and resizing them in the appropriate locations. Refer to Figure 8.11 as you go through each step so that you know how to resize and place the controls.
Begin by clicking on the form and setting its Size property to 655, 412.
Add a Panel control to the form and resize it, as shown in Figure 8.11.
Hint | A Panel control is a control that that stores and organizes other controls. |
Add 10 TextBox controls inside the Panel control and resize them.
Add a Label control to the form. By default, Visual C++ names it Label1.
Just to the right of the Labell control, add a TextBox control and a Button control.
Add a GroupBox control at the bottom of the form and resize it until it takes up the bottom 30 percent of the form.
Inside the GroupBox control, add four Label and four TextBox controls.
Add a PictureBox control to the lower-right corner of the form and set its size to 200,200.
Finally, add an ImageList control, which will appear in a Component Tray, located at the bottom of the Form Designer.
Figure 8.11: Examining the Layout of the Hangman game's user interface.
Now that you've completed the overall layout for the Hangman game user interface, you can begin form and control property customization.
Let's begin by modifying properties that are associated with the game's form, as shown in Table 8.2.
Property | Value |
---|---|
Name | frmMain |
Cursor | Hand |
FormBorderStyle | Fixed3D |
MaximizeBox | false |
MinimizeBox | false |
Size | 540,360 |
StartPosition | CenterScreen |
Text | Hangman |
Next, modify the properties associated with the Panel control, as shown in Table 8.3.
Property | Value |
---|---|
Name | pnlLetters |
BackColor | Menu |
BorderStyle | Fixed3D |
Then modify the properties associated with the GroupBox control, as shown in Table 8.4.
Property | Value |
---|---|
Name | grpStats |
Text | Game Stats: |
Now modify the properties associated with the TextBox controls, as shown in Table 8.5.
Control | Property | Value |
---|---|---|
TextBox1 | Name | txtLetter1 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox2 | Name | txtLetter2 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox3 | Name | txtLetter3 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox4 | Name | txtLetter4 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox5 | Name | txtLetter5 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox6 | Name | txtLetter6 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox7 | Name | txtLetter7 |
BackColor | Menu | |
B Bold orderStyle | None | |
Font.Size | 36 | |
Font. | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox8 | Name | txtLetter8 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox9 | Name | txtLetter9 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox10 | Name | txtLetter10 |
BackColor | Menu | |
BorderStyle | None | |
Font.Size | 36 | |
Font.Bold | true | |
ForeColor | Menu Highlight | |
ReadOnly | true | |
TabStop | false | |
TextBox11 | Name | txtInput |
CharacterCasing | Upper | |
Font.Size | 18 | |
Font.Bold | true | |
TextBox12 | Name | txtMisses |
ReadOnly | true | |
TabStop | false | |
TextBox13 | Name | txtGuesses |
ReadOnly | true | |
TabStop | false | |
TextBox14 | Name | txtGamesWon |
ReadOnly | true | |
TabStop | false | |
TextBox15 | Name | txtGamesLost |
ReadOnly | true | |
TabStop | false |
Then modify the properties associated with the Button control, as shown in Table 8.6.
Property | Value |
---|---|
Name | btnSubmit |
Font.Bold | true |
Text | Submit |
Modify the properties associated with the Label controls, as shown in Table 8.7.
Control | Property | Value |
---|---|---|
Label1 | Name | 1blInput |
Text | Enter A Letter Guess: | |
Font.Bold | true | |
Label2 | Name | lblNoMissed |
Text | No. Of Misses: | |
Font.Bold | true | |
Label3 | Name | 1blGuesses |
Text | No. of Guesses: | |
Font.Bold | true | |
Label4 | Name | 1blGamesWon |
Text | No. Games Won: | |
Font.Bold | true | |
Label5 | Name | lblGamesLost |
Text | No. Games Lost: | |
Font.Bold | true |
From there, modify the properties associated with the PictureBox control, as shown in Table 8.8.
Property | Value |
---|---|
Name | pbcHangmanBmp |
BorderStyle | Fixed3D |
Size | 200,200 |
SizeMode | StretchImage |
Finally, change the Name property of the ImageList control to imlHangmanBmps and add the following bitmap images to the Images property (col1ection), as shown in Table 8.9. Don't worry about having to create these bitmap images yourself. I have included copies of them with the source for the game project on the book's companion Web site.
Property | File | Index No. |
---|---|---|
Name | 100.bmp | 0 |
Name | 80.bmp | 1 |
Name | 60.bmp | 2 |
Name | 40.bmp | 3 |
Name | 20.bmp | 4 |
Name | 10.bmp | 5 |
Name | 0.bmp | 6 |
Begin coding the Hangman game by double-clicking on the game's form, which causes Visual C++ to open the Code Editor and position the cursor on the game's Load event function, as shown here:
private: System::Void frmMain_Load(System::Object^ sender,\ System::EventArgs^ e) { }
Above the Load function, add the code to the area reserved for module variables, as shown next:
private: /// <summary> /// Required designer variable. /// </summary> //Array to hold game words array<String^>^ strWordListArray; //String for game's current word String^ strGameWord; //String for missed guesses String^ strWrong; //Counter for missed guesses Int32 intMisses; //Counter for correct guesses Int32 intNoRight; //Counter for games won Int32 intGamesWon; //Counter for games lost Int32 intGamesLost: //Controls display of Hangman image Int32 intHangmanNo;
These statements define a special type of .NET array capable of handling arrays of strings, two variables (handles) for holding the game's current word and missed guesses, and several Int32 counters that track the player's progress. The final variable, intHangmanNo, controls which image of the gallows to display, based on the player's progress in the game.
Now add the following statements to the Form1_Load function:
private: System::Void frmMain_Load(System::Object^ sender,\ System::EventArgs^ e) { strGameWord = gcnew String(""); strWrong = gcnew String(""); intGamesWon = 0; intGamesLost = 0; intHangmanNo = 6; //Populate the array with words LoadWordArray(); //Set up the game's starting values InitializeGameStats(); StartTheGame(); //Start the game }
The function begins by setting aside memory for two of the game's String handles—strGameWord and strWrong—and initializing both to an empty value (for example, using ""). Next, the number of games won and lost is set, as is the variable, intHangmanNo, that will later control what gallows image to display. Finally, the Form1_Load function sets up gameplay by calling three custom functions. The LoadWordArray function populates the strWordListArray array with 20 secret words. To create it, scroll down below the Form1_Load function and enter the following statements:
private: Void LoadWordArray( Void ) { const Int32 cintArraySize = 20; StrWordListArray = \ gcnew array<String^>(cintArraySize); strWordListArray->SetValue( "ELEPHANT", 0 ); strWordListArray->SetValue( "CARRIAGE", 1 ); strWordListArray->SetValue( "ENVELOPE", 2 ); strWordListArray->SetValue( "PRESIDENT", 3 ); strWordListArray->SetValue( "ELLIPTICAL", 4 ); strWordListArray->SetValue( "MARKER", 5 ); strWordListArray->SetValue( "BATTLESHIP", 6 ); strWordListArray->SetValue( "TERMINATE", 7 ); strWordListArray->SetValue( "REJOICE", 8 ); strWordListArray->SetValue( "LIBERTY", 9 ); strWordListArray->SetValue( "CLASSIC", 10 ); strWordListArray->SetValue( "REFLEX", 11 ); strWordListArray->SetValue( "VERSION", 12 ); strWordListArray->SetValue( "DWELLING", 13 ); strWordListArray->SetValue( "APARTMENT", 14 ); strWordListArray->SetValue( "MATRIX", 15 ); strWordListArray->SetValue( "FAUCET", 16 ); strWordListArray->SetValue( "BEDROOM", 17 ); strWordListArray->SetValue( "VOLLEY", 18 ); strWordListArray->SetValue( "WHISTLE", 19 ); }
This function first uses gcnew to initialize an object to hold all the strings. The format is a bit tricky to understand at first, but what the function does is to first declare a constant, cintArraySize, that is set to the value 20. This constant then sets the size of the array requested (20 elements). The gcnew keyword allocates an array (using the array keyword) of String handles, which are denoted by the syntax String^. The resulting block of memory is then populated with all the words within the game using the SetValue method for each element.
Next, the InitializeGameStats function sets the game statistics displayed in the GroupBox control to zeroes. You'll need to create this function from scratch by inserting it just under the LoadWordArray function. Its format is as follows:
private: Void InitializeGameStats( Void ) { //Reset and display games won and lost intGamesWon = 0; intGamesLost = 0; txtGamesWon->Text = intGamesWon.ToString(); txtGamesLost->Text = intGamesLost.ToString(); }
The StartTheGame function is responsible for setting up the game to play a new round. It does so by making three function calls, which reset variable defaults, retrieve a secret game word, and format the word for display. The function ends by setting focus to the txtInput control to ready the game for the player input:
private: Void StartTheGame( Void ) { //Reset game settings ResetDefaultSettings(); //Get a word for the player to guess RetrieveWord(); //Format the display of the mystery word FormatGameWord(); //Prepare to accept the player's guess txtInput->Focus(); }
Next, create the ResetDefaultSettings function, shown next, to prepare the game to play a new round:
private: Void ResetDefaultSettings( Void ) { //Reset all game settings intMisses = 0; intNoRight = 0; txtGuesses->Text = ""; txtMisses->Text = intMisses.ToString(); txtInput->Text = ""; intHangmanNo = 6; pbcHangmanBmp->Image =\ imlHangmanBmps->Images->default[intHangmanNo]; }
Note that in addition to resetting variable default values, the final statement in the previous function displays an empty hangman's gallows graphic in the PictureBox control.
Now create the RetrieveWord function. The function first creates a random number generating object, randNumGen, from the Random object and seeds it with the current time (expressed as DateTime::Now). Then you choose a random number based on a range specified by strWordListArray->Length, which is the length of the array. You use this value to access the array using strWordListArray->GetValue(intRandomNo)->ToString(). Note that this last statement looks more complicated than it actually is. It gets the value of the location specified by intRandomNo using the array object's GetValue method and then converts that value to a String data type. This allows the value to be assigned to strGameWord, thus creating the secret word:
private: Void RetrieveWord( Void ) { //Seed random number generator with current time DateTime moment = DateTime::Now; Random^ randNumGen = gcnew Random( moment.Millisecond ); //Declare a variable to hold a randomly // generated number Int32 intRandomNo = 0; //Use the Random object's Next method to retrieve // a number between zero and the number // of words in the array intRandomNo = randNumGen->Next( strWordListArray->Length ); //Use the random number to select a word from // the array strGameWord = \ strWordListArray->GetValue(intRandomNo)->ToString(); }
Next, create the FormatGameWord function, as shown next. The function begins by assigning an empty string to each of the 10 TextBox controls that display the letters making up the game's secret word. The function then sets up a for loop to display an underscore character for each letter in the secret word:
private: Void FormatGameWord( Void ) { //Game words are limited to 10 or fewer characters. // Each letter is stored in a separate TextBox // control. Start formatting by setting the value // stored in each TextBox control to a blank character txtLetter1->Text = ""; txtLetter2->Text = ""; txtLetter3->Text = ""; txtLetter4->Text = ""; txtLetter5->Text = ""; txtLetter6->Text = ""; txtLetter7->Text = ""; txtLetter8->Text = ""; txtLetter9->Text = ""; txtLetter10->Text = ""; //Loop once for each word in the current letter and // set an underscore in the appropriate text box for( Int32 intCounter = 0; intCounter <= strGameWord->Length; intCounter++ ) { if( intCounter == 1 ) txtLetter1>Text = "_"; if( intCounter == 2 ) txtLetter2->Text = "_"; if( intCounter == 3 ) txtLetter3->Text = "_"; if( intCounter == 4 ) txtLetter4->Text = "_"; if( intCounter == 5 ) txtLetter5->Text = "_"; if( intCounter == 6 ) txtLetter6->Text = "_"; if( intCounter == 7 ) txtLetter7->Text = "_"; if( intCounter == 8 ) txtLetter8->Text = "_"; if( intCounter == 9 ) txtLetter9->Text = "_"; if( intCounter == 10 ) txtLetter10->Text = "_"; } }
You can create the next function, shown below, by returning to the Form Designer, double-clicking on the btnSubmit control, and then modifying the function:
private: System::Void btnSubmit_Click(System::Object^ \ sender, System::EventArgs^ e) { //Make sure input is valid if( !PlayerInputValid() ) return; //Exit on invalid guess //If the letter has already been guessed, display an error if( txtGuesses->Text->Contains( txtInput->Text->ToUpper() ) ) MessageBox::Show("Error:" "That letter has already been guessed."); else //Check whether a correct letter was guessed { //Add the letter to the list of already guessed letters txtGuesses->Text = String::Concat( txtGuesses->Text, txtInput->Text ); //Look for the letter in the word if( !strGameWord->Contains( txtInput->Text->ToUpper() ) ) { //The player's guess was wrong //Append letter to the list of missed guesses strWrong = String::Concat( strWrong, " ", txtInput->Text->ToUpper() ); //Update and display missed guesses intMisses += 1; txtMisses->Text = intMisses.ToString(); //Reduce the counter used to control the Hangman picture and // update graphic intHangmanNo -= 1; pbcHangmanBmp->Image =\ imlHangmanBmps->Images->default[intHangmanNo]; //End game at six wrong guesses if( intMisses == 6 ) { //Update lost games and inform player intGamesLost += 1; txtGamesLost->Text = intGamesLost.ToString(); //Call procedure to see if player wants to play again MessageBox::Show( "Sorry. You have lost." ); PromptForNewGame(); return; } } //Use for loop to check each letter in the word for( Int32 intCounter = 0; intCounter <= \ strGameWord->Length; intCounter++ ) { //Select one letter at a time if( String::Compare( strGameWord, intCounter, txtInput->Text, 0, true ) == 0 ) { //Update and display guesses intNoRight += 1; FlipGameLetter( intCounter ); } } } //See if the player has guessed every letter in the word if( intNoRight == strGameWord->Length ) { intGamesWon += 1; txtGamesWon->Text = intGamesWon.ToString(); //Call the procedure to see if the player wants to play again MessageBox::Show( "Congratulations. You have Won!" ); PromptForNewGame(); } txtInput->Text = ""; txtinput->Focus(); }
The function begins by making sure that the player input is correct by calling the PlayerInputValid function. If the input is okay, the function continues to execute.
Next, the function uses an if...else statement to see if the player has entered a letter guess that has already been tried. This is done by converting input to uppercase and then searching the string for an instance of the letter. Specifically, the txtInput->Text property is converted to uppercase using the ToUpper method. Then it is supplied as an argument to the txtGuesses->Text object's Contains method. (Incidentally, this method is available to all String objects.)
If the player guesses a new letter, that letter is appended to the end of the string displayed in the txtGuesses control. This is done, as in previous chapters, using the highly useful String::Concat method, which can join several strings.
Next, an embedded if statement checks whether the player's guess is wrong by using the object's Contains method. If the player guesses incorrectly, the StrWrong string containing incorrect guesses is updated. Then the value holding the number of misses is updated and stored in the txtMisses control, and the graphic displayed in the PictureBox control is changed to reflect the number of misses so far. A check is then made to see if the player has missed the maximum of six guesses and lost the game.
If the player has not lost, a for loop iterates through the secret word comparing each of its letters with the letter the player entered. For each correct letter, the game updates the counter, keeping track of the number of right answers. It also displays the corresponding letter using the FlipGameLetter function.
Finally, a check is made to see if the player has guessed all the letters that make up the secret word, thus winning the game. If the player has not won the game, the txtInput control is cleared and given focus, thus preparing the game for the player's next guess.
Next, you need to manually create the PlayerInputValid function. This function needs to return a value, so you must define it as shown here:
private: Boolean PlayerInputValid( Void ) { //Create a list of letters to compare input to String^ strAlphabet = gcnew String( \ "ABCDEFGHIJKLMNOPORSTUVWXYZ" ); //See if the player clicked on Submit without // entering a guess if( txtInput->Text == "" ) { MessageBox::Show("Error: You must enter a letter."); txtInput->Text = ""; txtInput->Focus(); return false; } //See if the player entered more than one character if( txtInput->Text->Length > 1 ) { MessageBox::Show("Error: Please enter only 1 letter." ); txtInput->Text = ""; txtInput->Focus(); return false; } //See if the player entered a number if( !strAlphabet->Contains( txtInput->Text ) ) { MessageBox::Show("Error: Please enter a letter."); txtInput->Text = ""; txtInput->Focus(); return false; } return true; }
The function performs three input validation tests to make sure the player has provided an acceptable guess. It accomplishes this using three if statements. The first if statement makes sure that the player has entered something. The second if statement makes sure that the player has not entered more than one character, and the third if statement makes sure that the player hasn't entered a number.
Each time the player correctly guesses a letter, the FlipGameLetter function executes. You need to manually create this function, as shown here:
private: Void FlipGameLetter( Int32 intLetterNumber ) { //Match the letter to be displayed with the // correct text box and show the letter switch( intLetterNumber ) { case 0 : txtLetter1>Text = txtInput->Text->ToUpper(); break; case 1 : txtLetter2->Text = txtInput->Text->ToUpper(); break; case 2 : txtLetter3->Text = txtInput->Text->ToUpper(); break; case 3 : txtLetter4->Text = txtInput->Text->ToUpper(); break; case 4 : txtLetter5->Text = txtInput->Text->ToUpper(); break; case 5 : txtLetter6->Text = txtInput->Text->ToUpper(); break; case 6 : txtLetter7->Text = txtInput->Text->ToUpper(); break; case 7 : txtLetter8->Text = txtInput->Text->ToUpper(); break; case 8 : txtLetter9->Text = txtinput->Text->ToUpper(); break; case 9 : txtLetterl0->Text = txtInput->Text->ToUpper(); break; } }
This function consists of a single switch statement, which compares the player's guess to each letter in the secret word and flips (that is, displays) any letters that match the guess.
The last function that you need to create is the PromptForNewGame function. You must manually define this function and then set it up as shown here:
private: Void PromptForNewGame( Void ) { //Variable used to hold player response Windows::Forms::DialogResult drPlayAgain; //Prompt player to try again drPlayAgain = MessageBox::Show( "Would you like to play again?", "Hangman", MessageBoxButtons::YesNo, MessageBoxIcon::Question, MessageBoxDefaultButton::Button1 ); //if player clicks on Yes, set up a new game if( drPlayAgain == Windows::Forms::DialogResult::Yes ) StartTheGame(); //Start a new game else // End the game this->Close(); }
The function uses the MesssageBox::Show method to ask if the player would like to play another round. If the player clicks on the Yes button, the StartTheGame function executes. Otherwise, the Close method, accessed through your form application's this keyword, terminates the application.
The Hangman game is now complete. Press F5 and test the game to make sure everything is working properly. If you run into errors, check to see if you have made one or more typing mistakes. If you decide to share the game with others, remember to test all aspects of the game, including feeding it good and bad input.