
/*
   This code is, for the moment, totally uncommented.  Sorry.
   
   David Eck
   Department of Mathematics and Computer Science
   Hobart and William Smith Colleges
   Geneva, NY   14456
   E-mail:  eck@hws.edu
   WWW:     http://math.hws.edu/eck
   
   June 18, 1996
   
   NOTE:  YOU CAN DO ANYTHING YOU WANT WITH THIS CODE, EXCEPT COPYRIGHT IT,
          PATENT IT, OR OTHERWISE TRY TO CLAIM CREDIT FOR IT.
          
*/


import java.awt.*;
import java.util.Random;

public class HandCraftCA extends java.applet.Applet implements Runnable, LayoutManager{

   CACanvas CA;
   CADemo CD;
   RuleCanvas RC;

   Random rand;
   Color[] color;
   Thread runner;
   
   int sleepTime = 100;
   
   boolean changed = false;
   
   int[] rules;
   

   public void addLayoutComponent(String name, Component comp) {
   }
   
   public void removeLayoutComponent(Component comp) {
   }
   
   public Dimension preferredLayoutSize(Container parent) {
      return new Dimension(270,470);
   }
   
   public Dimension minimumLayoutSize(Container parent) {
      return new Dimension(150,245);
   }
   
   public void layoutContainer(Container parent) {
      int width = parent.size().width - 20;
      int height = parent.size().height - 20;
      int CAheight = (int)((double)width / 45.0 * 12.0 + 1.0);
      parent.getComponent(0).reshape(10,10,width, CAheight);
      int botHeight = height - 10 - CAheight;
      parent.getComponent(1).reshape(10, 20 + CAheight, width, botHeight);
   }

   public void init() {
      setBackground(Color.lightGray);
      rand = new Random();
      int[] changeRule = new int[50];
      int[] changeState = new int[50];
      color = new Color[3];
      color[0] = Color.black;
      color[1] = Color.red;
      color[2] = Color.blue;

      Panel bot = new Panel();
      bot.setLayout(new GridLayout(1,2,10,10));
      
      Panel botleft = new Panel();
      botleft.setLayout(new BorderLayout(5,5));
      CD = new CADemo(this);
      botleft.add("Center",CD);
      Panel blbtn = new Panel();
      blbtn.add(new Button("New"));
      blbtn.add(new Button(">>"));
      blbtn.add(new Button(">>>"));
      botleft.add("South",blbtn);
      bot.add(botleft);
      
      Panel botright = new Panel();
      botright.setLayout(new BorderLayout(5,5));
      CA = new CACanvas();
      CA.properties(3,3,null,color,true);
      rules = new int[27];
      botright.add("Center",CA);
      Panel brbtn = new Panel();
      brbtn.add(new Button("Random"));
      brbtn.add(new Button("Restart"));
      botright.add("South",brbtn);
      bot.add(botright);
           
      RC = new RuleCanvas(this);
      
      setLayout(this);
      add(RC);
      add(bot);

   }

   public void start() {
      randomizeCA();
      CA.set(null);
      RC.setup();
      CD.setup();
      doRandom();
      if (runner == null) {
         runner = new Thread(this);
         runner.start();
      }
   }
   
   public void stop() {
      if (runner != null) {
         runner.stop();
         runner = null;
      }
   }
   
   public Insets insets() {
      return new Insets(10,10,10,10);
   }
   
   synchronized void setChanged() {
      changed = true;
   }
   
   synchronized void makeChanges() {
      if (changed) {
         for (int i=0; i<27; i++)
            CA.setRule(i,rules[i]);
      }
      changed = false;
   }
   
   public void run() {
      while (true) {
         makeChanges();
         CA.next();
         try { Thread.sleep(sleepTime); }
         catch (InterruptedException e) { }
      }
   }
   
   void randomizeCA() {
      for (int i=0; i<27; i++) {
         int r = (int)(rand.nextDouble() * 3);
         if (r >= 3)
            r = 3;
         rules[i] = r;
      }
      setChanged();
   }
   
   void doRandom() {
      int w = CA.getWidth();
      int[] cell = new int[w];
      for (int i=0; i<w; i++) {
         int r = (int)(rand.nextDouble() * 3);
         if (r >= 3)
            r = 3;
         cell[i] = r;
      }
      CA.set(cell);
   }
   
   void doCopy(boolean tile) {
      int w = CA.getWidth();
      int cell[] = new int[w];
      if (tile) {
         int i=0;
         int j=0;
         while (i<w) {
            cell[i] = CD.cell[0][j];
            i++;
            j++;
            if (j >= CD.cell[0].length)
               j = 0;
         }
      }
      else {
         for (int i = 0; i < w; i++)
            cell[i] = 0;
         int left = (w/2 - CD.cell[0].length/2);
         for (int j=0; j<CD.cell[0].length; j++)
            cell[left+j] = CD.cell[0][j];
      }
      CA.set(cell);
   }
   
   public boolean action(Event evt, Object arg) {  
      if (evt.target instanceof Button) {
         if (arg.equals("New")) {
            CD.doNew();
            CD.repaint();
         }
         else if (arg.equals(">>"))
            doCopy(false);
         else if (arg.equals(">>>"))
            doCopy(true);
         else if (arg.equals("Random"))
            doRandom();
         else if (arg.equals("Restart"))
            CA.reset();
      }
      return true;
   }

}


class RuleCanvas extends Canvas {

   int rulePatchW, rulePatchH;
   int statePatchW, statePatchH;
   HandCraftCA owner;
   int hilited;
   
   RuleCanvas(HandCraftCA theOwner) {
      owner = theOwner;
      setBackground(Color.lightGray);
      hilited = -1;
  }
   
   void setup() {
      rulePatchW = size().width / 9;
      rulePatchH = size().height / 3;
      statePatchW = rulePatchW / 5;
      statePatchH = rulePatchH / 4;
   }
   
   public boolean mouseDown(Event evt, int x, int y) {
       int row = y / rulePatchH;
       int col = x / rulePatchW;
       if (row < 3 && col < 9) {
          int rule = 9*row + col;
          if (owner.rules[rule] == 2)
             owner.rules[rule] = 0;
          else
             owner.rules[rule]++;
          owner.setChanged();
          Graphics g = getGraphics();
          g.setColor(owner.color[owner.rules[rule]]);
          int a = col*rulePatchW+2*statePatchW;
          int b = row*rulePatchH+2*statePatchH;
          g.fillRect(a+1,b+1,statePatchW-1,statePatchH-1);
          owner.CD.propagate();
       }
       return true;
   }
   
   void putHilite(Graphics g) {
      int row = hilited / 9;
      int col = hilited % 9;
      int h = statePatchH / 2;
      int w = statePatchW / 2;
      g.drawRect(col*rulePatchW + w, row*rulePatchH + h, rulePatchW - statePatchW, rulePatchH - statePatchH);
      g.drawRect(col*rulePatchW + w + 1, row*rulePatchH + h + 1, rulePatchW - statePatchW - 2, rulePatchH - statePatchH - 2);
   }
   
   public void paint(Graphics g) {
      setup();
      for (int row = 0; row < 3; row++) {
         for (int col = 0; col < 9; col++) {
            int rule = 9*row+col;
            int a = col*rulePatchW;
            int b = row*rulePatchH;
            g.setColor(Color.lightGray);
            g.fill3DRect(a,b,rulePatchW-1,rulePatchH-1,true);
            g.setColor(owner.color[row]);
            g.fillRect(a+statePatchW,b+statePatchH,statePatchW,statePatchH);
            g.setColor(owner.color[col/3]);
            g.fillRect(a+2*statePatchW,b+statePatchH,statePatchW,statePatchH);
            g.setColor(owner.color[col%3]);
            g.fillRect(a+3*statePatchW,b+statePatchH,statePatchW,statePatchH);
            g.setColor(owner.color[owner.rules[rule]]);
            g.fillRect(a+2*statePatchW,b+2*statePatchH,statePatchW,statePatchH);
            g.setColor(Color.gray);
            g.drawRect(a+statePatchW,b+statePatchH,statePatchW,statePatchH);
            g.drawRect(a+2*statePatchW,b+statePatchH,statePatchW,statePatchH);
            g.drawRect(a+3*statePatchW,b+statePatchH,statePatchW,statePatchH);
            g.drawRect(a+2*statePatchW,b+2*statePatchH,statePatchW,statePatchH);
         }
      }
      if (hilited != -1) {
         g.setColor(Color.green);
         putHilite(g);
      }
   }
   
   void hilite(int newHilite) {
      if (newHilite != hilited) {
          setup();
          Graphics g = getGraphics();
          if (hilited != -1) {
             g.setColor(Color.lightGray);
             putHilite(g);
          }
          hilited = newHilite;  
          if (hilited != -1) {
             g.setColor(Color.green);
             putHilite(g);
          }
          g.dispose();
      }
   }
   
}


class CADemo extends Canvas {

   int[][] cell;
   int rows,cols;
   int leftOffset, topOffset;
   int cellWidth, cellHeight;
   HandCraftCA owner;
   int hilitedRow = -1;
   int hilitedCol = -1;
 //  Color hiliteColor = new Color(0,175,0);
   Color hiliteColor = Color.yellow;

   CADemo(HandCraftCA theOwner) {
       owner = theOwner;
   }

   void setup() {
      cellWidth = 15;
      cellHeight = 15;
      cols = (size().width-3) / cellWidth;
      rows = (size().height-3) / cellHeight;
      leftOffset = (size().width - cols*cellWidth + 1) / 2;
      topOffset = (size().height - rows*cellHeight + 1) / 2;
      cell = new int[rows][cols];
      doNew();
   }
   
   void doNew() {
      for (int j=0; j< cols; j++) {
          double r = owner.rand.nextDouble();
          if (r < 0.33333334)
             cell[0][j] = 0;
          else if (r < 0.666666667)
             cell[0][j] = 1;
          else
             cell[0][j] = 2;
      }
      for (int row=1; row<rows; row++)
         for (int col=0; col<cols; col++) {
            int left = (col == 0)? cols - 1 : col - 1;
            int right = (col == cols - 1) ? 0 : col + 1;
            int rule = 9*cell[row-1][left] + 3*cell[row-1][col] + cell[row-1][right];
            cell[row][col] = owner.rules[rule];
          }
   }
   
   void putHilite(Graphics g) {
      int left = (hilitedCol == 0)? cols - 1 : hilitedCol - 1;
      int right = (hilitedCol == cols - 1)? 0 : hilitedCol + 1;
      int r = hilitedRow - 1;
      g.drawRect(leftOffset+left*cellWidth-2,topOffset+r*cellHeight-2, cellWidth+1,cellHeight+1);
      g.drawRect(leftOffset+hilitedCol*cellWidth-2,topOffset+r*cellHeight-2, cellWidth+1,cellHeight+1);
      g.drawRect(leftOffset+right*cellWidth-2,topOffset+r*cellHeight-2, cellWidth+1,cellHeight+1);
      g.drawRect(leftOffset+hilitedCol*cellWidth-2,topOffset+hilitedRow*cellHeight-2, cellWidth+1,cellHeight+1);
      g.drawRect(leftOffset+left*cellWidth-1,topOffset+r*cellHeight-1, cellWidth-1,cellHeight-1);
      g.drawRect(leftOffset+hilitedCol*cellWidth-1,topOffset+r*cellHeight-1, cellWidth-1,cellHeight-1);
      g.drawRect(leftOffset+right*cellWidth-1,topOffset+r*cellHeight-1, cellWidth-1,cellHeight-1);
      g.drawRect(leftOffset+hilitedCol*cellWidth-1,topOffset+hilitedRow*cellHeight-1, cellWidth-1,cellHeight-1);
   }

   public void paint(Graphics g) {
      cellWidth = (size().width-3) / cols;
      cellHeight = (size().height-3) / rows;
      leftOffset = (size().width - cols*cellWidth) / 2;
      topOffset = (size().height - rows*cellHeight) / 2;
      for (int i=0; i<rows; i++)
         for (int j=0; j<cols; j++) {
            if (cell[i][j] < 0)
               g.setColor(Color.white);
            else 
              g.setColor(owner.color[cell[i][j]]);
            g.fillRect(leftOffset+j*cellWidth,topOffset+i*cellHeight, cellWidth-2,cellHeight-2);
      }
      if (hilitedRow != -1) {
         g.setColor(hiliteColor);
         putHilite(g);
      }
   }
   
   void hilite(int row, int col) {
      if (row != hilitedRow || col != hilitedCol) {
          Graphics g=getGraphics();
          if (hilitedRow != -1) {
             g.setColor(Color.lightGray);
             putHilite(g);
          }
          hilitedRow = row;
          hilitedCol = col;
          if (hilitedRow != -1) {
             g.setColor(hiliteColor);
             putHilite(g);
          }
          g.dispose();
      }
   }
   
   public boolean mouseMove(Event evt, int x, int y) {
      cellWidth = (size().width-3) / cols;
      cellHeight = (size().height-3) / rows;
      leftOffset = (size().width - cols*cellWidth) / 2;
      topOffset = (size().height - rows*cellHeight) / 2;
      int row = (y - topOffset) / cellHeight;
      int col = (x - leftOffset) / cellWidth;
      if (row > 0 && row < rows && col >= 0 && col < cols) {
         int left = (col == 0)? cols-1 : col-1;
         int right = (col == cols-1)? 0 : col+1;
         if (cell[row-1][left] >= 0 && cell[row-1][col] >= 0 && cell[row-1][right] >= 0) {
            int rule = 9*cell[row-1][left] + 3*cell[row-1][col] + cell[row-1][right];
            owner.RC.hilite(rule);
            hilite(row,col);
         }
         else {
            owner.RC.hilite(-1);
            hilite(-1,-1);
         }
      }
      else {
         owner.RC.hilite(-1);
         hilite(-1,-1);
      }
      return true;
   }
   
   public boolean mouseDrag(Event evt, int x, int y) {
      return mouseMove(evt, x, y);
   }
   
   public boolean mouseExit(Event evt, int x, int y) {
      cellWidth = (size().width-3) / cols;
      cellHeight = (size().height-3) / rows;
      leftOffset = (size().width - cols*cellWidth) / 2;
      topOffset = (size().height - rows*cellHeight) / 2;
      owner.RC.hilite(-1);
      hilite(-1,-1);
      return true;
   }
   
   public boolean mouseDown(Event evt, int x, int y) {
      cellWidth = (size().width-3) / cols;
      cellHeight = (size().height-3) / rows;
      leftOffset = (size().width - cols*cellWidth) / 2;
      topOffset = (size().height - rows*cellHeight) / 2;
      int row = (y - topOffset) / cellHeight;
      int col = (x - leftOffset) / cellWidth;
      if (row == 0 && col >= 0 && col < cols) {
         cell[row][col]++;
         if (cell[row][col] >= 3)
            cell[row][col] = 0;
         Graphics g = getGraphics();
         g.setColor(owner.color[cell[row][col]]);
         g.fillRect(leftOffset+col*cellWidth,topOffset+row*cellHeight, cellWidth-2,cellHeight-2);
         g.dispose();
         propagate();
      }
      else
         return mouseMove(evt, x, y);      
      return true;
   }
   
   void propagate() {
      cellWidth = (size().width-3) / cols;
      cellHeight = (size().height-3) / rows;
      leftOffset = (size().width - cols*cellWidth) / 2;
      topOffset = (size().height - rows*cellHeight) / 2;
      Graphics g = getGraphics();
      for (int row=1; row<rows; row++) {
         for (int col=0; col<cols; col++) {
               int left = (col == 0)? cols - 1 : col - 1;
               int right = (col == cols - 1) ? 0 : col + 1;
               int rule = 9*cell[row-1][left] + 3*cell[row-1][col] + cell[row-1][right];
               if (cell[row][col] != owner.rules[rule]) {
                  cell[row][col] = owner.rules[rule];
                  g.setColor(owner.color[cell[row][col]]);
                  g.fillRect(leftOffset+col*cellWidth,topOffset+row*cellHeight, cellWidth-2,cellHeight-2);
               }
            }
      }
      g.dispose();
   }
   
}
