// Michael Tartaglia
// 1 April 2003
// Simulating the memesis of butterflies, this program shows the principle of adaption
//		to a specific environment. Butterflies will get eaten by birds, only if they can
//    can be seen.


import java.awt.*;
import java.applet.Applet;

public class Butterflies extends Applet {
	private final int wide = 10,	// WIDTH OF TREE IN BUTTERFLIES
		    	  high = 18;			// HEIGHT OF TREE IN BUTTERFLIES
	private Butterfly[][] tree = new Butterfly[high][wide];	// TREE OF BUTTERFLIES
	private boolean[][] living = new boolean[high][wide];		// WHO IS LIVING ON THE TREE
	private Color bgColor;			// TREE TRUNK COLOR
	private Image img;				// BUFFER FOR TREE
	private Graphics canvas;		// GRAPHICS PKG FOR TREE
	private Stack xEaten, yEaten;	// STACK RECORD OF WHO IS EATEN
	private double mutationRadius = .350;	// ALLOWABLE GENERATION MUTAION
	
	public void init() {
	 String gP = getParameter("bgValue");		// GET BACKGROUND COLOR VALUE FROM APPLET PARAM
	 int greyLevel;
	 	if (gP == null) greyLevel = 200;			// SET DEFAULT TO 200 (OUT OF 255)
		else greyLevel = Integer.parseInt(gP);	// IF THERE WAS A COLOR ENTERED, USE IT
	 	if (greyLevel > 255) greyLevel = 255;	// UPPER BOUNDARY CHECK
	 	else if (greyLevel < 0) greyLevel = 0;	// LOWER BOUNDARY CHECK
	 bgColor = new Color(greyLevel,greyLevel,greyLevel);
	 xEaten = new Stack();		// CREATE STACK OF X COORDINATES OF THOSE EATEN
	 yEaten = new Stack();		//   "      "   "  Y      "       "   "     "

	 // FILL TREE WITH FIRST GENERATION OF BUTTERFLIES
	 for (int i = 0; i < high; i++)
	  for (int j = 0; j < wide; j++) {
		tree[i][j] = new Butterfly(Math.random(), bgColor);	// RANDOM WING COLOR
		living[i][j] = true;	// ALL LIVING
	  }
	 img = this.createImage(750,300);
	 canvas = img.getGraphics();
	}



	public void birdAttacks()
	// FUNCTION: simulating a bird attack, the program finds the "most
	//				 visible" butterflies on the tree trunk, by finding the
	//				 difference between wing color value and the trunk color.
	//				 those with a greater difference in color are more likely
	//				 to be eaten
	{
	 int yPos, xPos, i, j;
	 double wingColor;

	 for (i = 0; i < high; i++)
	  for (j = 0; j < wide; j++) {
	   if (Math.random() > tree[i][j].visibility(bgColor)) {	// IF BUTTERFLY IS "VISIBLE DURING ATTACK"
	    yEaten.push((double)i);	// ADD THE NEW FREE SPACE LOCATION'S Y ...
	    xEaten.push((double)j);	// ... AND X COORDINATES TO STACK
	    tree[i][j] = null;			// DELETE BUTTERFLY
	    living[i][j] = false;		// ACCOUNT FOR DEAD BUTTERFLY'S SPACE
	   }
	  }
	 while (!yEaten.isEmpty()) {	// FILL SPACES LEFT BY EATEN BUTTERFLIES
	  do { 								// GET A RANDOM LIVING BUTTERFLY AS PARENT
		i = (int)(Math.random()*1000)%high;
		j = (int)(Math.random()*1000)%wide;
	  } while (!living[i][j]);
	  wingColor = tree[i][j].getMutation(mutationRadius);	// MUTATE WING COLOR BASED ON PARENT
	  xPos = (int)xEaten.pop();	// GET LOCATION OF DEAD BUTTERFLY
	  yPos = (int)yEaten.pop();
	  tree[yPos][xPos] = new Butterfly(wingColor, bgColor);	// PLACE NEW BUTTERFLY ON TREE
	 }
	 for (i = 0; i < high; i++)
	  for (j = 0; j < wide; j++)
	   living[i][j] = true;		// SET ALL TO LIVING
	}



	public void paintTree() {
	 // GET SIZE OF TREE
	 int x = tree[0][0].getWidth(),
	     y = tree[0][0].getHeight();

	 // CLEAR TREE
	 canvas.setColor(bgColor);
	 canvas.fillRect(1,1,x*high+4,y*wide+5);
	 canvas.setColor(Color.black);
	 canvas.drawRect(1,1,x*high+4,y*wide+5);
	 
	 // PAINT ON BUTTERFLIES
	 for (int i = 0; i < high; i++)
	  for (int j = 0; j < wide; j++)
		tree[i][j].paintButterfly(3+i*x, 3+j*y, canvas);
	}



	public void update(Graphics g) {paint(g);}
	public void paint(Graphics g) {
	 try {Thread.sleep(250);}	// WAIT...
	 catch (InterruptedException ie) {}
	 birdAttacks();		// SIMULATE BIRD ATTACK
	 paintTree();			// PAINT RESULTING TREE
	 g.drawImage(img,0,0,this);
	 repaint();		// LOOP
	}
}




class Butterfly {
	private Color wingColor, bgColor;	// WING AND BACKGROUND COLOR
	private int[] x = {0, 10, 12, 16, 18, 26, 26, 18, 14, 10, 0},
		      y = {0, 4,  1,  1,  4,  0,  18, 16, 20, 16, 18};	// BUTTERFLY IMG COORDINATES
	private double ID;			// WING COLOR AS % OF 255
	private int width = 26,		// WIDTH OF BUG
				   height = 20;	// HEIGHT OF BUG

	public Butterfly(double shade, Color bg) {
		ID = shade;			// ID OF BUTTERFLY
		shade *= 255;		// MULTIPLY SHADE (IN DECIMAL) BY 255 TO GET ACTUAL COLOR ID
		if (shade > 255) shade = 255;		// UPPER BOUNDARY CHECK
		else if (shade < 0) shade = 0;	// LOWER BOUNDARY CHECK
		wingColor = new Color((int)(ID*255),
				      (int)(ID*255),
				      (int)(ID*255));	// SET WING COLOR AS SHADE OF GRAY
		bgColor = bg;		// SET BG COLOR
	}

	public double getID() { return ID; }

	public double visibility(Color bgColor) {
	// CALCULATE HAMMING DISTANCE BETWEEN WING AND BACKGROUND COLOR VALUES
		int greyLevel = bgColor.getBlue();
		return (1/Math.abs((255*ID)-greyLevel + 1));
	}

	public int getWidth() {return width;}
	public int getHeight() {return height;}
	
	public double getMutation(double mR) {
	// FUNCTION: returns mutation ID for the purpose of establishing offspring
		double index = ID,
		       delta = Math.random()*mR;	// MAXIMUM MUTATION RADIUS * RANDOM = NEW WING COLOR ID
		index = (Math.random() > 0.5 ? index-delta : index+delta); // RANDOMLY BRIGHTER OR DARKER WING
		if (index > 1) index = 1;			// UPPER BOUNDARY CHECK
		else if (index < 0) index = 0;	// LOWER BOUNDARY CHECK
		return index;
	}

	public void paintButterfly(int xOffset, int yOffset, Graphics g) {
	// FUNCTION: paint butterfly onto image buffer passed as "g", 
	//				 at point ("xOffset","yOffset")
		int[] xPos = new int[x.length],
		      yPos = new int[y.length];
		System.arraycopy(x,0,xPos,0,x.length);
		System.arraycopy(y,0,yPos,0,y.length);
		for (int i = 0; i < x.length; ++i) xPos[i] += xOffset;
		for (int i = 0; i < y.length; ++i) yPos[i] += yOffset;
		g.setColor(wingColor);
		g.fillPolygon(xPos,yPos,11);
		g.setColor(bgColor);
		g.drawPolygon(xPos,yPos,11);
	}
}
