It's time to pretty things up and add some frills to the
final version.
I've added one button to clear the array, and another to randomly set some cells. The logic for these buttons is simple. However, when I added the buttons to the NORTH part of my BorderLayout, I discovered that each button took up the whole area and covered the previous button. As a result, I had to make a Panel (with a default FlowLayout manager), add the buttons to the panel, and add the panel to the NORTH of the BorderLayout. I also added two TextField elements, one to control the size of the board and one to control the speed (time between steps), and also one Label element for each TextField, to say what it's for. One cosmetic flaw remains: when the size of the board is changed, the spaces between ovals aren't cleaned up properly. (It looks fine if you clear the board before changing the size.) Fixing this would be a simple matter of drawing a gray rectangle filling the canvas before redrawing it at the new size. |
import java.awt.*; import java.applet.Applet; import java.awt.event.*; // needed for ActionListener import java.util.*; // needed for Random public class Life extends Applet { static int boardSize = 10; Cell [][] board; Cell [][] nextBoard; TextField sizeField; TextField speedField; MyCanvas canvas; SeparateSubTask subtask; boolean running = false; static Random rand = new Random (); int speed = 5; /** * 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 ()); Button goButton = new Button ("Stop/Go"); Button clearButton = new Button ("Clear"); Button addCellsButton = new Button ("Add Cells"); Panel buttonPanel = new Panel (); buttonPanel.add (goButton); buttonPanel.add (clearButton); buttonPanel.add (addCellsButton); add (BorderLayout.NORTH, buttonPanel); goButton.addActionListener (new GoButton ()); clearButton.addActionListener (new ClearButton ()); addCellsButton.addActionListener (new AddCellsButton ()); Label sizeLabel = new Label ("Size:"); Label speedLabel = new Label ("Speed:"); sizeField = new TextField ("" + boardSize, 4); speedField = new TextField ("" + speed, 4); Panel textPanel = new Panel (); textPanel.add (sizeLabel); textPanel.add (sizeField); textPanel.add (speedLabel); textPanel.add (speedField); add (BorderLayout.SOUTH, textPanel); sizeField.addActionListener (new SizeField ()); speedField.addActionListener (new SpeedField ()); 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 evt) { if (running) { subtask.stop (); running = false; } else { subtask = new SeparateSubTask (); subtask.start (); running = true; } } } class ClearButton implements ActionListener { public void actionPerformed (ActionEvent evt) { board = emptyBoard (boardSize); canvas.board = board; canvas.paint (canvas.getGraphics ()); repaint (); } } class AddCellsButton implements ActionListener { public void actionPerformed (ActionEvent evt) { for (int i = 0; i < boardSize; i++) for (int j = 0; j < boardSize; j++) if (rand.nextFloat () < 0.10) board[i][j].setAlive (true); canvas.paint (canvas.getGraphics ()); repaint (); } } class SizeField implements ActionListener { public void actionPerformed (ActionEvent evt) { int newSize; try { newSize = Integer.parseInt (evt.getActionCommand ()); } catch (Exception e) { sizeField.setText ("" + boardSize); return; } if (newSize < 5 || newSize > 50) { sizeField.setText ("" + boardSize); return; } if (running) subtask.suspend (); boardSize = newSize; board = emptyBoard (boardSize); canvas.board = board; canvas.boardSize = boardSize; canvas.paint (canvas.getGraphics ()); repaint (); if (running) subtask.resume (); } } class SpeedField implements ActionListener { public void actionPerformed (ActionEvent evt) { int newSpeed; try { newSpeed = Integer.parseInt (evt.getActionCommand ()); } catch (Exception e) { speedField.setText ("" + speed); return; } if (newSpeed < 1 || newSpeed > 100) { speedField.setText ("" + speed); return; } speed = newSpeed; } } 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 (2000 - 20 * speed); } 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; } }