"The Same Game": A Simple Game from Start to Finish

By Ben Marchant

Document/View Architecture

The document/view architecture is an interesting paradigm where we separate the actual application data from the displaying of that data to the user. The document contains all of the data while the view gets the data from the document and displays it to the user is some fashion. Here our data is the actual game board, the time it takes to complete the game and other related information. Our view displays the game board as colored blocks and allows the users to click them. The view handles the user interaction and modifies the game data in the document accordingly, then the view is updated to reflect the changes and the cycle continues.

By selecting the document/view architecture option in the MFC Application Wizard, the code base is generated along with all of the mechanics relating the two.

The Document: Keeping Your Data

It is finally time to get started coding. Before we can display anything on the screen we need data to back it up so we'll start with developing the document portion of the application followed by the display of that data.

First we'll create a class that represents our game board, let's call it CSameGameBoard. Create a new class by right-clicking on the SameGame project in the Solution Explorer and selecting "Add -> Class..." or "Add Class..." from the Project menu. We want a C++ class from the C++ group and click "Add". This will bring up the Generic C++ Class Wizard. Fill it out with the class name that we chose as it appears below.

Now let's fill in the game board class. Here is the code for the header file.

#pragma once

class CSameGameBoard
{
public:
  /*  Default Constructor */
  CSameGameBoard(void);
  /*  Destructor */
  ~CSameGameBoard(void);
  /*  Function to randomly setup the board */
  void SetupBoard(void);
  /*  Get the color at a particular location */
  COLORREF GetBoardSpace(int row, int col);
  /*  Accessor functions to get board size information */
  int GetWidth(void) const { return m_nWidth; }
  int GetHeight(void) const { return m_nHeight; }
  int GetColumns(void) const { return m_nColumns; }
  int GetRows(void) const { return m_nRows; }
  /*  Function to delete the board and free memory */
  void DeleteBoard(void);
private:
  /*  Function to create the board and allocate memory */
  void CreateBoard(void);
  /*  2D array pointer */
  int** m_arrBoard;
  /*  List of colors, 0 is background and 1-3 are piece colors */
  COLORREF m_arrColors[4];
  /*  Board size information */
  int m_nColumns;
  int m_nRows;
  int m_nHeight;
  int m_nWidth;
};

This class is conceptually quite simple. It contains a pointer, called m_arrBoard, to a two dimensional array of integers that represent empty (0) or one of the three colors (1-3). We add member variables to keep track of the rows (m_nRows), columns (m_nColumns), pixel width (m_nHeight) and height (m_nHeight). There are also functions to create, set up and delete the board.

The create method needs to allocate the two dimensional array to store the game board and initializes all of the blocks to empty. The setup method will reset the game board by randomly choosing a color for each space on the board. Finally the delete method de-allocates the memory that we are using for the game board to eliminate memory leaks.

In the board there is also an array COLORREF types. A COLORREF is simply a 32-bit unsigned integer that contains RGBA color values for MFC applications. This array contains the colors for the background, at index zero, and the block colors at indices one through three. These indices are the same numbers that are held in the two dimensional array of integers. In the constructor below we are using the RGB macro to create a COLORREF value from three integers representing the red, green and blue values.

Below is the implementation of the CSameGameBoard class, in SameGameBoard.cpp.

#include "StdAfx.h"
#include "SameGameBoard.h"

CSameGameBoard::CSameGameBoard(void)
: m_arrBoard(NULL),
  m_nColumns(15), m_nRows(15),
  m_nHeight(35),  m_nWidth(35)
{
  m_arrColors[0] = RGB(  0,  0,  0);
  m_arrColors[1] = RGB(255,  0,  0);
  m_arrColors[2] = RGB(255,255, 64);
  m_arrColors[3] = RGB(  0,  0,255);
}

CSameGameBoard::~CSameGameBoard(void)
{
  //  Simply delete the board
  DeleteBoard();
}

void CSameGameBoard::SetupBoard(void)
{
  //  Create the board if needed
  if(m_arrBoard == NULL)
    CreateBoard();
  //  Randomly set each square to a color
  for(int row = 0; row < m_nRows; row++)
    for(int col = 0; col < m_nColumns; col++)
      m_arrBoard[row][col] = (rand() % 3) + 1;
}

COLORREF CSameGameBoard::GetBoardSpace(int row, int col)
{
  //  Check the bounds of the array
  if(row < 0 || row >= m_nRows || col < 0 || col >= m_nColumns)
    return m_arrColors[0];
  return m_arrColors[m_arrBoard[row][col]];
}

void CSameGameBoard::DeleteBoard(void)
{
  //  Don't delete a NULL board
  if(m_arrBoard != NULL)
  {
    for(int row = 0; row < m_nRows; row++)
    {
      if(m_arrBoard[row] != NULL)
      {
        //  Delete each row first
        delete [] m_arrBoard[row];
        m_arrBoard[row] = NULL;
      }
    }
    //  Finally delete the array of rows
    delete [] m_arrBoard;
    m_arrBoard = NULL;
  }
}

void CSameGameBoard::CreateBoard(void)
{
  //  If there is already a board, delete it
  if(m_arrBoard != NULL)
    DeleteBoard();
  //  Create the array of rows
  m_arrBoard = new int*[m_nRows];
  //  Create each row
  for(int row = 0; row < m_nRows; row++)
  {
    m_arrBoard[row] = new int[m_nColumns];
    //  Set each square to be empty
    for(int col = 0; col < m_nColumns; col++)
      m_arrBoard[row][col] = 0;
  }
}

Now that we have our game board encapsulated into an object we can create an instance of that object in the document class. Remember that the document class contains all of our game data and it is separated from the view or display code. We will then set up the document as follows. Here is the header file, SameGameDoc.h (changes bolded).

#pragma once

#include "SameGameBoard.h"

class CSameGameDoc : public CDocument
{
protected: // create from serialization only
  CSameGameDoc();
  virtual ~CSameGameDoc();
  DECLARE_DYNCREATE(CSameGameDoc)

// Attributes
public:

  // Operations
public:

  /*  Functions for accessing the game board */
  COLORREF GetBoardSpace(int row, int col)
  { return m_board.GetBoardSpace(row, col); }
  void SetupBoard(void)   { m_board.SetupBoard(); }
  int GetWidth(void)      { return m_board.GetWidth(); }
  int GetHeight(void)     { return m_board.GetHeight(); }
  int GetColumns(void)    { return m_board.GetColumns(); }
  int GetRows(void)       { return m_board.GetRows(); }
  void DeleteBoard(void)  { m_board.DeleteBoard(); }


  // Overrides
public:
  virtual BOOL OnNewDocument();

protected:

  /*  Instance of the game board */
  CSameGameBoard m_board;


  // Generated message map functions
protected:
  DECLARE_MESSAGE_MAP()
};

Most of this code should look familiar to you except for a few things that are MFC specific. For now we can ignore the DECLARE_DYNCREATE and DECLARE_MESSAGE_MAP lines, they are boilerplate MFC directives.

At this point the document is actually a very simple wrapper to the game board class. In later articles we'll add more functionality that will require changes to the document but for now it is fairly simple. We added an instance of the game board class and then seven functions that call similar functions on the board. This will allow the view to access the board information via the document. The source file for the document (SameGameDoc.cpp) is also very simple since all of the functions that we added have their implementation in-line (changes bolded).

#include "stdafx.h"
#include "SameGame.h"

#include "SameGameDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CSameGameDoc
IMPLEMENT_DYNCREATE(CSameGameDoc, CDocument)
BEGIN_MESSAGE_MAP(CSameGameDoc, CDocument)
END_MESSAGE_MAP()

// CSameGameDoc construction/destruction
CSameGameDoc::CSameGameDoc()
{
}

CSameGameDoc::~CSameGameDoc()
{
}

BOOL CSameGameDoc::OnNewDocument()
{
  if (!CDocument::OnNewDocument())
    return FALSE;

  //  Set (or reset) the game board
  m_board.SetupBoard();

  return TRUE;
}

Really all we added was the call to the SetupBoard function in the OnNewDocument handler in the document. All this does is allows the user to start a new game with the built-in accelerator Ctrl+N or from the menu File->New.

As we continue through the series of articles we'll be adding new functions to both the game board and the document to implement different features for the game but for now we are done with the document and are ready to display this information in the view.

Continue to Page 3: The View: Drawing your Game