Introduction to Computer Science II

Project 3 for 2007-Oct-05

The Game of Life


The Game of Life

The Game of Life is an example of cellular automata---that is, it is the result of a collection of simple entities (cells) that behave automatically by following simple rules. It is, thus, not really a game in any usual sense. This particular cellular automata exhibits some surprisingly complex behavior, as groups of cells can move in definite directions and then interact with other groups of cells that they encounter. That is, higher-level, more complex behavior emerges from the simple, lower-level rules that control the cells.

Here is how the game works: Consider a rectangular grid of size n x m (rows by columns), known here as a universe. Each of the nm positions in the grid is occupied by a cell. Each cell is either in the state of being dead or of being alive at any given time. Time itself moves in discrete steps, which we will call generations. Thus, at generation 0, there are nm cells, some of which are alive, and the rest are dead. This state, at generation 0, is the initial state of the game.

When time advances from generation g to generation g+1, each cell must take the two following steps to evolve:

  1. Count live neighbors: The cell must examine each adjacent cell in the grid (there are 8 of them) and count how many of them are current (in generation g) alive.

  2. Apply rules: The cell must then apply the following rules to determine its state in generation g+1:

A game consists of advancing time through the generations until one of the following things occurs:

  1. A predetermined maximum generation number is reached.

  2. The universe becomes static---that is, in going from one generation to the next, none of the cells changes state.

Note that it is possible for a universe to enter a cycle of repeating states, where some sequence of states repeats indefinitely. Detecting such a case would be difficult (although not impossible!), and we will ignore that as a possible way of ending a game. [Question: Is it possible for a universe not to repeat?]


Your assignment

Write a program that carries out a Game of Life. Specifically, a run of your program will look like this:

(temp2)[79]~/classes/current/intro-II/projects/development/game-of-life> java PlayLife tests/X-pattern.init 4 Text
Generation = 0, Population = 21
+---------+
-+-------+-
--+-----+--
---+---+---
----+-+----
-----+-----
----+-+----
---+---+---
--+-----+--
-+-------+-
+---------+

Generation = 1, Population = 20
-----------
-+-------+-
--+-----+--
---+---+---
----+++----
----+-+----
----+++----
---+---+---
--+-----+--
-+-------+-
-----------

Generation = 2, Population = 24
-----------
-----------
--+-----+--
---+++++---
---++-++---
---+---+---
---++-++---
---+++++---
--+-----+--
-----------
-----------

Generation = 3, Population = 20
-----------
-----------
---+++++---
--+-----+--
--+-----+--
--+-----+--
--+-----+--
--+-----+--
---+++++---
-----------
-----------

Generation = 4, Population = 44
-----------
----+++----
---+++++---
--+-+++-+--
-+++---+++-
-+++---+++-
-+++---+++-
--+-+++-+--
---+++++---
----+++----
-----------

In order to get started with this code, you will need to copy a directory of pre-written, pre-compiled classes from my directory into your home directory, like so:

cp -r ~sfkaplan/public/cs12/project-3 .

There are four classes provided. We first describe what each class provides and how to use them. However, these classes also make some assumptions about your code and the methods that you will write, so will next describe the Game class that you must write, specifying what methods it must contain.

PlayLife

This is the class that is responsible for kicking off the game. It contains the main() method, and it will handle the command-line arguments. It is from main() that a Game object is created and then called. More on that below, where the Game class is described.

UserInterface and TextUserInterface

This pair of classes is responsible for displaying the state of the game onto the screen. Our first concern is how to create an object of this type. Specifically, your Game object should, at some point, create one with a line that looks something like this:

UserInterface ui = UserInterface.create("Text", this);

There are two things worth explaining here: First, note that we don't use the new keyword. I will ask that you ignore this detail for now, knowing that we will address it in a later project when we create a graphical interface for this program. Second, note the use of this. The variable this, which you never declare, is always available inside any object, and is an object's pointer to itself. Thus, we are passing, to the UserInterface.create method, a pointer of the Game object that is creating the UserInterface object in the first place. By doing so, methods inside the UserInterface object can call on methods within the Game object, about which we say more below.

The UserInterface object that you create has one critical method:

public void display ()

This method will take care of displaying the state of the game for you. In this case, it does so by printing the grid to the screen. Whenever you want the state of the game to be show (e.g., after evolving to the next generation), just call this method and it will take care of the rest.

Support

This class just provides some methods that should, ideally, make your life easier. The first two are intended to prevent you from needed to deal with exceptions. (If you recall, you often must append throws IOException to methods that use file operations, and then any methods that call those methods, etc. Using the following two methods will eliminate that need, at least within this program.)

public static Scanner makeScannerForFile (String filename)
public static boolean hasNextInt (Scanner sc)
public static int nextInt (Scanner sc)

So, to understand how to use these two methods, let's consider a sample method:

  public static int foo (String someFileName) throws IOException {

      Scanner sc = new Scanner(new File(someFileName));
      int x = 0;
      if (sc.hasNextInt()) {
          x = sc.nextInt();
      }
      return x;

  }

In this example, any method that calls foo must also declare that it throws IOException. To avoid that mess, we can use the Support methods, like so:

  public static int foo (String someFileName) {

      Scanner sc = Support.makeScannerForFile(someFileName);
      int x = 0;
      if (Support.hasNextInt(sc)) {
          x = Support.nextInt(sc);
      }
      return x;

  }

Additionally, there is a rather general method to make your code cleaner when you encounter problems. Specifically, when you encounter an error, your program should likely print an error and end the program. The following method does both of those steps for you, given an error message to display:

public static void abort (String message)

Thus, you write something like the following in your code:

  if (row < 0) {
      Support.abort("ERROR: Rows numbers must be non-negative: " + row);
  }

What your Game class must contain

Some of the code in the four classes above assume that there is a Game class from which a Game object can be made. It will further assume that there are certain methods in the Game object on which it can call. Here is the list of those methods and the things that they are expected to do:

The rest is up to you. That is, the specific manner in which you store the grid and interact with each cell is up to you. So long as the methods described above perform as specified, the program will work.

What your Cell class must contain

The TextUserInterface depends on cells being stored in Cell objects that it can access through the Game class's getCell method. Moreover, it depends on the following method existing within each Cell object:


Dividing into classes

In class, we will go over, in detail and with examples, how to create new types of data, known as instantiable classes. It is from such classes that objects can be made. Our new trick, starting now, will be to divide our computation into objects. So, in thinking of how the Game of Life should be programmed, think of the elements of the program and how they can be divided into objects. We already have a Game object to represent a whole Game of Life. Certainly there should be Cell objects. You may wish to break down the task further, defining other object types (classes!) -- that is up to you!


Testing your code

You copied, from my directory, useful files that will help in testing your code. In particular, within your project-3 subdirectory, there is another subdirectory named tests. Go look inside it. You will find two types of files:

There are three pairs of these files:

How to use the files for testing: It's too hard, beyond the simple case, to check the output of your program against the .results files with your eyes; doing so may cause blindness, seizures, or insanity. Instead, you can use some automation to do the comparing for you. To do so, follow these steps:

  1. Store the output in a file: When you run your program, don't let the output just dump onto a screen; instead, redirect the output into a file for later examination and comparison, like this:

    java PlayLife tests/X-pattern.init 10 Text > run.results

    The > symbol will redirect the output of your Java program into the file whose name follows that symbol---in this case, a file named run.results. You can open this file (e.g., in emacs) to see the output after the program is done.

  2. Compare: You can now compare the results of your program with the standard results that I provided, like so:

    cmp run.results test/X-pattern.results

    If the two files are exactly the same, cmp prints nothing at all. Thus, no news is good news. If it finds a discrepency, it will tell you on what line(s) and at what position(s) on the line(s).


Submitting your work

From within your project-3 subdirectory, use the following command to submit you work (all of your Java code files) when you are done:

cs12-submit project-3 *.java

This assignment is due Friday, 2007-Oct-05, at 11:59 pm

Scott F. H. Kaplan
Last modified: Fri Oct 5 10:35:41 EDT 2007