Adjusting Board Size and Block Count
Introduction
Our version of the SameGame is really taking shape. We have a game
that can be played from start to finish in five different levels of
difficulty. In this article we'll be adding more options to customize
the game play. We'll add the ability for the user to adjust the size of
the blocks and the number of blocks on the game board. In order to get
these values from the user we'll create a dialog box to prompt them for
input.
Adjusting the Size and Block Count
The first step is to update the game board and document classes to be
able to adjust these options. Starting with the game board we'll make
some very minor adjustments to the header file only. We already have
"getter" functions in the game board class to get the values of the
width and height of the blocks in pixels and the number of rows and
columns on the board. Now we'll add "setter" functions to set these
values. Update the accessor function area in the game board class in
SameGameBoard.h to look like the following (changes bolded).
/* Accessor functions to get/set board size information */ int GetWidth(void) const { return m_nWidth; } void SetWidth(int nWidth) { m_nWidth = (nWidth >= 3) ? nWidth : 3; } int GetHeight(void) const { return m_nHeight; } void SetHeight(int nHeight) { m_nHeight = (nHeight >= 3) ? nHeight : 3; } int GetColumns(void) const { return m_nColumns; } void SetColumns(int nColumns) { m_nColumns = (nColumns >= 5) ? nColumns : 5; } int GetRows(void) const { return m_nRows; } void SetRows(int nRows) { m_nRows = (nRows >= 5) ? nRows : 5; }
I've added the set functions right after the get functions for easier
reading and comprehension of the class. The functions are fairly
simple. We use a simple test on each of the values to make sure that
they don't go below a specified value, three for the pixel height/width
and five for the row/column values. Numbers smaller than these will
mess up the aesthetics of our game board.
Next we update the document in a similar fashion adding "setter"
functions to go along with our "getters". These functions are added to
the SameGameDoc.h header file just like the game board (changes bolded).
int GetWidth(void) { return m_board.GetWidth(); } void SetWidth(int nWidth) { m_board.SetWidth(nWidth); } int GetHeight(void) { return m_board.GetHeight(); } void SetHeight(int nHeight) { m_board.SetHeight(nHeight); } int GetColumns(void) { return m_board.GetColumns(); } void SetColumns(int nColumns) { m_board.SetColumns(nColumns); } int GetRows(void) { return m_board.GetRows(); } void SetRows(int nRows) { m_board.SetRows(nRows); }
Now all we have to do is prompt the user for new sizes, resize the
game board, resize the window and repaint...all this will go in the
event handlers for the menu options. All of these things are very
trivial except for prompting the user for new sizes. We'll create a new
dialog to do this. Creating a new dialog begins with displaying the
resource editor window in Visual Studio. Open up the Resource View
under the "View" menu "Resource View" or the accelerator key
Ctrl+Shift+E. You'll recognize this view from when we edited the menu
system for our game. This time instead of opening the Menu option, open
the Dialog option. You'll see that there is one dialog already there
with the ID of IDD_ABOUTBOX. If you've noticed under the About menu
there is a dialog that comes up with "About" information. This dialog
is automatically generated by the MFC Application Wizard when we first
set up our project. Adding a new dialog is simple, just right-click on
the Dialog option in the Resource View and select "Insert Dialog" and a
new, mostly blank, dialog will appear with the ID of IDD_DIALOG1 like
the image below.
Once
you double-click on the IDD_DIALOG1 option, the dialog editor will
appear in the main window in Visual Studio. I said earlier that the
dialog was a "mostly blank" dialog because MFC generates a dialog with
both an Ok button and a Cancel button. Yours should look something like
this when we first begin.
We'll
use this dialog to prompt the user for both board size, rows and
columns, and block size, width and height, so we'll have to make it
generic. This dialog won't do much for us until we add some new
controls. We'll add labels, edit boxes, and another button to the
dialog to make it functional. We first need to bring up the dialog
editor Toolbox through the View menu then "Toolbox" or Ctrl+Alt+X.
Let's take a look at the Toolbox.
This
is a list of what are called Common Controls. These are Windows
controls that are common to most Windows applications. Most, I'm sure,
look very familiar to you. There is a button, check box, etc. You can research these controls
if they don't look familiar to you. We'll be using the "Static Text"
control to indicate what input the user is supposed to input to each of
the "Edit Controls". We'll also add a button to allow the user to
restore the default values. To add a control, simply click and drag the
desired control from the Toolbox right on to the dialog in the Dialog
Editor. Let's start with the button, click and drag one just below the
Ok and Cancel buttons, like so.
To
change the text on the button face, just click on the button so it is
selected like the image above and start to type "Defaults". Now we need
a couple of "Edit Controls" added. Click and drag a couple on the
dialog. In order to line them up you can click on the rulers above and
to the left in the Dialog Editor to create guides that snap to the
controls. I've added a few to line up my edit controls with the
buttons.
Finally
let's add a couple of "Static Text" controls to describe to the user
what to type into the Edit Controls. I'm going to add two new guides
that will line the static text up with the middle of the edit controls
and extend the size of the static text. These text controls will have
their text inserted programmatically so that it can change depending on
the type of data we want from the user.
This
is how our dialog will look for both of the menu options that we'll be
working with in this article, "Block Size..." and "Block Count..." All
we have to do is change the title of the dialog, the static text and the
values in the edit controls based on what we want to prompt the user
for. In order to do this we need to make a few changes to the IDs of
the controls that we've added. As they sit we are unable to interact
with the Static Text controls; they need new IDs. Pull up the
properties window by pressing Alt+Enter or from the menu View->Other
Windows->Properties Window. When you click on a control, the
properties for that control will come up. I've selected the ID option
that needs to be changed.
The
ID IDC_STATIC is a reserved ID that is for all generic static text
controls; let's change it to something like IDC_STATIC_TEXT_1. While
we're at it, let's change the IDs for all of the controls and the
dialog. To do this, leave the properties window up and click on a
different control. This will fill in the properties for that particular
control. Then just change the IDs. We'll need the top static text to
be IDC_STATIC_TEXT_1 and the other IDC_STATIC_TEXT_2. Then rename the
edit controls to IDC_EDIT_VALUE_1 for the top edit control and
IDC_EDIT_VALUE_2 for the other. We do this so that we can dynamically
change the text depending on the data we want the user to enter. The
button we added for "Defaults" we'll rename to IDC_BUTTON_DEFAULTS.
Finally change the ID of the dialog from IDD_DIALOG1 to
IDD_DIALOG_OPTIONS. These changes will help when creating the dialog
and displaying it to the user.
Once the dialog is set up in the dialog editor, we need to produce
code for it. Simple enough, just right-click on the dialog itself, not
any of the controls, and select "Add Class..." You'll be presented with
the MFC Class Wizard. This gives us a class that represents this
dialog that we've created that we can use in our code. Add in the class
name COptionsDialog and everything will fill itself out. Clicking
finish will generate the OptionsDialog.h and OptionsDialog.cpp files.
Before
we look at the code that has been created for us let's make some
variables in this class that represent the controls that we added. Go
back to the dialog editor and right-click on each of the controls, one
by one, and select "Add Variable..." to bring up the Add Member Variable
Wizard. This will add a variable to the class that we just created and
associate it with the control that we right-clicked on. Since I first
clicked on the first static text control the wizard will look like the
image below. Fill in the Variable Name with m_ctrlStaticText1 like I
did and click Finish. This will add all of the necessary code to the
dialog class.
For
now we can keep the defaults. We want a control variable of type
CStatic so that we can change the text in the static text control
whenever we want. We'll select a different option when we get to the
edit controls. Repeat this with the second static text control giving
it the variable name m_ctrlStaticText2.
Now right-click on the first edit control and select "Add
Variable..." This time we want a "Control Variable" but a Value control
variable so drop down the "Category" and select value. This will
change the "Variable Type" options from types of MFC controls to types
of values; the default is likely to be CString, but select "int". Type
in the name of the variable m_nValue1. Here we are setting an integer
as the storage place for the value in the edit control and when the user
clicks Ok, that value will be stored in the variable that we've created
without us writing any code to do so. See the screenshot below to see
what I did.
Repeat this process with the second edit control and give it the name of m_nValue2.
Now we need to add an event-handler to the class for the Defaults
button. Just right-click on the button and select "Add Event
Handler..." This will bring up the Event Handler Wizard that will allow
you to create all kinds of event handlers for all of your classes. If
you right-clicked on the Defaults button the wizard will default to the
BN_CLICKED, which stands for button clicked, event for the defaults
button. Just click "Add and Edit" and you'll be whisked away to the
options dialog code where a button event handler will be waiting for
you.
The
last thing that we are going to need is an override for the
OnInitDialog function. In a previous article we talked about overrides
so I'm going to skip the explanation. From the OptionsDialog header
open up the properties window and select the Overrides button. This
will bring up a list of overrides, we are looking for the OnInitDialog.
Click the drop-down and Add OnInitDialog. You'll see something like
the image below.
At
this point there will be a lot of MFC generated code for you to look
through, most of which is beyond the scope of this article. We'll cover
some of it. First let's take a look at, and add to, the header file
OptionsDialog.h. Here is the code (changes bolded).
#pragma once #include "afxwin.h" // COptionsDialog dialog class COptionsDialog : public CDialog { DECLARE_DYNAMIC(COptionsDialog) public: // Standard Constructor COptionsDialog(bool bRowColumn, CWnd* pParent = NULL); virtual ~COptionsDialog(); // Dialog Data enum { IDD = IDD_DIALOG_OPTIONS }; protected: // DDX/DDV support virtual void DoDataExchange(CDataExchange* pDX); DECLARE_MESSAGE_MAP() public: CStatic m_ctrlStaticText1; CStatic m_ctrlStaticText2; int m_nValue1; int m_nValue2; afx_msg void OnBnClickedButtonDefaults(); virtual BOOL OnInitDialog(); private: /* Is this dialog for row/column (true) or width/height (false)? */ bool m_bRowColumnDialog; };
We added another variable to the argument list for the constructor so
that the dialog can be built for both the row/column and width/height
information. If we pass in "true" then the dialog will prompt the user
for number of rows and columns in the game board. When it is "false"
the dialog will ask for width and height of the blocks in the game
board. We implement all of this functionality in the following (changes
bolded).
// OptionsDialog.cpp : implementation file #include "stdafx.h" #include "SameGame.h" #include "OptionsDialog.h" // COptionsDialog dialog IMPLEMENT_DYNAMIC(COptionsDialog, CDialog) COptionsDialog::COptionsDialog(bool bRowColumn, CWnd* pParent) : CDialog(COptionsDialog::IDD, pParent) , m_nValue1(0) , m_nValue2(0) , m_bRowColumnDialog(bRowColumn) { } COptionsDialog::~COptionsDialog() { } void COptionsDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_STATIC_TEXT_1, m_ctrlStaticText1); DDX_Control(pDX, IDC_STATIC_TEXT_2, m_ctrlStaticText2); DDX_Text(pDX, IDC_EDIT_VALUE_1, m_nValue1); DDX_Text(pDX, IDC_EDIT_VALUE_2, m_nValue2); } BEGIN_MESSAGE_MAP(COptionsDialog, CDialog) ON_BN_CLICKED(IDC_BUTTON_DEFAULTS, &COptionsDialog::OnBnClickedButtonDefaults) END_MESSAGE_MAP() // COptionsDialog message handlers void COptionsDialog::OnBnClickedButtonDefaults() { // Do things differently for the different dialogs if(m_bRowColumnDialog) m_nValue1 = m_nValue2 = 15; // 15x15 board else m_nValue1 = m_nValue2 = 35; // 35x35 blocks // Have the controls updated to the new values UpdateData(false); } BOOL COptionsDialog::OnInitDialog() { CDialog::OnInitDialog(); // Setup the dialog based on the dialog type if(m_bRowColumnDialog) { // First update the title of the dialog SetWindowText(_T("Update Block Count")); // Next change the static text labels m_ctrlStaticText1.SetWindowText(_T("Rows")); m_ctrlStaticText2.SetWindowText(_T("Columns")); } else { // First update the title of the dialog SetWindowText(_T("Update Block Size")); // Next change the static text labels m_ctrlStaticText1.SetWindowText(_T("Block Width")); m_ctrlStaticText2.SetWindowText(_T("Block Height")); } return TRUE; }
The first thing we do is update the constructor to take the Boolean
value that tells us what type of dialog we are creating. This is
simple. The next change we've made is to reset the values to the
defaults, for rows/columns the values are both 15 and for the
width/height the values are both 35. Then to update the controls with
the new values we must call the UpdateData function passing in false as
the argument. The argument is a Boolean flag that indicates the
direction of update or what is being updated, the control (false) or the
variable (true). Passing in true would reset the changes that you just
made by going to the control, reading the contents, and saving it to
the variable. We want to update the control with the new value of the
variable so we pass in false.
Lastly we put the code into the OnInitDialog function. This is a
function that is called right before the dialog is first shown to the
user. This is where we can set up the dialog. So for a row/column
dialog we set the title of the dialog to "Update Block Count" with the
SetWindowText function and the _T() macro which we talked about in the
second article. Then we update the text in the static text controls by
using the function of the same name in that control object. We set them
to "Rows" and "Columns". If it is a width/height dialog we change the
title and labels to reflect that. That is all that needs to be done in
the dialog, it should all function properly now.
Our final step is to set up a couple of event handlers in the view
for the two menu options that we are working with. We do this through
the Properties window from the header file for the view
(SameGameView.h). Click on the events button, the one that looks like a
lightning bolt, expand the ID_SETUP_BLOCKCOUNT option and add the
COMMAND event handler. Do this with the ID_SETUP_BLOCKSIZE option also.
We won't need to make any changes to the view's header file other
than the changes that adding those two handlers did automatically.
Below are the only changes that were made automatically.
afx_msg void OnSetupBlockcount(); afx_msg void OnSetupBlocksize();
The real changes happen in the implementation or source file of the
view, SameGameView.cpp. In order to use the options dialog that we just
created we must include the header file in the view's source file.
Here are the first few lines of that source file (changes bolded).
#include "stdafx.h" #include "SameGame.h" #include "SameGameDoc.h" #include "SameGameView.h" #include "OptionsDialog.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
Just a simple include of the OptionsDialog.h file. Next we fill in
the two event handlers that we just added. Both of these functions are
essentially the same except for in the five bolded locations in each function that we see below.
void CSameGameView::OnSetupBlockcount() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Create the options dialog COptionsDialog dlg(true, this); // Set the row and column values dlg.m_nValue1 = pDoc->GetRows(); dlg.m_nValue2 = pDoc->GetColumns(); // Display the dialog if(dlg.DoModal() == IDOK) { // First delete the board pDoc->DeleteBoard(); // Get the user selected values pDoc->SetRows(dlg.m_nValue1); pDoc->SetColumns(dlg.m_nValue2); // Update the board pDoc->SetupBoard(); // Resize the view ResizeWindow(); } } void CSameGameView::OnSetupBlocksize() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Create the options dialog COptionsDialog dlg(false, this); // Set the width and height values dlg.m_nValue1 = pDoc->GetWidth(); dlg.m_nValue2 = pDoc->GetHeight(); // Display the dialog if(dlg.DoModal() == IDOK) { // First delete the board pDoc->DeleteBoard(); // Get the user selected values pDoc->SetWidth(dlg.m_nValue1); pDoc->SetHeight(dlg.m_nValue2); // Update the board pDoc->SetupBoard(); // Resize the view ResizeWindow(); } }
Just like every other event handler that we've created, we first get a pointer
to the document. Then we create the dialog by instantiating an instance of
that class. Here is the first difference between the two functions. In the first
function, OnSetupBlockcount, we pass in the true value, and in the second
function, we pass in
false. We've already discussed what this does but here is where we do
it. The next two lines set the public integer values m_nValue1 and m_nValue2
to be the rows and columns for the first function and the width and height for
the second. By setting these values before calling DoModal on the dialog we
ensure that the values start in their corresponding edit controls. The next
line is where we actually pop-up and display the dialog with the DoModal
function. This shows the dialog and doesn't return control to the application
until the user clicks Ok, Cancel or the X button to close. Then the function
returns a value based on how the user closed the dialog. Here we are testing
for Ok so we compare the return value against IDOK. If the user clicked Ok
then we continue making changes. If not we ignore everything that happened and
continue on.
If the user clicked Ok we first have to delete the old game board and
free the memory. Once that is done we can use our "setter" functions
on the document to store those values that the user selected. This is
the final set of differences; we use different setters depending on the
function because we are working with different values in each function.
Once they are set in the document and the game board we need to create a
new game board by calling SetupBoard. This will create a new game
board with the new selected options. Finally we resize the window to
match the new number of rows/columns or the new block size. Your game
should now be able to look like these.
Conclusion
Our game development is nearing complete. With only one article
left, we've come a very long way. In this article we created a new
options dialog to be able to prompt the user for size information. We
then updated the game board accordingly. These options allow the user
to have many different experiences. What level can you completely clear
at with a game board of five rows by five columns? There are lots of
combinations of options that can change the difficulty of the game and
make you change your strategy. That is what makes a game fun, begin
able to replay it with different options in different ways.
No comments:
Post a Comment
if You Need Any Help Then Write Us:We Come Back With Solution