// Name: Anita Blake              Partner: Ted Forrester
// ID:   014916                   ID:      642708
// Sec:  Yan T 2:30               Sec:     Artemov R 1:25

// Date: March 30, 2000
// Project 5: Following the Bouncing Ball

import java.text.DecimalFormat;
import java.awt.*;

// a Molecule moving around in a World
class Molecule {
    double mass;    // mass
    double px, py;  // (px,py) = position of the center
    double vx, vy;  // (vx,vy) = velocity
    double radius;  // radius
    Color color;    // color
    World world;    // World where the molecule "lives"

    // create a molecule of color c, mass m, radius r, world w;
    // place randomly in the world, 
    // with random velocity |vx|, |vy| <= maxv = 5
    Molecule(Color c, int m, int r, World w) {
        double maxv = 5; // maximum magnitude of vx, vy
        mass = m; radius = r; color = c; world = w;
        px = r + Math.random() * (w.size - 2.0 * r);
        py = r + Math.random() * (w.size - 2.0 * r);
        vx = (Math.random() * 2.0 - 1.0) * maxv;
        vy = (Math.random() * 2.0 - 1.0) * maxv;
    }

    // paint the molecule
    void paint(Graphics g) {
        g.setColor(color);
        int left = (int) (px-radius), top = (int) (py-radius);
        int diameter = (int) (2.0*radius);
        g.fillOval(left, top, diameter, diameter);
    }

    // move the molecule one time step
    void move() {
        px += vx * world.step;
        py += vy * world.step;
        double wmin = radius; // topmost, leftmost walls
        double wmax = world.size - radius; // bottommost, rightmost walls
        // bounce off walls
            if (px <= wmin) { px = 2.0 * wmin - px; vx = -vx; }
            else if (px >= wmax) { px = 2.0 * wmax - px; vx = -vx; }
            if (py <= wmin) { 
                py = 2.0 * wmin - py; vy = -vy;
                world.hit += 2.0 * mass * vy;
            }
            else if (py >= wmax) { py = 2.0 * wmax - py; vy = -vy; }
    }

    // check for collision with m and update velocities if so
    void collide(Molecule m) {
        double dt = world.step;             // size of time step
        // variable definitions: named to match write-up
        double x1 = px,     y1 = py;        // center of self
        double x2 = m.px,   y2 = m.py;      // center of m
        double vx1 = vx,    vy1 = vy;       // velocity of self
        double vx2 = m.vx,  vy2 = m.vy;     // velocity of m
        double r = radius,  r2 = m.radius;  // radii of self, m
        double m1 = mass,   m2 = m.mass;    // mass of self, m
        
        // if extents don't overlap, then there is no collision
            if (x1+Math.max(0,vx1)*dt+r < x2+Math.min(0,vx2)*dt-r2
             || x1+Math.min(0,vx1)*dt-r > x2+Math.max(0,vx2)*dt+r2
             || y1+Math.max(0,vy1)*dt+r < y2+Math.min(0,vy2)*dt-r2
             || y1+Math.min(0,vy1)*dt-r > y2+Math.max(0,vy2)*dt+r2
               ) return;
        // ok, (assume) there is a collision:
        // update velocities according to formula from write-up
            double fx = x2 - x1, fy = y2 - y1;
            double s = 2.0 * (fx * (vx2-vx1) + fy * (vy2 - vy1))
                     / ((fx * fx + fy * fy) * (1.0 / m1 + 1.0 / m2));
            vx += (s/m1) * fx; vy += (s/m1) * fy;
            m.vx -= (s/m2) * fx; m.vy -= (s/m2) * fy;
    }

    // return kinetic energy
    double energy() {
        return 0.5 * mass * (vx*vx + vy*vy);
    }
}

// a World is a square container containing Molecules
class World extends Frame {
    double step = 0.5; // the length of time of each time step
    double size;       // width=height of container
    double hit = 0.0;  // cumulative vertical momentum applied to the top wall
    double time = 0.0; // elapsed time from start of simulation
    double P;          // pressure
    double V;          // area (not volume)
    double N;          // number of molecules
    double T;          // temperature measured as average kinetic energy

    Molecule m1,m2,m3; // 3 molecules in the world

    // create a new world of size s
    public World(int s) {
        size = s;
        V = size*size;
        N = 3;
        m1 = new Molecule(Color.red, 1, 4, this);
        m2 = new Molecule(Color.green, 4, 5, this);
        m3 = new Molecule(Color.blue, 16, 7, this);
        show(); // show; apparently needed to getInsets
        setSize(getInsets().left+getInsets().right+(int)size,
               getInsets().top+getInsets().bottom+(int)size);
        setBackground(Color.white);
    }

    // move each Molecule one time step
    public void move() {
        m1.collide(m2); m2.collide(m3); m3.collide(m1);
        m1.move(); m2.move(); m3.move();
        T = (m1.energy() + m2.energy() + m3.energy())/N;
        time += step;
        P = hit / time / size;
    }

    // draw all the Molecules safely
    synchronized public void mypaint(Graphics g) {
        g.translate(getInsets().left,getInsets().top);
        g.clearRect(0,0,(int)size,(int)size); // erase old molecules
        m1.paint(g);
        m2.paint(g);
        m3.paint(g);
    }

    // draw all the Molecules -- call mypaint to be safe:
    // need the "synchronization".
    public void paint(Graphics g) {
        mypaint(g); 
    }
    
    // print statistics: P, T, PV/NT
    void printStats() {
        DecimalFormat fmt = new DecimalFormat("0.0000");
        System.out.println("P=" + fmt.format(P)
                          +" T=" + fmt.format(T)
                          +" PV/NT=" + fmt.format((P*V)/(N*T)));
    }
}

public class Project5 extends Thread {
    public void run() {
        int worldsize = 150; // size of world (the container)
        World w = new World(worldsize);
        while (true) {
            try { sleep(20); } catch (InterruptedException e) { }
            // perform one time step
                w.move();
                w.paint(w.getGraphics());
                w.printStats();
        }
    }
    public static void main(String[] args) {
        new Thread(new Project5()).start();
    }
}