/* * 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; }