//
import java.awt.*;

public class test extends BufferedApplet
{
   // DATA FIELDS IN EACH TILE

   final int TYPE  = 0;
   final int STATE = 1;
   final int BUG   = 2;

   // TYPES OF TILES

   final int NIL = 0;
   final int NW  = 1;
   final int SW  = 2;
   final int SE  = 3;
   final int NE  = 4;
   final int AND = 5;
   final int OR  = 6;
   final int NTYPES = 7;

   // STATES OF LOGIC TILES

   final int N = 1;
   final int S = 2;
   final int E = 3;
   final int W = 4;
   final int P = 5;
   final int NSTATES = 6;

   // BUG DIRECTIONS

   final int BN  = 1;
   final int BW  = 2;
   final int BS  = 4;
   final int BE  = 8;

   int w = 0, h = 0, ni, nj;
   Color buttonColor = new Color(255,200,160);
   Color feltColor = new Color(90,130,90);
   Color tileColor = new Color(190,150,115);
   Color bugColor = tileColor.brighter();
   int tiles[][][], newTiles[][][];
   int mx, my;
   Rectangle stepRect, runRect;
   double startTime = 0;

   public boolean keyUp(Event e, int key) {
      int i = x2i(mx);
      int j = y2j(my);
      int tile[] = tiles[i][j];
      int ANDStates[] = {E,W,P}, ORStates[] = {N,S,P};
      switch (key) {
      case '\u03EC': tile[BUG] = BN; break; // UP ARROW
      case '\u03ED': tile[BUG] = BS; break; // DOWN ARROW
      case '\u03EE': tile[BUG] = BW; break; // LEFT ARROW
      case '\u03EF': tile[BUG] = BE; break; // RIGHT ARROW
      case 127     : tile[BUG] =  0; break; // DELETE
      case '|'     : tile[TYPE] = OR ; tile[STATE] = 0; break;
      case '-'     : tile[TYPE] = AND; tile[STATE] = 0; break;
      case ' '     : tile[TYPE] = tile[STATE] = 0; break;
      case '?':
	 for (i = 0 ; i < ni ; i++)
	 for (j = 0 ; j < nj ; j++) {
	    tile = tiles[i][j];
	    tile[TYPE] = tile[STATE] = tile[BUG] = 0;
	    if ((i+j) % 2 == 0) {
	       tile[TYPE] = AND + random(2);
	       tile[STATE] = (tile[TYPE]==AND ? ANDStates : ORStates)[random(3)];
	       tile[BUG] = 0;
	    }
	    if (tile[TYPE] == 0 && (i+j) % 2 == 0)
	       tile[TYPE] = 1 + random(4);
	    if (tile[TYPE] == 0)
	       tile[BUG] = 1 + random(4);
         }
	 break;
      }
      damage = true;
      return true;
   }

   java.util.Random R = new java.util.Random();
   int random(int n) { return Math.abs(R.nextInt()) % n; }

   public boolean mouseMove(Event e, int x, int y) {
      mx = x;
      my = y;
      return true;
   }

   final int STEP  = 0;
   final int RUN   = 1;
   final int TILES = 2;
   final int DOWN  = 3;
   final int UP    = 4;

   int mouseState = UP;
   boolean isRunning = false;

   public boolean mouseDown(Event e, int x, int y) {
      if (stepRect.inside(x,y))
	 mouseState = STEP;
      else if (runRect.inside(x,y))
	 mouseState = RUN;
      else if (x < h)
	 mouseState = TILES;
      else
	 mouseState = DOWN;
      damage = true;
      return true;
   }

   public boolean mouseUp(Event e, int x, int y) {
      switch (mouseState) {
      case STEP:
	 isRunning = false;
	 step();
	 break;
      case RUN:
	 isRunning = ! isRunning;
	 break;
      case TILES:
         int i = x2i(x);
         int j = y2j(y);
         int type = tiles[i][j][TYPE];
	 switch (type) {
	 case NIL: type = NE ; break;
	 case NE : type = SE ; break;
	 case SE : type = SW ; break;
	 case SW : type = NW ; break;
	 default : type = NIL; break;
         }
         tiles[i][j][TYPE] = type;
	 break;
      }
      damage = true;
      mouseState = UP;
      return true;
   }

   void step() {

      // IF BUG IS ON A NEGATION, THEN TURN IT 90 DEGREES

      for (int i = 0 ; i < ni ; i++)
      for (int j = 0 ; j < nj ; j++) {
	 int tile[] = tiles[i][j];
	 int bug = tile[BUG];
	 if (bug != 0) {
            switch (tile[TYPE]) {
            case NW: if ((bug&BS)!=0) {bug&=~BS;bug|=BW;} if ((bug&BE)!=0) {bug&=~BE;bug|=BN;} break;
            case NE: if ((bug&BS)!=0) {bug&=~BS;bug|=BE;} if ((bug&BW)!=0) {bug&=~BW;bug|=BN;} break;
            case SW: if ((bug&BN)!=0) {bug&=~BN;bug|=BW;} if ((bug&BE)!=0) {bug&=~BE;bug|=BS;} break;
            case SE: if ((bug&BN)!=0) {bug&=~BN;bug|=BE;} if ((bug&BW)!=0) {bug&=~BW;bug|=BS;} break;
	    }
	    tile[BUG] = bug;
         }
      }

      for (int i = 0 ; i < ni ; i++)
      for (int j = 0 ; j < nj ; j++) {
         newTiles[i][j][TYPE ] = tiles[i][j][TYPE];
         newTiles[i][j][STATE] = tiles[i][j][STATE];
         newTiles[i][j][BUG  ] = tiles[i][j][BUG];
      }

      // MOVE BUG TO THE NEXT TILE OVER

      for (int i = 0 ; i < ni ; i++)
      for (int j = 0 ; j < nj ; j++) {
	 int tile[] = tiles[i][j];
	 int newTile[] = newTiles[i][j];
	 int bug = tile[BUG];
	 if ((bug&BN)!=0) {newTile[BUG] &= ~BN; setBug(i,j-1, BN);}
	 if ((bug&BS)!=0) {newTile[BUG] &= ~BS; setBug(i,j+1, BS);}
	 if ((bug&BW)!=0) {newTile[BUG] &= ~BW; setBug(i-1,j, BW);}
	 if ((bug&BE)!=0) {newTile[BUG] &= ~BE; setBug(i+1,j, BE);}
      }

      for (int i = 0 ; i < ni ; i++)
      for (int j = 0 ; j < nj ; j++) {
         tiles[i][j][STATE] = newTiles[i][j][STATE];
         tiles[i][j][BUG  ] = newTiles[i][j][BUG  ];
      }
   }

   void setBug(int i, int j, int bug) {
      i = (i + ni) % ni;
      j = (j + nj) % nj;
      int tile[] = newTiles[i][j];
      int state = tile[STATE];
      bug = tile[BUG] | bug;
      switch (tile[TYPE]) {
      case OR:
	 switch (state) {
	 case 0:
	    switch (bug) {
	    case BN: state = N; bug = 0; break;
	    case BS: state = S; bug = 0; break;
	    case BE: state = P; bug = 0; break;
	    case BW: state = P; bug = 0; break;
	    case BN|BE: case BN|BW: case BS|BE: case BS|BW: bug = BN|BS; break;
	    case BN|BS|BE: case BN|BE|BW: state = N; bug = BN|BS; break;
	    case BN|BS|BW: case BS|BE|BW: state = S; bug = BN|BS; break;
	    }
	    break;
	 case N:
	 case S:
	    if (bug != 0 && bug != (BN|BS|BE|BW))
	       state = 0;
	    switch (bug) {
	    case BN: case BS: case BE: case BW: bug = BN|BS; break;
	    case BN|BS: case BN|BE: case BS|BE: bug = BN|BS|BE; break;
	    case BE|BW: case BN|BW: case BS|BW: bug = BN|BS|BW; break;
	    case BN|BS|BE: case BN|BS|BW: case BN|BE|BW: case BS|BE|BW: bug = BN|BS|BE|BW; break;
	    }
	    break;
         case P:
	    if (bug != 0 && bug != (BN|BS|BE|BW))
	       state = 0;
	    switch (bug) {
	    case BN: case BS: bug = BN|BS; break;
	    case BE: case BW: bug = BE|BW; break;
	    case BN|BS: case BN|BE: case BS|BE: bug = BN|BS|BE; break;
	                case BN|BW: case BS|BW: bug = BN|BS|BW; break;
	    case BE|BW: bug = BE|BW|BN; break;
	    case BN|BS|BE: case BN|BS|BW: case BN|BE|BW: case BS|BE|BW: bug = BN|BS|BE|BW; break;
	    }
	    break;
	 }
	 break;
      case AND:
	 switch (state) {
	 case 0:
	    switch (bug) {
	    case BN: case BS: state = P; bug = 0; break;
	    case BE:          state = E; bug = 0; break;
	    case BW:          state = W; bug = 0; break;
	    }
	    break;
         case P:
	    switch (bug) {
	    case BN: case BS: state = 0; bug = BN|BS; break;
	    case BE: case BW: state = 0; bug = BE|BW; break;
	    }
	    break;
	 case E:
	 case W:
	    switch (bug) {
	    case BN: case BS:
	    case BE: case BW: state = 0; bug = BE|BW; break;
            }
	    break;
	 }
	 break;

      // IF ANOTHER BUG IS ALSO COMING IN, SQUEEZE THEM BOTH INTO THIS TILE

      default:
	 switch (tile[BUG]) {
	 case BE: switch (bug) { case BW: bug = BE|BW; break; case BN: bug = BN|BE; break; case BS: bug = BS|BE; break; }
	 case BW: switch (bug) { case BE: bug = BE|BW; break; case BN: bug = BN|BW; break; case BS: bug = BS|BW; break; }
	 case BN: switch (bug) { case BS: bug = BN|BS; break; case BE: bug = BN|BE; break; case BW: bug = BN|BW; break; }
	 case BS: switch (bug) { case BN: bug = BN|BS; break; case BE: bug = BS|BE; break; case BW: bug = BS|BW; break; }
	 }
	 break;
      }
      tile[STATE] = state;
      tile[BUG] = bug;
   }

   int clicks = 0;

   public void render(Graphics g) {
      if (w == 0) {
         w = bounds().width;
         h = bounds().height;
         ni = h/32;
         nj = h/32;
         tiles = new int[ni][nj][3];
         newTiles = new int[ni][nj][3];
	 stepRect = new Rectangle(w - 16 - 40, 16, 40, 20);
	 runRect  = new Rectangle(stepRect.x, stepRect.y + stepRect.height + 16, stepRect.width, stepRect.height);
      }
      if (isRunning) {
	 double time = System.currentTimeMillis() / 1000.;
	 if (startTime == 0)
	    startTime = time;
         int newClicks = (int)(4 * (time - startTime));
	 if (newClicks > clicks) {
	    step();
	    clicks = newClicks;
         }
      }

      g.setColor(feltColor);
      g.fillRect(0,0,w,h);

      g.setColor(buttonColor);
      g.fill3DRect(stepRect.x, stepRect.y, stepRect.width, stepRect.height, mouseState != STEP);
      g.setColor(Color.black);
      g.drawString("STEP", stepRect.x + 4, stepRect.y + 15);

      g.setColor(buttonColor);
      g.fill3DRect(runRect.x, runRect.y, runRect.width, runRect.height, mouseState != RUN);
      g.setColor(Color.black);
      g.drawString(isRunning ? "STOP" : "RUN", runRect.x + 4, runRect.y + 15);

      g.setColor(Color.black);
      for (int i = 0 ; i <= ni ; i++)
         g.drawLine(i2x(i)-16,0,i2x(i)-16,h);
      for (int j = 0 ; j <= nj ; j++)
         g.drawLine(0,j2y(j)-16,h,j2y(j)-16);

      for (int i = 0 ; i < ni ; i++)
      for (int j = 0 ; j < nj ; j++) {
         int tile[] = tiles[i][j];
	 int type = tile[TYPE];
	 int state = tile[STATE];
	 int bug = tile[BUG];
         drawTile(g, type, state, i2x(i), j2y(j));
	 switch (type) {
	 case NW: if (bug==BS) bug = BS|BW; else if (bug==BE) bug = BN|BE; break;
	 case NE: if (bug==BS) bug = BS|BE; else if (bug==BW) bug = BN|BW; break;
	 case SW: if (bug==BN) bug = BN|BW; else if (bug==BE) bug = BS|BE; break;
	 case SE: if (bug==BN) bug = BN|BE; else if (bug==BW) bug = BS|BW; break;
	 }
         drawBug(g, bug, i2x(i), j2y(j));
      }

      animating = isRunning;
   }
   int i2x(int i) { return 16 + 32 * i; }
   int j2y(int j) { return 16 + 32 * j; }
   int x2i(int x) { return x / 32; }
   int y2j(int y) { return y / 32; }

   void drawTile(Graphics g, int type, int state, int x, int y) {
      g.setColor(tileColor.darker());
      g.fillRect(x - 13, y - 13, 29, 29);
      g.setColor(tileColor.brighter());
      g.fillRect(x - 15, y - 15, 30, 30);
      g.setColor(tileColor);
      g.fillRect(x - 14, y - 14, 29, 29);

      g.setColor(Color.black);
      switch (type) {
      case NE: g.fillRect(x-5,y+2, 8,3); g.fillRect(x-5,y-3, 3,8); break;
      case NW: g.fillRect(x-3,y+2, 8,3); g.fillRect(x+2,y-3, 3,8); break;
      case SE: g.fillRect(x-5,y-5, 8,3); g.fillRect(x-5,y-5, 3,8); break;
      case SW: g.fillRect(x-3,y-5, 8,3); g.fillRect(x+2,y-5, 3,8); break;
      case AND:
         switch (state) {
         case 0: g.fillRect(x-7,y-1,15,3); break;
         case E: drawTriangle(g,x-7,y-7,x-7,y+7,x+7,y); break;
         case W: drawTriangle(g,x+7,y-7,x+7,y+7,x-7,y); break;
         case P: g.drawLine(x-4,y,x+4,y); break;
         }
         break;
      case OR:
         switch (state) {
         case 0: g.fillRect(x-1,y-7,3,15); break;
         case S: drawTriangle(g,x-7,y-7,x+7,y-7,x,y+7); break;
         case N: drawTriangle(g,x-7,y+7,x+7,y+7,x,y-7); break;
         case P: g.drawLine(x,y-4,x,y+4); break;
         }
         break;
      }
   }

   void drawTriangle(Graphics g, int ax,int ay,int bx,int by,int cx,int cy) {
      int X[] = {ax,bx,cx}, Y[] = {ay,by,cy};
      g.drawPolygon(X,Y,3);
   }

   void drawBug(Graphics g, int bug, int x, int y) {
      switch (bug) {
      case BE|BW|BN:
      case BN:    drawBug(g,x-8,y+9,x+8,y+9,x,y-9); break;
      case BE|BW|BS:
      case BS:    drawBug(g,x-8,y-9,x+8,y-9,x,y+9); break;
      case BN|BS|BE:
      case BE:    drawBug(g,x-9,y-8,x-9,y+8,x+9,y); break;
      case BN|BS|BW:
      case BW:    drawBug(g,x+9,y-8,x+9,y+8,x-9,y); break;
      case BN|BE: drawBug(g,x-10,y-2,x+2,y+10,x+8,y-8); break;
      case BS|BE: drawBug(g,x-10,y+2,x+2,y-10,x+8,y+8); break;
      case BN|BW: drawBug(g,x+10,y-2,x-2,y+10,x-8,y-8); break;
      case BS|BW: drawBug(g,x+10,y+2,x-2,y-10,x-8,y+8); break;
      case BN|BS: drawBug(g,x-8,y-1,x+8,y-1,x,y-10);
                  drawBug(g,x-8,y+1,x+8,y+1,x,y+10); break;
      case BE|BW: drawBug(g,x+1,y-8,x+1,y+8,x+10,y);
                  drawBug(g,x-1,y-8,x-1,y+8,x-10,y); break;
      }
   }

   void xdrawBug(Graphics g, int ax,int ay,int bx,int by,int cx,int cy) {
      int x = (ax+bx+cx) / 3, y = (ay+by+cy) / 3;
      x = x / 32 * 32 + 16;
      y = y / 32 * 32 + 16;
      g.setColor(tileColor.brighter());
      g.fill3DRect(x-12,y-12,24,24,true);
      int X[] = {ax,bx,cx}, Y[] = {ay,by,cy};
      g.setColor(Color.black);
      g.fillPolygon(X,Y,3);
      g.drawPolygon(X,Y,3);
   }

   void drawBug(Graphics g, int ax,int ay,int bx,int by,int cx,int cy) {
      int x = (2*ax+2*bx+cx)/5, y = (2*ay+2*by+cy)/5;
      int X[] = {ax,x,bx,cx}, Y[] = {ay,y,by,cy};

      draw(g, X,Y, 1, 1,Color.black);
      draw(g, X,Y,-1,-1,bugColor.brighter());
      draw(g, X,Y, 0, 0,bugColor);
   }
   void draw(Graphics g, int X[], int Y[], int dx, int dy, Color color) {
      for (int i = 0 ; i < X.length ; i++) {
         X[i] += dx;
	 Y[i] += dy;
      }
      g.setColor(color);
      g.fillPolygon(X,Y,X.length);
      g.drawPolygon(X,Y,Y.length);
      for (int i = 0 ; i < X.length ; i++) {
         X[i] -= dx;
	 Y[i] -= dy;
      }
   }
}