CS105: Introduction to Computer Programming: C++

Assignment #7 Playing the Abstract Game of Life

Due

Wednesday, March 31st at NOON

Overview

You will be coding the Game of Life again! But there are some twists this time. Life contains a two-dimensional grid of cells. A cell can only be in one of two states: alive or dead. There are two kinds of cells: ConwayCells and FredkinCells. Once the grid is manually populated with live and/or dead cells, the grid represents the 0th generation of Life. After that, everything is automatic, and Life evolves from the 1st to the Nth generation. A generation is simply the state of the grid (i.e. the layout of the live and dead cells). Live ConwayCells are denoted with an asterisk, "*", and dead cells are denoted with a period, "." (when printing the board please insert spaces between cells). A ConwayCell has 8 neighbors, if it's an interior cell, 5 neighbors, if it's an edge cell, and 3 neighbors, if it's a corner cell. The example below is of 1 ConwayCell that is alive surrounded by 8 ConwayCells that are dead:

. . .
. * .
. . .
ConwayCells do not have the notion of age, FredkinCells do. A FredkinCells' age is initially zero and only increments by one if the cell is alive and stays alive. Live FredkinCells are denoted with their age, if their age is less than 10, otherwise denoted with a plus, "+", and dead cells are denoted with a minus, "-". A FredkinCell has 4 neighbors, if it's an interior cell, 3 neighbors, if it's an edge cell, and 2 neighbors, if it's a corner cell. The example below is of 1 FredkinCell that is alive and of age 5 surrounded by 4 FredkinCells that are dead:
-
- 5 -
-
The rules for going from one generation to the next for ConwayCells are:
  1. a dead cell becomes a live cell, if exactly 3 neighbors are alive
  2. a live cell becomes a dead cell, if less than 2 or more than 3 neighbors are alive
The rules for going from one generation to the next for FredkinCells are:
  1. a dead cell becomes a live cell, if 1 or 3 neighbors are alive
  2. a live cell becomes a dead cell, if 0, 2, or 4 neighbors are alive

Instructions

You will define the following classes:
  1. AbstractCell, an abstract class that is the base class of class ConwayCell and class FredkinCell
  2. Cell, a handle class (not in inheritance hierarchy) that manages derived class objects of class AbstractCell
  3. ConwayCell, a concrete class
  4. FredkinCell, a concrete class
  5. Life, a concrete class to test and play the game of life with either ConwayCell, FredkinCell or Cell (based on the input file below).
If Life is instantiated with Cell, then when a FredkinCell's age is to become 2, and only then, it becomes a live ConwayCell instead.
To push Life forward one generation, traverse the grid of cells, only notice the live cells, and for each of them, visit the neighbors and increment their respective neighbor counts. Remember that the two kinds of cells have different definitions of neighbor. Traverse the grid a second time, set the next state, and zero out the neighbor count. Remember that the two kinds of cells have different rules for the next state.

CLARIFICATION on the Cell class: It should NOT be a derived class of AbstractCell. Life will have to contain a grid of cells to implement the game of life. Probably the easiest way to contain many cells is with a Standard Template Library (STL) container such as vector. Because it is not possible to place AbstractCells themselves into a vector, and it is a bad idea to place AbstractCell* (pointers to AbstractCell) into a vector, the Cell class will solve these problems. The reason it is bad for pointers to AbstractCell to be in a vector is because to assign one vector to another, one would need assignment operators and copy constructors in the AbstractCell class - but it is abstract. So, the solution is to have a wrapper class - class Cell. It's like a smart pointer if you want to read about them. But all you need to know is that you pass an already allocated pointer to either a FredkinCell or ConwayCell (aka a pointer to an AbstractCell) to the constructor of the Cell class. It'll wrap objects of those 2 derived classes, and manage their memory - aka delete them in its destructor. Then it can have an assignment operator and a copy constructor so you can just assign one vector to another in your Life class. Also, it will need wrapper functions for your (virtual) methods defined in AbstractCell. For example, if you defined a virtual function evolve in AbstractCell, you would need a function in your Cell class called evolve so that in the Life class you can just call Cell->evolve, which will simply be a wrapper and polymorphically call the appropriate evolve function of your classes derived from AbstractCell. So this is what is intended by the Cell class - Good luck!

In a main program, you will read in initial board states from an input file and iterate through several generations of the game of life. You will also create a Makefile to compile all your C++ files.

  1. Take an input file LifeConwayCell.in, LifeFredkinCell.in, and LifeCell.in. The format is thus: The first line specifies what kind of game you are playing (either "Conway", "Fredkin", or "Cell") for ease of setup, the second line says the number of rows, the third the number of columns in the initial board that follows. You will read in your initial board and initialize your game to start with that as the 0th generation. For the version of the program you turn in, input from the command line (using cin) the string name of the file you will take as your input. Only prompt for 1 file and then process that file entirely as described on the listserv.
  2. For output, output to cout several generations of the game of life given the first initial generation. Here are the output files corresponding to the input above: LifeConwayCell.out, LifeFredkinCell.out, LifeCell.out. Instructions specifying the exact format of output TO COUT : For ConwayCells only, output generations 0, 100, 200, 300, and 1000. For all others, output generations 0, 1, 2, 3, 4, and 5 - following the format of output in the files above. But you do NOT need to output to a file yourself, just output to cout.
  3. You will need a Makefile that compiles all of your C++ classes, and creates an executable "game" file to run your program. You can pattern your Makefile after the example one used in class that is available in the cs105/code directory. Make sure the lines after the rule: line starts with a tab! (Not spaces)
  4. Compile and run your code:
      computer% make clean
      computer% make
      computer% ./game
    
  5. I recommend using the CS department linux machines. Make sure to test your code on the CS department machines before turning the assignment in!
  6. When you're happy with your code (and there are no warnings!), use the turnin program to submit all of your files, .h and .cpp, and Makefile. MAKE SURE YOUR NAME IS ON THE TOP OF EACH FILE (except the Makefile)! Use jbsartor as the grader and assign7 ( assign7Mulligan if it is late) as the assignment name.
      computer% turnin --submit jbsartor assign7 (all .h and .cpp files)  Makefile
    
    You can turn in your files as many times as you want - I will only take the last one submitted.

Submission Checklist

Extra Credit (+8%)

Make your concrete Life class a template class instead:

  1. Life<T>, a concrete class
This means that Life can be instantiated with either ConwayCell, FredkinCell or just Cell - corresponding to the input files above. You can still have a main function inside the Life.cpp file to play your game of life - but this gives you practice with template classes.