
/*
   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 EdgeOfChaosCA extends java.applet.Applet implements Runnable {

   Choice statesChoice, neighborsChoice, isotropyChoice, speedChoice;
   Random rand;
   CACanvas CA;
   Label info;
   String infoString;
   Scrollbar scroll;
   int sleepTime;
   int scrollMax;
   Thread runner;
   Color[] color;
   int states,neighbors,ruleCt,lambdaCt;
   int[] rules;
   int[] lambdaRule;
   int[] lambdaState;
   
   int[] isoMate;
   int isoStates;
   int isoNeighbors;
   int isoCt;
   boolean anisotropic;
   
   int actionNeeded = 0;
   final static int
         actionNew = 1,
         actionRandom = 2,
         actionRandomClump = 3,
         actionReset = 4,
         actionScroll = 5,
         actionClear = 6,
         actionStartup = 7,
         actionStop = 8,
         actionOneDotStart = 9;

   public void init() {
   
      rand = new Random();
      
      Panel mainPanel = new Panel();
      mainPanel.setLayout(new BorderLayout(10,10));
      Panel controlPanel = new Panel();
      controlPanel.setLayout(new GridLayout(10,1,3,3));
      
      statesChoice = new Choice();
      statesChoice.addItem("2 States");
      statesChoice.addItem("3 States");
      statesChoice.addItem("4 States");
      statesChoice.addItem("5 States");
      statesChoice.addItem("6 States");
      statesChoice.addItem("7 States");
      statesChoice.addItem("8 States");
      statesChoice.addItem("9 States");
      statesChoice.addItem("10 States");
      statesChoice.select(2);
      
      neighborsChoice = new Choice();
      neighborsChoice.addItem("3 Neighbors");
      neighborsChoice.addItem("5 Neighbors");
      neighborsChoice.addItem("7 Neighbors");
      neighborsChoice.addItem("9 Neighbors");
      neighborsChoice.select(1);
      
      isotropyChoice = new Choice();
      isotropyChoice.addItem("Isotropic");
      isotropyChoice.addItem("Anisotropic");
      isotropyChoice.select(0);
      
      speedChoice = new Choice();
      speedChoice.addItem("Fastest");
      speedChoice.addItem("Fast");
      speedChoice.addItem("Moderate");
      speedChoice.addItem("Slow");
      speedChoice.addItem("Slower");
      speedChoice.select(1);
      sleepTime = 10;
      
      infoString = "4 States, 5 Neighbors, Isotropic; 544 Rules; Lambda = ";
      info = new Label(infoString + "0.333 ");
      info.setFont(new Font("TimesRoman", Font.BOLD, 12));
      mainPanel.add("North",info);
      
      CA = new CACanvas();
      Color[] color = new Color[10];
      color[0] = Color.white;
      color[1] = Color.black;
      color[2] = Color.red;
      color[3] = Color.blue;
      color[4] = Color.yellow;
      color[5] = Color.cyan;
      color[6] = Color.magenta;
      color[7] = Color.green;
      color[8] = Color.gray;
      color[9] = Color.orange;
      CA.properties(5,4,null,color,false);
      mainPanel.add("Center",CA);
      
      scroll = new Scrollbar(Scrollbar.VERTICAL,185,15,0,543);
      scrollMax = 543;
      mainPanel.add("East",scroll);
      
      controlPanel.add(new Button("New"));
      controlPanel.add(statesChoice);
      controlPanel.add(neighborsChoice);
      controlPanel.add(isotropyChoice);
      controlPanel.add(speedChoice);
      controlPanel.add(new Button("Restart"));
      controlPanel.add(new Button("Clear"));
      controlPanel.add(new Button("Random Start"));
      controlPanel.add(new Button("Random Clump"));
      controlPanel.add(new Button("One Dot Start"));
         
      setLayout(new BorderLayout(10,10));
      add("East", controlPanel);
      add("Center", mainPanel);
   }
   
   synchronized void setAction(int act) {
      actionNeeded = act;
   }
   
   synchronized int checkAction() {
      int temp = actionNeeded;
      actionNeeded = 0;
      return temp;
   }
   
   public void start() {
      if (runner == null) {
         runner = new Thread(this);
         setAction(actionStartup);
         runner.start();
      }
   }
   
   public void stop() {
      if (runner != null) {
         setAction(actionStop);
         runner.stop();
         runner = null;
      }
   }
   
   public Insets insets() {
      return new Insets(3,3,3,3);
   }
   
   public void run() {
      boolean running = true;
      while (running) {
         int act = checkAction();
         switch (act) {
            case 0:
               CA.next();
            break;
            case actionStartup:
                try { Thread.sleep(500); }   // delay start 1/2 second
                catch (InterruptedException e) { }
                doNew();
            break;
            case actionStop:
               running = false;
            break;
            case actionClear:
               CA.clear();
            break;
            case actionReset:
               CA.reset();
            break;
            case actionRandom:
               doRandom();
            break;
            case actionRandomClump:
               doRandomClump();
            break;
            case actionOneDotStart:
               CA.set(null);
            break;
            case actionNew:
               doNew();
            break;
            case actionScroll:
               do {
                  try { Thread.sleep(250); }
                  catch (InterruptedException e) { }
               } while (checkAction() == actionScroll);
               if (scroll.getValue() != lambdaCt) {
                  newLambda();
                  CA.reset();
               }
            break;
         }
         if (sleepTime > 0) {
            try { Thread.sleep(sleepTime); }
            catch (InterruptedException e) { }
         }
         else
           Thread.yield();
      }
   }
   
   void doNew() {
      states = statesChoice.getSelectedIndex() + 2;
      neighbors = neighborsChoice.getSelectedIndex()*2 + 3;
      anisotropic = (isotropyChoice.getSelectedItem().equals("Anisotropic"));
      ruleCt = 1;
      for (int i = 1; i <= neighbors; i++)
         ruleCt = ruleCt*states;
      rules = new int[ruleCt];
      if (anisotropic) {
         lambdaRule = new int[ruleCt];
         lambdaState = new int[ruleCt];
         makeRules();
      }
      else {
         if (isoStates != states || isoNeighbors != neighbors)
            computeisoMate();
         lambdaRule = new int[isoCt];
         lambdaState = new int[isoCt];
         makeIsoRules();
         ruleCt = isoCt;
      }
      scrollMax = ruleCt-1;
      infoString = "" + states + " States, "
                      + neighbors + " Neighbors, "
                      + isotropyChoice.getSelectedItem() + "; "
                      + ruleCt + " Rules; Lambda = ";
      int scrollValue = (int)((double)ruleCt / 3.0 + 0.5);
      int scrollInc = (int)((double)(ruleCt) / 25.0) + 1;
      scroll.setValues(scrollValue,scrollInc,0,scrollMax);
      info.setText(infoString + "0." + 
                (int)(((double)scrollValue / (double)scrollMax) * 1000.0));
      CA.properties(states,neighbors,rules,color,false);
      newLambda();
      doRandom();
   }
   
   void makeRules() {
      rules[0] = 0;
      lambdaState[0] = 0;
      lambdaRule[0] = 0;
      for (int i=1; i < ruleCt; i++) {
          rules[i] = 0;
          lambdaRule[i] = i;
          lambdaState[i] = 1 + (int)(rand.nextDouble()*(states-1));
          if (lambdaState[i] >= states)
            lambdaState[i] = states;
      }
      for (int i = ruleCt-1; i>1; i--) {
         int r = 1 + (int)(rand.nextDouble()*i);
         if (r > i)
            r = i;
         int temp = lambdaRule[i];
         lambdaRule[i] = lambdaRule[r];
         lambdaRule[r] = temp;
      }
      lambdaCt = 0;
   }
   
   void makeIsoRules() {
      boolean[] used = new boolean[ruleCt];
      for (int i=0; i<ruleCt; i++) {
         used[i] = false;
         rules[i] = 0;
      }
      lambdaState[0] = 0;
      lambdaRule[0] = 0;
      int k=1;
      for (int i=1; i<ruleCt; i++) {
          if (!used[i]) {
             lambdaRule[k] = i;
             used[isoMate[i]] = true;   
             k++;
          }
      }
      for (int i = isoCt-1; i>1; i--) {
         int r = 1 + (int)(rand.nextDouble()*i);
         if (r > i)
            r = i;
         int temp = lambdaRule[i];
         lambdaRule[i] = lambdaRule[r];
         lambdaRule[r] = temp;
      }
      for (int i=1; i<isoCt; i++) {
          lambdaState[i] = 1 + (int)(rand.nextDouble()*(states-1));
          if (lambdaState[i] >= states)
            lambdaState[i] = states;
      }
      lambdaCt = 0;
   }
   
   void newLambda() {
      int newLambdaCt = scroll.getValue();
      if (anisotropic) {
         if (newLambdaCt > lambdaCt) {
            for (int i=lambdaCt+1; i<=newLambdaCt; i++)
               CA.setRule(lambdaRule[i],lambdaState[i]);
         }
         else if (newLambdaCt < lambdaCt) {
            for (int i=lambdaCt; i>newLambdaCt; i--)
               CA.setRule(lambdaRule[i],0);
         }
      }
      else {
         if (newLambdaCt > lambdaCt) {
            for (int i=lambdaCt+1; i<=newLambdaCt; i++) {
               CA.setRule(lambdaRule[i],lambdaState[i]);
               CA.setRule(isoMate[lambdaRule[i]],lambdaState[i]);
             }
         }
         else if (newLambdaCt < lambdaCt) {
            for (int i=lambdaCt; i>newLambdaCt; i--) {
               CA.setRule(lambdaRule[i],0);
               CA.setRule(isoMate[lambdaRule[i]],0);
             }
         }
      }
      lambdaCt = newLambdaCt;
   }
   
   void computeisoMate() {
      if (isoMate == null || isoMate.length < ruleCt)
         isoMate = new int[ruleCt];
      isoMate[0] = 0;
      for (int i=1; i<ruleCt; i++)
         isoMate[i] = -1;
      int[] num = new int[9];
      for (int i=0; i<=8; i++)
         num[i] = 0;
      isoCt = 1;
      int y=0;
      do {
         int j=0;
         while (j < neighbors && ++num[j] >= states) {
            num[j] = 0;
            j++;
         }
         y++;
         if (isoMate[y] == -1) {
            int x = num[0];
            for (int k = 1; k<neighbors; k++)
               x = states*x + num[k];
            isoMate[x] = y;
            isoMate[y] = x;
            isoCt++;
         }
      } while (y < ruleCt-1);
      isoStates = states;
      isoNeighbors = neighbors;
   }
   
   void doRandom() {
      CA.set(null);
      int width = CA.getWidth();
      for (int i=0; i<width; i++) {
         int c = (int)(rand.nextDouble() * states);
         if (c >= states)
            c = 0;
         CA.setCell(i,c);
      }
      CA.setStart();
   }
   
   void doRandomClump() {
      CA.set(null);
      int width = CA.getWidth();
      for (int i=0; i<width; i++)
         CA.setCell(i,0);
      int left = (width / 2) - (width / 10);
      int right = (width / 2) + (width / 10);
      for (int i=left; i<=right; i++) {
         int c = (int)(rand.nextDouble() * states);
         if (c >= states)
            c = 0;
         CA.setCell(i,c);
      }
      CA.setStart();
   }
   
   void doSpeed(int speed) {
      switch (speed) {
         case 0: sleepTime = 0;
         break;
         case 1: sleepTime = 15;
         break;
         case 2: sleepTime = 50;
         break;
         case 3: sleepTime = 200;
         break;
         case 4: sleepTime = 500;
         break;
      }
   }
   
   void checkNeighbors() {
      int n = neighborsChoice.getSelectedIndex();
      int s = statesChoice.getSelectedIndex();
      int max = 0;
      switch (s) {
         case 0: max = 3;
         break;
         case 1: max = 3;
         break;
         case 2: max = 2;
         break;
         case 3: max = 1;
         break;
         case 4: max = 1;
         break;
         case 5: max = 1;
         break; 
         default: max = 0;
         break;
       }
      if (n > max)
         neighborsChoice.select(max);
   }
   
   public boolean action(Event evt, Object arg) {  
      if (evt.target instanceof Button) {
         if (arg.equals("New"))
            setAction(actionNew);
         else if (arg.equals("Restart"))
            setAction(actionReset);
         else if (arg.equals("Random Start"))
            setAction(actionRandom);
         else if (arg.equals("Random Clump"))
            setAction(actionRandomClump);  
         else if (arg.equals("Clear"))
            setAction(actionClear);  
         else if (arg.equals("One Dot Start"))
            setAction(actionOneDotStart);
      }
      else if (evt.target == speedChoice)
         doSpeed(speedChoice.getSelectedIndex());
      else if (evt.target == neighborsChoice || evt.target == statesChoice)
         checkNeighbors();
      return true;
   }
   
   public boolean handleEvent(Event evt) {
      if (evt.id == Event.SCROLL_ABSOLUTE ||
            evt.id == Event. SCROLL_LINE_DOWN ||
            evt.id == Event. SCROLL_LINE_UP ||
            evt.id == Event. SCROLL_PAGE_DOWN ||
            evt.id == Event. SCROLL_PAGE_UP) {
          int val = scroll.getValue();
          if (val == scrollMax)
             info.setText(infoString + "1.00");
          else {
             int x = (int)(((double)val / (double)scrollMax) * 1000.0);
             if (x>=100)
                info.setText(infoString + "0." + x);
             else if (x>=10)
                info.setText(infoString + "0.0" + x);
             else
                info.setText(infoString + "0.00" + x);
          }
          setAction(actionScroll);
          return true;
      }
      else
         return super.handleEvent(evt);
   }

} // class EdgeCA

