//               Mr. Mazer
//       Tod Semple (tsemple@hmc.edu)
//            Copyright 1996
//
// maze.java
// 3/26/96
//

import java.awt.*;
import java.applet.*;
import java.io.*;
import java.net.*;
import java.lang.*;

/**
 * This is the canvas the has the count down timer
 */
class CTimer extends Canvas
{
	long StartTime; // in ms
    Thread paintTimerThread;
    maze theMaze;
    int level;
    public int timeLeft;

    CTimer(maze maze_arg,int level_arg)
    {
        StartTime=0;
        theMaze = maze_arg;
        level = level_arg;
    }

    /**
     * This is used by the add() function so it knows what size to start at.
     */
    public Dimension minimumSize()
    {
        return new Dimension(100,20);
    }

    /**
     * This is used by the add() function so it knows what size to start at.
     */
    public Dimension preferredSize()
    {
        return new Dimension(100,20);
    }

    /**
     * Start the timer.
     */
    public void Start()
    {
        StartTime=System.currentTimeMillis();
        repaint();

        // Make the thread that tell this canvas to paint
        // itself ever second.
        paintTimerThread = new Thread(new PaintTimer(this));
        paintTimerThread.start();
    }

    public void paint(Graphics g)
	{
        // Clear the canvas
        g.setColor(Color.white);
		g.fillRect(0,0,size().width,size().height);

        // has timer not starter yet?
        if (StartTime==0)
            return;

        // Determin the time left and display it.

        long EllapseTime = System.currentTimeMillis() - StartTime;

        timeLeft = (int)(theMaze.getMazeSize() + level*2 - EllapseTime/1000);

        if (timeLeft<0)
            timeLeft=0;

        g.setColor(Color.black);
        String message = "Level:" + level + "   Time Left: " + timeLeft + " Seconds";
		g.drawString(message, size().width/2-60,size().height-5);

        // are we out of time?
        if (timeLeft==0)
        {
            // Player Loses
            theMaze.setState(theMaze.state_Lost);
        }
	}

    protected void finalize()
    {
        paintTimerThread.stop();
    }
}

/**
 * This thread just sends paint messages to the timer canvas.
 */
class PaintTimer implements Runnable 
{
    PaintTimer(Canvas canvas_arg)
    {
        can=canvas_arg;
    }

    Canvas can;
           
	public void run()
	{
        // Send paint messages to the timer canvas

		while (true)
		{
			try 
			{
				Thread.sleep(1000);
				can.repaint();
			} 
			catch (InterruptedException e) 
			{
				return;
			}
		}
	}
}

/**
 * A wall class
 */
class CWall
{
	boolean Down;
	boolean Right;
}

/**
 * This is the maze canvas
 */
class CMaze extends Canvas
{
	protected int MazeWidth = 0;
	protected int MazeHeight = 0;

    protected boolean MadeItYet = false;

    // Location of starting position in maze
	int startX = 0; 
	int startY = 0;

    // Location of the exit
	int endX = 0;
	int endY = 0;

    static protected Image imageMazer;
    static protected Image imageExit;
    protected maze App;

    protected boolean thinWalls;

    CMaze(maze app_arg, int Width,int Height)
    {
        App = app_arg;

		MazeWidth = Width;
		MazeHeight = Height;

        // Change the wall thickness if there are a lot
        // of walls for visibility

        if (Width>=46 || Height>46)
            thinWalls=true;
        else
            thinWalls=false;

        // java has too many bugs to use images right now...

        /*if (imageMazer==null)
            imageMazer = App.getImage(App.getCodeBase(),"person.gif");
        if (imageExit==null)
            imageExit = App.getImage(App.getCodeBase(),"coffee.gif");*/
    }

    // Array of all the walls (note that there are walls
    // on the bottom and right that are never turned on)
	CWall Walls[][];

	/**
	 * Make the maze
	 */
	public void Make()
	{
		int x,y;

        // Make a new wall double array.

		Walls = new CWall[MazeWidth][MazeHeight];

		for (x=0;x<MazeWidth;x++)
			for (y=0;y<MazeHeight;y++)
				Walls[x][y] = new CWall();

		MakeWalls();

        // Start the timer and stuff
        App.StartMaze();
	}

    /*public void paintNow()
    {
        paint(getGraphics());
    }*/
    
    /**
	 * Draw all the maze walls
	 */
    public void paint(Graphics g)
	{
        // Clear the background
		g.setColor(Color.white);
		g.fillRect(0,0,size().width,size().height);

        if (!MadeItYet)
        {
            // Make all the wall and draw them at the same time.
            Make();
            MadeItYet=true;
        }
        else
        {
            // Draw all the walls

            int x,y;

		    for (x=0;x<MazeWidth;x++)
			    for (y=0;y<MazeHeight;y++)
			    {
				    if (Walls[x][y].Down)
					    DrawWall(g,x,y,WALL_DOWN);
				    
				    if (Walls[x][y].Right)
					    DrawWall(g,x,y,WALL_RIGHT);
			    }
        }

        // Draw the player and the exit

		showMazer(g,true);
		showExit(g,true);
	}

    /**
     * Draw or clear the player icon in the maze
     */
    public void showMazer(Graphics g,boolean show)
	{
        int x1 = XCornerToPixel(startX)+3;
		int y1 = YCornerToPixel(startY)+3;
		int width = XCornerToPixel(startX+1)-3-x1;
		int height = YCornerToPixel(startY+1)-3-y1;

        if (thinWalls)
        {
            // Make the mazer icon a little bigger so
            // it's more visible
            x1--;
            y1--;
            width+=2;
            height+=2;
        }

		if (show)
        {
            if (imageMazer!=null)
                g.drawImage(imageMazer,x1,y1,width,height,App);
            else // no picture loaded
            {
			    g.setColor(new Color(127,0,127));
                g.fillOval(x1,y1,width,height);
            }
        }
        else // hide 
        {
			g.setColor(Color.white);
            g.fillRect(x1,y1,width+1,height+1);
        }		
	}

    /**
     * Draw or clear the exit icon in the maze
     */
	void showExit(Graphics g,boolean show)
	{
        int x1 = XCornerToPixel(endX)+3;
		int y1 = YCornerToPixel(endY)+3;
		int width = XCornerToPixel(endX+1)-2-x1;
		int height = YCornerToPixel(endY+1)-2-y1;

        if (thinWalls)
        {
            // Make the mazer icon a little bigger so
            // it's more visible
            x1--;
            y1--;
            width+=2;
            height+=2;
        }

		if (show)
        {
            if (imageExit!=null)
                g.drawImage(imageExit,x1,y1,width,height,App);
            else // no picture loaded
            {
			    g.setColor(Color.green);
                g.fillRect(x1,y1,width,height);
            }		
        }
        else // hide 
        {
			g.setColor(Color.white);
            g.fillRect(x1,y1,width+1,height+1);
        }		
	}

    final int WALL_DOWN = 1;
    final int WALL_RIGHT = 2;

	/**
	 * Draw a single maze wall
	 */
	void DrawWall(Graphics g,int xindex,int yindex,int down)
	{
		int x1,y1,x2,y2;
		
		x1 = XCornerToPixel(xindex);
		y1 = YCornerToPixel(yindex);

		if (down==WALL_DOWN)
		{
			x2 = x1;
			y2 = YCornerToPixel(yindex+1);

			if (!thinWalls)
            {
                g.setColor(new Color(0,0,0));
			    g.fillRect(x1-1, y1-1, 3, y2-y1+1);
            }

			g.setColor(new Color(0,0,255));
			g.drawLine(x1, y1, x2, y2);
		}
		else // WALL_RIGHT
		{
			x2 = XCornerToPixel(xindex+1);
			y2 = y1;

			if (!thinWalls)
            {
    			g.setColor(new Color(0,0,0));
	    		g.fillRect(x1-1, y1-1, x2-x1+1, 3);
            }

			g.setColor(new Color(0,0,255));
			g.drawLine(x1, y1, x2, y2);
		}
	}

	/**
	 * Take a corner x-coordinate and return a pixel x-coordinate 
	 */
	int XCornerToPixel(int x)
	{
		return 1 + ((size().width-2) * x / (MazeWidth-1));
	}

	/**
	 * Take a corner y-coordinate and return a pixel y-coordinate 
	 */
	int YCornerToPixel(int y)
	{
		return 1 + ((size().height-2) * y / (MazeHeight-1));
	}

	/**
	 * Makes a maze such that there is exacly one path from every place in the maze
	 * to every other place in the maze.
	 */
	void MakeWalls()
	{
		int x,y;

		// Clear all the walls
		for (x=0;x<MazeWidth;x++)
			for (y=0;y<MazeHeight;y++)
			{
				Walls[x][y].Down=false;
				Walls[x][y].Right=false;
			}

		Graphics g = getGraphics();
        
        // Make border walls
		for (x=0;x<MazeWidth-1;x++)
		{
			Walls[x][0].Right=true;
			Walls[x][MazeHeight-1].Right=true;
			DrawWall(g,x,0,WALL_RIGHT);
			DrawWall(g,x,MazeHeight-1,WALL_RIGHT);
        }
		for (y=0;y<MazeHeight-1;y++)
		{
			Walls[0][y].Down=true;
			Walls[MazeWidth-1][y].Down=true;
			DrawWall(g,0,y,WALL_DOWN);
			DrawWall(g,MazeWidth-1,y,WALL_DOWN);
		}

        // This algorithm to makes the walls goes like this:
        //   1) Pick an empty corner
        //   2) Pick direction
        //   3) Draw wall in direction until it meets another wall
        //   4) Do until no corners left

		// Number of interior wall corners that need to be made.
		int nCorners = (MazeWidth-2)*(MazeHeight-2);

		// Still corners left?
		while (nCorners>0)
		{
			// Start at upper left corner
			x=1;
			y=1;

			// Find a random empty corner (in line O(# corners) time)

			int skip = (int)(Math.random() * nCorners) + 1;

			while (skip>0)
			{
				x++;
				if (x==MazeWidth-1)
				{
					x=1;
					y++;
					if (y==MazeHeight-1)
						y=1;
				}

				if (WallAt(x,y))
					continue;

				skip--;
			}

			// Pick a random direction from the empty corner and
			// draw a line until we hit another wall.

			boolean keepgoing;

			int WallDirection=(int)(Math.random() * 4);
			switch (WallDirection)
			{
			case 0: // go right
				do
				{
					keepgoing = !WallAt(x+1,y);
					nCorners--;
					Walls[x][y].Right=true;
			        DrawWall(g,x,y,WALL_RIGHT);
					x++;
				} while (keepgoing); 
				break;

			case 1: // go left
				do
				{
					keepgoing = !WallAt(x-1,y);
					nCorners--;
					Walls[x-1][y].Right=true;
			        DrawWall(g,x-1,y,WALL_RIGHT);
					x--;
				} while (keepgoing); 
				break;

			case 2: // down
				do 
				{
					keepgoing = !WallAt(x,y+1);
					nCorners--;
					Walls[x][y].Down=true;
			        DrawWall(g,x,y,WALL_DOWN);
					y++;
				} while (keepgoing); 
				break;

			case 3: // up 
				do 
				{
					keepgoing = !WallAt(x,y-1);
					nCorners--;
					Walls[x][y-1].Down=true;
			        DrawWall(g,x,y-1,WALL_DOWN);
					y--;
				} while (keepgoing); 
				break;
			}
		}

		// Make Starting and Ending positions

		startX = (int)(Math.random() * (MazeWidth-1));
		startY = (int)(Math.random() * (MazeHeight-1));

		while (!isAlley(startX,startY))
		{
			startX++;
			if (startX==MazeWidth-1)
			{
				startX=1;
				startY++;
				if (startY==MazeHeight-1)
					startY=1;
			}
		}

    	endX = (int)(Math.random() * (MazeWidth-1));
		endY = (int)(Math.random() * (MazeHeight-1));

		while (!isAlley(endX,endY) || (endX==startX && endY==startY))
		{
			endX++;
			if (endX==MazeWidth-1)
			{
				endX=1;
				endY++;
				if (endY==MazeHeight-1)
					endY=1;
			}
		}
	}

    /**
	 * Puts a start or ending alley (place with only one way out).
	 */
	boolean isAlley(int x,int y)
	{
		int numWalls=0;
		int NewCorners=0;

		if (Walls[x+1][y].Down==true) // right
			numWalls++;
		if (Walls[x][y].Down==true) // left
			numWalls++;
		if (Walls[x][y].Right==true) // up
			numWalls++;
		if (Walls[x][y+1].Right==true) // down
			numWalls++;

		return numWalls>=3;
	}

	/**
	 * Is there a wall coming into (x,y)?
	 */
	boolean WallAt(int x,int y)
	{
		if (Walls[x][y].Right || Walls[x][y].Down)
			return true;

		if (x>0 && Walls[x-1][y].Right)
			return true;

		if (y>0 && Walls[x][y-1].Down)
			return true;

		return false;
	}

	/**
	 * Moves the player down one place
	 */
	public void moveDown()
	{
		Graphics g=getGraphics();
		showMazer(g,false);
		if (Walls[startX][startY+1].Right==false)
			startY++;
		showMazer(g,true);
	}

    /**
	 * Moves the player up one place
	 */
	public void moveUp()
	{
		Graphics g=getGraphics();
		showMazer(g,false);
		if (Walls[startX][startY].Right==false)
			startY--;
		showMazer(g,true);
	}

    /**
	 * Moves the player left one place
	 */
	public void moveLeft()
	{
		Graphics g=getGraphics();
		showMazer(g,false);
		if (Walls[startX][startY].Down==false)
			startX--;
		showMazer(g,true);
	}

	/**
	 * Moves the player right one place
	 */
	public void moveRight()
	{
		Graphics g=getGraphics();
		showMazer(g,false);
		if (Walls[startX+1][startY].Down==false)
			startX++;
		showMazer(g,true);
	}

	/**
	 * Is the playing in the exit?
	 */
	public boolean solved()
	{
		if (startX==endX && startY==endY)
			return true;

		return false;
	}

	/**
	 * Flash the player and the exit so the user may see them easily
	 */
    public void flash()
    {
        Graphics g=getGraphics();
        showMazer(g,true);
        showExit(g,true);
        App.delay(100);
        showMazer(g,false);
        showExit(g,false);
        App.delay(100);
    }
}

/**
 * The maze applet
 */
public class maze extends java.applet.Applet implements Runnable 
{
	protected int State = 0;
	
	final int StartWidth = 400;
	final int StartHeight = 400;

	protected int MazeSize;	
    protected int level;
    protected int score;
    protected int timeLeft;

	protected CMaze Maze;
	protected CTimer Timer;

    /**
     * Initialize the applet. Resize and load images.
     */
    public void init() 
    {
        int x,y;

	    resize(StartWidth,StartHeight);
    }

    /**
     * Returns the number of spaces on a size of the maze
     */
    public int getMazeSize()
    {
        return MazeSize;
    }

    /**
     * Paint the screen.
     */
    public void paint(Graphics g)
    {
        int x;

		switch (State)
		{
        case state_Welcome:
	        g.setFont(new java.awt.Font("Times", Font.PLAIN, 25));
	        g.setColor(Color.blue);

	        g.drawString("Welcome to Mr. Mazer", size().width/2-120,size().height/3);            
            drawPressAKey(g);
			break;

        case state_Playing:
			break;

		case state_Won:
	        g.setFont(new java.awt.Font("", Font.PLAIN, 25));
	        g.setColor(new Color(0,0,127));

            x = size().width/2-60;

	        g.drawString("Whahoo!!!!!", x-20,50);
            g.setFont(new java.awt.Font("", Font.PLAIN, 15));
            g.drawString("Level: "+level, x,80);
            g.drawString("Time Left: "+timeLeft, x,110);
            g.drawString("Score This Level: "+timeLeft+" * "+level*100+
                         " = "+(timeLeft*level*100), x,140);
            g.drawString("Total Score: "+score, x,170);
            drawPressAKey(g);
			break;

		case state_Lost:
	        g.setFont(new java.awt.Font("", Font.PLAIN, 25));
	        g.setColor(Color.red);

            x = size().width/2-60;

            g.drawString("Game Over!!!!!", x-20,50);
            g.setFont(new java.awt.Font("", Font.PLAIN, 15));
            g.drawString("Level: "+level, x,80);
            g.drawString("Final Score: "+score, x,110);
            drawPressAKey(g);
			break;
        }
    }

    /**
     * Draws "press a key" as the bottom of the screen
     */
    void drawPressAKey(Graphics g)
    {
        g.setColor(Color.black);
        g.setFont(new java.awt.Font("Times", Font.PLAIN, 12));
        g.drawString("Press a key to continue...",
                     size().width/2-100,size().height-5);
    }

    public void update(Graphics g)
	{
	    g.clearRect(0, 0, size().width, size().height);
        paintAll(g);
    }

    public boolean keyDown(java.awt.Event evt, int key) 
	{
		if (State==state_Playing)
		{
			if (key == evt.DOWN)
				Maze.moveDown();
			if (key == evt.UP)
				Maze.moveUp();
			if (key == evt.LEFT)
				Maze.moveLeft();
			if (key == evt.RIGHT)
				Maze.moveRight();

			if (Maze.solved())
				setState(state_Won);
		}
        else
        {
            // Continue with user presses a non-arrow key

			if (key != evt.DOWN && 
                key != evt.UP &&
                key != evt.LEFT &&
                key != evt.RIGHT)
            {
                nextState();
            }
       }

		return true;
    }

    long LastTime = 0; // last time user clicked mouse

    public boolean mouseDown(java.awt.Event evt, int x, int y) 
	{
        // This is a fix for a java problem in windows 95. It sends
        // two mouse down message sometime when there should be only
        // one.

		long NowTime = System.currentTimeMillis();

        if (NowTime-LastTime < 300)
            return true; // throw out quick double click

        LastTime = NowTime;

        if (State!=state_Playing)
            nextState();

	    return true;
    }

    /**
     * Goto the next state
     */
    protected void nextState()
    {
        switch(State)
        {
        case state_Welcome: setState(state_Playing);break;
        case state_Playing: setState(state_Won);break;
        case state_Won: setState(state_Playing);break;
        case state_Lost: setState(state_Welcome);break;
        }
    }

    public final int state_Welcome = 0;
    public final int state_Playing = 1;
    public final int state_Won = 2;
    public final int state_Lost = 3;

    /**
     * Set the state
     */
    protected void setState(int newState)
    {
        // Get rid of old components
        removeAll(); 

        State=newState;
        switch(State)
	    {
	    case state_Welcome:
            MazeSize = 16;
            level=0;
            score=0;
		    repaint();
		    break;

	    case state_Playing:
            MazeSize+=2;
            level++;

            setLayout(new BorderLayout(10,10));
            Maze=new CMaze(this,MazeSize,MazeSize);
            Timer=new CTimer(this,level);
            add("South",Timer);
		    add("Center",Maze);

            repaint();
            break;

	    case state_Won:
            timeLeft = Timer.timeLeft;
            score += timeLeft*level*100;
            removeAll(); 
		    repaint();
		    break;

        case state_Lost:
            removeAll(); 
		    repaint();
		    break;
	    }       
    }

    /**
     * Flash the mazer then start the clock
     */
    public void StartMaze()
    {
        delay(800);

        for (int i=0;i<6;i++)
            Maze.flash();

        Timer.Start();
    }

    /**
     * Start the applet.
     */
    public void start() 
	{
        setState(state_Welcome);
		requestFocus();
    }

    public String getAppletInfo() 
	{
       return "_____Mr. Mazer_____\n" +
              "___by Tod Semple___\n" +
              "_(tsemple@hmc.edu)_\n" +
              "_____3/26/95_______\n";
    }

    public void run() {}

    /**
     * Shortcut for sleeping for a while
     */
    void delay(int ms)
    {
		try 
		{
			Thread.sleep(ms);
		} 
		catch (InterruptedException e) 
		{
			return;
		}
    }
}

