import processing.core.*; 
import processing.xml.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class PongQuest extends PApplet {

/** Pong Quest
*   File for setting initial system and global variables and
*   polling the keyboard.
*
*   @author: Caffeine (a.k.a. L. Scotty Hoag)
*/

/** Sets initial system variables. */
public void setup() {
    size(800, 600);
    frameRate(60);
    noStroke();
    noSmooth();
    //smooth();
    game = new Main();    
    
    leftArrowKeyDown = rightArrowKeyDown = shiftKeyDown = 
        upArrowKeyDown = downArrowKeyDown = false;
    aKeyDown = sKeyDown = dKeyDown = wKeyDown = pKeyDown = 
        spaceKeyDown = false;        
}

/** Main game loop. */
public void draw() {
    game.update();
}

/** Polls keys that have been pressed. */
public void keyPressed() {
    
    switch(key) {
    case CODED: 
        switch(keyCode) {
        case UP:
            upArrowKeyDown = true;
            break;
        case DOWN:
            downArrowKeyDown = true;
            break;
        case LEFT:
            leftArrowKeyDown = true;
            break;
        case RIGHT:
            rightArrowKeyDown = true;
            break;
        case SHIFT:
            shiftKeyDown = true;
            break;
        }
        break;
        
    case 'a':
    case 'A':
        aKeyDown = true;
        break;
    case 's':
    case 'S':
        sKeyDown = true;
        break;
    case 'd':
    case 'D':
        dKeyDown = true;
        break;
    case 'w':
    case 'W':
        wKeyDown = true;
        break;
    case 'p':
    case 'P':
        pKeyDown = true;
        break;
    case 'j':
    case 'J':
        jKeyDown = true;
        break;
    case 'k':
    case 'K':
        kKeyDown = true;
        break;
    case 'l':
    case 'L':
        lKeyDown = true;
        break;
    case 'i':
    case 'I':
        iKeyDown = true;
        break;
    case ' ':
        spaceKeyDown = true;
        game.handleKeys(key);
        break;
    }

}

/** Polls keys that have been released. */
public void keyReleased() {
    
    switch(key) {
    case CODED:
        switch(keyCode) {
        case UP:
            upArrowKeyDown = false;
            break;
        case DOWN:
            downArrowKeyDown = false;
            break;
        case LEFT:
            leftArrowKeyDown = false;
            break;
        case RIGHT:
            rightArrowKeyDown = false;
            break;
        case SHIFT:
            shiftKeyDown = false;
            break;
        }
        break;
        
    case 'a':
    case 'A':
        aKeyDown = false;
        break;
    case 's':
    case 'S':
        sKeyDown = false;
        break;
    case 'd':
    case 'D':
        dKeyDown = false;
        break;
    case 'w':
    case 'W':
        wKeyDown = false;
        break;
    case 'p':
    case 'P':
        pKeyDown = false;
        break;
    case 'j':
    case 'J':
        jKeyDown = false;
        break;
    case 'k':
    case 'K':
        kKeyDown = false;
        break;
    case 'l':
    case 'L':
        lKeyDown = false;
        break;
    case 'i':
    case 'I':
        iKeyDown = false;
        break;
    case ' ':
        spaceKeyDown = false;
        break;
    }
}


/* Globals */
Main game;
boolean upArrowKeyDown, downArrowKeyDown, spaceKeyDown; 
boolean leftArrowKeyDown, rightArrowKeyDown, shiftKeyDown;
boolean aKeyDown, sKeyDown, dKeyDown, wKeyDown, pKeyDown;
boolean jKeyDown, kKeyDown, lKeyDown, iKeyDown;

/** Graphics */
PFont font;
PImage storyScreen;
PImage goodEnding;
PImage badEnding; 
PImage poster_a;
PImage poster_s;
PImage poster_w; 
PImage poster_d; 
PImage poster_j; 
PImage poster_k; 
PImage poster_i; 
PImage poster_l; 
PImage poster_shift_left; 
PImage poster_shift_right; 
PImage poster_space_left; 
PImage poster_space_right; 
PImage poster_reset; 
PImage arrow_down; 
PImage arrow_left; 
PImage arrow_up; 
PImage arrow_right;
PImage poster_ball;
PImage poster_paddle_left;
PImage poster_paddle_right;
PImage poster_or;
PImage poster_up_arrow_key;
PImage poster_down_arrow_key;
PImage poster_left_arrow_key;
PImage poster_right_arrow_key;

/* Direction Constants */
static final int SOUTH = 0;
static final int WEST = 1;
static final int NORTH = 2;
static final int EAST = 3;

/* Object IDs */
static final int EMPTY = 0;
static final int BALL = 1;
static final int PADDLE = 10;
static final int HPADDLE = 11;
static final int VPADDLE = 12;
static final int DOOR = 20;
static final int FLOOR_PANEL_H = 30;
static final int FLOOR_PANEL_V = 31;

static final int POSTER_A = 100;
static final int POSTER_S = 101;
static final int POSTER_W = 102;
static final int POSTER_D = 103;
static final int POSTER_J = 104;
static final int POSTER_K = 105;
static final int POSTER_I = 106;
static final int POSTER_L = 107;
static final int POSTER_SHIFT_LEFT = 108;
static final int POSTER_SHIFT_RIGHT = 109;
static final int POSTER_SPACE_LEFT = 110;
static final int POSTER_SPACE_RIGHT = 111;
static final int POSTER_RESET = 112;
static final int ARROW_DOWN = 113;
static final int ARROW_LEFT = 114;
static final int ARROW_UP = 115;
static final int ARROW_RIGHT = 116;
static final int POSTER_BALL = 117;
static final int POSTER_PADDLE_LEFT = 118;
static final int POSTER_PADDLE_RIGHT = 119;
static final int POSTER_OR = 120;
static final int POSTER_UP_ARROW_KEY = 121;
static final int POSTER_DOWN_ARROW_KEY = 122;
static final int POSTER_LEFT_ARROW_KEY = 123;
static final int POSTER_RIGHT_ARROW_KEY = 124;



/** The under-appreciated Ball from Pong, now known more appropriatly
*   as General Ball.
*
*   @Author: Caffeine (L. Scotty Hoag)
*/

public class Ball extends BaseObject {
    
    /** Creates a new Ball. */
    public Ball(int x, int y, int w, int h, int o, Main game, 
        float bounciness) 
    {
        super(x, y, w, h, o, game);
        name = "ball";
        objectID = BALL;
        
        isMovingObject = true;
        maxXVel = 5;
        maxYVel = 5;
        xAccel = 0.3f;
        yAccel = 0.3f;
        xDecel = 0.1f;
        yDecel = 0.1f;
        this.bounciness = bounciness;
    }
    
    /** Updates THIS Ball's logic. */
    public void update(double t) {
        if (isDead) {
            return;
        }
        
        handleKeys();
        updateGravity();
        capMaxVel();
        moveMe();
        checkCollision();
        
        if ((y > (height + 20)) || 
            (x > (width * 1.5f)) || 
            (x < (width * -0.5f)) ||
            (y < (height * -2))) 
        {
            println("X = " + this.x + ", Y = " + this.y);
            die();
        }
    }
    
    public void handleCollision(BaseObject obj, int direction) {
        switch(obj.getObjectID()) {
        case PADDLE:
        case HPADDLE:
        case FLOOR_PANEL_H:
            this.hBounce(obj, direction);
            break;
        case VPADDLE:
        case FLOOR_PANEL_V:
            this.vBounce(obj, direction);
            break;
        case DOOR:
            game.aWinnerIsYou();
            break;
        }
    }
    
    /** Handles all of the keyboard keys for movement and action. 
    *   a, A: Move left.
    *   d, D: Move right. 
    *   shift: Bounce higher. */
    private void handleKeys() {
        if (aKeyDown) {
            xVel += -(xAccel);
        } else if (xVel < 0) {
            xVel += xDecel;
        }
        
        if (dKeyDown) {
            xVel += xAccel;
        } else if (xVel > 0) {
            xVel += -(xDecel);
        }
        
        /*
        if (sKeyDown) {
            yVel += -(yAccel);
        } else if (xVel > 0) {
            yVel += yDecel;
        }
        
        
        if (wKeyDown) {
            yVel += yAccel;
        } else if (xVel > 0) {
            yVel += -(yDecel);
        }*/
        
        
        if (wKeyDown || shiftKeyDown) {
            bounceMultiplier = 100;
            maxXVel = maxYVel = 6.5f;
        } else {
            bounceMultiplier = 1;
            maxXVel = maxYVel = 5;
        }
    }
    
    /** Handles a bounce when the ball is traveling in the 
    *   stated direction towards a horizontal paddle. */
    private void hBounce(BaseObject obj, int direction) {
        //Test here later for short-edge side collisions with
        //HPaddles and H FloorPanels...
        switch(direction) {
        case SOUTH:
            this.y = obj.getY() - this.h;
            yVel *= -(bounceMultiplier * bounciness);
            break;
        case NORTH:
            this.y = obj.getY() + obj.getH();
            yVel *= -(bounceMultiplier * bounciness);
            break;
        case WEST:
            this.x = obj.getX() + obj.getW();
            xVel = -(bounceMultiplier * bounciness);
            break;
        case EAST:
            this.x = obj.getX() - this.w;
            xVel = -(bounceMultiplier * bounciness);
            break;
        }
    }
    
    /** Handles a bounce when the ball is traveling in the 
    *   stated direction towards a vertical paddle. */
    private void vBounce(BaseObject obj, int direction) {
        //TODO: Move this into the collision detection method...
        // Currently, hitting the bottom short edge of a vertical
        // wall produces no bounce. Adding the function really
        // screws with stuff...
        /*
        if (this.y < obj.getY()) {
            direction = SOUTH;
        } else if ((this.y + (this.h * 0.5)) > (obj.getY() + obj.getH())) {
            direction = NORTH;
        }
        */
        
        switch(direction) {
        case WEST:
            this.x = obj.getX() + obj.getW();
            xVel *= -(bounceMultiplier * bounciness);
            if (obj.getObjectID() == VPADDLE) {
                yVel = -1 * abs(yVel * (bounceMultiplier * bounciness));
            }
            break;
        case EAST:
            this.x = obj.getX() - this.w;
            xVel *= -(bounceMultiplier * bounciness);
            if (obj.getObjectID() == VPADDLE) {
                yVel = -1 * abs(yVel * (bounceMultiplier * bounciness));
            }
            break;
        case SOUTH:
            this.y = obj.getY() - this.h;
            yVel = -1 * abs(yVel * (bounceMultiplier * bounciness));
            break;
        case NORTH:
            this.y = obj.getY() + obj.getH();
            if (obj.getObjectID() == VPADDLE) {
               yVel *= -(bounceMultiplier * bounciness);
            }
            //yVel = abs(yVel * (bounceMultiplier * bounciness));
            break;
        }
    }
    
    public void die() {
        super.die();
        game.playerHasDied();
    }
    
    public void draw() {
        if (!isDead) {
            setColor();
            rect(x, y, w, h);
        }
    }
    
    int bounceMultiplier;
    float bounciness;
}






/** The basic class that all game objects inherit from.
*
*   @author: Caffeine (a.k.a. L. Scotty Hoag)
*/
public class BaseObject {
    public BaseObject(float x, float y,  int w, int h, int o, Main game) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.minSpeed = 0.2f;
        this.orientation = o;
        this.game = game;
        this.objectID = 0;
        this.isDead = false;
        this.isMovingObject = false;
        this.name = "baseobject";
        this.img = null;
        this.myColor = 255;
        
        lastX = x;
        lastY = Y;
        xVel = yVel = xAccel = yAccel = xDecel = yDecel = 
            maxXVel = maxYVel = 0;
    }
    
    /** Updates the logic for this object. */
    public void update(double t) {
    }
    
    /** Draws this object on the screen. */
    public void draw() {
        if (img != null) {
            setColor();
            image(img, x, y);
        }
    }
    
    /** Sets the color of this object. */
    public void setColor() {
        fill(this.myColor);
    }
    
    public void die() {
        isDead = true;
        xVel = yVel = 0.0f;
    }
    
    /** Checks to see if THIS has collided with anything. */
    public void checkCollision() {
        BaseObject[] world = game.getWorld();
        int objectsInGame = game.getObjectsInGame();
        int direction;

        for (int i = 0; i < objectsInGame; i++) {
            if (isDead) {
                return;
            }
            direction = this.collide(world[i]);
            if(direction > -1) {
                this.handleCollision(world[i], direction);
            }
        }
    }
    
    /** Checks if THIS object has collided with OBJ. Returns -1 if no
    *   collision has occured, or an int representing the direction 
    *   that this object was traveling at collision time: 0 (SOUTH) for 
    *   a collision from above, 1 (WEST) if from the right, 2 (SOUTH) 
    *   if from below, and 3 (EAST) if from the left. */
    public int collide(BaseObject obj) {      
        if (!isMovingObject || (obj == this)) {
            return -1;
        }
        
        if (!((this.getObjectID() == BALL) && 
            ((obj.getObjectID() == VPADDLE) || 
                (obj.getObjectID() == FLOOR_PANEL_V)))) 
        {
            if ((((obj.getX() < this.x) && (this.x < (obj.getX() + obj.getW()))) || //Test X
                ((obj.getX() < (this.x + this.w)) && ((this.x + this.w) < (obj.getX() + obj.getW())))
                ))
            {
                if (((obj.getY() < (this.y + this.h)) && ((this.y + this.h) < (obj.getY() + obj.getH()))) ||
                    ((this.lastY < obj.getY()) && (obj.getY() < this.y)))
                {
                    return SOUTH;
                }
                
                if (((obj.getY() < this.y) && (this.y < (obj.getY() + obj.getH()))) ||
                    ((this.y < obj.getY()) && (obj.getY() < this.lastY)))
                {
                    return NORTH;
                }
            }
            
        }
        
        if ((((obj.getY() < this.y) && (this.y < (obj.getY() + obj.getH()))) || //Test X
            ((obj.getY() < (this.y + this.h)) && ((this.y + this.h) < (obj.getY() + obj.getH())))
            ))
        {
            if (((obj.getX() < (this.x + this.w)) && ((this.x + this.w) < (obj.getX() + obj.getW()))) ||
                ((this.lastX < obj.getX()) && (obj.getX() < this.x)))
            {
                return EAST;
            }
            
            if (((obj.getX() < this.x) && (this.x < (obj.getX() + obj.getW()))) ||
                ((this.x < obj.getX()) && (obj.getX() < this.lastX)))
            {
                return WEST;
            }
        }
        
        return -1;
    }

    /** Handles the collision between THIS and OBJ. */
    public void handleCollision(BaseObject obj, int direction) {
    }
    
    /** Applies the laws of gravity to this object. */
    public void updateGravity() {
        yVel += game.getGravityForce();
    }
    
    /** Moves this object based on its x and y velocities. */
    public void moveMe() {
        lastX = x;
        lastY = y;
        x += xVel;
        y += yVel;
        
        if ((-(minSpeed) < xVel) && (xVel < (minSpeed))) {
            xVel = 0.0f;
        }
        
        if ((-(minSpeed) < yVel) && (yVel < (minSpeed))) {
            yVel = 0.0f;
        }
    }
    
    /** Returns the orientation of this object. */
    public int getOrientation() {
        return orientation;
    }
    
    /** Returns this object's x coordinate. */
    public float getX() {
        return x;
    }
    
    /** Returns this object's y coordinate. */
    public float getY() {
        return y;
    }
    
    public float getLastX() {
        return lastX;
    }
    
    public float getLastY() {
        return lastY;
    }
    
    /** Returns this object's x coordinate. */
    public float getW() {
        return w;
    }
    
    /** Returns this object's y coordinate. */
    public float getH() {
        return h;
    }    
    
    /** Returns the integer identifier of this object. */
    public int getObjectID() {
        return objectID;
    }
    
    /** True iff this object has been destroyed. */
    public boolean isDead() {
        return isDead;
    }
    
    /** Caps the velocity if it exceeds the max speed for THIS object. */
    public void capMaxVel() {
        if (abs(xVel) >= maxXVel) {
            if (xVel > 0) {
                xVel = maxXVel;
            } else {
                xVel = -(maxXVel);
            }
        }
        
        if (abs(yVel) >= maxYVel) {
            if (yVel > 0) {
                yVel = maxYVel;
            } else {
                yVel = -(maxYVel);
            }
        }
    }
    
    /** Returns the name of this object. */
    public String name() {
        return name;
    }
    
    int w, h, orientation, objectID;
    int myColor;
    float x, y, lastX, lastY;
    float xVel, yVel, xAccel, yAccel, xDecel, yDecel, maxXVel, maxYVel;
    float minSpeed;
    boolean isMovingObject, isDead;
    String name;
    Main game;
    PImage img;
}






/** Touching this sends the player to a new area. */
public class Door extends BaseObject {
    
    /** Creates a new door. */
    Door(float x, float y,  int w, int h, int o, Main game) {
        super(x, y, w, h, o, game);
        this.name = "door";
        this.objectID = 20;
        img = loadImage("/data/graphics/door/CastleDoor.png");
    }
}





/** A stationary paddle. */
public class FloorPanel extends Paddle {
    
    /** A new FloorPanel. */
    public FloorPanel(int x, int y, int w, int h, int o, Main game) {
        super(x, y, w, h, o, game);
        name = "floorpanel";
        objectID = o;

        isMovingObject = false;
        maxXVel = 0.0f;
        maxYVel = 0.0f;
        xAccel = 0.0f;
        yAccel = 0.0f;
        xDecel = 0.0f;
        yDecel = 0.0f;
        minSpeed = 0.0f;
        myColor = 0xff767676;
    }
    
    /** Updates THIS FloorPanel's logic. FloorPanels don't move, so
    *   this doesn't really do anything. */
    public void update(double t) {
    }
}






/** A Paddle that is aligned and can move horizontally. */
public class HPaddle extends Paddle {
    
    /** A new HPaddle. */
    public HPaddle(int x, int y, int w, int h, int o, Main game) {
        super(x, y, w, h, o, game);
        name = "hpaddle";
        objectID = HPADDLE;

        
        isMovingObject = true;
        maxXVel = 5;//8;
        maxYVel = 5;//8;
        xAccel = 0.3f;//5;
        yAccel = 0.3f;//5;
        xDecel = 0.1f;//3;
        yDecel = 0.1f;//3;
        this.minSpeed = 0.3f;        
    }
    
    /** Handles all of the keyboard keys for movement and action. 
    *   Left Arrow: Move left.
    *   Right Arrow: Move right. */
    protected void handleKeys() {
        handleLeftArrowKey();
        handleRightArrowKey();
    }
}






/** The game engine for Pong Quest.
*
*   @Author: Caffeine (L. Scotty Hoag)
*/

/*
TODO:
    Switch over to frameCount.
*/

public class Main {
    
    /** Creates a new game. */
    public Main() {
        font = loadFont("data/text/ArcadeN-48.vlw");
        levelLoader = new MapMaker();
        gameHasStarted = false;
        cutScene = true;
        currentLevel = 0;
        gravityForce = 0.2f;
        gameOver = false;
        timeOnStoryScreen = 0;
        backgroundColor = 0;
        dBox_W1 = 20;
        dBox_W2 = (height / 3);
        dBox_W3 = width - 40;
        dBox_W4 = 2 * (height / 3) - (height/3);
        dBox_B1 = 30;
        dBox_B2 = (height / 3) + 10;
        dBox_B3 = width - 60;
        dBox_B4 = 2 * (height / 3) - (height/3) - 20;
        
        loadImages();        
    }
    
    /** Traverses the game logic to update gameplay or show a 
    *   system screen. */
    public void update() {
        if (gameHasStarted) {
            if (!cutScene) {
                if (!gameOver) {
                    if (aWinnerIsYou) {
                        ++currentLevel;
                        resetWorld();
                    } else {
                        updateAndDrawGameObjects();
                    }
                } else {
                    showEndingScreen();
                }                    
                
            } else {
                showStoryScreen();
            }
        } else {
            showTitleScreen();
        }
    }

    /** Updates all of the game objects and then draws them.*/
    private void updateAndDrawGameObjects() {
        int time = frameCount;
        for (int i = 0; i < objectsInGame; i++) {
            world[i].update(time);
        }
        
        background(backgroundColor);
        for (int i = 0; i < backgroundObjectsInGame; i++) {
            backgroundObjects[i].draw();
        }
        for (int i = 0; i < objectsInGame; i++) {
            world[i].draw();
        } 
        
        if (playerIsDead) {
            fill(255);
            rect(dBox_W1, dBox_W2, dBox_W3, dBox_W4);
            fill(0);
            rect(dBox_B1, dBox_B2, dBox_B3, dBox_B4);  
            fill(0xffD60202);
            textFont(font, 24);
            textAlign(CENTER, CENTER);
            text("Doh'!\nBrave Sir Ball has fallen." + 
                "\n\nPress SPACE to return\nto the battlefield.",
                0, 0, width, height);
        }
        //printScore();
    }
    
    /** Resets the game world after each level. */
    private void resetWorld() {
        world = null;
        world = new BaseObject[MAX_NUM_OF_LEVEL_OBJECTS];
        backgroundObjects = new BaseObject[MAX_NUM_OF_LEVEL_OBJECTS];
        objectsInGame = 0;
        backgroundObjectsInGame = 0;
        aWinnerIsYou = false;
        playerIsDead = false;
        aWinnerIsYou = !(loadMap());
        //points = 0;
    }
    
    /** Shows either the good or bad ending based on whether the player
    *   has won or not. */
    private void showEndingScreen() {
        if (aWinnerIsYou) {
            image(goodEnding, 0, 0);
        } else {
            image(badEnding, 0, 0);
        }
    }
    
    /** Shows a brief non-interactive story scene. */
    private void showStoryScreen() {
        background(backgroundColor);
        image(storyScreen, 0, 0);
        if (timeOnStoryScreen == 0) {
            timeOnStoryScreen = millis();
        } else if (millis() > (timeOnStoryScreen + CUTSCENE_TIME)) {
            fill(255, 255, 255);
            textFont(font, 24);
            textAlign(CENTER, BOTTOM);
            text("Press 'SPACE' to Continue", 0, 0, width, height - 5);
        }
    }
    
    /** Shows the title screen. */
    private void showTitleScreen() {
        background(backgroundColor);
        fill(255, 255, 255);
        textFont(font, 48);
        textAlign(CENTER, CENTER);
        text("PONG\nQuest", 0, 0, width, height);
        textFont(font, 24);
        textAlign(CENTER, BOTTOM);
        text("Press 'SPACE' to Start", 0, 0, width, height - 5);
    }
    
    /** Handles some keys for starting the game, passing cutscenes,
    *   and reseting levels. */
    public void handleKeys(int keyCode) {
        //if (keyCode == ' ') {
        if (gameHasStarted) {
            if (cutScene == false) {
                resetWorld();
            }
            
            if (millis() > (timeOnStoryScreen + CUTSCENE_TIME)) {
                cutScene = false;
                gameOver = false;
                resetWorld();
            }
            
            if (gameOver) {
                gameOver = false;
                gameHasStarted = false;
                aWinnerIsYou = false;
                cutScene = true;
                timeOnStoryScreen = 0;
                currentLevel = 0;
            }
        } else {
            gameHasStarted = true;
        }
        //}
    }
    
    /** Loads a new map. Returns true if a new level was loaded or 
    *   false if not.*/
    private boolean loadMap() {
        /*
        if (currentLevel > LAST_LEVEL) {
            gameOver = true;
            return false;
        }
        */
        
        int x, y = -(Y_BLOCK_SIZE);
        String file = 
            LEVELFILES + "" + currentLevel + "" + FILEEXTENSION;
        boolean madeNewLevel = 
            levelLoader.makeMap(file, BLOCKS_PER_ROW, BLOCKS_PER_COL);
        if (!madeNewLevel) {
            gameOver = true;
            return false;
        }
        
        int map[][] = levelLoader.getMap();
        for (int i = 0; i < map.length; i++) {
            x = 0;
            y += Y_BLOCK_SIZE;
            for (int j = 0; j < map[0].length; j++) {
                switch (map[i][j]) {
                case -1:
                    
                case EMPTY:
                    break;     
                case BALL:
                    world[objectsInGame] = 
                        new Ball(x, y, 10, 10, 0, this, 1.0f);
                    ++objectsInGame;
                    break;
                case PADDLE:
                    world[objectsInGame] = 
                        new Paddle(x, y, X_BLOCK_SIZE, Y_BLOCK_SIZE, 0, this);
                    ++objectsInGame;
                    break;
                case HPADDLE:
                    world[objectsInGame] = 
                        new HPaddle(x, y, HPADDLE_WIDTH, HPADDLE_HEIGHT, 0, this);
                    ++objectsInGame;
                    break;
                case VPADDLE:
                    world[objectsInGame] = 
                        new VPaddle(x, y, VPADDLE_WIDTH, VPADDLE_HEIGHT, 0, this);
                    ++objectsInGame;
                    break;
                case DOOR:
                    world[objectsInGame] = 
                        new Door(x, y, X_BLOCK_SIZE, Y_BLOCK_SIZE, 0, 
                            this);
                    ++objectsInGame;
                    break;
                case FLOOR_PANEL_H:
                    world[objectsInGame] = 
                        new FloorPanel(x, y, 
                            HPADDLE_WIDTH + 10, HPADDLE_HEIGHT, 
                            FLOOR_PANEL_H, this);
                    ++objectsInGame;
                    break;
                case FLOOR_PANEL_V:
                    world[objectsInGame] = 
                        new FloorPanel(x, y, 
                            VPADDLE_WIDTH, VPADDLE_HEIGHT + 10,
                            FLOOR_PANEL_V, this);
                    ++objectsInGame;
                    break;
                case POSTER_A:
                case POSTER_S:
                case POSTER_W:
                case POSTER_D:
                case POSTER_J:
                case POSTER_K:
                case POSTER_I:
                case POSTER_L:
                case POSTER_SHIFT_LEFT: 
                case POSTER_SHIFT_RIGHT:
                case POSTER_SPACE_LEFT:
                case POSTER_SPACE_RIGHT:
                case POSTER_RESET:
                case ARROW_DOWN:
                case ARROW_LEFT:
                case ARROW_UP:
                case ARROW_RIGHT:
                case POSTER_BALL:
                case POSTER_PADDLE_LEFT:
                case POSTER_PADDLE_RIGHT:
                case POSTER_OR:
                case POSTER_UP_ARROW_KEY:
                case POSTER_DOWN_ARROW_KEY:
                case POSTER_LEFT_ARROW_KEY:
                case POSTER_RIGHT_ARROW_KEY:
                    backgroundObjects[backgroundObjectsInGame] = 
                        new Poster(x, y, X_BLOCK_SIZE, Y_BLOCK_SIZE, 
                            map[i][j], this);
                    ++backgroundObjectsInGame;
                    break;
                }
                
                x += X_BLOCK_SIZE;
            }
        }
        return true;
    }
    
    /** Returns the number of objects in the game. */
    public int getObjectsInGame() {
        return objectsInGame;
    }
    
    /** Returns the number of background objects in the game. */
    public int getBackgroundObjectsInGame() {
        return backgroundObjectsInGame;
    }
    
    /** Returns the array of objects in the world. 
    *   NOTE: Use getObjectsInGame to get the max iterator, not the 
    *   world's array length! */
    public BaseObject[] getWorld() {
        return world;
    }
    
    /** Returns the current force of gravity. */
    public float getGravityForce() {
        return gravityForce;
    }
    
    /** Returns true when a level has been completed or the player has
    *   won the game, and false otherwise. */
    public void aWinnerIsYou() {
        aWinnerIsYou = true;
    }
    
    /** Let's the game engine know that the player has died. */
    public void playerHasDied() {
        playerIsDead = true;
    }
    
    /** Loads all of the background images into the game. */
    private void loadImages() {
        storyScreen  = loadImage("./data/graphics/cutscenes/StoryScreen.png");
        goodEnding = loadImage("./data/graphics/cutscenes/GoodEnding.png");
        badEnding = loadImage("./data/graphics/cutscenes/BadEnding.png");
            
        poster_a = loadImage("/data/graphics/posters/keyboard/aKey.png");
        poster_s = loadImage("/data/graphics/posters/keyboard/sKey.png");
        poster_w = loadImage("/data/graphics/posters/keyboard/wKey.png");
        poster_d = loadImage("/data/graphics/posters/keyboard/dKey.png");
        poster_j = loadImage("/data/graphics/posters/keyboard/jKey.png");
        poster_k = loadImage("/data/graphics/posters/keyboard/kKey.png");
        poster_i = loadImage("/data/graphics/posters/keyboard/iKey.png");
        poster_l = loadImage("/data/graphics/posters/keyboard/lKey.png");
        poster_shift_left = loadImage("/data/graphics/posters/keyboard/shiftKey_left.png");
        poster_shift_right = loadImage("/data/graphics/posters/keyboard/shiftKey_right.png");
        poster_space_left = loadImage("/data/graphics/posters/keyboard/spaceKey_left.png");
        poster_space_right = loadImage("/data/graphics/posters/keyboard/spaceKey_right.png");
        poster_reset = loadImage("/data/graphics/posters/keyboard/reset.png");
        
        arrow_down = loadImage("/data/graphics/posters/arrows/downArrow_01.png");
        arrow_left = loadImage("/data/graphics/posters/arrows/leftArrow_01.png");
        arrow_up = loadImage("/data/graphics/posters/arrows/upArrow_01.png");
        arrow_right= loadImage("/data/graphics/posters/arrows/rightArrow_01.png");
        
        poster_ball = loadImage("/data/graphics/posters/keyboard/ball.png");
        poster_paddle_left = loadImage("/data/graphics/posters/keyboard/paddle_left.png");
        poster_paddle_right = loadImage("/data/graphics/posters/keyboard/paddle_right.png");
        poster_or = loadImage("/data/graphics/posters/keyboard/or.png");
        
        poster_up_arrow_key = loadImage("/data/graphics/posters/keyboard/upArrowKey.png");
        poster_down_arrow_key = loadImage("/data/graphics/posters/keyboard/downArrowKey.png");
        poster_left_arrow_key = loadImage("/data/graphics/posters/keyboard/leftArrowKey.png");
        poster_right_arrow_key = loadImage("/data/graphics/posters/keyboard/rightArrowKey.png");
    }
    
    /* Game Variables */
    int currentLevel, finalLevel;
    int objectsInGame;
    int backgroundObjectsInGame;
    int timeOnStoryScreen;
    int backgroundColor;
    
    float dBox_W1;
    float dBox_W2;
    float dBox_W3;
    float dBox_W4;
    float dBox_B1;
    float dBox_B2;
    float dBox_B3;
    float dBox_B4;
    float gravityForce;
    boolean gameOver;
    boolean gameHasStarted;
    boolean aWinnerIsYou;
    boolean playerIsDead;
    boolean cutScene;
    MapMaker levelLoader;
    BaseObject[] world;
    BaseObject[] backgroundObjects;

    static final int MAX_NUM_OF_LEVEL_OBJECTS = 750;    
    static final int X_BLOCK_SIZE = 32;
    static final int Y_BLOCK_SIZE = 20;
    static final int BLOCKS_PER_ROW = 30;
    static final int BLOCKS_PER_COL = 25;
    static final int HPADDLE_WIDTH = 32;
    static final int HPADDLE_HEIGHT = 10;
    static final int VPADDLE_WIDTH = 10;
    static final int VPADDLE_HEIGHT = 32;
    static final int CUTSCENE_TIME = 2000;
    static final int LAST_LEVEL = 0;
    static final String LEVELFILES = "data/levels/level_";
    static final String FILEEXTENSION = ".txt"; 
}



/** Creates tile-based level maps based on .txt files. */
public class MapMaker {
    
    /** Creates a new MapMaker with a null map. */
    MapMaker() {
        map = null;
    }
    
    /** Makes maps based on the input file found at FILENAME. 
    *   Returns true if the level file was found and successfully
    *   created. Returns false if any problems occur or the file 
    *   was not found. */
    public boolean makeMap(String fileName, int rows, int columns) {
        map = null;
        String[] temp = null;
        
        String[] levelLines = loadStrings(fileName);
        if (levelLines == null) {
            return false;
        }

        map = new int[rows][columns];
        
        for (int i = 0; i < rows; i++) {
            temp = split(levelLines[i], ' ');
            for (int j = 0; j < columns; j++) {
                map[i][j] = Integer.parseInt(temp[j]);
            }
        }
        return true;
    }
    
    /** Returns the recently created map. If the map was not created 
    *   or has already has already been returned once, this returns
    *   null. */
    public int[][] getMap() {  
        return map;
    }
    
    /** The game map. */
    int[][] map;
}






public class Paddle extends BaseObject {
    
    public Paddle(int x, int y, int w, int h, int o, Main game) {
        super(x, y, w, h, o, game);
        name = "paddle";
        objectID = PADDLE;

        isMovingObject = true;
        maxXVel = 8;
        maxYVel = 8;
        xAccel = 0.5f;
        yAccel = 0.5f;
        xDecel = 0.3f;
        yDecel = 0.3f;
        this.minSpeed = 0.3f;
    }
    
    /** Handles collisions for the Paddle. */
    public void handleCollision(BaseObject obj, int direction) {
    }
    
    /** Updates THIS Paddle's logic. */
    public void update(double t) {
        if (isDead) {
            return;
        }
        
        handleKeys();   
        capMaxVel();
        moveMe();
        checkCollision();
    }
    
    /** Handles all of the keyboard keys for movement and action. 
    *   Left Arrow: Move left.
    *   Right Arrow: Move right. */
    protected void handleKeys() {
        handleLeftArrowKey();
        handleRightArrowKey();
        handleUpArrowKey();
        handleDownArrowKey();
    }
    
    /** Changes velocity based on the left arrow key. */
    protected void handleLeftArrowKey() {
        if (leftArrowKeyDown || jKeyDown) {
            xVel += -(xAccel);
        } else if (xVel < 0) {
            xVel += xDecel;
        }
    }
    
    /** Changes velocity based on the right arrow key. */
    protected void handleRightArrowKey() {
        if (rightArrowKeyDown || lKeyDown) {
            xVel += xAccel;
        } else if (xVel > 0) {
            xVel += -(xDecel);
        }
    }

    /** Changes velocity based on the up arrow key. */
    protected void handleUpArrowKey() {
        if (upArrowKeyDown || iKeyDown) {
            yVel += -(yAccel);
        } else if (yVel < 0) {
            yVel += yDecel;
        }
    }
    
    /** Changes velocity based on the left down key. */
    protected void handleDownArrowKey() {
        if (downArrowKeyDown || kKeyDown) {
            yVel += yAccel;
        } else if (yVel > 0) {
            yVel += -(yDecel);
        }
    }
    
    /** Draws this paddle. */
    public void draw() {
        if (!isDead) {
            setColor();
            rect(x, y, w, h);
        }
    }
}






/** Simply a placeholder graphic in the background. */
public class Poster extends BaseObject {
    
    /** A new Poster. */
    Poster(float x, float y,  int w, int h, int o, Main game) {
        super(x, y, w, h, o, game);
        
        switch(o) {
        case POSTER_A:
            img = poster_a;
            name = "poster_a";
            break;
        case POSTER_S:
            img = poster_s;
            name = "poster_s";
            break;
        case POSTER_W:
            img = poster_w;
            name = "poster_w";
            break;
        case POSTER_D:
            img = poster_d;
            name = "poster_d";
            break;
        case POSTER_J:
            img = poster_j;
            name = "poster_j";
            break;
        case POSTER_K:
            img = poster_k;
            name = "poster_k";
            break;
        case POSTER_I:
            img = poster_i;
            name = "poster_i";
            break;
        case POSTER_L:
            img = poster_l;
            name = "poster_l";
            break;
        case POSTER_SHIFT_LEFT:
            img = poster_shift_left;
            name = "poster_shift_left";
            break;
        case POSTER_SHIFT_RIGHT:
            img = poster_shift_right;
            name = "poster_shift_right";
            break;            
        case POSTER_SPACE_LEFT:
            img = poster_space_left;
            name = "poster_space_left";
            break;
        case POSTER_SPACE_RIGHT:
            img = poster_space_right;
            name = "poster_space_right";
            break;
        case POSTER_RESET:
            img = poster_reset;
            name = "poster_reset";
            break;
        case ARROW_DOWN:
            img = arrow_down;
            name = "arrow_down";
            break;
        case ARROW_LEFT:
            img = arrow_left;
            name = "arrow_left";
            break;
        case ARROW_UP:
            img = arrow_up;
            name = "arrow_up";
            break;
        case ARROW_RIGHT:
            img = arrow_right;
            name = "arrow_right";
            break;
        case POSTER_BALL:
            img = poster_ball;
            name = "poster_ball";
            break;
        case POSTER_PADDLE_LEFT:
            img = poster_paddle_left;
            name = "poster_paddle_left";
            break;
        case POSTER_PADDLE_RIGHT:
            img = poster_paddle_right;
            name = "poster_paddle_right";
            break;
        case POSTER_OR:
            img = poster_or;
            name = "poster_or";
            break;
        case POSTER_UP_ARROW_KEY:
            img = poster_up_arrow_key;
            name = "poster_up_arrow_key";
            break;
        case POSTER_DOWN_ARROW_KEY:
            img = poster_down_arrow_key;
            name = "poster_down_arrow_key";
            break;
        case POSTER_LEFT_ARROW_KEY:
            img = poster_left_arrow_key;
            name = "poster_left_arrow_key";
            break;
        case POSTER_RIGHT_ARROW_KEY:
            img = poster_right_arrow_key;
            name = "poster_right_arrow_key";
            break;
        }                                           
    }
}





/** A Paddle that is aligned and can move vertically. */
public class VPaddle extends Paddle {
    
    /** A new VPaddle. */
    public VPaddle(int x, int y, int w, int h, int o, Main game) {
        super(x, y, w, h, o, game);
        name = "vpaddle";
        objectID = VPADDLE;

        
        isMovingObject = true;
        maxXVel = 5;//8;
        maxYVel = 5;//3;
        xAccel = 0.5f;//0.5;
        yAccel = 0.5f;//0.2;
        xDecel = 0.2f;//0.3;
        yDecel = 0.2f;//1.0;
        this.minSpeed = 0.5f;//0.5;
        
        roundedOffset = w / 2;
    }
    
    /** Handles all of the keyboard keys for movement and action. 
    *   Up Arrow: Move up.
    *   Down Arrow: Move down. */
    protected void handleKeys() {
        handleUpArrowKey();
        handleDownArrowKey();
    }
    
    public void draw() {
        super.draw();
        smooth();
        ellipse(x + roundedOffset, y, w, w);
        ellipse(x + roundedOffset, y + h, w, w);
        noSmooth();
    }
    
    private float roundedOffset;
}






  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#DFDFDF", "PongQuest" });
  }
}
