/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                       *
 *   JavaWorld Library, Copyright 2011 Bryan Chadwick                    *
 *                                                                       *
 *   FILE: ./examples/FallingWorld.java                                  *
 *                                                                       *
 *   This file is part of JavaWorld.                                     *
 *                                                                       *
 *   JavaWorld is free software: you can redistribute it and/or          *
 *   modify it under the terms of the GNU General Public License         *
 *   as published by the Free Software Foundation, either version        *
 *   3 of the License, or (at your option) any later version.            *
 *                                                                       *
 *   JavaWorld is distributed in the hope that it will be useful,        *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
 *   GNU General Public License for more details.                        *
 *                                                                       *
 *   You should have received a copy of the GNU General Public License   *
 *   along with JavaWorld.  If not, see <http://www.gnu.org/licenses/>.  *
 *                                                                       *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package examples;

import image.*;
import world.World;

/** Falling Balls Demo in World style.  When the mouse is dragged, balls
 *     are created with a random upward velocity and then fall off the bottom of
 *     the screen */
public class FallingWorld extends World{
    
    public static void main(String[] s){
        new FallingWorld().bigBang();
    }
    
    Random rand = new Random();
    LoB balls;
    
    /** Construct an empty World */
    FallingWorld(){ this(new ELoB()); }
    
    /** Construct a World with the given list-of Balls */
    FallingWorld(LoB lob){
        this.balls = lob;
    }
    
    /** Set the tick rate to 25/second */
    public double tickRate(){ return 0.04; }
        
    /** In order to draw the World, draw the balls into an EmptyScene */
    public Scene onDraw(){
        return this.balls.draw(new EmptyScene(400,400));    
    }
    
    /** When the mouse is dragged, we add a new random Ball */
    public FallingWorld onMouse(int x, int y, String me){
        if(!me.equals("drag"))
            return this;
        else
            return new FallingWorld(new CLoB(this.rand.randBall(x,y), this.balls));
    }
    
    /** On each tick, we apply gravity, move them, and filter out
     *    those off screen */
    public FallingWorld onTick(){
        return new FallingWorld(this.balls.gravity().move().filter());    
    }
}

/** Represents a Ball with a location and velocity */
class Ball{
    int x;  // Horiz. Location
    int y;  // Vertical Location
    int vx; // Horiz. Velocity
    int vy; // Vertical Velocity
    String color;
    
    Ball(int x, int y, int vx, int vy, String color){
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
        this.color = color;
    }

    /** Move this Ball in the direction it's going */
    Ball move(){
        return new Ball(this.x+this.vx, this.y+this.vy, this.vx, this.vy, this.color);
    }

    /** Gravitize (increase Y velocity) of this Ball */
    Ball gravity(){
        return new Ball(this.x, this.y, this.vx, this.vy+3, this.color);    
    }

    /** Draw this Ball into the given Scene */
    Scene draw(Scene scn){
        return scn.placeImage(new Circle(10, "solid", this.color)
                        .overlay(new Circle(10, "outline", "black")),
                            this.x, this.y);
    }
    
    /** Is this Ball off the screen? */
    boolean offscreen(){
        return (this.x > 400 || this.y > 400 ||
                this.x < 0 || this.y < 0);    
    }
}

/** Represents a List of Balls */
abstract class LoB{
    
    /** Move all the balls in this LoB */
    abstract LoB move();
    
    /** Gravitize (increase Y velocity) all the balls in this LoB */
    abstract LoB gravity();
    
    /** Draw all the balls in this LoB into the given Scene */
    abstract Scene draw(Scene scn);

    /** Filter out the offscreen Balls */
    abstract LoB filter();
}

/** Represents an empty List of Balls */
class ELoB extends LoB{

    /** Construct a (the) Empty list of Balls*/
    ELoB(){}
    
    /** Move all the balls in this LoB (None) */
    LoB move(){ return this; }
    
    /** Gravitize (increase Y velocity) all the balls (None) */
    LoB gravity(){ return this; }
    
    /** Draw all the balls in this LoB (None) */
    Scene draw(Scene scn){ return scn; }
    
    /** Filter out the offscreen Balls (None) */
    LoB filter(){ return this; }
}

/** Represents a non-empty List of Balls */
class CLoB extends LoB{

    Ball first;
    LoB rest;

    /** Construct a Cons LoB (with a first and rest) */
    CLoB(Ball first, LoB rest){
        this.first = first;
        this.rest = rest;
    }
 
    /** Move all the balls in this LoB */
    LoB move(){ return new CLoB(this.first.move(), this.rest.move()); }
    
    /** Gravitize (increase Y velocity) all the balls in this LoB */
    LoB gravity(){ return new CLoB(this.first.gravity(), this.rest.gravity()); }
    
    /** Draw all the balls in this LoB into the given Scene */
    Scene draw(Scene scn){ return this.first.draw(this.rest.draw(scn)); }
    
    /** Filter out the offscreen Balls */
    LoB filter(){
        if(this.first.offscreen())return this.rest.filter();
        return new CLoB(this.first, this.rest.filter());
    }
}

/** Class for creating random objects (Colors and Balls) */
class Random{
    /** Random Integer */
    int rand(int i){ return (int)(Math.random()*i); }
    
    /** Color for a given number */
    String colorFor(int i){
        if(i == 0)return "red";
        else if(i == 1)return "blue";
        else if(i == 2)return "green";
        else if(i == 3)return "orange";
        else if(i == 4)return "yellow";
        else if(i == 5)return "purple";
        else return "black";
    }
    
    /** Random Ball at X/Y */
    Ball randBall(int x, int y){
        return new Ball(x,y,rand(11)-5, -10-rand(10), colorFor(rand(6)));    
    }
}