//
//
// worms.java : Simple Simulator for
// "Self-Replicating Worms That Increase Structural Complexity through Gene Transmission"
// Ver. 1.0 in Java
//
// Copyright (C) 1999-2000
// Hiroki Sayama
// sayama@necsi.net
// New England Complex Systems Institute
// 24 Mt. Auburn St., Cambridge, MA 02138, USA
//
// Comments, questions and suggestions are welcome.
//
//

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

public class worms extends Applet implements Runnable {

  Thread th = null;
  Graphics g;
  
  final int quiescent  = -1,

            modeMask   = 1,
            modeP      = 0,
            modeA      = 1,

            linkMask   = 6,
            linkT      = 0,
            linkR      = 2,
            linkB      = 4,
            linkL      = 6,
  
            signMask   = 56,
            signC      =  8,
            signR      = 16,
            signL      = 32;
            
  int       space = 50, pixel = 12, timestep, cell[], sp, interval, fromx, fromy, tox, toy;
  boolean   periodic, running, onestep,
            displaying, displayswitch, magnifying, magnifyswitch, selectingpoint;

  Button    pausebutton;
  Choice    choiceofspace, choiceofboundary;
  TextField initialdensitytext, randomseedtext;
  Checkbox  displaycheckbox, magnifycheckbox;

  Random rand = new Random();
  

  /*** initialization ***/

  public void init() {

    g = getGraphics();
    setForeground(Color.black);
    setBackground(Color.white);
    setLayout(new BorderLayout());
    
    Panel p = new Panel();
    p.setLayout(new GridLayout(20, 1));

    p.add(pausebutton = new Button("Start"));
    p.add(new Button("Step"));
    p.add(new Button("Reset"));
    p.add(new Label(" "));

    p.add(new Label("Size of space"));
    choiceofspace = new Choice();
    choiceofspace.addItem("50x50");
    choiceofspace.addItem("100x100");
    choiceofspace.addItem("200x200");
    choiceofspace.select("200x200");
    p.add(choiceofspace);
    p.add(new Label(" "));

    p.add(new Label("Boundary conditions"));
    choiceofboundary = new Choice();
    choiceofboundary.addItem("Cut off");
    choiceofboundary.addItem("Periodic");
    choiceofboundary.select("Cut off");
    p.add(choiceofboundary);
    p.add(new Label(" "));

    p.add(new Label("Initial density (0 - 1)"));
    p.add(initialdensitytext = new TextField("", 10));
    p.add(new Label("(0.1 if blank)"));
    p.add(new Label(" "));

    p.add(new Label("Seed of random numbers"));
    p.add(randomseedtext = new TextField("", 10));
    p.add(new Label("(time is used if blank)"));
    p.add(new Label(" "));
    
    p.add(displaycheckbox = new Checkbox("Display"));
    displaycheckbox.setState(true);
    displaying = true;
    p.add(magnifycheckbox = new Checkbox("Magnify"));
    magnifycheckbox.setState(false);
    magnifying = false;

    add("East", p);

    reinit();
  }

  public void reinit() {
    int x, y;
    String tempstring;
    Double initialdensityDouble;
    double initialdensity = 0.0;

    running = onestep = displayswitch = magnifyswitch = selectingpoint = false;

    switch (choiceofspace.getSelectedIndex()) {
      case 0 : space = 50; pixel = 12; break;
      case 1 : space = 100; pixel = 6; break;
      case 2 : space = 200; pixel = 3; break;
    }
    
    fromx = fromy = 0;
    tox = toy = space - 1;
    magnifying = false;
    magnifycheckbox.setState(false);

    switch (choiceofboundary.getSelectedIndex()) {
      case 0 : periodic = false; break;
      case 1 : periodic = true; break;
    }

    tempstring = initialdensitytext.getText();
    if (tempstring.equals("")) initialdensitytext.setText(tempstring = "0.1");
    initialdensityDouble = Double.valueOf(tempstring);
    initialdensity = initialdensityDouble.doubleValue();

    tempstring = randomseedtext.getText();
    if (tempstring.equals("")) rand.setSeed(System.currentTimeMillis());
    else rand.setSeed(Long.parseLong(tempstring));

    cell = new int[space * space * 2];
    sp = 0;
    for (x = 0; x < space; x ++) {
      for (y = 0; y < space; y ++) {
        if (rand.nextDouble() < initialdensity)
          cell[sp + y * space + x] = ((int) (rand.nextDouble() * 4.0)) << 1;
        else cell[sp + y * space + x] = quiescent;
      }
    }
    
    timestep = 0;
    showStatus("Time: " + Integer.toString(timestep));
  }


  /*** graphic routines ***/

  public void display_cell(int ip, int x, int y) {
    int cx, cy, rx, ry, lx, ly;
    
    if (cell[ip] == quiescent) return;
    if (x < fromx || x > tox || y < fromy || y > toy) return;

    int dx = (x - fromx) * pixel, dy = (y - fromy) * pixel;
    
    switch (pixel) {
      
    case 12:
      g.drawRect(dx + 3, dy + 3, 7, 7);
      if ((cell[ip] & modeMask) == modeA) {
        g.setColor(Color.lightGray);
        g.fillRect(dx + 4, dy + 4, 6, 6);
        g.setColor(Color.black);
      }
      cx = cy = rx= ry = lx = ly = 5;
      switch(cell[ip] & linkMask) {
      case linkT:
        g.fillRect(dx + 4, dy - 1, 6, 2);
        g.fillRect(dx + 5, dy + 1, 4, 2);
        cx = 5; cy = 7; rx = 3; ry = 5; lx = 7; ly = 5;
        break;
      case linkR:
        g.fillRect(dx + 13, dy + 4, 2, 6);
        g.fillRect(dx + 11, dy + 5, 2, 4);
        cx = 3; cy = 5; rx = 5; ry = 3; lx = 5; ly = 7;
        break;
      case linkB:
        g.fillRect(dx + 4, dy + 13, 6, 2);
        g.fillRect(dx + 5, dy + 11, 4, 2);
        cx = 5; cy = 3; rx = 7; ry = 5; lx = 3; ly = 5;
        break;
      case linkL:
        g.fillRect(dx - 1, dy + 4, 2, 6);
        g.fillRect(dx + 1, dy + 5, 2, 4);
        cx = 7; cy = 5; rx = 5; ry = 7; lx = 5; ly = 3;
        break;
      }
      if ((cell[ip] & signC) == signC) g.fillRect(dx + 1 + cx, dy + 1 + cy, 2, 2);
      if ((cell[ip] & signR) == signR) g.fillRect(dx + 1 + rx, dy + 1 + ry, 2, 2);
      if ((cell[ip] & signL) == signL) g.fillRect(dx + 1 + lx, dy + 1 + ly, 2, 2);
      break;

    case 6:
      switch(cell[ip] & linkMask) {
      case linkT: g.fillRect(dx + 2, dy    , 4, 6); break;
      case linkR: g.fillRect(dx + 2, dy + 2, 6, 4); break;
      case linkB: g.fillRect(dx + 2, dy + 2, 4, 6); break;
      case linkL: g.fillRect(dx    , dy + 2, 6, 4); break;
      }
      break;

    case 3:
      switch(cell[ip] & linkMask) {
      case linkT: g.fillRect(dx + 2, dy    , 1, 3); break;
      case linkR: g.fillRect(dx + 2, dy + 2, 3, 1); break;
      case linkB: g.fillRect(dx + 2, dy + 2, 1, 3); break;
      case linkL: g.fillRect(dx    , dy + 2, 3, 1); break;
      }
      break;
    }
  }

  public void erase_cell(int ip, int x, int y) {
    if (cell[ip] == quiescent) return;
    if (x < fromx || x > tox || y < fromy || y > toy) return;

    g.setColor(Color.white);
    int dx = (x - fromx) * pixel, dy = (y - fromy) * pixel;

    switch (pixel) {
      
    case 12:
      g.fillRect(dx + 3, dy + 3, 8, 8);
      switch(cell[ip] & linkMask) {
      case linkT:
        g.fillRect(dx + 4, dy - 1, 6, 2);
        g.fillRect(dx + 5, dy + 1, 4, 2);
        break;
      case linkR:
        g.fillRect(dx + 13, dy + 4, 2, 6);
        g.fillRect(dx + 11, dy + 5, 2, 4);
        break;
      case linkB:
        g.fillRect(dx + 4, dy + 13, 6, 2);
        g.fillRect(dx + 5, dy + 11, 4, 2);
        break;
      case linkL:
        g.fillRect(dx - 1, dy + 4, 2, 6);
        g.fillRect(dx + 1, dy + 5, 2, 4);
        break;
      }
      break;

    case 6:
      switch(cell[ip] & linkMask) {
      case linkT: g.fillRect(dx + 2, dy    , 4, 6); break;
      case linkR: g.fillRect(dx + 2, dy + 2, 6, 4); break;
      case linkB: g.fillRect(dx + 2, dy + 2, 4, 6); break;
      case linkL: g.fillRect(dx    , dy + 2, 6, 4); break;
      }
      break;

    case 3:
      switch(cell[ip] & linkMask) {
      case linkT: g.fillRect(dx + 2, dy    , 1, 3); break;
      case linkR: g.fillRect(dx + 2, dy + 2, 3, 1); break;
      case linkB: g.fillRect(dx + 2, dy + 2, 1, 3); break;
      case linkL: g.fillRect(dx    , dy + 2, 3, 1); break;
      }
      break;
    }
      
    g.setColor(Color.black);
  }

  public void blackFrame() {
    g.drawRect(0, 0, 601, 601);
  }

  public void paint(Graphics g) {
    int ip, x, y;

    blackFrame();
    if (displaying) {
      for (ip = sp, y = 0; y < space; y ++)
        for (x = 0; x < space; x ++)
          display_cell(ip ++, x, y);
    }
  }


  /*** thread and event management routines ***/

  public void start() {
    if (th == null) {
      th = new Thread(this);
      th.start();
    }
  }

  public void stop() {
    if (th != null) {
      th.stop();
      try{th.join();}
      catch(InterruptedException e){}
      th = null;
    }
  }

  public boolean action(Event ev, Object arg) {
    boolean selectoff = false;

    if (selectingpoint) {
      showStatus("Magnification cancelled");
      magnifycheckbox.setState(false);
      selectoff = true;
    }
        
    if (ev.target instanceof Checkbox) {
      if (ev.target == displaycheckbox) displayswitch = true;
      else if ((ev.target == magnifycheckbox) && (!selectingpoint)) magnifyswitch = true;
      if (selectoff) selectingpoint = false;
      return true;
    }

    if (selectoff) selectingpoint = false;

    if (ev.target instanceof Button) {
      String button = (String) arg;
      if (button.equals("Stop")) {
        pausebutton.setLabel("Start");
        running = onestep = false;
      }
      else if (button.equals("Start")) {
        pausebutton.setLabel("Stop");
        running = true;
      }
      else if (button.equals("Step")) {
        onestep = true;
        if (running) {
          pausebutton.setLabel("Start");
          running = false;
        }
      }
      else if (button.equals("Reset")) {
        if (running) pausebutton.setLabel("Start");
        running = onestep = false;
        stop();
        reinit();
        repaint();
        start();
      }
      return true;
    }
    
    else if ((ev.target instanceof Choice) ||
             (ev.target instanceof TextField)) {
      if (running) pausebutton.setLabel("Start");
      running = onestep = false;
      stop();
      reinit();
      repaint();
      start();
      return true;
    }
    
    else return false;
  }
  
  public boolean mouseDown (Event ev, int x, int y) {
    if (!selectingpoint) return false;
    else if ((x >= 1) && (x <= 600) && (y >= 1) && (y <= 600)) {
      int bx = (x - 1) / pixel;
      int by = (y - 1) / pixel;
      fromx = bx < 25 ? 0 : bx - 25; if (fromx > space - 50) fromx = space - 50;
      fromy = by < 25 ? 0 : by - 25; if (fromy > space - 50) fromy = space - 50;
      tox = fromx + 49;
      toy = fromy + 49;
      magnifying = true;
      pixel = 12;
      showStatus("Magnified");
      selectingpoint = false;
      return true;
    }
    else return false;
  }
  

  /*** state-transition rules ***/

  public int state(int x, int y) {
    if (x >= 0 && x < space && y >= 0 && y < space) return cell[sp + y * space + x];
    else if (!periodic) return quiescent;
    else {
      if (x < 0) x += space;
      if (x >= space) x -= space;
      if (y < 0) y += space;
      if (y >= space) y -= space;
      return cell[sp + y * space + x];
    }
  }

  public int root(int ip, int x, int y) {
    int c = cell[ip], r = quiescent;
    
    if (c < 0) return r;
    
    switch (c & linkMask) {
    case linkT: if (((r = state(x, y - 1)) & linkMask) == linkB) r = quiescent; break;
    case linkR: if (((r = state(x + 1, y)) & linkMask) == linkL) r = quiescent; break;
    case linkB: if (((r = state(x, y + 1)) & linkMask) == linkT) r = quiescent; break;
    case linkL: if (((r = state(x - 1, y)) & linkMask) == linkR) r = quiescent; break;
    }
    
    return r;
  }
  
  public boolean linked(int ip, int x, int y) {
    int c = cell[ip], nc;
    if ((c & linkMask) != linkT && (nc = state(x, y - 1)) >= 0)
      if ((nc & linkMask) == linkB) return true;
    if ((c & linkMask) != linkR && (nc = state(x + 1, y)) >= 0)
      if ((nc & linkMask) == linkL) return true;
    if ((c & linkMask) != linkB && (nc = state(x, y + 1)) >= 0)
      if ((nc & linkMask) == linkT) return true;
    if ((c & linkMask) != linkL && (nc = state(x - 1, y)) >= 0)
      if ((nc & linkMask) == linkR) return true;
    
    return false;
  }
  
  public int encode(int ip, int x, int y) {
    int code = 0, nc;

    switch (cell[ip] & linkMask) {
    case linkT:
      if ((nc = state(x + 1, y)) >= 0) if ((nc & linkMask) == linkL) code += signL;
      if ((nc = state(x, y + 1)) >= 0) if ((nc & linkMask) == linkT) code += signC;
      if ((nc = state(x - 1, y)) >= 0) if ((nc & linkMask) == linkR) code += signR;
      break;
    case linkR:
      if ((nc = state(x, y - 1)) >= 0) if ((nc & linkMask) == linkB) code += signR;
      if ((nc = state(x, y + 1)) >= 0) if ((nc & linkMask) == linkT) code += signL;
      if ((nc = state(x - 1, y)) >= 0) if ((nc & linkMask) == linkR) code += signC;
      break;
    case linkB:
      if ((nc = state(x, y - 1)) >= 0) if ((nc & linkMask) == linkB) code += signC;
      if ((nc = state(x + 1, y)) >= 0) if ((nc & linkMask) == linkL) code += signR;
      if ((nc = state(x - 1, y)) >= 0) if ((nc & linkMask) == linkR) code += signL;
      break;
    case linkL:
      if ((nc = state(x, y - 1)) >= 0) if ((nc & linkMask) == linkB) code += signL;
      if ((nc = state(x + 1, y)) >= 0) if ((nc & linkMask) == linkL) code += signC;
      if ((nc = state(x, y + 1)) >= 0) if ((nc & linkMask) == linkT) code += signR;
      break;
    }
    return code;
  }

  public boolean hitbyT(int x, int y) {
    int s = state(x, y - 1);
    if (s >= 0)
      if ((s & modeMask) == modeA) {
        if ((s & linkMask) == linkT && (s & signC) == signC) return true;
        if ((s & linkMask) == linkR && (s & signL) == signL) return true;
        if ((s & linkMask) == linkL && (s & signR) == signR) return true;
      }
    return false;
  }

  public boolean hitbyR(int x, int y) {
    int s = state(x + 1, y);
    if (s >= 0)
      if ((s & modeMask) == modeA) {
        if ((s & linkMask) == linkR && (s & signC) == signC) return true;
        if ((s & linkMask) == linkB && (s & signL) == signL) return true;
        if ((s & linkMask) == linkT && (s & signR) == signR) return true;
      }
    return false;
  }

  public boolean hitbyB(int x, int y) {
    int s = state(x, y + 1);
    if (s >= 0)
      if ((s & modeMask) == modeA) {
        if ((s & linkMask) == linkB && (s & signC) == signC) return true;
        if ((s & linkMask) == linkL && (s & signL) == signL) return true;
        if ((s & linkMask) == linkR && (s & signR) == signR) return true;
      }
    return false;
  }

  public boolean hitbyL(int x, int y) {
    int s = state(x - 1, y);
    if (s >= 0)
      if ((s & modeMask) == modeA) {
        if ((s & linkMask) == linkL && (s & signC) == signC) return true;
        if ((s & linkMask) == linkT && (s & signL) == signL) return true;
        if ((s & linkMask) == linkB && (s & signR) == signR) return true;
      }
    return false;
  }

  public int nextState(int ip, int x, int y) {
    int c = cell[ip], n, r;
    boolean ht, hr, hb, hl;

    if (c == quiescent) { /*** quiescent ***/
      ht = hitbyT(x, y); hr = hitbyR(x, y); hb = hitbyB(x, y); hl = hitbyL(x, y);
      if ( ht && !hr && !hb && !hl) return linkT;
      if (!ht &&  hr && !hb && !hl) return linkR;
      if (!ht && !hr &&  hb && !hl) return linkB;
      if (!ht && !hr && !hb &&  hl) return linkL;
      return quiescent;
    }

    else { /*** structure ***/
    
      r = root(ip, x, y);

      if ((c & modeMask) == modeP) { /*** passive ***/
        if (r >= 0) {
          n = c - (c & signMask) + (r & signMask);
          if (!linked(ip, x, y) && (n & signMask) != 0) n |= modeA;
        }
        else {
          n = c - (c & signMask) + encode(ip, x, y);
          n |= modeA;
        }
        if (hitbyT(x, y)) n |= state(x, y - 1) & signMask;
        if (hitbyR(x, y)) n |= state(x + 1, y) & signMask;
        if (hitbyB(x, y)) n |= state(x, y + 1) & signMask;
        if (hitbyL(x, y)) n |= state(x - 1, y) & signMask;
        return n;
      }

      else { /*** active ***/
        if (r >= 0 && !linked(ip, x, y)) {
          n = c - modeA - (c & signMask) + (r & signMask);
          if (hitbyT(x, y)) n |= state(x, y - 1) & signMask;
          if (hitbyR(x, y)) n |= state(x + 1, y) & signMask;
          if (hitbyB(x, y)) n |= state(x, y + 1) & signMask;
          if (hitbyL(x, y)) n |= state(x - 1, y) & signMask;
          return n;
        }
        else return quiescent;
      }
    }
  }
  
  
  /*** simulation engine ***/

  public void switchcheck() {
    boolean changed = false;
    
    if (displayswitch == true) {
      displayswitch = false;
      displaying = displaycheckbox.getState();
      changed = true;
    }
    
    if (magnifyswitch == true) {
      magnifyswitch = false;
      if (magnifying) {
        magnifying = false;
        switch (choiceofspace.getSelectedIndex()) {
        case 0 : space = 50; pixel = 12; break;
        case 1 : space = 100; pixel = 6; break;
        case 2 : space = 200; pixel = 3; break;
        }
        fromx = fromy = 0;
        tox = toy = space - 1;
        showStatus("Magnification finished");
        changed = true;
      }
      else {
        if (pixel == 12) magnifycheckbox.setState(false);
        else {
          pausebutton.setLabel("Start");
          running = onestep = false;
          showStatus("Click where you want to magnify");
          selectingpoint = true;
          while (selectingpoint) {
            try{th.sleep(1);}
            catch(InterruptedException e){}
          };
          if (magnifying) changed = true;
        }
      }
    }
    if (changed) repaint();
  }
  
  public void run() {
    int ip, np, x, y;

    while (true) {

      while (!running && !onestep) {
        switchcheck();
        try{th.sleep(1);}
        catch(InterruptedException e){}
      }
      
      onestep = false;
      
      for (ip = sp, np = (sp == 0) ? space * space : 0, y = 0; y < space; y ++)
        for (x = 0; x < space; x ++) {
          if ((cell[np] = nextState(ip, x, y)) != cell[ip]) {
            if (displaying) {
              if (pixel == 12) {
                erase_cell(ip, x, y);
                display_cell(np, x, y);
              }
              else {
                if (cell[ip] < 0 || cell[np] < 0 ||
                    ((cell[ip] & linkMask) != (cell[np] & linkMask))) {
                  erase_cell(ip, x, y);
                  display_cell(np, x, y);
                }
              }
            }
          }
          ip ++; np ++;
        }

      sp = (sp == 0) ? space * space : 0;
      if (displaying) blackFrame();

      timestep ++;
      showStatus("Time: " + Integer.toString(timestep));

      switchcheck();
      try{th.sleep(1);}
      catch(InterruptedException e){}
    }
  }
}

