/* An applet to implement J.H.C. Conway's Game of Life   */
/* We work on a rectangular grid with toroidal boundary conditions */

import java.awt.*;

public class LifeApplet extends java.applet.Applet
        implements Runnable
{


final int HORPIXELS = 640;     // Edit the html if you change these
final int VERTPIXELS = 480;
final int CELLSIZE = 10;          // size of side of cells in pixels
final int DEFAULT_SLEEP_INTERVAL = 10;  // millisecs to wait between generations

// states of the program 
final int SETUP = 0;        // setting up initial population
final int RUNNING = 1;     // letting population evolve
final int SUSPENDED = 2;

int rows,cols;
int NumberOfSpots;
int time;
int state;
int sleep_time = DEFAULT_SLEEP_INTERVAL;



int CurrentState[][];     // The current and previous population arrays
int LastState[][];

boolean grid;    // whether or not a grid has been fully drawn
boolean clear;
boolean force_redraw;

Thread myThread;
Panel MenuPanel;
Label sizeReadout;
Label generationReadout;
MyCanvas PlayingField;  // class MyCanvas is defined below. Place where the
                        // populations are drawn.

public void init() 

{       // set up the on screen layout of the app. This is a border layout
        // with a control panel having several buttons and readouts across
        // the top, and a canvas below where each successive generation is
        // drawn.


        setLayout( new BorderLayout());
        PlayingField = new MyCanvas(this);
        MenuPanel = new Panel();
        add("North",MenuPanel);
        add("Center",PlayingField);
        MenuPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        MenuPanel.setBackground(Color.magenta);
        MenuPanel.add(new Button("Start"));
        MenuPanel.add(new Button("Pause"));
        MenuPanel.add(new Button("Reset"));
        MenuPanel.add(new Label("Size: "));
        sizeReadout = new Label("0     ",Label.LEFT);
        MenuPanel.add((Label)sizeReadout);
        MenuPanel.add(new Label("Time: "));
        generationReadout = new Label("1    ",Label.LEFT);
        MenuPanel.add((Label)generationReadout);
        PlayingField.setBackground(Color.blue);

        CurrentState = new int[HORPIXELS/CELLSIZE][VERTPIXELS/CELLSIZE];
        LastState = new int[HORPIXELS/CELLSIZE][VERTPIXELS/CELLSIZE];
        cols = HORPIXELS/CELLSIZE;
        rows = VERTPIXELS/CELLSIZE;
       

        
}


public void start()
{
        // Initialize state of the app

        for(int j=0;j<cols;j++)
                for(int i = 0;i<rows;i++){
                        CurrentState[j][i]=0;
                        LastState[j][i]=0;
                }

        state = SETUP;
        grid = false;
        force_redraw = false;
        clear = true;
        NumberOfSpots = 0;
        time = 1;
        generationReadout.setText(Integer.toString(time));


   if(myThread == null){
        myThread = new Thread(this);
        myThread.start();

   }


  
}

public void stop()
{
   if(myThread != null) {
        myThread.stop();
        myThread = null;
   }
}


// We override handleEvent here in order to make sure we redraw the grid
// whenever anything happens. We really only care about exposing the
// undrawn portion of the grid, but I don't feel like singling out just
// the events that indicate that, so we'll always redraw the grid.

public boolean handleEvent(Event evt)
{
       
        if(evt.id == Event.MOUSE_ENTER){
         grid = false;
         force_redraw=true;
        }
        return super.handleEvent( evt);
}

// Method to handle button clicks, etc

public boolean action(Event evt, Object arg)
{
        if(evt.target instanceof Button){
                if(((Button)evt.target).getLabel().compareTo("Start")==0)
                        state = RUNNING;
                if(((Button)evt.target).getLabel().compareTo("Pause")==0)
                        state = SUSPENDED;
                if(((Button)evt.target).getLabel().compareTo("Reset")==0){
                        stop();
                        start();
                }
        }
        return true;
}

public void run()
{



        while(state == SETUP)
                if(!grid){
                        update(PlayingField);
                        grid = true;
                }
                else ;  // loop while user sets up initial generation


       
        while(state == RUNNING || state == SUSPENDED){
              if(state == RUNNING){
                NewGeneration();
                time++;
                sizeReadout.setText(Integer.toString(NumberOfSpots));
                generationReadout.setText(Integer.toString(time));
                MenuPanel.repaint();
                update(PlayingField );
              }

              // For some reason I don't quite understand, it doesn't work
              // to include the following in the loop. Apparently this is
              // a "critical section" -- if you interrupt by clicking the
              // pause button during a sleep interval, you can never get
              // running again.

                try {myThread.sleep(sleep_time);}
                catch (InterruptedException e) {}
        }
}





public void update(Canvas can)
{

        Graphics g;
        g = can.getGraphics();

        if(clear){

        // clear playing field 
                g.clearRect(0,0,HORPIXELS,VERTPIXELS);
          
                clear = false;
        }

        if(!grid){          // Only redraw grid when grid is set false

        g.setColor(Color.black);  // grid color
        for(int j =0; j<HORPIXELS;j += CELLSIZE)
                g.drawLine(j,0,j,VERTPIXELS);
        for(int j =0; j<VERTPIXELS;j += CELLSIZE)
                g.drawLine(0,j,HORPIXELS,j);
        grid = true;
        }

        g.setColor(Color.red);

        // draw spots in occupied cells, but only update when something
        // has changed to avoid "flicker"
     
       if(! force_redraw){
        for(int j =0; j<HORPIXELS/CELLSIZE; j++){
           for(int i =0; i<VERTPIXELS/CELLSIZE; i++){
                      if(CurrentState[j][i] != LastState[j][i]){
                           if(CurrentState[j][i]==1)
                              g.setColor(Color.red);
                           else
                              g.setColor(Color.blue);
                           g.fillOval(j*CELLSIZE+1,i*CELLSIZE+1,CELLSIZE-2,CELLSIZE-2);
                     }      

           }
        }
       }
       else {
        for(int j =0; j<HORPIXELS/CELLSIZE; j++){
           for(int i =0; i<VERTPIXELS/CELLSIZE; i++){
             if(CurrentState[j][i] != LastState[j][i] || CurrentState[j][i]==1){
                           if(CurrentState[j][i]==1)
                              g.setColor(Color.red);
                           else
                              g.setColor(Color.blue);
                           g.fillOval(j*CELLSIZE+1,i*CELLSIZE+1,CELLSIZE-2,CELLSIZE-2);
            }             

           }
        }
        force_redraw = false;
       }
        
}


// Add or remove spot in initial generation in response to user mouse
// click, then repaint the screen

public void toggleSpot(int x, int y)
{

        int col,row;

        // see which row and column the spot is in
        col = x/CELLSIZE;
        row = y/CELLSIZE;

        if(CurrentState[col][row] == 1){
          LastState[col][row]=1;
          CurrentState[col][row] = 0;
          NumberOfSpots--;
        }
        else {
          LastState[col][row] = 0;
          CurrentState[col][row] = 1;
          NumberOfSpots++;
        }

        // update control panel
        sizeReadout.setText(Integer.toString(NumberOfSpots));
        MenuPanel.repaint();

        // update population display
        update(PlayingField);
        

}

// The following routine is the heart of the application. It updates the
// current generation according to the rules of life.

public void NewGeneration()
{

        int i,j;
        int neighbors;

        // copy Current State to Last State
        for(j=0;j<cols;j++)
                for(i=0;i<rows;i++)
                        LastState[j][i]=CurrentState[j][i];

        // Loop over full array and create the new generation according
        // to the rules

        NumberOfSpots = 0;

        for(j=0;j<cols;j++)
                for(i=0;i<rows;i++){
                        neighbors = LastState[j][North(i)] +
                                    LastState[East(j)][North(i)] +
                                    LastState[East(j)][i] +
                                    LastState[East(j)][South(i)] +
                                    LastState[j][South(i)] +
                                    LastState[West(j)][South(i)] +
                                    LastState[West(j)][i] +
                                    LastState[West(j)][North(i)];

                        switch(neighbors) {

                                case 3:
                                        CurrentState[j][i] = 1;
                                        NumberOfSpots++;
                                        break;
                                case 2:
                                        if(LastState[j][i] == 1){
                                          CurrentState[j][i] = 1;
                                          NumberOfSpots++;
                                        }
                                        break;

                                default:
                                        CurrentState[j][i] = 0;
                        }
                }
}


// The following routines are needed to handle the toroidal boundary
// conditions

public int North(int i)
{
        if(i>0) return i-1;
        else return rows-1;
}

public int South(int i)
{
        if(i<rows-1) return i+1;
        else return 0;
}

public int West(int i)
{
        if(i>0) return i-1;
        else return cols-1;
}

public int East(int i)
{
        if(i<cols-1)return i+1;
        else return 0;
}



}

// We subclass canvas to allow us to respond to mouse clicks during the
// setup phase of the program.  The API docs say that Canvas must be
// subclassed in order to add any interesting functionality. Apparently,
// it's true.


class MyCanvas extends Canvas {


LifeApplet parent;               // link to surrounding applet

MyCanvas(LifeApplet target){

        this.parent = target;
}

public boolean mouseDown(Event evt, int x, int y) 
{
        
                 if(parent.state == parent.SETUP)   // only respond to mouse during setup
                         parent.toggleSpot(x,y);
                 return true;

}


}

