Conway's Game of Life
Source listing
//=================================================================
// File: GOFMain.cpp
// Purpose: This file contains main() which just instantiates
// an instance of the GameOfLife and calls its play function.
// Author: Dr. Rick Coleman
//=================================================================
#include <iostream>
#include "GameOfLife.h"
using namespace std;
int main(int argc, char **argv)
{
GameOfLife *gof;
// Instantiate the game application object
gof = new GameOfLife();
// Start the game
gof->playGame();
// Game over so do normal exit
return 0;
}
//=================================================================
// File: GameOfLife.h
// Purpose: This defines the main application class which handles
// all of the user interface.
// Author: Dr. Rick Coleman
//=================================================================
#ifndef GAMEOFLIFE_H
#define GAMEOFLIFE_H
#include "Gameboard.h"
class GameOfLife
{
private:
Gameboard *m_oGameboard; // The gameboard object
public:
GameOfLife(); // Class constructor
~GameOfLife(); // Class destructor
void playGame(); // Main game playing controller function
private:
void clearScreen(); // Scroll all of current display off screen
};
#endif
//=================================================================
// File: GameOfLife.cpp
// Purpose: This is the implementation file for the main application
// class which handles all of the user interface.
// Author: Dr. Rick Coleman
//=================================================================
#include <iostream>
#include <stdio.h>
#include "GameOfLife.h"
using namespace std;
//-----------------------------------------------------
// Class constructor
//-----------------------------------------------------
GameOfLife::GameOfLife()
{
// Instantiate the game board
m_oGameboard = new Gameboard();
}
//-----------------------------------------------------
// Class destructor
//-----------------------------------------------------
GameOfLife::~GameOfLife()
{
// Destroy the game board
delete m_oGameboard;
}
//-----------------------------------------------------
// playGame()
// Purpose: Main game playing controller
// Args: none
// Returns: void
//-----------------------------------------------------
void GameOfLife::playGame()
{
char ch[32]; // input catcher
bool quitPlay = false;
bool setupDone = false;
int row, col;
int numGen; // Number of generations to display
int genNumber; // Number of current generation
// Clear the command screen. To avoid any platform specific system commands
// we will clear the screen just by printing a number of newline characters
// forcing the current display to scroll off
clearScreen();
// Print the instructions
cout << " Conway's Game of Life \n";
cout << "This simple game was popular in the late 60s and early 70s.\n";
cout << "In this game of life the world is a 25 x 25 cell grid in which\n";
cout << "organisms live and die based on three simple rules.\n\n";
cout << " 1. If an organism has fewer than 2 neighboring cells it\n";
cout << " dies from loneliness in the next generation.\n";
cout << " 2. If an organism has 4 or more neighboring cells it\n";
cout << " dies from over-crowding and disappears in the next generation.\n";
cout << " 3. If an empty cell has exactly 3 neighbors then a new\n";
cout << " organism will be born in this cell in the next generation.\n\n";
cout << "You will be asked to place organisms on the grid by specifying the\n";
cout << "row and column. You may place as many as you wish. You will then\n";
cout << "be asked how many generations to display. Each generation will then\n";
cout << "be shown. Press any key to see the next generation. After all of the\n";
cout << "generations have been shown you may choose to show more generations,\n";
cout << "start a new game, or quit.\n\n";
cout << "Press the Enter/Return key to begin...";
cin.getline(ch, '\n');
// Note: as this program was being developed the above instruction code was entered
// then the application was compiled and run to test this portion of the code.
while(!quitPlay)
{
m_oGameboard->printLayout(-1);
setupDone = false;
while(!setupDone)
{
// Get setup
cout << "Enter a row and column where a cell should be placed then press Enter.\n";
cout << "Example: type 2 15 then press Enter to place a cell in row 2, column 15.\n";
cout << "Enter at least one negative number to end the setup, e.g. -1 0.\n";
cin >> row >> col;
if((row >= BOARDDIM) || (col >= BOARDDIM))
cout << "***Error: row and column index must be less than " << BOARDDIM << ".\n";
else if((row < 0) || (col < 0))
setupDone = true;
else
{
m_oGameboard->setCellOccupied(row, col);
m_oGameboard->printLayout(-1);
}
}
cout << "\n\nEnter the number of generations to show then press Enter.\n";
numGen = 0; // init
cin.ignore();
cin.getline(ch, '\n');
try
{
numGen = atoi(ch); // convert to an int
}
catch(...) // If conversion failed default to 0
{
cout << "Error: Unable to read number of generations. Must be a number.\n";
numGen = 0;
}
if(numGen > 0)
clearScreen();
// Note: After the above "setupDone" loop was written it was run and numbers
// entered to ensure each of the if..elseif..else routes were tested
genNumber = 1;
while(genNumber <= numGen)
{
m_oGameboard->createNextGeneration();
m_oGameboard->printLayout(genNumber);
if(genNumber <= numGen)
cout << "\nPress the Enter key to see the next generation\n";
cin.getline(ch, '\n');
if(genNumber == numGen)
{
// Ask user if more generation of this layout are needed
cout << "\n\nAll generations have been displayed. Would you like to display\n";
cout << "more generations with this setup? (Press Y or N then press Enter)\n";
cin.getline(ch, '\n');
if((ch[0] == 'Y') || (ch[0] == 'y'))
{
cout << "Enter the number of additional generations you want to see\n";
cout << "then press Enter.\n";
int moreGens = 0;
cin.getline(ch, '\n');
try
{
moreGens = atoi(ch); // convert to an int
numGen += moreGens;
}
catch(...) // If conversion failed default to 0
{
cout << "Error: Unable to read number of generations. Must be a number.\n";
moreGens = 0;
}
}
}
genNumber++;
}
// Note: After the above show generations loop was written it was tested for one
// generation and checked to be sure everything was being advanced correctly.
// Then it was run for several generations. Finally the code to add generations
// was added and tested.
cout << "\n\nWould you like to play again? (Press Y or N then press Enter)\n";
cin.getline(ch, '\n');
if((ch[0] != 'Y') && (ch[0] != 'y'))
quitPlay = true;
else
m_oGameboard->clearLayout();
// Note: After all of the above was tested a final test was run in which a setup was
// created, run for 5 generations, then run for another 5 generations. Finally a
// second setup was created and run for 5 generations before terminating the application.
}
cout << "\n\nHope you enjoyed the game. Come play again some time.\n";
}
//-----------------------------------------------------
// clearScreen()
// Purpose: Clear the screen by forcing everything to
// scroll up and off.
// Args: none
// Returns: void
//-----------------------------------------------------
void GameOfLife::clearScreen()
{
for(int i=0; i<50; i++)
cout << "\n";
}
//=================================================================
// File: Gameboard.h
// Purpose: This file defines the game board class.
// Author: Dr. Rick Coleman
//=================================================================
#ifndef GAMEBOARD_H
#define GAMEBOARD_H
#include "Cell.h"
#define BOARDDIM 25
class Gameboard
{
private:
Cell m_oGameboardArray[BOARDDIM][BOARDDIM]; // The gameboard object
public:
Gameboard(); // Class constructor
~Gameboard(); // Class destructor
void setCellOccupied(int row, int col); // Set a cell as occupied
void createNextGeneration(); // Calculate the next generation layout
void printLayout(int gen); // Print the current layout
void clearLayout(); // Clear the current layout and start over
};
#endif
//=================================================================
// File: Gameboard.cpp
// Purpose: This file implements the game board class.
// Author: Dr. Rick Coleman
//=================================================================
#include <:iostream>
#include "Gameboard.h"
using namespace std;
//-----------------------------------------------------
// Class constructor
//-----------------------------------------------------
Gameboard::Gameboard()
{
// Nothing to do here yet
}
//-----------------------------------------------------
// Class destructor
//-----------------------------------------------------
Gameboard::~Gameboard()
{
// Nothing to do here
}
//-----------------------------------------------------
// setCellOccupied()
// Purpose: Set a cell as occupied
// Args: row - row of occupied cell
// col - column of ocupied cell
// Returns: void
//-----------------------------------------------------
void Gameboard::setCellOccupied(int row, int col)
{
m_oGameboardArray[row][col].setOccupied(true);
}
//-----------------------------------------------------
// createNextGeneration()
// Purpose: Tell each cell to calculate its next
// generation status
// Args: none
// Returns: void
//-----------------------------------------------------
void Gameboard::createNextGeneration()
{
bool UL, Above, UR, LF, RT, LL, Below, LR; // flags for positions
// Note: if flags are off the gameboard the occupied flag is false
for(int row=0; row<:BOARDDIM; row++) // For each row
{
for(int col=0; col<:BOARDDIM; col++) // For each column
{
// Set UL flag
if((col==0) || (row==0)) // top row or 1st col have no UL
UL = false;
else
UL = m_oGameboardArray[row-1][col-1].isOccupied();
// Set Above flag
if(row == 0) // top row has no above
Above = false;
else
Above = m_oGameboardArray[row-1][col].isOccupied();
// Set UR
if((row == 0) || (col == (BOARDDIM-1))) // top row or last column have no UR
UR = false;
else
UR = m_oGameboardArray[row-1][col+1].isOccupied();
// Set LF
if(col == 0) // 1st column has no left
LF = false;
else
LF = m_oGameboardArray[row][col-1].isOccupied();
// Set RT
if(col == (BOARDDIM-1)) // last column has no right
RT = false;
else
RT = m_oGameboardArray[row][col+1].isOccupied();
// Set LL
if((row == (BOARDDIM-1)) || (col == 0)) // bottom row or 1st column have no LL
LL = false;
else
LL = m_oGameboardArray[row+1][col-1].isOccupied();
// Set Below
if(row == (BOARDDIM-1)) // bottom row has no Below
Below = false;
else
Below = m_oGameboardArray[row+1][col].isOccupied();
// Set LR
if((row == (BOARDDIM-1)) || (col == (BOARDDIM-1)))
LR = false;
else
LR = m_oGameboardArray[row+1][col+1].isOccupied();
// Tell this cell to update itself for the next generation
m_oGameboardArray[row][col].calculateNextGeneration(UL, Above, UR, LF, RT, LL, Below, LR);
} // end col loop
} // end row loop
// Now tell all cells to update the current status for the next generation
for(int row=0; row<:BOARDDIM; row++) // For each row
{
for(int col=0; col<:BOARDDIM; col++) // For each column
{
m_oGameboardArray[row][col].setNextGeneration();
}
}
}
//-----------------------------------------------------
// printLayout()
// Purpose: Print the current layout.
// Args: gen - Generation number to print. If less
// than zero don't print number as this is
// part of the setup.
// Returns: void
//-----------------------------------------------------
void Gameboard::printLayout(int gen)
{
if(gen >= 0)
cout <:<: "\nGeneration " <:<: gen <:<: "\n";
// Print column numbers
cout <:<: " ";
for(int i=0; i<:BOARDDIM; i++)
{
cout <:<: (i/10);
}
cout <:<: "\n ";
for(int i=0; i<:BOARDDIM; i++)
{
cout <:<: (i%10);
}
cout <:<: "\n";
for(int row=0; row<:BOARDDIM; row++)
{
// Print row number
cout <:<: (row/10) <:<: (row%10);
// Print all cells
for(int col=0; col<:BOARDDIM; col++)
{
if(m_oGameboardArray[row][col].isOccupied())
cout <:<: "o";
else
cout <:<: ".";
}
cout <:<: "\n";
}
}
//-----------------------------------------------------
// clearLayout()
// Purpose: Reset all cells to unoccupied
// Args: none
// Returns: void
//-----------------------------------------------------
void Gameboard::clearLayout()
{
for(int row=0; row<:BOARDDIM; row++)
{
for(int col=0; col<:BOARDDIM; col++)
{
m_oGameboardArray[row][col].setOccupied(false);
}
}
}
//=================================================================
// File: Cell.h
// Purpose: This file defines the class representing a single
// cell on the game board.
// Author: Dr. Rick Coleman
//=================================================================
#ifndef CELL_H
#define CELL_H
#include <iostream>
using namespace std;
class Cell
{
private:
bool m_bIsOccupied; // Flag if there is a cell here
bool m_bOccupiedNextGen; // Flag if this cell is to be occupied in the next generation
public:
Cell(); // Class constructor
~Cell(); // Class destructor
bool isOccupied(); // Get the current occupied flag
void setOccupied(bool occ); // Set this cell's occupied status. Only used in setup
// Set up the next generation for this cell. The flags tell this cell the
// current state of each of its neighbors in the order upper left, above,
// upper right, left, right, lower left, below, lower right
void calculateNextGeneration(bool UL, bool Above, bool UR,
bool Left, bool Right,
bool LL, bool Below, bool LR);
void setNextGeneration(); // Set the is occupied flag for the next generation
};
#endif
//=================================================================
// File: Cell.cpp
// Purpose: This file implements the class representing a single
// cell on the game board.
// Author: Dr. Rick Coleman
//=================================================================
#include "Cell.h"
//-----------------------------------------------------
// Class constructor
//-----------------------------------------------------
Cell::Cell()
{
// Init the flags
m_bIsOccupied = false;
m_bOccupiedNextGen = false;
}
//-----------------------------------------------------
// Class destructor
//-----------------------------------------------------
Cell::~Cell()
{
// Nothing to do here
}
//-----------------------------------------------------
// isOccupied
// Purpose: Return the m_bIsOccupied flag
//-----------------------------------------------------
bool Cell::isOccupied()
{
return m_bIsOccupied;
}
//-----------------------------------------------------
// setOccupied
// Purpose: Set the m_bIsOccupied flag to true
//-----------------------------------------------------
void Cell::setOccupied(bool occ)
{
m_bIsOccupied = occ;
if(!m_bIsOccupied)
m_bOccupiedNextGen = false;
}
//-----------------------------------------------------
// calculateNextGeneration()
// Purpose: Set up the next generation for this cell.
// The args tell this cell the current state of each
// of its neighbors in the order upper left, above,
// upper right, left, right, lower left, below,
// lower right.
// Args: 8 boolean flags
// Returns: void
//-----------------------------------------------------
void Cell::calculateNextGeneration(bool UL, bool Above, bool UR,
bool Left, bool Right,
bool LL, bool Below, bool LR)
{
// Count the number of occupied cells
int count = 0;
if(UL) count++;
if(Above) count++;
if(UR) count++;
if(Left) count++;
if(Right) count++;
if(LL) count++;
if(Below) count++;
if(LR) count++;
// Check rule 1: If an organism has < 2 neighbors it dies from loneliness in the next generation
if(count < 2)
m_bOccupiedNextGen = false;
else if(count >= 4) // Check rule 2: If an organism has >= 4 neighboring cells it dies of overcrowding
m_bOccupiedNextGen = false;
else if((count == 3) && (!m_bIsOccupied)) // Check rule 3: If an empty cell has exactly 3 neighbors then
m_bOccupiedNextGen = true; // new organism will be born in this cell in the next generation.
else // Nothing changes
m_bOccupiedNextGen = m_bIsOccupied;
}
//-----------------------------------------------------
// setNextGeneration()
// Purpose: Set the current occupied flag to the next
// generation occupied flag.
// Args: none
// Returns: void
//-----------------------------------------------------
void Cell::setNextGeneration()
{
m_bIsOccupied = m_bOccupiedNextGen;
}