/*
 * SwarmApplet.java
 *
 * (C) 2005, Alex S.
 */

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.util.*;

class Ant {

    public float x;
    public float y;
    public float speed;
    public float direction;

    public int mode;
    public static final int SEEKING = 0;
    public static final int CARRYING = 1;

    public float pheromone;

    public Ant(float x,float y,float speed,float direction,int mode,float pheromone){
        this.x = x;
        this.y = y;
        this.speed = speed;
        this.direction = direction;
        this.mode = mode;
        this.pheromone = pheromone;
    }

    public int getX(){
        return (int)Math.floor(x);
    }
    public int getY(){
        return (int)Math.floor(y);
    }

}

class StateProps {
    public float value;

    public int type;
    public static final int NEUTRAL = 0;
    public static final int GIVING = 1;
    public static final int TAKING = 2;
    
    public StateProps(float v){
        value = v;
        type = NEUTRAL;
    }
}

class World {
        
    public int width,height;
    public StateProps[] states;
    public Vector ants;

    public World(int w,int h){
        width = w;
        height = h;
        states = new StateProps[w * h];
        for(int i=0;i<states.length;i++)
            states[i] = new StateProps((float)0.5);
        ants = new Vector();
    }

    public StateProps getVal(int x,int y){
        return states[((y+height)%height) * width + ((x+width)%width)];
    }

    public float findmaxdir(int x,int y,float dir){
        int[] xs = {1,1,0,-1,-1,-1, 0, 1};
        int[] ys = {0,1,1, 1, 0,-1,-1,-1};

        StateProps[] vals = new StateProps[xs.length];
        for(int i=0;i<vals.length;i++){
            vals[i] = this.getVal(x + xs[i],y + ys[i]);
        }
        int max = 0;
        int allsame = 1;
        StateProps lastval = vals[max];
        for(int i=1;i<vals.length;i++){
            if(vals[max].value < vals[i].value){
                max = i;
            }
            if(lastval.value != vals[i].value){
                allsame = 0;
            }
        }
        if(allsame == 0){
            return (float)Math.atan2(ys[max],xs[max]);
        }
        return dir;
    }

    public float findmindir(int x,int y,float dir){
        int[] xs = {1,1,0,-1,-1,-1, 0, 1};
        int[] ys = {0,1,1, 1, 0,-1,-1,-1};

        StateProps[] vals = new StateProps[xs.length];
        for(int i=0;i<vals.length;i++){
            vals[i] = this.getVal(x + xs[i],y + ys[i]);
        }
        int min = 0;
        int allsame = 1;
        StateProps lastval = vals[min];
        for(int i=1;i<vals.length;i++){
            if(vals[min].value > vals[i].value){
                min = i;
            }
            if(lastval.value != vals[i].value){
                allsame = 0;
            }
        }
        if(allsame == 0){
            return (float)Math.atan2(ys[min],xs[min]);
        }
        return dir;
    }


    public void moveants(){

        Enumeration enum = ants.elements();
        while(enum.hasMoreElements()){
            Ant ant = (Ant)enum.nextElement();

            StateProps box = getVal(ant.getX(),ant.getY());

            // adjust ant's pheromone (via learning rule; goes down to 0.5)
            ant.pheromone = (float)(ant.pheromone + 0.01 * (0.5 - ant.pheromone));

            if(box.type == box.GIVING){
                ant.mode = Ant.CARRYING;
                ant.pheromone = (float)1.0;
            }else if(box.type == box.TAKING){
                ant.mode = Ant.SEEKING;
                ant.pheromone = (float)0.0;
            }else{
                // adjust current box's value (via a learning rule on this ant's value)
                box.value = (float)(box.value + 0.01 * (ant.pheromone - box.value));
            }

            if(ant.mode == Ant.CARRYING){
                ant.direction = findmindir((int)ant.x,(int)ant.y,ant.direction);
            }else if(ant.mode == Ant.SEEKING){
                ant.direction = findmaxdir((int)ant.x,(int)ant.y,ant.direction);
            }

            float sin = (float)Math.sin(ant.direction);
            float cos = (float)Math.cos(ant.direction);
            float nx = ant.x + cos * ant.speed;
            float ny = ant.y + sin * ant.speed;

            
            if(nx > width-1){
                nx -= width;
                //cos = -cos;
            }
            if(nx < 0){
                nx += width;
                //cos = -cos;
            }
            if(ny > height-1){
                ny -= height;
                //sin = -sin;
            }
            if(ny < 0){
                ny += height;
                //sin = -sin;
            }
            
            //ant.direction = (float)Math.atan2(sin,cos);
            ant.x = nx;
            ant.y = ny;
        }
    }
}

/**
 * BufferedCanvas 
 */
class BufferedPixCanvas extends Canvas {

    private Applet parent = null;
    private Image image;
    private Graphics buffer = null;
    private Thread t;
    private Rectangle r = new Rectangle(0, 0, 0, 0);

    private World world;    

    public boolean renderAnts = true;
    public boolean renderSeeking = true;
    public boolean renderCarrying = true;

    public BufferedPixCanvas(Applet p){
        parent = p;
    }

    public void update(Graphics g) {
        if (r.width != bounds().width || r.height != bounds().height) {
            image = parent.createImage(bounds().width, bounds().height);
            buffer = image.getGraphics();
            r = bounds();
            init();
        }
        world.moveants();
        render(buffer);
        if (image != null){
            g.drawImage(image,0,0,this);
        }
    }

    // initialize swarm, and food sources (food destinations).
    public void init(){

        world = new World(r.width / 4,r.height / 4);
        
        world.getVal((int)(world.width * 1.5/4.0),
                     (int)(world.height * 2.5/4.0)).type = StateProps.GIVING;
        world.getVal((int)(world.width * 1.5/4.0),
                     (int)(world.height * 2.5/4.0)).value = (float)1.0;

        world.getVal((int)(world.width * 2.5/4.0),
                     (int)(world.height * 1.5/4.0)).type = StateProps.TAKING;
        world.getVal((int)(world.width * 2.5/4.0),
                     (int)(world.height * 1.5/4.0)).value = (float)0.0;

        for(int i=0;i<1000;i++){
            if(Math.random() > 0.5){
                world.ants.addElement(
                    new Ant(
                        (float)(Math.random()*world.width),
                        (float)(Math.random()*world.height),
                        (float)1,
                        (float)(Math.random()*Math.PI*2),
                        Ant.CARRYING,
                        (float)1.0
                    )
                );
            }else{
                world.ants.addElement(
                    new Ant(
                        (float)(Math.random()*world.width),
                        (float)(Math.random()*world.height),
                        (float)1,
                        (float)(Math.random()*Math.PI*2),
                        Ant.SEEKING,
                        (float)0.0
                    )
                );
            }
        }
    }

    /**
     * does the rendering (except for background)
     */
    public void render(Graphics g){
        
        float xr = r.width / world.width;
        float yr = r.height / world.height;

        for(int y=0;y<world.height;y++){
            for(int x=0;x<world.width;x++){
                int tx = (int)(x * xr);
                int ty = (int)(y * yr);
                StateProps box = world.getVal(x,y);
                g.setColor(
                    new Color(
                        (int)(box.value * 0xFF),
                        (int)(box.value * 0xFF),
                        (int)(box.value * 0xFF)
                    )
                );
                g.fillRect(tx,ty,(int)xr,(int)yr);
            }
        }

        if(renderAnts){
            Enumeration enum = world.ants.elements();
            while(enum.hasMoreElements()){
                Ant ant = (Ant)enum.nextElement();
                int tx = (int)(ant.x * xr);
                int ty = (int)(ant.y * yr);

                if((ant.mode == Ant.SEEKING && renderSeeking) || 
                   (ant.mode == Ant.CARRYING && renderCarrying) ){
                    g.setColor(
                        new Color(
                            (int)(ant.pheromone * 0xFF),
                            (int)(ant.pheromone * 0xFF),
                            (int)(ant.pheromone * 0xFF)
                        )
                    );
                    g.fillRect(tx,ty,(int)xr,(int)yr);
                    g.setColor(Color.yellow);
                    g.drawRect(tx,ty,(int)xr,(int)yr);
                }


            }
        }

        g.setColor(Color.darkGray);
        g.drawRect(0,0,r.width-1,r.height-1);
    }


    public boolean keyUp(Event evt,int key){
        if(key == ' '){
            renderAnts = !renderAnts;
            return true;
        }else if(key == 's'){
            renderSeeking = !renderSeeking;
        }else if(key == 'c'){
            renderCarrying = !renderCarrying;
        }
        return false;
    }

    int displaycnt = 3;

    public boolean mouseUp(Event evt,int x,int y){
        displaycnt = (displaycnt + 1) & 3;        
        renderSeeking = (displaycnt & 2) != 0;
        renderCarrying = (displaycnt & 1) != 0;
        return true;
    }

}


/**
 * very basic swarm applet
 */
public class SwarmApplet extends java.applet.Applet implements Runnable {
    Thread t = null;
    BufferedPixCanvas bc = null;    

    public void init(){
        setLayout(new BorderLayout());
        bc = new BufferedPixCanvas(this);

        /*
        Panel p  = new Panel();
        p.add(new Button("Hello World!"));
        p.add(new Button("Hello World!"));
        */

        add("Center",bc);
        //add("South",p);
        show();
    }    

    public void start() { 
        if (t == null) {
            t = new Thread(this); 
            t.start(); 
        }
    }
    public void stop() { 
        if (t != null) {
            t.stop(); 
            t = null; 
        }
    }

    public void run() { 
        try {
            while (true) {
                bc.repaint();
                repaint();
                t.sleep(30);
            } 
        } catch (InterruptedException e) {
        }
    }

}



