/* * Life.java * * Bruce Wilson, 10/20/98 * * Copyright (c) 1998, 2000, Bruce Wilson. * * 9/12/2000: Ability to get and set the current cells added. */ import java.applet.*; import java.awt.*; import java.util.*; /** * The Life Applet. */ public class Life extends Applet { public Life() { _current_grid = new LifeGrid( this, _rows, _columns ); _next_grid = new LifeGrid( this, _rows, _columns ); } public void init() { String param; if ( (param = getParameter("rows")) != null ) _rows = Integer.parseInt( param ); if ( (param = getParameter("columns")) != null ) _columns = Integer.parseInt( param ); if ( (param = getParameter("interval")) != null ) _interval = Integer.parseInt( param ); _current_grid = new LifeGrid( this, _rows, _columns ); _next_grid = new LifeGrid( this, _rows, _columns ); _current_grid.initCellsRandom(); } public void start() { println( "start()..." ); _generation = 1; if ( _life_thread == null ) { _life_thread = new LifeThread( this, _interval ); _life_thread.start(); } } public void stop() { println( "stop()..." ); if ( _life_thread != null ) { _life_thread.stop(); _life_thread = null; } } public void paint( Graphics g ) { Rectangle bounds = getBounds(); _current_grid.paintGridLines( g ); _current_grid.paintCells( g ); } /** * Pause the animation thread. */ public void pause() { showStatus( "Paused..." ); _life_thread.setPaused( true ); } /** * Resume running the animation thread. */ public void resume() { showStatus( "Resume..." ); _life_thread.setPaused( false ); } /** * Step forward one generation. */ public void step() { showStatus( "Step..." ); nextGeneration(); } /** * Clear all living cells from the grid. Does an implicit pause. */ public void clear() { showStatus( "Clear..." ); // might as well pause animation, we're about to get rid of everything _life_thread.setPaused( true ); _current_grid.clear(); _next_grid.clear(); _generation = 1; repaint(); } /** * Restart with a new, random initial pattern. */ public void restart() { _current_grid.clear(); _next_grid.clear(); _current_grid.initCellsRandom(); _generation = 1; repaint(); _life_thread.setPaused( false ); } /** * Get a string listing the locations of the * current set of cells in this grid. *

* Locations are returned in a parenthesized, lisp-like * notation: *

* * ((row,col) (row,col) ...) * * * @return A string listing the cell locations (lisp notation). */ public String getCells() { return _current_grid.getCells(); } /** * Populate this grid with cells using a list of * specified locations. */ public void setCells( String location_list ) { _current_grid.setCells( location_list ); repaint(); } /** * Create the next generation of cells from the current * generation. */ public boolean nextGeneration() { showStatus( "Generation " + _generation ); _next_grid.clear(); int cell_count = 0; int count; for ( int i = 0; i < _rows; i++ ) { for ( int j = 0; j < _columns; j++ ) { count = _current_grid.countSurroundingCells( i, j ); // // "Conway's Genetic Laws": // // 1. Cells with 2 or 3 neighbors live on in next generation. // // 2. Cells with 4 or more neighbors die from overcrowding. // // 3. A new cell is born in any empty cell with exactly 3 neighbors. // if ( _current_grid.hasCell(i, j) ) { if ( (count == 2) || (count == 3) ) { _next_grid.addCell( i, j ); ++cell_count; } } else { if (count == 3) { _next_grid.addCell( i, j ); ++cell_count; } } } } // flip grid reference handles LifeGrid gtemp = _current_grid; _current_grid = _next_grid; _next_grid = gtemp; Graphics g = this.getGraphics(); _current_grid.paintCells( g ); _current_grid.showStatistics( g, _generation, cell_count ); g.dispose(); ++_generation; return (cell_count > 0); } /** * Set the time interval between generations. * * @param interval time interval in milliseconds. */ public void setInterval( int interval ) { _interval = interval; if ( _life_thread != null ) _life_thread.setInterval( interval ); } /** * Get the time interval between generations. * * @return The time interval in milliseconds. */ public int getInterval() { return ((_life_thread != null) ? _life_thread.getInterval() : _interval); } /** * Respond to a mouse down event by invoking a "hit" * event in the current grid. */ public boolean mouseDown( Event evt, int x, int y ) { _current_grid.hit( x, y ); Graphics g = this.getGraphics(); _current_grid.paintCells( g ); g.dispose(); return true; } void println( String msg ) { System.out.println( "life: " + msg ); } void errorln( String msg ) { System.err.println( "life: " + msg ); } private LifeGrid _current_grid; private LifeGrid _next_grid; private LifeThread _life_thread; private int _generation; private int _interval = 100; // milliseconds private int _rows = 20; private int _columns = 20; } /** * The grid on which cells are created, born, and die. * * For this implementation, a wrapper around a double-dimensioned * boolean array (alive or not ?). */ class LifeGrid { LifeGrid( Life applet, int rows, int columns ) { _grid = new boolean[ rows ][ columns ]; _rows = rows; _columns = columns; Rectangle bounds = applet.getBounds(); _grid_size = new Dimension( bounds.width, bounds.height ); _grid_size.width -= 2 * _outer_margin; _grid_size.height -= 2 * _outer_margin; _life_applet = applet; _background = applet.getBackground(); } /** * Create a template image for a cell, from which all cells will * be drawn. */ Image createCellImage( Component c, int width, int height ) { Image cell_image = c.createImage( width, height ); Graphics gcell = cell_image.getGraphics(); gcell.setColor( Color.black ); gcell.fillOval( 0, 0, width, height ); gcell.dispose(); return cell_image; } /** * Create a template image for the background behind a cell, for * "blanking out" the cell when it dies. */ Image createBlankImage( Component c, int width, int height, Color bgcolor ) { Image blank_image = c.createImage( width, height ); Graphics gblank = blank_image.getGraphics(); gblank.setColor( bgcolor ); gblank.fillRect( 0, 0, width, height ); gblank.dispose(); return blank_image; } /** * Randomly adds cells about 1/3 of the time. */ void initCellsRandom() { int rows = _rows; int columns = _columns; for ( int i = 0; i < rows; i++ ) { for ( int j = 0; j < columns; j++ ) { if ( Math.random() > 0.66 ) addCell( i, j ); } } } /** * Add a living cell to the given grid location. */ final void addCell( int row, int column ) { _grid[ row ][ column ] = true; } /** * Remove a living cell from the given grid location. */ final void removeCell( int row, int column ) { _grid[ row ][ column ] = false; } /** * Tell whether this location has a living cell in it. */ final boolean hasCell( int row, int column ) { return _grid[ row ][ column ]; } /** * Get a string listing the locations of the * current set of cells in this grid. *

* Locations are returned in a parenthesized, lisp-like * notation: *

* * ((row,col) (row,col) ...) * * * @return A string listing the cell locations (lisp notation). */ String getCells() { StringBuffer buf = new StringBuffer(); for ( int i = 0; i < _rows; i++ ) { for ( int j = 0; j < _columns; j++ ) { if( hasCell( i, j ) ) buf.append( "(" + i + "," + j + ") " ); } } return buf.toString(); } // end getCells() /** * Populate this grid with cells using a list of * specified locations. */ void setCells( String location_list ) { String list = location_list.trim(); if ( list.length() == 0 ) { _life_applet.errorln( "Zero length list, returning" ); return; } StringTokenizer tok = new StringTokenizer( list, "(,) " ); String token; int row, col; while ( tok.hasMoreTokens() ) { token = tok.nextToken(); // _life_applet.println( "token: " + token ); row = Integer.parseInt( token ); if ( tok.hasMoreTokens() ) { token = tok.nextToken(); // _life_applet.println( "token: " + token ); col = Integer.parseInt( token ); if ( (row < 0) || (row >= _rows) ) { _life_applet.errorln( "Error: cell row out of bounds: " + row ); continue; } if ( (col < 0) || (col >= _columns) ) { _life_applet.errorln( "Error: cell column out of bounds: " + col ); continue; } addCell( row, col ); } } } // end setCells() /** * Count how many living cells surround the given grid location */ final int countSurroundingCells( int r, int c ) { int count = 0; int rstart = (r > 0) ? r - 1 : r; int rend = (r < (_rows - 1)) ? r + 1 : r; int cstart = (c > 0) ? c - 1 : c; int cend = (c < (_columns - 1)) ? c + 1 : c; for ( int i = rstart; i <= rend; i++ ) { for ( int j = cstart; j <= cend; j++ ) { if ( (i == r) && (j == c) ) continue; if ( _grid[i][j] ) ++count; } } return count; } final void paintGridLines( Graphics g ) { int grid_width = _grid_size.width; int grid_height = _grid_size.height; int xleft = _outer_margin; int xright = _outer_margin + grid_width; int ytop = _outer_margin; int ybottom = _outer_margin + grid_height; int rows = _rows; int columns = _columns; int x, y; int i; for ( i = 0; i <= columns; i++ ) { x = xleft + (i * grid_width) / columns; g.drawLine( x, ybottom, x, ytop ); } for ( i = 0; i <= rows; i++ ) { y = ytop + (i * grid_height) / rows; g.drawLine( xleft, y, xright, y ); } } final void paintCells( Graphics g ) { int grid_width = _grid_size.width; int grid_height = _grid_size.height; int rows = _rows; int columns = _columns; int omargin = _outer_margin; int cmargin = _cell_margin; int cell_width = ((_grid_size.width - 2 * _outer_margin) / _columns) - 2 * _cell_margin; int cell_height = ((_grid_size.height - 2 * _outer_margin) / _rows) - 2 * _cell_margin; // Wait until it's time to draw the cells to create the template // cell images - the applet may not have been fully constructed // yet when the constructor for this class is called. if ( _cell_image == null ) _cell_image = createCellImage( _life_applet, cell_width, cell_height ); if ( _blank_image == null ) _blank_image = createBlankImage( _life_applet, cell_width, cell_height, _background ); // Use double buffering to redraw grid Graphics gscreen; if ( _offscreen == null ) { _offscreen = _life_applet.createImage( grid_width + 2 * _outer_margin, grid_height + 2 * _outer_margin ); gscreen = _offscreen.getGraphics(); paintGridLines( gscreen ); } else { gscreen = _offscreen.getGraphics(); } int x, y; for ( int i = 0; i < rows; i++ ) { for ( int j = 0; j < columns; j++ ) { x = (omargin + (j * grid_width) / columns) + cmargin; y = (omargin + (i * grid_height) / rows) + cmargin; if ( _grid[i][j] ) { gscreen.drawImage( _cell_image, x, y, null ); } else { gscreen.drawImage( _blank_image, x, y, null ); } } } g.drawImage( _offscreen, 0, 0, null ); gscreen.dispose(); } final void showStatistics( Graphics g, int generation, int population ) { g.drawString( "Current generation: " + generation + ", population: " + population, _outer_margin, _grid_size.height + 2 * _outer_margin - 2 ); } /** * Clear all cells from the grid (no redraw). * *

* Just set all entries in the grid to "false" (no cell). */ final void clear() { int rows = _rows; int columns = _columns; for ( int i = 0; i < rows; i++ ) { for ( int j = 0; j < columns; j++ ) { _grid[ i ][ j ] = false; } } } /** * Handle a mouse hit on the grid - create a new cell. */ void hit( int x, int y ) { int r = _rows * (y - _outer_margin) / _grid_size.height; int c = _columns * (x - _outer_margin) / _grid_size.width; if ( hasCell(r, c) ) removeCell( r, c ); else addCell( r, c ); } private boolean[][] _grid; private Image _offscreen = null; private Image _cell_image = null; private Image _blank_image = null; private Life _life_applet; private Color _background; private int _rows; private int _columns; private Dimension _grid_size; private int _cell_margin = 1; private int _outer_margin = 14; } /** * Animator thread for the Life applet. */ class LifeThread extends Thread { public LifeThread( Life life, int interval ) { _life = life; _interval = interval; _paused = false; } public void run() { while( true ) { try { sleep( _interval ); } catch( InterruptedException e ) { } if ( ! _paused ) _life.nextGeneration(); } } public void setInterval( int interval ) { _interval = interval; } public int getInterval() { return _interval; } public void setPaused( boolean paused ) { _paused = paused; } public boolean isPaused() { return _paused; } private Life _life; private int _interval; // milliseconds between generations private boolean _paused; }