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; }