Overview · Rules · Files · Tasks · Simulation · Exploration · Testing · Report Format · Checklist

Overview

In Project 2 you will build Conway's Game of Life, a cellular automaton in which a grid of cells changes from one generation to the next according to a small set of rules. Each Cell is either alive or dead. A Landscape stores a two-dimensional grid of Cells, and the state of each Cell in the next generation depends on the Cells surrounding it.

From Lab 2 to Project 2: In Lab 2 you practiced traversing a 2D array, working with rows and columns, handling boundaries, and separating a backing model from its displayed view. Those ideas now become the foundation of a larger object-oriented program. Instead of an int[][], your model will contain a Cell[][].

The supplied LandscapeDisplay class provides the graphical window. Your primary job is not to write graphics code. Your job is to build and update the model correctly so that the display has the correct information to draw.

Cell
  ↓
Cell[][] inside Landscape
  ↓
neighbor relationships
  ↓
Game of Life rules
  ↓
next generation
  ↓
LandscapeDisplay

Conway's Game of Life Rules

A Cell's next state depends on the number of its living neighbors. A Cell can have as many as eight neighbors: above, below, left, right, and the four diagonals. Cells on the boundary of the Landscape have fewer neighbors.

One generation changes simultaneously

Every Cell's next state must be determined from the same current generation. Imagine that you are processing the Landscape from left to right. If you calculate the next state of the first Cell and immediately change it in the original Landscape, then the next Cell may see that new state when it calls getNeighbors(). It would be making its decision using a mixture of the current generation and the next generation.

Instead, think of the update as having two separate grids:

CURRENT GENERATION             NEXT GENERATION
(the grid you read)            (the grid you build)

 X                             ? ? ?
XXX              ---->           ? ? ?
                               ? ? ?

For every location:

  1. Look at the Cell and its neighbors in the current Landscape.
  2. Determine what that Cell should become in the next generation.
  3. Store that result in a separate grid.
  4. Do not change the current Landscape while you are still using it.

Only after you have calculated the next state of every Cell should the new grid replace the old one.

current generation
       ↓
calculate every Cell's next state
       ↓
build complete next generation
       ↓
replace the old generation

This guarantees that every Cell follows Conway's rules using exactly the same generation of neighbors. Think of the two arrays as the grid you read and the grid you build.

How Cell state is represented

Internally, each Cell stores its state as a boolean: true means alive and false means dead. Cell.toString() represents those states as "1" and "0". When displaying an entire Landscape as text, Landscape.toString() uses X for a living Cell and a blank space for a dead Cell so that patterns are easier to see.

Files Provided

You should already have a Project2 folder in the Projects folder of your personal Google Drive from Lab 2. Your completed Grid.java should already be there. Download the Project 2 files below into that same Project2 folder.

Cell.java CellTester.java Landscape.java LandscapeTester.java LandscapeDisplay.java
Work in order. The later tasks depend on the earlier ones. Experiment with each class and method yourself, then use the appropriate tester before building the next layer.

Tasks

Task 1 — Build the Cell Class

A Cell represents one location in the Game of Life. It stores one piece of state: whether that Cell is currently alive.

Before running the tester, create several Cells in a main() method, change their states, and print them. Make sure the behavior makes sense to you.

Then compile and run CellTester for the Cell basics.

Task 2 — Build the Landscape

A Landscape is the model for the simulation. Its central data structure is a two-dimensional array of Cell references:

Cell[][] landscape

Complete the Landscape construction and access methods:

Why the boolean-grid constructor is useful: Random Landscapes are useful for simulations, but known patterns are much easier to reason about when testing. A boolean[][] lets you create an exact starting configuration and know what should happen next.

Use main() to create small Landscapes such as 2×3 and 3×3 grids. Print their dimensions, inspect individual Cells, and print the whole Landscape before running LandscapeTester.

Task 3 — Find the Neighbors of a Cell

Complete getNeighbors(int row, int col). It returns an ArrayList<Cell> containing references to all valid neighboring Cells around the requested location. Use Java's built-in java.util.ArrayList for this method.

For a location at (row, col), the possible surrounding locations are:

(row-1,col-1)  (row-1,col)  (row-1,col+1)
(row,  col-1)   [row,col]    (row,  col+1)
(row+1,col-1)  (row+1,col)  (row+1,col+1)
Connection to Lab 2: This is the boundary problem from Lab 2 in a new form. Before accessing a possible neighbor, determine whether its row and column are valid locations in the backing 2D array.

Experiment with a small 3×3 Landscape. Check the neighbor list for a corner, an edge, and the center. Then run the neighbor portion of LandscapeTester.

Task 4 — Implement the Game of Life Rule

Return to Cell.java and complete updateState(ArrayList<Cell> neighbors). This method receives the neighboring Cells from the current generation and changes this Cell's alive/dead state according to the Game of Life rules.

  1. Count how many Cells in neighbors are alive.
  2. Use the current state of this Cell and that count to determine its next state.
  3. Update this Cell's state.

Test examples yourself: a living Cell with two living neighbors should survive; a living Cell with four should die; a dead Cell with three should become alive. Then run CellTester again and complete the state-rule tests.

Task 5 — Advance the Landscape One Generation

Complete Landscape.advance(). This is where the Cell rules and the 2D Landscape come together.

Do not update the original Cells one at a time. All next-generation states must be calculated from the same original generation.

A useful design is:

  1. Create a new Cell[][] with the same dimensions as the current Landscape.
  2. Visit each location in the original Landscape.
  3. At each location, create a new Cell based on the current state of the original Cell.
  4. Determine that new Cell's next state using the neighbors from the original Landscape, and place the new Cell in the corresponding location of the new array.
  5. Only after every location has been processed, make the Landscape's backing array refer to the completed new array.

The important rule is: read from the old generation while building the new generation. Do not change the old generation as you go.

Use a known pattern created with Landscape(boolean[][] grid) to predict the next generation yourself. Then run the advance() portion of LandscapeTester.

Task 6 — Visualize and Run the Simulation

LandscapeDisplay.java is provided. Read through it, but you are not expected to implement the Swing window machinery. The display stores a Landscape and asks the Landscape to draw itself.

MODEL                         VIEW
Landscape                    LandscapeDisplay
Cell[][] landscape   ---->   graphical window

The draw(Graphics g, int scale) method is already provided in Landscape.java. Once your model works, run LandscapeDisplay and confirm that the graphical view agrees with the states stored in your Landscape.

Then create LifeSimulation.java. Model its setup on LandscapeDisplay.main(). Your simulation should create a Landscape and LandscapeDisplay and repeatedly:

  1. pause briefly with Thread.sleep(...),
  2. call advance(), and
  3. call repaint() on the display.

Start small. First display only the initial board. Then advance exactly one generation and stop. Compare the result with what you expected before running a long animation.

Once that works, use command-line arguments to make useful simulation settings easy to change, such as grid dimensions, initial chance, or number of generations.

Exploration

After your simulation is working, use it to investigate how the initial conditions affect the long-term behavior of the Game of Life.

Investigate the relationship between initial probability of living Cells and the number of living Cells remaining after many generations. Develop a hypothesis, design an experiment, and gather enough data to evaluate your hypothesis.

The previous version of this project used 1000 generations as a useful long-run observation point and suggested testing initial probabilities across the range from 0 to 1. You may use that structure for your experiment. Run enough trials that your conclusions are supported by evidence rather than by one random run.

Think experimentally: Decide what you will vary, what you will measure, what you will hold constant, and how many trials you need before collecting your final results.

Testing

The provided testers are designed to help you make incremental progress. Work through the project in order. Before using a tester, experiment with the method yourself in main() using small inputs whose behavior you can predict.

javac *.java
java CellTester
java LandscapeTester
Basic Tester Self Check: Passing the provided testers is an important milestone, but the testers perform only basic checks. Passing all tests does not guarantee that every possible case in the project is correct.

The graphical display is also useful evidence, but a picture that looks reasonable is not proof that the underlying algorithm is correct. Use both predictable small tests and the visualization.

Report Format

Your intended audience is your peers who are not in this class but who have taken a computer science course. Your reader should be able to understand what you built, the important computer science ideas behind it, what experiment you performed, and what your results mean without having completed this assignment themselves.

Your project report should contain the following sections. Please include a header for each section.

Abstract

Briefly summarize the project in your own words. Identify both the core computer science ideas and the application: a 2D array of objects, neighbor relationships, simultaneous state updates, and their use in Conway's Game of Life.

Explain why a 2D array is an appropriate structure for this problem and briefly identify the experiment you performed. Detailed results belong in the Results section.

Before finishing your Abstract, ask yourself:

Results

Present evidence that your simulation is working and the evidence from your exploration. Include an image of a Landscape's initial state and an image of the following state after one update. Explain why the change is consistent with the Game of Life rules.

Then present the results of your experiment on initial conditions and long-term behavior. Organize numerical results clearly; a table or graph will often be more useful than a large block of raw output.

Evidence → Observation → Interpretation
  1. Evidence: Present the relevant output, image, table, graph, or numerical result.
  2. Observation: Describe what the evidence actually shows.
  3. Interpretation: Explain what you think the result means and why it may have occurred.

Your discussion should address questions such as:

Extensions

Describe any work you completed beyond the required project. Focus on the design and behavior of the extension: what capability you added, what classes or methods changed, how the new logic works, and how you determined that it worked.

Useful extensions might involve new starting patterns, additional visualization, alternative rules, improved simulation controls, or additional experiments. If you did not complete an extension, simply state that no extension was completed.

References/Acknowledgements

List the people and outside resources that contributed to your work. Include people you worked with or received help from, including TAs and instructors, and acknowledge outside references that helped you understand an algorithm, library, or programming technique.

Before You Submit

Remember: The provided testers perform only basic checks. Passing all of their tests does not guarantee full credit on the project. Your implementation must satisfy the complete project specification.