Let's get started on the development of this chapter's game project, the Guess a Number game. To create this game, you follow the same five development steps that you've used to create previous chapter projects.
The Guess a Number game is played on a single window. This game collects the player's input by adding radio buttons and a check box to the user interface. In addition, instead of displaying output using MessageBox::Show, the game displays information messages for the player to read in a TextBox control located on the user interface.
The advantage of collecting player input and displaying game output on the user interface is that it makes the game run more smoothly. The player doesn't have to constantly stop and respond to pop-up windows as the game runs. To create the user interface, you have to learn how to work with several new types of controls, including the GroupBox, RadioButton, and CheckBox controls.
The Guess a Number game is made up of one form and the 17 controls listed in Table 6.2.
Control Type | Control Name | Description |
---|---|---|
GroupBox1 | grpRange | Contains radio buttons, a check box, and a button, which control the game's configuration settings |
GroupBox2 | grpScore | Displays the number of games won by the player and contains a button that resets the score |
RadioButton 1 | rbnControl10 | Sets the range of game numbers to 1 through 10 |
RadioButton2 | rbnControl100 | Sets the range of game numbers to 1 through 100 |
RadioButton3 | rbnControl1000 | Sets the range of game numbers to 1 through 1000 |
CheckBox1 | chkVerbose | Determines the level of messaging displayed by the game |
Label1 | lblGamesWon | Identifies the TextBox control where the total number of games won is displayed |
Label2 | lblInstructions | Identifies the TextBox control where the player enters guesses |
Label3 | lblFeedback | Identifies the TextBox control where game output is displayed |
Button1 | btnDefaults | Resets default RadioButton and CheckBox control settings |
Button2 | btnReset | Resets the number of games won back to 0 to start a new game session |
Button3 | btnCheckGuess | Processes the player's guess to see if the player guessed low, high, or won the game |
Button4 | btnNewGame | Starts a new game |
TextBox1 | txtGamesWon | Displays the number of games that the player has won |
TextBox2 | txtInput | Collects and displays player guesses |
TextBox3 | txtOutput | Displays output messages generated during gameplay |
StatusStrip1 | stsControl | Displays information messages during gameplay |
The first step in creating the Guess a Number game is to open Visual C++ and create a new project, as outlined here:
If you have not already done so, start up Visual C++ 2005 Express, click on File, and select New Project. The New Project dialog box appears.
Select Windows Application template.
Type Guess a Number 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++ now creates a new project for you and displays a new form upon which you can design the game's user interface.
The first step in laying out the user interface is adding controls to the form and moving and resizing them to the appropriate locations. The following function outlines the overall steps involved in creating the game's user interface. As you go through each step, make sure that you reference Figure 6.9 so that you know where to place each control.
Start by adding two GroupBox controls to the form. Position and resize them, as shown in Figure 6.8.
Hint | A GroupBox control is a container that organizes other controls. The GroupBox control displays a caption, set using its Text property, and displays a visible border. |
Add three RadioButton controls to the form and move them into the first GroupBox control.
Hint | A RadioButton control allows you to collect True/False or On/Off information. RadioButton controls are used together in groups to give users the ability to pick between mutually exclusive choices. |
Add a CheckBox control to the form and move it to the right of the last RadioButton control.
Hint | A CheckBox control lets you collect True/False or On/Off information. Unlike RadioButton controls, you can use CheckBox controls individually. When selected, the CheckBox control displays an x. |
Add a Button control to the first GroupBox control and resize it.
Add a Label, a TextBox, and a Button control to the second GroupBox and resize and position them.
Add two additional Label controls and position them toward the middle of the form.
Add a TextBox control to the right of the first Label control and increase its width by approximately 30 percent.
Add another TextBox control underneath the second Label control and resize it until it spans the length of the form.
Add two more Button controls to the bottom area of the second GroupBox.
Finally, add a StatusStrip control to the bottom of the form and select a StatusLabel from its drop-down list.
Figure 6.8: Completing the interface design for the Guess a Number game.
The overall layout of your new application's form is now complete.
It is time for you to customize various properties belonging to the form and the controls that you have placed on it. Begin by changing the form properties listed in Table 6.3.
Property | Value |
---|---|
Name | frmMain |
Cursor | Hand |
FormBorderStyle | Fixed3D |
StartPosition | CenterScreen |
Text | Guess a Number |
Next, make the changes shown in Table 6.4 to the GroupBox controls.
Control | Property | Value |
---|---|---|
GroupBox1 | Name | grpRange |
Text | Select Range | |
GroupBox2 | Name | grpScore |
Text | Score |
Make the changes shown in Table 6.5 to the RadioButton controls.
Control | Property | Value |
---|---|---|
RadioButton1 | Name | rbnControl10 |
Text | Range: 1 to 10 | |
RadioButton2 | Name | rbnControl100 |
Checked | True | |
Text | Range: 1 to 100 | |
RadioButton3 | Name | rbnControl1000 |
Text | Range: 1 to 1000 |
Make the changes shown in Table 6.6 to the Button controls.
Control | Property | Value |
---|---|---|
Button1 | Name | btnDefaults |
Text | Reset Defaults | |
Button2 | Name | btnReset |
Text | Reset Score | |
Button3 | Name | btnCheckGuess |
Enabled | False | |
Text | Check Guess | |
Button4 | Name | btnNewGame |
Text | NewGame |
Make the changes shown in Table 6.7 to the CheckBox control.
Property | Value |
---|---|
Name | chkVerbose |
Checked | True |
CheckState | Checked |
Text | Verbose Messaging |
Make the changes shown in Table 6.8 to the Label controls.
Control | Property | Value |
---|---|---|
Label1 | Name | lblGamesWon |
Font.Bold | True | |
Text | No. of Games Won: | |
Label2 | Name | lblInstructions |
Font.Bold | True | |
Font.Size | 10 | |
Text | Enter Your Guess: | |
Label3 | Name | lblFeedback |
Font.Bold | True | |
Text | Feedback and Results |
Make the changes shown in Table 6.9 to the TextBox controls.
Control | Property | Value |
---|---|---|
TextBox1 | Name | txtGamesWon |
ReadOnly | True | |
TabStop | False | |
TextBox2 | Name | txtInput |
Enabled | False | |
TextBox3 | Name | txtOutput |
ReadOnly | True | |
TabStop | False |
Make the changes shown in Table 6.10 to the StatusStrip control.
Object | Property | Value |
---|---|---|
StatusStrip1 | Name | stsControl |
SizingGrip | False | |
toolstripStatus1 | Name | stsLabel |
Text | Game Ready! |
That's it. At this point, you have configured all the form and control properties that need to be set at design time.
Begin by double-clicking on the form. This activates the Code Editor. Locate the block of code shown in the next program listing and add the statements shown in bold:
private: /// <summary> /// Required designer variable /// </summary> Int16 intRandomNumber; Int16 intTextGamesWon;
The intRandomNumber variable is used throughout the application to store the game's randomly generated secret number. The intTextGamesWon variable keeps track of the number of games won. Next, modify the form's Load event function, as shown next:
System::Void frmMain_Load(System::Object^ sender, System::EventArgs^ e) { //Clear game variables and text intRandomNumber = 0; intTextGamesWon = 0; txtGamesWon->Text = intTextGamesWon.ToString(); //Set focus on new game button btnNewGame->Focus(); }
The first two statements set the value of intRandomNumber and intTextGamesWon to 0. This ensures that the game won't access some random value in memory when these values are first used. The next statement initializes the display of the txtGamesWon text box, setting it to the value of intTextGamesWon. The last statement places focus on the btnNewGame control.
Next, we need to add logic to the btnDefaults control to reset the game's default settings, as controlled by the RadioButton and CheckBox controls located in the first GroupBox control. Do so by modifying the click event function for the btnDefaults control, as shown next:
private: System::Void btnDefaults_Click(System::Object^ sender, System::EventArgs^ e) { //Reset all game defaults rbnControl100->Checked = true; chkVerbose->Checked = true; //Set focus on text input txtInput->Focus(); }
The first statement selects the RadioButton that represents the range of 1 to 100. The second statement selects (by placing an x inside) the chkVerbose CheckBox control, and the third statement sets the focus to the txtInput control (to save the player the trouble of having to put it there before typing the next guess).
Next, let's add logic to the btnReset control so that the player can reset the value that tracks the number of games won to 0 at any time. Also, take note that the value is first assigned to the variable and then converted so that the txtGameWon->Text text property can display it:
private: System::Void btnReset_Click(System::Object^ sender, System::EventArgs^ e) { //Reset the game score intTextGamesWon = 0; txtGamesWon->Text = intTextGamesWon.ToString(); }
Next, add the following statements to the TextChanged event for the txtInput control:
private: System::Void txtInput_TextChanged(System::Object^ sender, System::EventArgs^ e) { //Toggle appropriate buttons when // text is being entered for the // guess btnCheckGuess->Enabled = true; btnNewGame->Enabled = false; }
The TextChanged event is triggered automatically whenever the user keys something into the TextBox control that is associated with the event. It is used in the Guess a Number game to control when the btnCheckGuess button is enabled and when the btnNewGame button is disabled.
Now we need to add logic to the application that randomly generates the game's secret number. To do so, I have decided to create a new function named GenerateRandomNumber() and place the logic to generate the random number in it. You can't automatically generate this function by double-clicking on an object in the Form Designer. Instead, you need to key it in entirely by hand, as shown next. You'll learn more about how to work with functions in Chapter 8, "Enhancing Code Structure and Organization," including how to create your own custom functions. For now, just key in the function, as shown here:
private: Void GenerateRandomNumber( Void ) { //Get a random number based on the // current time DateTime moment = DateTime::Now; Random^ randNumGen = gcnew Random( moment.Millisecond ); Int32 intUpperLimit = 0; //Check to see which radio button is // checked to determine upper // limit of random number if( rbnControl10->Checked == true ) intUpperLimit = 10; if( rbnControl100->Checked == true ) intUpperLimit = 100; if( rbnControl1000->Checked == true ) intUpperLimit = 1000; //Get the number based on the upper range intRandomNumber = randNumGen->Next( intUpperLimit ); }
When called by the btnNewGame control's click event function, the GenerateRandomNumber function first gets the computer's current time. Then it instantiates a new Random object called randNumGen and checks to see which RadioButton control is currently selected so that it knows which range to use when generating the game's secret number. After that, the function executes the randNumGen object's Next() method so that it can generate the random number. The Next() method is passed the value stored in the intUpperLimit variable so that it can specify the maximum range from which the random number should be selected (between 0 and the value of intRandomNumber).
Trick | The Random object's Next() method generates a random number. If called without passing it parameters, the Next() method generates a positive whole number. If passed a single integer value, the Next() method generates a random number between zero and the value of the integer argument. If passed two integer values, the Next() method generates a random number within the specified range. |
Now let's add the code required for the click event belonging to the btnCheckGuess control, as shown here:
private: System::Void btnCheckGuess_Click(System::Object^ sender. System::EventArgs^ e) { //Check the player's guesses //Declare variable to store current guess Double dblPlayerGuess = 0; //Declare variable to continuously keep // track of player guesses static Int32 intNoOfGuesses = 0; //First, check to see if text has been entered if( txtInput->Text->Length > 0 ) { //Try to convert the number and store it in // the dblPlayerGuess variable; if the player // accidentally enters text or a fraction, // the Double::TryParse method fails gracefully if( Double::TryParse( txtInput->Text, dblPlayerGuess ) ) { //Enable button btnCheckGuess->Enabled = true; //Check whether player guess equals number if( dblPlayerGuess == intRandomNumber ) { txtInput->Text = ""; intNoOfGuesses += 1; //Check whether verbose messaging is on if( chkVerbose->Checked == true ) { //Create the message string txtOutput->Text = String::Concat( "Congratulations! " "You've won the Guess A Number Game! " "Number of guesses made = ", intNoOfGuesses.ToString() ); } else txtOutput->Text = "Congratulations!"; //Reset number of guesses intNoOfGuesses = 0; //Disable text input txtInput->Enabled = false; //Update number of games won intTextGamesWon += 1; txtGamesWon->Text = intTextGamesWon.ToString(); //Enable the New Game button btnNewGame->Enabled = true; //Disable the check guess button btnCheckGuess->Enabled = false; //Enable all of the radio buttons rbnControl10->Enabled = true; rbnControl100->Enabled = true; rbnControl1000->Enabled = true; //Enable the two reset buttons btnDefaults->Enabled = true; btnReset->Enabled = true; stsLabel->Text = "Get Ready!"; } } //Is player guess less than number? if( dblPlayerGuess < intRandomNumber ) { //Clear text txtInput->Text = ""; //Increase guess count intNoOfGuesses += 1; //Check if verbose messaging is on if( chkVerbose->Checked == true ) { //Create the message string txtOutput->Text = String::Concat( "The number that " "you entered was too low. " "Please enter a higher number " "and try again. " "Number of guesses made = ", intNoOfGuesses.ToString() ); } else txtOutput->Text = "Too low."; } //Is player guess is greater than number if( dblPlayerGuess > intRandomNumber ) { //Clear text txtInput->Text = ""; //Increase guess count intNoOfGuesses += 1; //Check whether verbose messaging is on if( chkVerbose->Checked == true ) { //Create the message string txtOutput->Text = String::Concat( "The number that " "you entered was too high. " "Please enter a lower number " "and try again. " "Number of guesses made = ", intNoOfGuesses.ToString() ); } else txtOutput->Text = "Too high."; } } else { if( chkVerbose->Checked == true ) { //Create the message string txtOutput->Text = String::Concat( "Sorry, you " "entered a non-numeric guess. " "Please enter a number and " "try again. " ); } else txtOutput->Text = "Numeric input required."; } } else { if( chkVerbose->Checked == true ) { //Create the message string txtOutput->Text = String::Concat( "Sorry, but ", "a number is required to. ", "play. Please enter a ", "number and try again." ); } else txtOutput->Text = "No input provided."; } txtInput->Focus(); }
As you can see, this function is rather long and contains the bulk of the application's programming logic. It begins by declaring two variables. The first variable is dblPlayerGuess and is used to store and process the input provided by the player. A new value will be assigned to this variable each time the player clicks on the Button control labeled Check Guess. Therefore, it is declared as a local variable. However, the second variable, which is named intNoOfGuesses, is defined as a static variable, making its lifetime last for as long as the game runs. This allows it to maintain a count of the number of guesses that the player makes during each game.
Next, an if...else block is set up that determines which statements in the function execute based on whether the player entered input. If no input was provided, an error message is displayed in the txtOutput control. Otherwise, a second nested if...else block executes and checks whether the input that the player supplied is numeric. If the input is not numeric, an error message is displayed in the txtOutput control. If the input is numeric, one of three nested if blocks executes. The first if block checks whether the player won the game by guessing the secret number. The second if block checks whether the player's guess was too low, and the third if block checks whether the player's guess was too high.
If the player's guess was too high or too low, an error message is displayed in the txtOutput control. The message that is displayed depends on whether the chkVerbose control is checked. If the player guesses correctly, she wins the game, and a congratulatory message is displayed in the txtOutput control. In addition, the following actions are taken to prepare the game for another play:
The value of intNoOfGuesses is reset to 0.
The value indicating the number of games won is incremented, converted, and stored in the txtGamesWon control's text property.
The Check Guess button is disabled, and the New Game button is enabled.
The game's RadioButton controls are enabled, allowing the player to make changes to them if desired.
The game's Reset Default button is also enabled.
The message displayed in the game's StatusStrip control is updated.
Last but not least, it is time to add some code to the btnNewgame control's click event function, as shown here:
private: System::Void btnNewGame_Click(System::Object^ sender, System::EventArgs^ e) { //Set conditions for new game //Generate a new random number this->GenerateRandomNumber(); txtOutput->Text txtInput->Text = ""; //Set starting game buttons btnNewGame->Enabled = false; btnCheckGuess->Enabled = true; //Enable text input txtInput->Enabled = true; //Turn off radio buttons rbnControl10->Enabled = false; rbnControl100->Enabled = false; rbnControl1000->Enabled = false; //Disable the two reset buttons btnDefaults->Enabled = true; btnReset->Enabled = true; //Set status strip text stsLabel->Text = "Enter your guess."; txtInput->Focus(); }
When clicked, the code for the button causes the GenerateRandomNumber function to be called. Note that it uses the format this->GenerateRandomNumber(). The keyword this was used so that the form could refer to (and hence access) one of its own elements. Next, the function clears any text display in the txtInput and txtOutput controls. Following this, the btnNewGame control is disabled, and the btnCheckGuess control is enabled. Then the txtInput control is enabled to allow the player to enter a guess, and the game's RadioButton control and btnReset control are disabled, preventing the player from making configuration changes while a new game is being played. Finally, an instructional message is displayed on the game's StatusStrip control, and the cursor is placed in the txtInput control.
That's it. The Guess a Number game is ready to run. Press F5 to see how it works. If you have errors, double-check your typing. Otherwise, pass it on to your friends and ask them to play and to report any problems back to you if they run into anything faulty.