Version 9

It's boring to always use the same Life pattern. This version allows us to click on any cell in the Life array to change its color. We do this by adding implements MouseListener to our MyCanvas class. MyCanvas also gains an init method for attaching the listener.

When we implement something, we commit to supplying all its methods. There are several mouse-does-something methods that we have to supply, but the ones we don't care about don't have to do anything. The one we do care about, mousePressed, just does some arithmetic to figure out which cell was clicked, and toggles the value of that cell.

Top     Version 8     Version 10    

Source code:

import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;    // needed for ActionListener

public class Life extends Applet
{
  static int boardSize = 10;
  Cell [][] board;
  Cell [][] nextBoard;
  Button goButton;
  MyCanvas canvas;
  SeparateSubTask subtask;
  boolean running = false;

  /**
   * Creates an empty (all cells dead) playing board of the given boardSize.
   */
  static Cell[][] emptyBoard (int boardSize)
  {
    Cell[][] result = new Cell[boardSize][boardSize];

    for (int i = 0; i < boardSize; i++)
      for (int j = 0; j < boardSize; j++)
        result[i][j] = new Cell ();
    return result;
  }

  public void init ()
  {
    setLayout (new BorderLayout ());

    goButton = new Button ("Stop/Go");
    add (BorderLayout.NORTH, goButton);

    // Finally, add code to make the button do something
    goButton.addActionListener (new GoButton ());

    board = emptyBoard (boardSize);

    // -------------------------------- start of test data

    board[3][4].setAlive (true);
    board[4][5].setAlive (true);
    board[5][3].setAlive (true);
    board[5][4].setAlive (true);
    board[5][5].setAlive (true);

    // -------------------------------- end of test data

    canvas = new MyCanvas (board, boardSize);
    add (BorderLayout.CENTER, canvas);
  }

  class GoButton implements ActionListener
  {
    public void actionPerformed (ActionEvent e)
    {
      if (running)
	{
	  subtask.stop ();
	  running = false;
	}
      else
	{
	  subtask = new SeparateSubTask ();
	  subtask.start ();
	  running = true;
	}
    }
  }
  
  class SeparateSubTask extends Thread
  {
    public void run ()
    {
      int numberOfNeighbors;

      while (true)
	{
	  nextBoard = emptyBoard (boardSize);
	  for (int i = 0; i < boardSize; i++)
	    for (int j = 0; j < boardSize; j++)
	      {
		numberOfNeighbors = Cell.countLiveNeighbors (board, i, j);
		if (board[i][j].getAlive ())
		  {
		    if (numberOfNeighbors == 2 || numberOfNeighbors == 3)
		      nextBoard[i][j].setAlive (true);
		  }
		else // cell is dead
		  if (numberOfNeighbors == 3)
		    nextBoard[i][j].setAlive (true);
	      }
	  board = nextBoard;
	  canvas.board = nextBoard; // otherwise it still uses the old one
	  
	  // ...then make the changes visible.
	  canvas.paint (canvas.getGraphics ());
	  repaint ();
	  try { sleep (1000); } catch (Exception e) { }
	}
    }
  }
}

class MyCanvas extends Canvas implements MouseListener
{
  int boardSize;
  Cell board[][];
  Dimension d;    // Moved to here so it is available in mousePressed()
  int cellWidth;  // ditto
  int cellHeight; // ditto

  MyCanvas (Cell [][] board, int boardSize)
  {
    this.board = board;
    this.boardSize = boardSize;
    init ();  // Don't forget to start mouse listener
  }

  public void paint (Graphics g)
  {
    d = getSize ();
    cellWidth = d.width / boardSize;
    cellHeight = d.height / boardSize;

    for (int i = 0; i < boardSize; i++) {
      for (int j = 0; j < boardSize; j++) {
        if (board[i][j].getAlive ())
	  g.setColor (Color.blue);
	else
	  g.setColor (Color.white);
	g.fillOval (i * cellWidth, j * cellHeight, cellWidth, cellHeight);
      }
    }
  }
  
  public void init () {
    this.addMouseListener (this);
  }
  
  public void mousePressed (MouseEvent e) {
    Point p = e.getPoint ();
    int i = p.x / cellWidth;
    int j = p.y / cellHeight;
    board[i][j].setAlive (!board[i][j].getAlive ());
    repaint ();
  }
  
  public void mouseClicked (MouseEvent e) {}
  public void mouseReleased (MouseEvent e) {}
  public void mouseEntered (MouseEvent e) {}
  public void mouseExited (MouseEvent e) {}
  
}


/**
 * A Cell is one element of a two-dimensional array in John Conway's
 * "Game of Life."  It is either "alive" or "dead."
 * @author Dave Matuszek
 */

class Cell
{
  boolean alive = false;

  /**
   * Constructs a Cell in a given state (alive or dead).
   */
  Cell (boolean alive)
  {
    this.alive = alive;
  }

  /**
   * Constructs a Cell in a "dead" state.
   */
  Cell ()
  {
    this (false);
  }

  /**
   * Sets this cell to be alive or dead.
   */
  void setAlive (boolean alive)
  {
    this.alive = alive;
  }

  /**
   * Returns the state (alive or dead) of this cell.
   */
  boolean getAlive ()
  {
    return alive;
  }

  // Deleted: static boolean isAlive (int i, int j)

  /**
   * Count the number of living cells adjacent to this one.
   * Cells outside the array bounds are considered "dead."
   * This routine has been modified to eliminate references to
   * Life.isAlive ().
   */
  static int countLiveNeighbors (Cell [][] board, int i, int j)
  {
    int limit = Life.boardSize - 1;
    int count = 0;
    for (int ii = i - 1; ii <= i + 1; ii++)
      for (int jj = j - 1; jj <= j + 1; jj++)
      {
	if (ii == i && jj == j) continue;
	if (ii < 0 || ii > limit) continue;
	if (jj < 0 || jj > limit) continue;
	if (board[ii][jj].getAlive ()) count++;
      }
    return count;
  }
}