// C6Applet.java // // gg gg ggg ggg gggg gg 66 g g ggg gggg ggg gg gg // g g g g g g g g 6 g g g g g g g g // g g g ggg ggg gggg 666 g g ggg ggg gg g g // g g g g g g 6 6 g g g g g g // ggg g g gggg g g 66 gg g g gggg ggg // // // MR June 2000 // // Menno Rubingh (c) 2000 // http://www.rubinghscience.org/ // // ---------------------------------------------------------------------- // Copyright (C) 2000 Menno Rubingh // // This source code / program is free software; you are allowed to // redistribute it and/or to modify it under the terms of the GNU Public // Licence (GNU GPL) as published by the Free Software Foundation. See the // file 'COPYING' which should have accompanied this source file. // // This program comes with ABSOLUTELY NO WARRANTY. // ---------------------------------------------------------------------- import ../evol/java.applet._.css; import ../evol/java.awt._.css; import ../evol/java.lang._.css; import ../evol/java.util._.css; // -------------------------------------------------------------------------- // // Class C6Rand : Random-number generator that returns integers // within a specified range [0,m) where m > 0. // // Menno Rubingh (c) 1999, 2000 // // // Note that ''scaling down'' the return values of Random.nextInt() with the // ''%'' operator (i.e., // // return ( Math.abs(nextInt()) % m ); // // ) does NOT work well at all, since the random generator in Java's Random // class, like 99.9% of all random generators, is made in such a way that the // returned integer **VALUES** are distributed uniformly over the interval // between the minimum and maximum return values. The values obtained from // the ''%'' application quoted above, for an arbitrary choice of 'm', will // therefore very probably *NOT* be distibited uniformly over the interval // [0,m). // // See William H. Press et al., _Numerical Recepes in Fortran_, 2nd Edition, // Cambridge University Press, 1992 (ISBN 0-521-43064-X), Chapter 7 ''Random // Numbers'', section 7.1, p. 268. // class C6Rand extends Random { //Max. positive value of Java 4-byte 'int'. static private final int RMAX = (256*256*256*(256/2)) - 1; private int uintRand() //Return random integer in [0,RMAX) { return ( Math.abs( nextInt() ) ); } public int n_rand( int m ) //Return random integer in [0,m) { int K = ( RMAX / m ) + 1; return ( uintRand() / K ); } } // -------------------------------------------------------------------------- // // Class C6Gene : General-purpose class for n-bit gene. // // Implements general operations like mutation, crossover, etc. // // NOTE: For 'C6Gene(g2)', 'copy(g2)' and 'mingleMutual(g2,r), the two // C6Gene objects ''this'' and 'g2' must contain the same length // ''bits'' array, otherwise the results are ''strange''. class C6Gene { protected boolean[] bits; public C6Gene( int n ) // Initialize to length of n bits, and initialize with // all bits to value zero. { bits = new boolean[n]; for ( int i = 0; i < n; i++ ) { bits[i] = false; } } public C6Gene( C6Gene g2 ) // Copy constructor : copy contents of g2 to ''this''. { bits = new boolean[ g2.bits.length ]; copy( g2 ); } // -------- public void initRand( C6Rand r ) // Re-init contents of gene with random values, // keeping it at same # of bits. { for ( int i = 0; i < bits.length; i++ ) { bits[i] = ( r.n_rand( 2 ) == 1 ); } } // -------- public void copy( C6Gene g2 ) // Copy contents of g2 into ''this''. { for ( int i = 0; i < bits.length && i < g2.bits.length; i++ ) { bits[i] = g2.bits[i]; } } public void mutate( C6Rand r ) // Randomly flip one of the n bits in the gene. { int whichone = r.n_rand( bits.length ); bits[whichone] = ! bits[whichone]; } public void mingleMutual( C6Gene g2, C6Rand r ) { for ( int i = 0; i < this.bits.length && i < g2.bits.length; i++ ) { if ( r.n_rand(2) == 1 ) { boolean b1 = this.bits[i]; boolean b2 = g2.bits[i]; this.bits[i] = b2; g2.bits[i] = b1; } } } public void mingleIntoThis( C6Gene g2, C6Rand r ) { for ( int i = 0; i < this.bits.length && i < g2.bits.length; i++ ) { if ( r.n_rand(2) == 1 ) { this.bits[i] = g2.bits[i]; } } } // -------- // ''Read-only'' access functions : public boolean getBooleanValue( int where ) { return ( ( 0 <= where && where < bits.length ) ? bits[where] : false ); } public int getIntValue( int start, int len ) // Get numerical value, as an 'int', of the gene bits // beginning at bit nr. ''start'' (numbering from 0), // and continuing over ''len'' bits. { int val = 0; for ( int i = start; i < start+len && i < bits.length; i++ ) { val *= 2; if ( bits[i] ) { val += 1; } } return val; } } // -------------------------------------------------------------------------- // // Class C6Crea6ure : Creature with 6-bit gene. // // // +----+----+ ----+----+ ----+----+ // Gene layout : | c1 | c2 | d1 | d2 | s1 | s2 | // +----+----+ ----+----+ ----+----+ // index 0 1 2 3 4 5 // // c1, c2 = 2-bit creature color. // d1, d2 = Behaviour when other creature is colored different. // s1, s2 = Behaviour when other creature is colored same. // // d1 and s1 = ''Attack behaviour'' : 0 = flee, 1 = attack. // d2 and s2 = ''Mating behaviour'' : 0 = crossover, 1 = transplant. // class C6Crea6ure { // class constants public static final int GENELEN = 6; // constants to help interpret ''dirn'' values public static final int DX[] = { 1, 0, -1, 0 }; public static final int DY[] = { 0, 1, 0, -1 }; // member variables protected C6Gene gene; protected int x, y; protected int dirn; //Direction, in {0,1,2,3} protected boolean pa; //Previous Action // (true==attack, false==other). // -------- public C6Crea6ure() // Initialize with gene of length of 6 bits, as yet with // blank gene contents, position and direction. { gene = new C6Gene( GENELEN ); dirn = 0; x = 0; y = 0; pa = false; } // -------- public boolean getPA() { return pa; } public void setPA( boolean panew ) { pa = panew; } public void initGene( C6Rand r ) // Re-initialize gene with random gene contents. { gene.initRand( r ); } public void initPosAndDir( int xmax, int ymax, C6Rand r ) // Re-initialize 'x' to random x-position in [0,xmax), // re-init 'y' to random y-position in [0,ymax), // and re-init 'dirn' to random direction in {0,1,2,3}. { x = r.n_rand( xmax ); y = r.n_rand( ymax ); dirn = r.n_rand( 4 ); } public void mutate( C6Rand r ) { gene.mutate( r ); } // -------- public int getX() { return x; } public int getY() { return y; } // -------- public void randturn( C6Rand r ) // Let creature turn 90 degrees left or right w.r.t. its // current 'dirn', 50-50 chance. { if ( r.n_rand(2) == 0 ) { dirn = ( dirn + 1 ) % 4; } //turn right else { dirn = ( dirn + 3 ) % 4; } //turn left } public int xAhead() { return ( x + DX[dirn] ); } public int yAhead() { return ( y + DY[dirn] ); } public void moveTo( int xdest, int ydest ) { x = xdest; y = ydest; } // -------- protected int getColorValue() // Return color value in {0,1,2,3}. { return gene.getIntValue( 0, 2 ); } protected int getAttackBehaviourValue( boolean same ) // Return value in {3,2,1,0} { return gene.getIntValue( (same ? 4 : 2), 2 ); } public boolean attack( C6Crea6ure c2, C6Rand r ) // // Give the the ''this'' creature the ''opportunity'' to // attack creature 'c2', and see what the ''this'' creature // does. // // Return false if ''this'' doesn't ''want'' to attack. // Return true if ''this'' does attack 'c2' -- in this // case, the genetic information in 'c2', and possibly // also of ''this'' is changed. // { boolean same = ( this.getColorValue() == c2.getColorValue() ); int abv = this.getAttackBehaviourValue( same ); if ( abv == 3 /*11*/ ) { c2.gene.copy( this.gene ); return true; } else if ( abv == 2 /*10*/ ) { c2.gene.mingleIntoThis( this.gene, r ); return true; } else if ( abv == 1 /*01*/ ) { c2.gene.mingleMutual( this.gene, r ); return true; } else /* case 00 */ { return false; //No attack executed (flee) } } }// // -------------------------------------------------------------------------- // // Class C6World : Container for the crea6ures, and simultaneously // a ''canvas'' to draw them (and the ''chessboard they // walk around on) on the screen. // // // All the public 'C6World' member functions (methods) that affect the array // of crea6ures maintained in the 'C6World' object and that affect the data // associated with any single crea6ure, plus also the public 'C6World' member // functions that do drawing, are all 'synchronized': i.e., only one thread // at a time can enter and run code in only one single one of these // 'synchronized' member functions of the same 'C6World' object. This is // implemented in this way not only to keep the ''data integrity'' array of // crea6ures maintained by the 'C6World' object, but also to keep what is // shown (drawn) on the screen of the crea6ures always completely updated // and correct. // class C6World extends Canvas { // Constant maximum size (width and height) of chessboard. The // chessboard is ALWAYS square. public static final int MAXWSIZE = 15; // Member variables protected C6Rand r; // Reference to the central random generator // used by everything in the ''C6World''. protected int ncr; // Current # of creatures in C6World. protected int ixcurcr; // Index in crarr of next creature to be moved. protected C6Crea6ure[] crarr; protected int wsize; // Current size of the ''chessboard''. // -------- public C6World( C6Rand r0, int sz0 ) // Initialize to chessboard size 'sz0', and with 'r0' // as the random generator used. // But as yet with NO creatures in the C6World (= empty // array 'crarr[]'). { r = r0; wsize = sz0; // Initialize the crarr[] array object to suffice for the // max number of crea6ures in the max-size chessboard, // i.e. half the squares on that max-size chessboard // occupied. crarr = new C6Crea6ure[ (MAXWSIZE * MAXWSIZE)/2 ]; ncr = 0; ixcurcr = 0; //setBackground( Color. .... C6Crea6ure.BCKCOLOR ); } // -------- public synchronized void reinitAllGenes() { for ( int i = 0; i < ncr; i++ ) { crarr[i].initGene( r ); } } public synchronized void mutateOne() { if ( ncr <= 0 ) { return; } int whichone = r.n_rand( ncr ); crarr[whichone].mutate( r ); } // -------- public synchronized int getNCr() { return ncr; } // Return current # of crea6ures public synchronized int getWSize() { return wsize; } // Return current chessboard size // -------- protected C6Crea6ure squareContents( int x, int y ) // Return null if square (x,y) in C6World is empty or // if (x,y) is outside of the ''chessboard''; // otherwise return the creature on that square. { if ( ! ( 0 <= x && x < wsize && 0 <= y && y < wsize ) ) { return null; } C6Crea6ure rv = null; for ( int i = 0; i < ncr; i++ ) { if ( crarr[i].getX() == x && crarr[i].getY() == y ) { rv = crarr[i]; } } return rv; } // -------- public synchronized boolean insertNewCrea6ure() // Insert new extra crea6ure at random position that // is still empty, and with random gene and direction. // Refuse to add crea6ure if C6World already quite ''full''. { if ( ncr >= crarr.length ) { return false; } /*array full*/ if ( ncr >= (wsize*wsize)/2 ) { return false; } C6Crea6ure cnew = new C6Crea6ure(); cnew.initGene( r ); do //find a still-empty square { cnew.initPosAndDir( wsize, wsize, r ); } while( squareContents( cnew.getX(), cnew.getY() ) != null ); crarr[ncr] = cnew; ncr++; return true; /*success*/ } public synchronized boolean deleteLastCrea6ure() { if ( ncr <= 0 ) { return false; } crarr[ncr-1] = null; ncr--; if ( ixcurcr > ncr-1 ) { ixcurcr = 0; } return true; /*success*/ } // -------- public synchronized void reinitAllPositions() { // (Note that the coding of this function is an ugly, dirty, // implementation-specific trick.) // Step 1: Move everything ''outside of the board'' :-):-) for ( int i = 0; i < ncr; i++ ) { crarr[i].moveTo( -1, -1 ); } // Step 2: Put everything back in int ncrOrig = ncr; ncr = 0; //(to fool our func squareContents()) for ( int i = 0; i < ncrOrig; i++ ) { C6Crea6ure c = crarr[i]; do //find a still-empty square { c.initPosAndDir( wsize, wsize, r ); } while( squareContents( c.getX(), c.getY() ) != null ); ncr++; } } public synchronized void resizeWorld( int newsize ) { if ( ! ( 5 <= newsize && newsize < MAXWSIZE ) ) { return; } wsize = newsize; while ( ncr > (wsize*wsize)/2 ) { deleteLastCrea6ure(); } reinitAllPositions(); } // -------- public synchronized boolean doOneMove( boolean doPaint ) // Move one (the ''current'') creature, // perform the necessary ''incremental'' repainting on // the screen, and update ixcurcr. // If 'doPaint' == null, then don't do any drawing. // Return true if the move executed is the // last move in a ''cycle'' through all the // creatures on the chessboard. { if ( ncr <= 0 ) { return true; /*nothing to do*/ } Graphics g = getGraphics(); C6Crea6ure curcr = crarr[ixcurcr]; boolean ppa = curcr.getPA(); curcr.setPA( false ); // Erase old picture of curcr on screen if ( doPaint ) { drawCrea6ure( g, curcr, false ); } // See what's ahead int xdest = curcr.xAhead(); int ydest = curcr.yAhead(); // Take appropriate action if ( ppa || ! ( 0 <= xdest && xdest < wsize && 0 <= ydest && ydest < wsize ) ) { // // PREVOUS STATE IS 'PA'==TRUE, or // WALL AHEAD ==> turn. // curcr.randturn( r ); } else { C6Crea6ure othercr = squareContents( xdest, ydest ); if ( othercr == null ) { // // EMPTY SQUARE AHEAD ==> advance, or // with probability 1/5 turn. // // (The small turn probability serves to // increase the ''randomness'' with which // creatures run into each other and // exchange information.) // if ( r.n_rand(5) == 0 ) { curcr.randturn( r ); } else { curcr.moveTo( xdest, ydest ); } } else { // // OTHER CREATURE AHEAD ==> perform gene-coded // action. // if ( curcr.attack( othercr, r ) ) { // Attack executed ? ==> // set 'PA', and // redraw othercr, too. curcr.setPA( true ); if ( doPaint ) { drawCrea6ure( g, othercr, true ); } } else { //no attack ==> curcr turns instead. curcr.randturn( r ); } } } // Redraw curcr in its new state/position if ( doPaint ) { drawCrea6ure( g, curcr, true ); } // Update ixcurcr ixcurcr = ( ixcurcr + 1 ) % ncr; return ( ixcurcr == 0 ); } // -------- // // Graphics stuff // private static final Color BCKCOLOR = Color.gray; private static final Color colorarr[] = { Color.red, Color.yellow, Color.green, Color.blue }; private static final int BITPERIOD = 3; private static final int BITWIDTH = 2; private static final int GHALFWIDE = BITPERIOD * (C6Crea6ure.GENELEN/2); private static final int GHALFHIGH = 4; private static final int BODYRADIUS = GHALFWIDE + 2; private static final int NOSERADIUS = BODYRADIUS / 3; public static final int SQSZ = 2*BODYRADIUS + 4; //Diameter of the square in the ''C6World'' chessboard // which a creature occupies. public static final int WMARGIN = 3; public static final int CHBMAXSIZE = SQSZ*MAXWSIZE + 2*WMARGIN; // Chessboard total max size in pixels public Dimension preferredSize() // This function is necessary to get the FlowLayout in the // constructor of our C6Applet work correctly with // our ''C6World'' Canvas. { return ( new Dimension( CHBMAXSIZE, CHBMAXSIZE ) ); } public Dimension minimumSize() { return ( new Dimension( CHBMAXSIZE, CHBMAXSIZE ) ); } protected void drawCrea6ure( Graphics g, C6Crea6ure c, boolean drawNotErase ) // Draw crea6ure in its correct (x,y) position on the // chessboard. // If 'drawNotErase' == false, then erase the crea6ure (i.e., // overpaint with background color) instead of drawing it. { int or = ( CHBMAXSIZE - (SQSZ-1)*wsize ) / 2; // 'or' = graphics pixel coordinate within Canvas of the // CENTER of the top-left square of the chessboard // in its current size of 'wsize' squares. The // chessboard is drawn centered within the Canvas. int x0 = or + ( SQSZ * c.x ); int y0 = or + ( SQSZ * c.y ); // (x0,y0) = graphics pixel coordinate within Canvas // of the center of the chessboard square occupied by // the crea6ure 'c'. g.setColor( BCKCOLOR ); //initialize for erase // Draw colored body if ( drawNotErase ) { g.setColor( colorarr[ c.getColorValue() ] ); } g.fillOval( x0-BODYRADIUS-1, y0-BODYRADIUS-1, 2*BODYRADIUS, 2*BODYRADIUS ); // Draw direction marker (the ''nose'' of the creature) if ( drawNotErase ) { g.setColor( Color.black ); } g.fillOval( x0 + BODYRADIUS*C6Crea6ure.DX[c.dirn] - NOSERADIUS, y0 + BODYRADIUS*C6Crea6ure.DY[c.dirn] - NOSERADIUS, 2*NOSERADIUS, 2*NOSERADIUS ); if ( ! drawNotErase ) { return; } // Draw grey background box for the gene g.setColor( Color.gray ); g.fillRect( x0 - GHALFWIDE - 1, y0 - GHALFHIGH - 1, 2*GHALFWIDE+ 1, 2*GHALFHIGH + 2 ); // Draw the gene for ( int i = 0; i < C6Crea6ure.GENELEN; i++ ) { g.setColor( c.gene.getBooleanValue( i ) ? Color.white : Color.black ); g.fillRect( x0 + (i - C6Crea6ure.GENELEN/2)*BITPERIOD, y0 - GHALFHIGH, BITWIDTH, 2*GHALFHIGH ); } } public synchronized void paint( Graphics g ) { int or = ( CHBMAXSIZE - (SQSZ-1)*wsize ) / 2; // Draw background for ''world'' g.setColor( BCKCOLOR ); g.fillRect( or - (SQSZ/2) - WMARGIN, or - (SQSZ/2) - WMARGIN, wsize*SQSZ + 2*WMARGIN, wsize*SQSZ + 2*WMARGIN ); // Draw the creatures for( int i = 0; i < ncr; i++ ) { drawCrea6ure( g, crarr[i], true ); } } } // -------------------------------------------------------------------------- // // Class C6AnimationManager : Thread which autonomously runs the continuous // automatic updating of the movement of the // crea6ures. // class C6AnimationManager extends Thread { private C6Applet ourapp; private int cycleTime; private boolean repaintMode; //true ==> repaint each individual // creature move. //false ==> repaint whole world after // a complete cycle through // all creatures. C6AnimationManager( C6Applet app_init ) //Constructor { ourapp = app_init; cycleTime = 600; repaintMode = false; } public void toggleRepaintMode() { repaintMode = ! repaintMode; } public void run() { while( true ) { int nCr = ourapp.world.getNCr(); int n = ( nCr == 0 ? 1 : nCr ); try { sleep( cycleTime / n ); } catch ( InterruptedException e ) { //empty } boolean last; last = ourapp.world.doOneMove( repaintMode ); if ( last && ( repaintMode == false ) ) { ourapp.world.repaint(); } if ( last ) { ourapp.iLabel ++ ; if ( ourapp.iLabel > 100 ) { ourapp.iLabel = 0; } ourapp.ourLabel.setText( "[" + ourapp.iLabel + "]"); } } } } // -------------------------------------------------------------------------- public class C6Applet extends Applet { // Constants //Fixed size of total ''Frame'' of our Applet: public static final int APPXSIZE = C6World.CHBMAXSIZE + 10; public static final int APPYSIZE = C6World.CHBMAXSIZE + 75; //Initial chessboard size: private static final int WSZINI = 10; // Instance variables public C6Rand r; // Use *ONE* central random generator. private C6AnimationManager am; public C6World world; public int iLabel = 0; Label ourLabel; public C6Applet() // Constructor { am = null; //am = new C6AnimationManager( this ); r = new C6Rand(); // Initialize our central random generator. // Add buttons and add our ''C6World'' Canvas: setLayout( new FlowLayout() ); add( new Button( "reinit" ) ); add( new Button( "world++" ) ); add( new Button( "world--" ) ); add( new Button( "ncrea++" ) ); add( new Button( "ncrea--" ) ); add( new Button( "mutate1" ) ); add( new Button( "ff" ) ); //''Fast Forward'' add( new Button( "vmode" ) ); //add( new Label( APPXSIZE + "x" + APPYSIZE ) ); add( ourLabel = new Label( "[" + iLabel + "]" ) ); world = new C6World( r, //Random generator used by 'world' WSZINI ); //initial chessboard size add( world ); } // ------ public void init() // Web browser tells applet to initialize { resize( APPXSIZE, APPYSIZE ); // Put some Creat6ures into the C6World : for ( int i = 0; i < 10; i++ ) { world.insertNewCrea6ure(); } } public void start() // Web browser tells applet to start { // Spawn off 'C6AnimationManager' subprocess: am = new C6AnimationManager( this ); am.start(); } public void stop() // Web browser tells applet to stop { // Kill the 'C6AnimationManager' subprocess again: am.stop(); am = null; } // ------ // // Our ''Event Handler'' : // public boolean action( Event e, Object arg ) { if ( e.target instanceof Button ) { String label = (String)arg; if ( label.equals( "reinit" ) ) { world.reinitAllGenes(); world.reinitAllPositions(); } else if ( label.equals( "world++" ) ) { world.resizeWorld( world.getWSize() + 1 ); } else if ( label.equals( "world--" ) ) { world.resizeWorld( world.getWSize() - 1 ); } else if ( label.equals( "ncrea++" ) ) { world.insertNewCrea6ure(); } else if ( label.equals( "ncrea--" ) ) { world.deleteLastCrea6ure(); } else if ( label.equals( "mutate1" ) ) { world.mutateOne(); } else if ( label.equals( "ff" ) ) { for ( int i = 0; i < ( 10 * world.getNCr() ); i++ ) { world.doOneMove( false ); } } else if ( label.equals( "vmode" ) ) { am.toggleRepaintMode(); } world.repaint(); return true; } return false; } // ------ static public void main( String args[] ) { C6Applet ourapp = new C6Applet(); ourapp.init(); Frame f = new Frame( "-- C6Applet --" ); f.resize( APPXSIZE, APPYSIZE ); f.add( "Center", ourapp ); f.show(); ourapp.start(); } }