// // Simple Java implementation of Conway's game of life. // (Currently) single-threaded. // // Designed to be run either as a stand-alone application or as an // applet inside a web browser or applet viewer. // Only supports command-line arguments in the stand-alone case. // // Michael L. Scott // November 1998; revised June 2006, November 2011. // import java.awt.*; // older of the two standard Java GUIs import java.applet.*; import java.awt.event.*; import java.util.*; public class Life extends Applet implements ActionListener, MouseListener { private LifeBoard board; // the game board itself; a graphical object private Worker worker; // the thread that updates the board private Flag flag; // for synchronization with the thread private Button startButton; private Button stopButton; private Button clearButton; private Label elapsedTime; private static int DOT_ITERS = -1; private long startTime; private static long numThreads = 1; // I currently don't do anything with this variable. // You should. // Print error message and exit. // private static void die(String msg) { System.err.print(msg); System.exit(-1); } // Examine command-line arguments for alternative running modes. // Includes one example at present, to change number of worker threads. // private static void parseArgs(String[] args) { for (int i = 0; i < args.length; i++) { if (args[i].equals("-t")) { if (++i >= args.length) { die("Missing number of threads\n"); } else { int nt = -1; try { nt = Integer.parseInt(args[i]); } catch (NumberFormatException e) { } if (nt > 0) { numThreads = nt; } else { die(String.format("Invalid number of threads: %s\n", args[i])); } } } else if (args[i].equals("-s")) { if (++i >= args.length) { die("Missing number of spin iterations\n"); } else { int ns = -1; try { ns = Integer.parseInt(args[i]); } catch (NumberFormatException e) { } if (ns > 0) { DOT_ITERS = ns; } else { die(String.format("Invalid number of spin iterations: %s\n", args[i])); } } } else { die(String.format("Unexpected argument: %s\n", args[i])); } } } // Called (only) when this program is run stand-alone: // public static void main(String[] args) { parseArgs(args); Life self = new Life(); Frame f = new Frame("Life"); self.build(f); f.pack(); // sizes window f.setVisible(true); self.work(); } // Do the main work of the program. // You'll need to execute this in multiple threads. // public void work() { while (true) { flag.await(); board.doGeneration(); } } // Called (only) when this program is run as an applet. // public void init() { build(this); new Worker(this).start(); // Init can't stick around and work; it needs to return to the // creator of the applet. } // Called by both init and main. // Assembles the components of the UI. // private void build(Container f) { setLayout(new BorderLayout()); // enable north/east/west/south positioning board = new LifeBoard(DOT_ITERS); board.setBackground(Color.white); f.add("Center", board); // put the board in the middle of the window Panel p = new Panel(); // a panel holds buttons startButton = new Button("Start"); stopButton = new Button("Stop"); clearButton = new Button("Clear"); elapsedTime = new Label(" "); // lots of room p.add(startButton); p.add(stopButton); p.add(clearButton); p.add(elapsedTime); f.add("South", p); // put the panel at the bottom of the window flag = new Flag(); startButton.addActionListener(this); stopButton.addActionListener(this); clearButton.addActionListener(this); board.addMouseListener(this); // for clicks on spots } // Event dispatcher. // Button pushes in the frame cause AWT to call this method. // public void actionPerformed(ActionEvent evt) { if (evt.getSource() == startButton) { startTime = new Date().getTime(); elapsedTime.setText(""); // blank out time indication flag.start(); } else if (evt.getSource() == stopButton) { long endTime = new Date().getTime(); flag.stop(); long duration = endTime-startTime; elapsedTime.setText(String.format("%d.%03d s", duration/1000, duration%1000)); } else if (evt.getSource() == clearButton) { if (flag.isStopped()) { board.clear(); elapsedTime.setText(""); // blank out time indication } // else do nothing } } // Mouse handler. // Need to declare the following, though we don't use them: // public void mouseClicked(MouseEvent evt) {} public void mouseEntered(MouseEvent evt) {} public void mouseExited(MouseEvent evt) {} public void mouseReleased(MouseEvent evt) {} // // This we use: // public void mousePressed(MouseEvent evt) { if (flag.isStopped()) { board.toggleClick(evt.getX(), evt.getY()); } // else do nothing } } // One instance of this Flag class is used for synchronization between // the GUI and worker thread(s). // class Flag { private volatile boolean stopped = true; public synchronized void start() { stopped = false; notify(); // wake up any thread waiting for the flag } public synchronized void stop() { stopped = true; } public boolean isStopped() { return stopped; } public synchronized void await() { while (stopped) { try { wait(); } catch(InterruptedException e) {} // do nothing } } }; // Canvas is a fundamental library class for a graphical window // This class is responsible for the square region in which the game // actually runs (but not the labels below it). // class LifeBoard extends Canvas { private int B[][]; // board contents private int A[][]; // scratch board private int T[][]; // temporary pointer // private static final variables are constants private static final int SPOT_SIZE = 7; private static final Color SPOT_COLOR = Color.blue; private static final int N = 100; private static final int CANVAS_SIZE = 800; // pixels private static final int DOT_TIME = 50000; // nanoseconds private static int DOT_ITERS = -1; public LifeBoard(int di) { // constructor A = new int[N][N]; // initialized to all 0 B = new int[N][N]; // initialized to all 0 setSize(CANVAS_SIZE, CANVAS_SIZE); if (di < 0) { calibrate(); System.err.printf("running %d spin iterations per dot\n", DOT_ITERS); } else { DOT_ITERS = di; } } // Spin for specified number of iterations. // DO NOT MODIFY THIS ROUTINE // private void spin(long iters) { do { } while (--iters > 0); } // Figure out how long I need to spin creating each dot // DO NOT MODIFY THIS ROUTINE // private void calibrate() { long nanos = Integer.MAX_VALUE; final int ITERS = 100000; // run multiple trials in case I get preempted during one or more for (int i = 0; i < 10; i++) { long start = System.nanoTime(); spin(ITERS); long trial = System.nanoTime() - start; if (trial < nanos) { nanos = trial; } } // Now min is a good estimate of the number of nanoseconds // consumed by spin(ITERS). // Compute the spin argument that would result in a delay of // approximately DOT_TIME. // NB: ITERS/nanos = DOT_ITERS/DOT_TIME DOT_ITERS = (int) ((double)ITERS/ (double)nanos * (double)DOT_TIME); } public void clear() { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { B[i][j] = 0; } } repaint(); // ask AWT to call paint sometime soon } // This is the function that actually plays the game. // public void doGeneration() { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { int im = (i+N-1) % N; int ip = (i+1) % N; int jm = (j+N-1) % N; int jp = (j+1) % N; switch (B[im][jm] + B[im][j] + B[im][jp] + B[i][jm] + B[i][jp] + B[ip][jm] + B[ip][j] + B[ip][jp]) { case 0 : case 1 : A[i][j] = 0; break; case 2 : A[i][j] = B[i][j]; break; case 3 : A[i][j] = 1; break; case 4 : case 5 : case 6 : case 7 : case 8 : A[i][j] = 0; break; } // pretend that calculating a dot is hard work: // DO NOT MODIFY THIS CALL spin(DOT_ITERS); } } T = B; B = A; A = T; repaint(); // ask AWT to call paint sometime soon } // Refresh handler. // Called by AWT when window is (re-)exposed. // Should not be called directly by user code. // public void paint(Graphics g) { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (B[i][j] == 1) { drawSpot(i, j, 1, g); } } } // draw border: g.setColor(Color.black); g.drawRect(0, 0, getWidth()-1, getHeight()-1); } public void toggleClick(int mx, int my) { Dimension d = (getSize()); int x = N * mx / d.width; int y = N * my / d.height; Graphics g = getGraphics(); mx = d.width * x / N; // round to nearest spot center my = d.height * y / N; // round to nearest spot center B[x][y] = 1 - B[x][y]; drawSpot(x, y, B[x][y], g); } private void drawSpot(int x, int y, int v, Graphics g) { Dimension d = (getSize()); int mx = d.width * x / N; // round to nearest spot center int my = d.height * y / N; // round to nearest spot center if (v == 1) { g.setColor(SPOT_COLOR); } else { g.setColor(getBackground()); } g.fillOval(mx, my, SPOT_SIZE, SPOT_SIZE); } }; class Worker extends Thread { private Life game; public Worker(Life g) { game = g; } // The run method is called by Thread.start. // It should not be called directly by user code. // public void run() { game.work(); } };