import java.applet.Applet;
import java.awt.image.*;
import java.awt.*;
import java.lang.Math;

class Controls
{
  double max;
  Scrollbar rate, amount;

  public double getRate()
  {
    return rate.getValue()/max;
  }

  public double getAmount()
  {
    return amount.getValue()/max;
  }
}

class Coords
{
  double x, y;
  Coords(double x, double y)
  {
    this.x=x;
    this.y=y;
  }
}

interface AffMap
{
  AffMap mutate();
  Coords apply(Coords coords);
}

class oAffineMap implements AffMap
{
  double elt[];
  Controls controls;

  oAffineMap(Controls c)
  {
    elt=new double[6];
    controls = c;
    do {
      for (int i=0; i<6; i++)
        elt[i]=2.0*Math.random()-1.0;
    } while (!isContraction());
  }

  public AffMap mutate()
  {
    oAffineMap map=new oAffineMap(controls);
    do {
      for (int i=0; i<6; i++)
        if (Math.random()>controls.getRate())
          map.elt[i]=elt[i]+(Math.random()-0.5)*controls.getAmount(); else
            map.elt[i]=elt[i];
    } while (!map.isContraction());
    return map;
  }

  public Coords apply(Coords coord)
  {
    return new Coords(elt[0]*coord.x+elt[1]*coord.y+elt[2],
                      elt[3]*coord.x+elt[4]*coord.y+elt[5]);
  }

  public boolean isContraction()
  {
    double a=elt[0]*elt[0]+elt[1]*elt[1];
    double b=elt[0]*elt[3]+elt[1]*elt[4];
    double c=b;
    double d=elt[3]*elt[3]+elt[4]*elt[4];
    double aa=-1;
    double bb=-a-d;
    double cc=a*d-b*c;
    double det=Math.sqrt(bb*bb-4*aa*cc);
    double s1=(-bb+det)/(2*aa);
    double s2=(-bb-det)/(2*aa);
    double max=s1;
    if (s2>max) max=s2;
    return (max<1.0);
  }
}

class AffineMap implements AffMap
{
  double elt[];
  double h, k;        // Translation
  double theta, phi;  // Rotation
  double r, s;        // Scaling
  Controls controls;

  AffineMap(Controls c)
  {
    controls = c;
    elt = new double[6];
    do {
      h = 2.0*Math.random()-1.0;
      k = 2.0*Math.random()-1.0;
      theta = 4.0*Math.PI*Math.random()-2.0*Math.PI;
      phi = 4.0*Math.PI*Math.random()-2.0*Math.PI;
      r = 2.0*Math.random()-1.0;
      s = 2.0*Math.random()-1.0;
      elt[0] = r*Math.cos(theta);
      elt[1] = -s*Math.sin(phi);
      elt[2] = h;
      elt[3] = r*Math.sin(theta);
      elt[4] = s*Math.cos(phi);
      elt[5] =k;
    } while (!isContraction());
  }

  public AffMap mutate()
  {
    AffineMap map=new AffineMap(controls);
    double rate = controls.getRate();
    double amount = controls.getAmount();
    do {
      if (Math.random()>rate)
        map.h = h + (Math.random()-0.5)*amount; else map.h = h;
      if (Math.random()>rate)
        map.k = k + (Math.random()-0.5)*amount; else map.k = k;
      if (Math.random()>rate)
        map.theta = theta + (Math.random()-0.5)*amount; else map.theta = theta;
      if (Math.random()>rate)
        map.phi = phi + (Math.random()-0.5)*amount; else map.phi = phi;
      if (Math.random()>rate)
        map.r = r + (Math.random()-0.5)*amount; else map.r = r;
      if (Math.random()>rate)
        map.s = s + (Math.random()-0.5)*amount; else map.s = s;
      map.elt[0] = map.r*Math.cos(theta);
      map.elt[1] = -map.s*Math.sin(phi);
      map.elt[2] = map.h;
      map.elt[3] = map.r*Math.sin(theta);
      map.elt[4] = map.s*Math.cos(phi);
      map.elt[5] = map.k;
    } while (!map.isContraction());
    return map;
  }

  public Coords apply(Coords coord)
  {
    return new Coords(elt[0]*coord.x+elt[1]*coord.y+elt[2],
                      elt[3]*coord.x+elt[4]*coord.y+elt[5]);
  }

  public boolean isContraction()
  {
    double a=elt[0]*elt[0]+elt[1]*elt[1];
    double b=elt[0]*elt[3]+elt[1]*elt[4];
    double c=b;
    double d=elt[3]*elt[3]+elt[4]*elt[4];
    double aa=-1;
    double bb=-a-d;
    double cc=a*d-b*c;
    double det=Math.sqrt(bb*bb-4*aa*cc);
    double s1=(-bb+det)/(2*aa);
    double s2=(-bb-det)/(2*aa);
    double max=s1;
    if (s2>max) max=s2;
    return (max<1.0);
  }
}

class IFS 
{
  int sizex, sizey;
  AffMap maps[];
  double probs[];
  double cutoffs[];
  Image img;
  Controls controls;

  static IndexColorModel cm;
  static int numMaps;
  static int numPoints;

  static {
    numMaps=4;
    numPoints=2000;
    byte red[]=new byte[256];
    byte green[]=new byte[256];
    byte blue[]=new byte[256];
    byte alpha[]=new byte[256];
    red[0]=(byte)0; green[0]=(byte)0; blue[0]=(byte)0; alpha[0]=(byte)0;
    red[1]=(byte)0; green[1]=(byte)0; blue[1]=(byte)255; alpha[1]=(byte)255;
    cm=new IndexColorModel(8,256,red,green,blue,alpha);
  }

  IFS(int sx, int sy, Controls c)
  {
    sizex=sx;
    sizey=sy;
    controls = c;
    maps=new AffMap[numMaps];
    probs=new double[numMaps];
    cutoffs=new double[numMaps];
    for (int i=0; i<numMaps; i++)
    {
      maps[i]=new oAffineMap(controls);
      probs[i]=Math.random();
    }
    normalizeProbs();
    img=null;
  }

  void normalizeProbs()
  {
    double total=0;
    for (int i=0; i<numMaps; i++) total+=probs[i];
    for (int i=0; i<numMaps; i++) probs[i]/=total;
    cutoffs[0]=probs[0];
    for (int i=1; i<numMaps; i++) cutoffs[i]=cutoffs[i-1]+probs[i];
    cutoffs[numMaps-1]=1;
  }

  int pickMap()
  {
    double value=Math.random();
    int i=0;
    while (value>cutoffs[i]) i++;
    return i;
  }

  synchronized Image getImage(Component comp)
  {
    if (img==null) {
      Coords xy=new Coords(0,0);
      for (int i=0; i<10; i++) xy=maps[pickMap()].apply(xy);
      double minx=xy.x, maxx=xy.x, miny=xy.y, maxy=xy.y;
      Coords coords[]=new Coords[numPoints];
      coords[0]=xy;
      for (int i=1; i<numPoints; i++)
      {
        coords[i]=maps[pickMap()].apply(coords[i-1]);
        minx=Math.min(minx,coords[i].x);
        miny=Math.min(miny,coords[i].y);
        maxx=Math.max(maxx,coords[i].x);
        maxy=Math.max(maxy,coords[i].y);
      }
      double size=Math.max(maxx-minx,maxy-miny);
      double xoff=0.5*(minx+maxx-size);
      double yoff=0.5*(miny+maxy-size);
      byte buf[]=new byte[sizex*sizey];
      for (int i=0; i<sizex*sizey; i++) buf[i]=(byte)0;
      for (int i=0; i<numPoints; i++)
      {
        int x=(int)((coords[i].x-xoff)/size*sizex);
        int y=(int)((coords[i].y-yoff)/size*sizey);
        if (x>=0 && y>=0 && x<sizex && y<sizey)
          buf[x+y*sizex]=(byte)1;
      }
      img=
        comp.createImage(new MemoryImageSource(sizex,sizey,cm,buf,0,sizex));
    }
    return img;
  }

  public void mutation(IFS ifs)
  {
    if (ifs!=this)
    {
      img=null;
      for (int i=0; i<4; i++)
        maps[i]=ifs.maps[i].mutate();
      for (int i=0; i<numMaps; i++)
      {
        do {
          if (Math.random()>controls.getRate())
            probs[i]=ifs.probs[i]+Math.random()*controls.getAmount()*2.0-
                controls.getAmount();
          probs[i]=ifs.probs[i];
        } while (probs[i]<=0.0);
      }
      normalizeProbs();
    }
  }
}

class Population extends Canvas
{
  IFS genomes[];
  int numx, numy, sizex, sizey;

  Population(int numx, int numy, int sizex, int sizey, Controls c)
  {
    genomes=new IFS[numx*numy];
    this.numx=numx;
    this.numy=numy;
    this.sizex=sizex;
    this.sizey=sizey;
    resize(numx*sizex,numy*sizey);
    for (int i=0; i<numx*numy; i++)
      genomes[i]=new IFS(sizex,sizey,c);
  }

  synchronized public boolean mouseDown(Event e, int x, int y)
  {
    IFS ifs=genomes[(y/sizey)*numx+(x/sizex)];
    for (int i=0; i<numx*numy; i++)
      genomes[i].mutation(ifs);
    repaint();
    return true;
  }

  public void paint(Graphics g)
  {
    int index=0;
    for (int y=0; y<numy; y++)
      for (int x=0; x<numx; x++)
      {
        g.drawImage(genomes[index++].getImage(this),x*sizex,y*sizey,null);
        g.setColor(Color.black);
        g.drawRect(x*sizex,y*sizey,sizex-1,sizey-1);
      }
  }
}

public class GeneticArt extends Applet
{
  Population pop;
  Thread worker;
  Controls cobj;
  TextField trate, tamount;

  public void init()
  {
    setLayout(new BorderLayout());

    Panel controls = new Panel();
    cobj = new Controls();
    cobj.max = 100;
    controls.setLayout(new GridLayout(2, 3));
    Label l1 = new Label("Stability:", Label.CENTER);
    cobj.rate = new Scrollbar(Scrollbar.HORIZONTAL, 80, 1, 0, 100);
    trate = new TextField(String.valueOf(0.8));
    Label l2 = new Label("Mutation amount:", Label.CENTER);
    cobj.amount = new Scrollbar(Scrollbar.HORIZONTAL, 30, 1, 0, 100);
    tamount = new TextField(String.valueOf(0.3));
    controls.add(l1); controls.add(cobj.rate); controls.add(trate);
    controls.add(l2); controls.add(cobj.amount); controls.add(tamount);
    add("South", controls);

    pop=new Population(4,4,100,100, cobj);
    add("North",pop);
  }

  public boolean handleEvent(Event e)
  {
    if (e.target == cobj.rate)
    {
      trate.setText(String.valueOf(cobj.getRate()));
      return true;
    } else if (e.target == cobj.amount)
    {
      tamount.setText(String.valueOf(cobj.getAmount()));
      return true;
    } else
      return false;
  }
}

