package mazerunner;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.util.Vector;

/** The UI for the MazeRunner Editor. Allows you to design the
  * layout of a maze including placing walls, exits, enemies, 
  * and yourself. Then you can use the maze you created in a game
  * @author Russ Wada copyright 1999 University of Hawaii
  * @version 1.0
  */
public class MazeEditor extends Applet implements ActionListener
{
    /** The grid where you can add and move components */
    EditorGrid editorGrid;
    
    /** Allows you to move components that are on the grid */
    Button moveButton = new Button("Move");
    
    /** Allows you to add walls to the grid */
    Button wallButton = new Button("Wall");
    
    /** Allows you to delete components */
    Button eraserButton = new Button("Eraser");
    
    /** Allows you to add enemies */
    Button enemyButton = new Button("Enemy");
    
    /** Allows you to add exits */
    Button exitButton = new Button("Goal");
    
    /** Allows you to create a game */
    Button startButton = new Button("Start");
    
    /** The last button you pressed. Since when buttons are selected
      * they are darkened, this will allow you to return the old button
      * to its original color when a new button is selected
      */
    Button lastButtonPressed = moveButton; //Originally move is selected so it's last button
    
    /** The default button color */
    Color defaultBGColor = Color.lightGray;
    
    /** The color a button turns to when selected */
    Color selectedButtonColor = Color.gray;
    
    /** The normal color of a button */
    Color defaultButtonColor;

    /** Applet initialization */
    public void init()
    {
        // Each components icon
        Image pix[] = new Image[4];
        // The names of each icon
        String pixName[] = {"wall.gif","enemy.gif","goal.gif","hero.gif"};
        // Insures that all images are loaded before starting
        MediaTracker tracker = new MediaTracker (this);
        // Create and load image 
        for (int i = 0; i < pixName.length; i++) {
            pix [i] = getImage (getClass().getResource(pixName[i]));
            tracker.addImage (pix [i], 0);
        }
        try {tracker.waitForAll (); // Wait for images
        } catch (InterruptedException error) {System.out.println("not done");}
        
        // Set the default button color by getting color from a unselected button
        defaultButtonColor = wallButton.getBackground();
        
        // Create a new grid and pass the images as a parameter
        editorGrid = new EditorGrid(pix);
        
        // Center the grid
        Panel holderPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
        holderPanel.add(editorGrid);
        holderPanel.setBackground(editorGrid.backgroundColor);
        
        // Create the selector button layout and init listeners
        Panel buttonPanel = new Panel();
        buttonPanel.setLayout(new GridLayout(10,1,2,2));
        buttonPanel.setBackground(defaultBGColor);
        buttonPanel.add(moveButton);
        buttonPanel.add(wallButton);
        buttonPanel.add(enemyButton);
        buttonPanel.add(exitButton);
        buttonPanel.add(eraserButton);
        
        // I don't want the start button directly under the eraser
        // So add 3 dummy Panels to make a large space
        buttonPanel.add(new Panel());
        buttonPanel.add(new Panel());
        buttonPanel.add(new Panel());
        buttonPanel.add(startButton);

        moveButton.addActionListener(this);
        wallButton.addActionListener(this);
        enemyButton.addActionListener(this);
        eraserButton.addActionListener(this);
        exitButton.addActionListener(this);
        startButton.addActionListener(this);
        
        // Make the color of the selected button the selected color
        lastButtonPressed.setBackground(selectedButtonColor);
        
        // Init the main layout
        setLayout(new BorderLayout(2,2));
        setBackground(defaultBGColor);
        add("West",buttonPanel);
        add("Center",holderPanel);
    }
  
    /** Handles button pressed events. Will either select a specific
      * component or create a maze game
      * @param event The event that was caught.
      */
    public void actionPerformed (ActionEvent event) {
        Button button;
        
        // Cast from object to button. If not button return
        try {
            button = (Button)event.getSource();
        } catch (Exception error) {return;}
        
        // Determines which button was pressed to determine which 
        // component was selected or to begin a game
        if (button == startButton)
        {
            editorGrid.createGame(this);
            // Return so that the start button doesn't change color
            return;
        }
        else if (button == wallButton) {
            editorGrid.select(editorGrid.WALL_ID);
        }
        else if (button == enemyButton)
        {
               editorGrid.select(editorGrid.ENEMY_ID);
        }
        
        else if(button == exitButton)
        {
            editorGrid.select(editorGrid.EXIT_ID);
        }
        
        else if (button == moveButton)
        {
            editorGrid.select(-1);
        }
        
        else {// source is eraser button
            editorGrid.select(editorGrid.BLANK_ID);
        }
        
        // If the button pressed is not the same as last button pressed
        // Change the color of the new button to the selected color
        // and change the color of the old button to the default
        if (button != lastButtonPressed)
        {
            button.setBackground(selectedButtonColor);
            lastButtonPressed.setBackground(defaultBGColor);
            lastButtonPressed = button;
        }
    }
    
    // Creates a border of 3 pixels 
    public Insets getInsets()
    { return new Insets(3,3,3,3); }

}

/** The grid of the editor. By clicking on a rectangle in the grid
  * you can add components, move components, or delete components
  * depending on what button was selected.
  * @author Russ Wada
  */  
class EditorGrid extends Canvas
{
    /** Image used for double buffering */
    Image offScreenImage;
    /** Graphics used for double buffering */
    Graphics offScreenGraphics;
    /** The default background color */
    Color backgroundColor = Color.black;
    /** The color of the grid lines */
    Color gridColor = Color.green;
    /** The height of each rectangle */
    int tileHeight = 30;
    /** The width of each rectangle */
    int tileWidth = tileHeight;
    /** The number of rows of rectangles */
    int numRows = 15;
    /** The number of columns of rectangles */
    int numColumns = 15;
    /** The height of the entire grid */
    int gridHeight = numRows*(1+tileHeight)+1;
    /** The width of the entire grid */
    int gridWidth = numColumns*(1+tileWidth)+1;
    /** An array of numbers representing what's in each rectangle or tile */
    int [] gridPosition = new int [numRows*numColumns];
    
    /** The button that was selected ID. Used to determine
      * what component to add, to delete a component or move
      * a component 
      */
    int selectedID=-1;//move originally selected
        
    /** The type of icon that is being moved. Used when "move" is selected */
    int movingIcon = -1;
    /** The x coordinate of the moving icon */
    int movingIconX;
    /** The y  coordinate of the moving icon */
    int movingIconY;
    
    /** The original grid position of the moving icon. Used when
      * the icon can't be moved to a certain position and must be
      * moved back 
      */
    int movingIconOldPosition;
    
    /** The wall's icon */
    Image wallImage;
    /** An enemie's icon */
    Image enemyImage;
    /** The exit's icon */
    Image exitImage;
    /** The hero's icon */
    Image heroImage;
    
    /** Blank space's ID */
    final static int BLANK_ID = 0; 
    /** Wall's ID */   
    final static int WALL_ID = 1;
    /** Enemie's ID */
    final static int ENEMY_ID = 2;
    /** Exit's ID */
    final static int EXIT_ID = 3;
    /** Hero's ID */
    final static int HERO_ID = 4;
    /** Inititialize the EditorGrid.
      * @param pix An array of Images that are the component's icons
      */     
    public EditorGrid(Image pix[])
    {
        super();
        // Make the upper left corner the hero's start point
        gridPosition[0] = HERO_ID;
        //Set the icon images
        wallImage = pix[0];
        enemyImage = pix[1];
        exitImage = pix[2];
        heroImage = pix[3];
        // Add mouse listeners to handle buttons being pressed
        addMouseListener (new MouseAdapter () {
            public void mousePressed (MouseEvent event) {
                pressed(event.getPoint());}
            public void mouseReleased (MouseEvent event) {
                released(event.getPoint ()); }});
        
        // Listener to handle mouse being dragged
        addMouseMotionListener(new MouseMotionAdapter () {
            public void mouseDragged (MouseEvent event) {
                dragged(event.getPoint () ); }});
                 
    }
    
    /** Called when the mouse button is pressed. Used only to determine
      * what component is selected when it is being moved. released
      * is used to add components.
      * @param point The point the mouse was clicked
      */
    void pressed(Point point)
    {
        // If component is to be added return. Will be added in
        // release()
        if (selectedID >= 0) return;
        // Find the indice represented by point
        int indice = findGridIndice(point);        
        // If not on the grid return
        if (indice == -1) return;
        // Determine what component is selected
        int gridSelection = gridPosition[indice];            
        //If the tile is blank return
        if (gridSelection==BLANK_ID) return;        
        //Store the icon ID of the selected component 
        movingIcon = gridSelection;
        // Store the selected components old position
        movingIconOldPosition = indice;
        // Remove the component from its original position
        gridPosition[indice] = BLANK_ID;
    }
    
    /** Handles mouse drags. Used in moving components from
      * postion to position
      * @param point Where the mouse clicked
      */
    void dragged(Point point)
    {
        //Determine x,y coordinates
        int x = point.x;
        int y = point.y;
        // If moving update the moving icon's position
        if (selectedID < 0) {
            movingIconX = x;
            movingIconY = y;
            repaint();                
        }
        // Else act as if it was a click. This allows you to 
        // rapidly add components by selecting one and then
        // dragging all over the screen
        else { released(point); }
    }

    /** Called when a tile is clicked or after an icon is dragged
      * onto a tile.
      * @param point Where the mouse clicked
      */
    void released(Point point)
    {
        // Determine where the mouse clicked
        int indice = findGridIndice(point);
        
        // If off the grid
        if (indice < 0)
        {
            // If was moving an icon
            if (selectedID < 0) {
                // If moving nothing. Sometimes this happens
                if (movingIcon < 0) return;
                // Put the icon back to its original position
                gridPosition[movingIconOldPosition] = movingIcon;
                //Not moving anything
                movingIcon = -1;
                repaint();
            }
            return;
        }

        // Find out what component was selected
        int gridSelection = gridPosition[indice];
        // If the tile is occupied and the eraser is not selected return
        if (gridSelection!=BLANK_ID && selectedID>BLANK_ID) return;
        // Don't erase the hero    
        if (selectedID==BLANK_ID && gridSelection==HERO_ID) return;
        
        // If the mover is selected put the moving icons number into
        // the slot
        if (selectedID < 0)
        {
            // Not moving anything
            if (movingIcon < 0) return;
            // If the new slot is occupied return icon to old position
            if (gridSelection != BLANK_ID) {
                gridPosition[movingIconOldPosition] = movingIcon;
            }
            // Else put icon into new slot
            else {
                gridPosition[indice] = movingIcon;
            }
            movingIcon = -1; //not being dragged anymore
        }
        // Else put the selected icon into this slot
        else {                
            gridPosition[indice] = selectedID;
        }
        repaint();
    }
    
    /** Given a point on the grid finds the corresponding indice 
      * in gridPosition.
      * @param point The point on the grid thats indice you want to find
      * @return The corresponding indice or -1 if not on the grid
      */
    private int findGridIndice(Point point)
    {
        //Determine coordinates
        int x = point.x;
        int y = point.y;
        
        //If not on the grid return -1
        if (x<0 || x>=gridWidth-1 || y<0 || y>=gridHeight-1)
        { return -1; }
        //Determine what tile was clicked
        int rowIndex = y/(tileHeight+1);
        int columnIndex = x/(tileWidth+1);
        return rowIndex*numRows+columnIndex;
    }
    
    /** An icon button was selected. Used to determine what 
      * to add, what to delete, or what to move.
      * @param id The ID of the component that is selected
      */  
    public void select(int id)
    {
        selectedID = id;
    }

    /** Repaints the screen.
      * @param graphics The graphics object
      */
    public void update(Graphics graphics)
    {
        paint(graphics);
    }

    /** Draws the grid and any components on it
      * @param graphics The graphics object
      */
    public void paint(Graphics graphics)
    {
        // If no double buffered image
        if (offScreenImage==null) return;
        // Drawing preparations
        Dimension size = getSize();
        offScreenGraphics.setColor(backgroundColor);
        offScreenGraphics.fillRect (0, 0, size.width, size.height);

        offScreenGraphics.setColor(gridColor);
        int x = 0;
        //drawing up to down lines of the grid
        while (x<gridWidth)
        {
            offScreenGraphics.drawLine(x,0,x,gridHeight-1);
            x+=tileWidth+1;
        }
        
        int y = 0;
        //drawing left to right lines of the grid
        while (y<gridHeight)
        {
            offScreenGraphics.drawLine(0,y,gridWidth-1,y);
            y+=tileHeight+1;
        }

        //drawing tiles (i.e. components in between the grid lines)
        for (int i=0; i<numRows; i++)
        {
            for(int j=0; j<numColumns; j++)
            {
                int gridSelection = gridPosition[i*numRows+j];
                if ( gridSelection==WALL_ID)
                {
                    offScreenGraphics.drawImage(wallImage,(tileWidth+1)*j+1,(tileHeight+1)*i+1,null);
                }
                else if ( gridSelection==ENEMY_ID)
                {
                    offScreenGraphics.drawImage(enemyImage,(tileWidth+1)*j+1,(tileHeight+1)*i+1,null);
                }
                else if ( gridSelection==EXIT_ID)
                {
                    offScreenGraphics.drawImage(exitImage,(tileWidth+1)*j+1,(tileHeight+1)*i+1,null);
                }
                else if ( gridSelection==HERO_ID)
                {
                    offScreenGraphics.drawImage(heroImage,(tileWidth+1)*j+1,(tileHeight+1)*i+1,null);
                }
                // This means that something is wrong
                else  if (gridSelection != 0) System.out.println(gridSelection);
               }
        }
        
        // If an icon is being moved draw it on the screen
        if (movingIcon != -1)
        {
            Image movingImage;
            if (movingIcon == WALL_ID) movingImage = wallImage;
            else if (movingIcon == ENEMY_ID) movingImage = enemyImage;
            else if (movingIcon == HERO_ID) movingImage = heroImage;
            else movingImage = exitImage;
            // Note that movingIconX and Y are global variables
            // set in drag
            offScreenGraphics.drawImage(movingImage,movingIconX-tileWidth/2,movingIconY-tileHeight/2,null);
        }
        graphics.drawImage(offScreenImage,0,0,null);
    }
    
    /** Used to readjust the screen
      * @param x The new x coordinate
      * @param y The new y coordinate
      * @param width The new width
      * @param height The new height
      */
    public void setBounds (int x, int y, int width, int height) {
        // let superclass reshape the component
        super.setBounds (x, y, width, height);
        // If the screen is too small return
        if (width<0 || height<0) return;   
        // return resources of old off-screen image buffer's graphics context if any
        if (offScreenGraphics != null) {offScreenGraphics.dispose ();}  
        // allocate new off-screen image buffer and it's graphics context   
        offScreenImage = createImage (width, height);
        offScreenGraphics = offScreenImage.getGraphics ();
    }
    
    /** Returns the desired size of the component as the size of the 
      * current image.
      * @return The current image size or (0,0) if it's null
      */
    public Dimension getPreferredSize(){
        return new Dimension(gridWidth,gridHeight);
    }
    
    /** Returns the minimum size requested by the component as the 
      * preferred size.
      * @return The preferred of the component which is the images size
      */
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }
    
    /** Creates a new MazeRunner game from the current grids layout.
      * @param applet This applet, which is used to load sound files
      */
    public void createGame(Applet applet)
    {
         MazeGame mazeGame = new MazeGame(applet,numRows,numColumns,gridPosition);
    }
}


/** A frame that holds the game where you can play the MazeRunner
  * game.
  * @author Russ Wada
  */
class MazeGame extends Frame
{
    /** Constructor.
      * @param applet An applet that is passed to the game canvas
      * @param nRows Number of rows of tiles (i.e. rectangles)
      * @param nCols Number of columns of tiles (i.e. rectangles)
      * @param map An array of ints representing locations of all components
      */
    public MazeGame(Applet applet, int nRows, int nCols, int map[])
    {    
        // Create a frame and allow it to be destroyed when the X
        // is clicked.
        super("MazeRunner");
        addWindowListener (new WindowAdapter () {
            public void windowClosing (WindowEvent event) {dispose ();}
        });
        
        // Create the game Canvas and size it.
        MazeCanvas mazeCanvas = new MazeCanvas(applet,nRows,nCols,map);
        add(mazeCanvas);
        setSize(500,500);
        show();
    }
}


/** The canvas for the game. All game animation as well as
  * events are handled here.
  * @author Russ Wada
  */
class MazeCanvas extends Canvas implements KeyListener
{
    /** Image used for double buffering */
    Image offScreenImage;
    /** Graphics used for double buffering */
    Graphics offScreenGraphics;
    /** The default background color */
    Color backgroundColor = Color.black;
    /** The height of each tile */
    int tileHeight = 30;
    /** The width of each tile */
    int tileWidth = tileHeight;
    /** The number of rows of tiles */
    int numRows;
    /** The number of columns of tiles */
    int numColumns;
    /** The height of the entire game maze */
    int gridHeight;
    /** The width of the entire game maze */
    int gridWidth;
    /** An array of numbers representing what's in each tile */
    int [] gridPosition;
    /** Total number of enemies */
    int totalEnemies;
    /** The animating thread. Specifies when to redraw the screen */
    Runner runner;
    /** The Artificial Intelligence of the game. Controls all enemies */
    EnemyHandler handler;
    /** The character you control */
    Character hero;
    /** The sound made when someone attacks */
    AudioClip attackSound;
    /** Whether you reached an exit or not */
    boolean won = false;
    /** The array of enemies */
    Character enemies[];
    
    /** The wall's icon */
    Image wallImage;
    /** The exit's icon */
    Image exitImage;
    
    /** Blank space's ID */
    final static int BLANK_ID = 0; 
    /** Wall's ID */   
    final static int WALL_ID = 1;
    /** Enemie's ID */
    final static int ENEMY_ID = 2;
    /** Exit's ID */
    final static int EXIT_ID = 3;
    /** Hero's ID */
    final static int HERO_ID = 4;

    /** Constructor.
      * @param applet An applet that is used to load sounds and images
      * @param nRows Number of rows of tiles (i.e. rectangles)
      * @param nCols Number of columns of tiles (i.e. rectangles)
      * @param map An array of ints representing locations of all components
      */
    public MazeCanvas(Applet applet,int nRows, int nColumns, int map[])
    {
        // Initializing important dimensions and the layout
        numRows = nRows;
        numColumns = nColumns;
        gridPosition = map;        
        gridHeight = numRows*tileHeight;
        gridWidth = numColumns*tileWidth;

        // Used to detect user commands
        addKeyListener(this);
        
        //Load Component images and store in a media tracker
        Class class_ = getClass ();
        Image pix[] = new Image[4];
        String pixName[] = {"wall.gif","enemy.gif","goal.gif","hero.gif"};
        MediaTracker tracker = new MediaTracker (this);
        for (int i = 0; i < pixName.length; i++) {
            pix [i] = applet.getImage (class_.getResource(pixName[i]));
            tracker.addImage (pix [i], 0);
        }
        
        // Load weapon images and store in tracker
        Image spears[] = new Image[4];
        for (int i=0; i<spears.length; i++)
        {
            spears[i] = applet.getImage(class_.getResource("spear"+i+".gif"));
            tracker.addImage(spears[i],0);
        }

        try {tracker.waitForAll ();
        } catch (InterruptedException error) {System.out.println("not done");}

        // Load wall and exit images
        wallImage = pix[0];
        exitImage =pix[2];
        
        // Load attack sound
        attackSound = applet.getAudioClip(class_.getResource("stab.au"));
        
        // Vectors used to determine how many enemies and exits there are
        Vector tempEnemyVector = new Vector();
        Vector tempExitVector = new Vector();

        //drawing tiles (i.e. components)
        for (int i=0; i<numRows; i++)
        {
            for(int j=0; j<numColumns; j++)
            {
                int gridSelection = gridPosition[i*numRows+j];
                // Add an enemy
                if ( gridSelection == ENEMY_ID)
                {
                    int x = tileWidth*j;
                    int y = tileHeight*i;
                    tempEnemyVector.addElement(new Character(x,y,pix[1],spears,attackSound));
                }
                
                //Add the hero
                else if (gridSelection == HERO_ID)
                {
                    int x = tileWidth*j;
                    int y = tileHeight*i;
                    hero = new Character(x,y,pix[3],spears,attackSound);
                }
            }
        }
        
        // Determine the amount of enemies and copy from vector to array
        totalEnemies = tempEnemyVector.size();
        enemies = new Character[totalEnemies];
        tempEnemyVector.copyInto(enemies);
        
        // Request focus for keyboard
        requestFocus();
        
        // Create the AI
        handler = new EnemyHandler();
        handler.start();
        
        // Create the animator
        runner = new Runner();
        runner.start();
    }
    
    /** Used to determine what key was pressed down.
      * Used to determine where to move or attack
      * @param event The key event
      */
    public void keyPressed(KeyEvent event){
        int code = event.getKeyCode();
        if (code == event.VK_UP) hero.moveUp(true);
        else if (code == event.VK_DOWN) hero.moveDown(true);
        else if (code == event.VK_LEFT) hero.moveLeft(true);
        else if (code == event.VK_RIGHT) hero.moveRight(true);
        else if (code == event.VK_S) hero.attack();
    }

    /** Not used but needs to be over ridden
      * @param event Not used
      */
    public void keyTyped(KeyEvent event){
    }

    /** Used to determine what key was released.
      * Used to determine when to stop moving
      * @param event The key event
      */
    public void keyReleased(KeyEvent event){
        int code = event.getKeyCode();
        if ((code == event.VK_UP)||
            (code == event.VK_DOWN)||
            (code == event.VK_LEFT)||
            (code == event.VK_RIGHT) )
        {   hero.halt();  }
    }
    
    /** Repaints the screen.
      * @param graphics The graphics object
      */
    public void update(Graphics graphics)
    {
        paint(graphics);
    }
    
    /** Draws the grid and any components on it
      * @param graphics The graphics object
      */
    public void paint(Graphics graphics)
    {
        // If no double buffering image
        if (offScreenImage==null) return;
        
        // Drawing preparations
        Dimension size = getSize();
        offScreenGraphics.setColor(backgroundColor);
        offScreenGraphics.fillRect (0, 0, size.width, size.height);
   
        // If reached an exit draw a victory message     
        if (won) 
        {
            offScreenGraphics.setFont(new Font("SansSerif",Font.BOLD,24));
            offScreenGraphics.setColor(Color.magenta);
            offScreenGraphics.drawString("YOU MADE IT!!!",100,200);
        }
        
        // Else play the game
        else
        {
            //drawing tiles (either exits or walls)
            for (int i=0; i<numRows; i++)
            {
                for(int j=0; j<numColumns; j++)
                {
                    // Determine what component is to be drawn
                    int gridSelection = gridPosition[i*numRows+j];
                    
                    // Draw a wall
                    if ( gridSelection == WALL_ID)
                    {
                        offScreenGraphics.drawImage(wallImage,tileWidth*j,tileHeight*i,null);
                    }
                    //Draw an exit
                    else if (gridSelection == EXIT_ID)
                    {
                        offScreenGraphics.drawImage(exitImage,tileWidth*j,tileHeight*i,null);
                    }
                    // Else is blank space   
                }
            }
            
            //Draw all dead enemies
            for (int i = 0; i < totalEnemies; i++)
            {
                if (!enemies[i].alive()) enemies[i].paintSelf(offScreenGraphics);
            }

            // Draw all Living enemies
            for (int i = 0; i < totalEnemies; i++)
            {
                if (enemies[i].alive()) enemies[i].paintSelf(offScreenGraphics);
            }
            // Draw your character
            hero.paintSelf(offScreenGraphics);
        }
        graphics.drawImage(offScreenImage,0,0,null);
    }
    
    /** Used to readjust the screen
      * @param x The new x coordinate
      * @param y The new y coordinate
      * @param width The new width
      * @param height The new height
      */
    public void setBounds (int x, int y, int width, int height) {
        // let superclass reshape the component
        super.setBounds (x, y, width, height);
        // If the screen is too small return
        if (width<0 || height<0) return;   
        // return resources of old off-screen image buffer's graphics context if any   
        if (offScreenGraphics != null) {offScreenGraphics.dispose ();}  
        // allocate new off-screen image buffer and it's graphics context   
        offScreenImage = createImage (width, height);
        offScreenGraphics = offScreenImage.getGraphics ();
    }


    /** Your character or an enemy. Contains methods to attack
      * and move. TO DO: Break into two subclasses Hero and Enemy 
      * @author Russ Wada
      */
    class Character
    {
        /** How far your character moves */
        protected final int movement = tileWidth/3;
        /** Facing up */
        protected final static int UP = 0;
        /** Facing Down */
        protected final static int DOWN = 1;
        /** Facing left */
        protected final static int LEFT = 2;
        /** Facing right */
        protected final static int RIGHT = 3;
        /** The upper left corner x coordinate */
        protected int x;
        /** The upper left corner y coordinate */
        protected int y;
        /** Amount of damage that can be taken before becoming inactive */
        protected int hitPoints = 5;
        /** The direction the character is facing */
        protected int direction; 
        /** Whether attacking or not */
        protected boolean isAttacking = false;
        /** Whether alive or not */
        protected boolean isAlive = true;
        /** What stage of attack character is in */
        protected int attackNumber;
        /** Used to determine if the character is hit */
        Rectangle mask;
        /** Sound maken when attacking */
        protected AudioClip attackSound;
        /** The characters self portrait */
        Image selfImage;
        /** Up, Down, Left, and Right weapon images */
        protected Image [] spearImages;
        /** Whether is moving or not */        
        boolean movingUp = false;
        boolean movingDown = false;
        boolean movingLeft = false;
        boolean movingRight = false;

        /** Characters constructor.
          * @param startX Starting x coordinate
          * @param startY Starting y coordinate
          * @param self Characters self portrait
          * @param spears Characters weapon images
          * @param clip Characters attack sound
          */
        public Character ( int startX, int startY,Image self, Image [] spears,AudioClip clip)
        {
            x = startX;
            y = startY;
            attackSound = clip;
            spearImages = spears;
            selfImage = self;
            // Used for collision detection
            mask = new Rectangle(x,y,tileWidth,tileHeight);
        }
                
        /** Draws itself and its weapon if applicable to the 
          * graphics context.
          * @param graphics The graphics context
          */
        public void paintSelf(Graphics graphics)
        {
            // If dead draw a grayish image of character
            if (!isAlive)
            {
                graphics.setColor(Color.darkGray);
                graphics.fillOval(x,y,tileWidth,tileHeight);
                return;
            }
            
            
            // Note attacking takes precedence over moving
            // In other words you won't be able to move until
            // after the attack
            if (isAttacking)
            {
                // Determine length of weapon
                int increment = attackNumber*12;
                // Used to in drawing the spear
                int tempX = x;
                int tempY = y;
                
                // Used to determine what, if anything, the spear
                // hits.
                int spearX = x;
                int spearY = y;
                
                // If in stage 1 play the attack sound
                if (attackNumber == 0) {
                    attackSound.play(); 
                }
                
                // Draw spear in respective direction
                if (direction == UP) tempY -= increment;
                else if (direction == DOWN) tempY += increment;
                else if (direction == LEFT) tempX -= increment;
                else tempX += increment;
                
                // Create spear hit mask with respect to direction.
                if (direction == UP)
                {
                    spearY = tempY;
                    spearX = x+tileWidth/2;
                }
                else if (direction == DOWN)
                {
                    spearY = tempY+tileHeight-1;
                    spearX = x+tileWidth/2;
                }
                else if (direction == LEFT)
                {
                    spearX = tempX;
                    spearY = y+tileHeight/2;
                }
                else {
                    spearX = tempX+tileWidth-1;
                    spearY = y+tileHeight/2;
                }

                // Draw spear
                graphics.drawImage(spearImages[direction],tempX,tempY,null);
                
                // If hero determine if any enemies are hit
                //if (this == hero) {
                    for (int i=0; i<totalEnemies; i++)
                    {
                        // If enemy is not alive can't hit it
                        if (!enemies[i].alive() || this==enemies[i]) continue;
                        if (enemies[i].damaged(spearX,spearY,direction)) {
                            isAttacking = false; // not attacking anymore
                            break;
                        }
                        
                    }
                //}
                // Else is enemy so determine if hero is hit
                if (this!=hero) 
                {
                    // hero must be alive to be hit
                    if (hero.alive()&&hero.damaged(spearX,spearY,direction)) {
                        isAttacking = false; //not attacking anymore
                    }
                }
                
                // Last attacking stage so not attacking anymore
                if (attackNumber == 2) isAttacking = false;
                attackNumber++; //Move to next stage           
            }
            
            // Character moving
            else if (movingUp)
            {
                direction = UP;
                // Move to new position if possible
                moveToNewPosition(direction);
            }
            else if (movingDown)
            {
                direction = DOWN;
                // Move to new position if possible
                moveToNewPosition(direction);
            }
            else if (movingLeft)
            {
                direction = LEFT;
                // Move to new position if possible
                moveToNewPosition(direction);
            }
            else if (movingRight)
            {
                direction = RIGHT;
                // Move to new position if possible
                moveToNewPosition(direction);
            }

            // Draw character in new position
            graphics.drawImage(selfImage,x,y,null);
            
        }
          
        /** Determines if this character has been hit and if it does
          * will cause the character to recoil and take damage.
          * @param hitX The x point of the attack
          * @param hitY The y point of the attack
          * @param direction The direction the attack came from
          * @return Whether the character was hit or not
          */ 
        boolean damaged(int hitX, int hitY, int direction)
        {
            // If the points are not in the mask not hit
            if (!mask.contains(hitX,hitY)) return false;
            
            // Else hit so move in the direction that it was hit.
            // Like recoil.
            moveToNewPosition(direction);
            
            // Decrement hitpoints and if 0 character not alive
            hitPoints--;
            if (hitPoints == 0) isAlive = false;
            return true;                
        }

        /** Launches an attack in the current direction */
        public void attack()
        {
            // If not attacking launch an attack. Can't launch
            // another attack if already attacking.
            if (!isAttacking)
            {
                isAttacking = true;
                attackNumber=0;
            }
        }
        
        /** Moves the character in direction specified if possible
          * @param direction The desired direction to be moved in
          */        
        private void moveToNewPosition(int direction)
        {   
            //Temporary coordinates 
            int newX = x;
            int newY = y;
            
            // Depending on direction determine where the character
            // will move and then determine if it's possible for it
            // to move there. If it can update the x and y coordinates
            if (direction==UP) { 
                newY -= movement;
                if (canMoveTo(x, newY)) {     
                    y = newY;
                }
            }
                
            else if (direction==DOWN) { 
                newY += movement;
                if (canMoveTo(x, newY)) {     
                    y = newY;
                }
            }
                
            else if (direction==LEFT) { 
                newX -= movement;
                if (canMoveTo(newX, y)) {     
                    x = newX;
                }
            }
                
            else {
                newX += movement;
                if (canMoveTo(newX, y)) {     
                    x = newX;
                }
            }
            
            // If this character is the hero and in the exit
            // the game is won
            if (this == hero && inExit(x,y) )
            { 
                // won is a global variable
                won = true;
            } 
            
            // Update the location of the mask
            mask.setLocation(x,y);
        }
                
        /** Tells the character to stop moving */
        public void halt()
        {
            movingUp = false;
            movingDown = false;
            movingLeft = false;
            movingRight = false;
        }

        /** Tells the character to move up */
        public void moveUp(boolean moving)
        {
            direction = UP; 
            movingUp = moving;
        }
          
        /** Tells the character to move down */
          public void moveDown(boolean moving)
        { 
            direction = DOWN;
            movingDown = moving;
        }

        /** Tells the character to move left */        
        public void moveLeft(boolean moving)
        {
            direction = LEFT; 
            movingLeft = moving;
        }
        
        /** Tells the character to move right */
        public void moveRight(boolean moving)
        { 
            direction = RIGHT;
            movingRight = moving;
        }

        
        /** Determines if the character can move to this new point.
          * @param x The new points upper left corner x coordinate
          * @param y The new points upper left corner y coordinate
          * @return Whether the character can move to the new point
          */
        boolean canMoveTo(int x, int y)
        {
            // If the character would end up off screen return false
            if (x<0 || y<0 || x+tileWidth>gridWidth || 
                y+tileHeight>gridHeight)
            { return false; }
            
            // If any of the character's corners is in a wall tile 
            // return false.
            if (gridPosition[y/tileHeight*numRows+x/tileWidth] == WALL_ID)
            { return false; }
                
            else if (gridPosition[y/tileHeight*numRows+(x+tileWidth-1)/tileWidth] == WALL_ID)
            { return false; }
            
            else if(gridPosition[(y+tileHeight-1)/tileHeight*numRows+x/tileWidth] == WALL_ID)
            { return false; }
            
            else if (gridPosition[(y+tileHeight-1)/tileHeight*numRows+(x+tileWidth-1)/tileWidth] == WALL_ID)
            { return false; }
            
            // If any of the characters corners ends up in an enemy return false
            for (int i=0; i<totalEnemies; i++)
            {
                if (enemies[i] == this||!enemies[i].alive()) continue;
                if (enemies[i].mask.contains(x,y) ||
                    enemies[i].mask.contains(x,y+tileHeight-1) ||
                    enemies[i].mask.contains(x+tileWidth-1,y) ||
                    enemies[i].mask.contains(x+tileWidth-1,y+tileHeight-1) )
                {  attack(); return false; }
            }
            
            // If not the hero (I.E. enemy) Determine if any corners in hero.
            // If there is launch an attack before returning false
            if (this != hero) {
                if (hero.mask.contains(x,y) ||
                    hero.mask.contains(x,y+tileHeight-1) ||
                    hero.mask.contains(x+tileWidth-1,y) ||
                    hero.mask.contains(x+tileWidth-1,y+tileHeight-1) )
                {  attack(); return false; }
            }
            
            // Can move
            return true;
        }
        
        /** Determines if the character just moved into an exit.
          * @param x The new points upper left corner x coordinate
          * @param y The new points upper left corner y coordinate
          * @return Whether the character ends up in an exit
          */
        boolean inExit(int x,int y)
        {
            // If the character would end up off screen return false
            if (x<0 || y<0 || x+tileWidth>gridWidth || 
                y+tileHeight>gridHeight)
            { return false; }
            
            // If character's corner in an exit return true
            if (gridPosition[y/tileHeight*numRows+x/tileWidth] == EXIT_ID)
            { return true; }
                
            else if (gridPosition[y/tileHeight*numRows+(x+tileWidth-1)/tileWidth] == EXIT_ID)
            { return true; }
            
            else if(gridPosition[(y+tileHeight-1)/tileHeight*numRows+x/tileWidth] == EXIT_ID)
            { return true; }
            
            else if (gridPosition[(y+tileHeight-1)/tileHeight*numRows+(x+tileWidth-1)/tileWidth] == EXIT_ID)
            { return true; }
            
            return false;
            
        }
        
        /** Whether the character is alive or not 
          * @return Whether alive or not
          */
        public boolean alive() {
            return isAlive;
        }
    }
    
    /** The enemies artificial intelligence */
    class EnemyHandler extends Thread
    {
        // How long before making new decisions
        final long delay = 300;
        public void run()
        {
            while(handler!=null)
            {
                try{ sleep(delay); }
                catch(InterruptedException error) {}
                
                // Move each enemy based upon a random
                // number generator. Not the best AI
                for (int i=0; i<totalEnemies; i++)
                {
                    Character enemy = enemies[i];
                    int direction = (int)(Math.random()*5);
                    if (direction <= 3) enemy.halt();
                    
                    if (direction == 0) enemy.moveUp(true);
                    else if (direction == 1) enemy.moveDown(true);
                    else if (direction == 2) enemy.moveLeft(true);
                    else if (direction == 3) enemy.moveRight(true);
                    // Else repeat last action
                }
            }
        }
    }
    

    /** The animator thread ask for a redraw after a specified amount
      * of time
      */
    class Runner extends Thread
    {
        final long delay = 100;
        public void run()
        {
            while(runner!=null)
            {
                try { sleep(delay);}
                catch (InterruptedException error) {}
                repaint();
                yield();
            }
        }
    }
}