
// Snakes and Ladders Java Applet
// James Raftery - 1BA2, 97750751
// Copyright (c) James Raftery <james@now.ie>, 1997-2000 2002.

// import required packages
import java.applet.*;
import java.awt.*;
import java.util.*;

// this class inherits from class Applet, and uses a Runnable interface
public class SandL extends Applet implements Runnable {

// local variable declarations - objects
private Player player1, player2, currentPlayer;
private Thread mainThread;
private Font mainFont, boldFont;
private Random dice;
private Image arrow, board, window, gameover;
private MediaTracker tracker;
private Graphics buffer;

// local variable declarations - constants
private final int SIDESIZE = 5;
private final int SQRSIZEPXLS = 80;
private final int HGAP = 30;

// local variable declaration - int array
// array of the special squares on the board. Takes the form
// of n pairs of numbers, the first digits of the pair being the number of
// special square (top of a snake/bottom of a ladder), and the second being
// the target (bottom of snake/top of ladder)
private int[] specialSquares = {2, 13, 7, 16, 11, 1, 15, 22, 20, 4, 21, 13};

// local variable declarations - boolean flags
private boolean OKtoRoll = false;
private boolean gameOver = false;
private boolean DEBUG = false;

// local variable declarations - the rest!
private int diceResult = 0;

// create objects and initialise variables
public void init () {

        setBackground (Color.white);

        mainFont = new Font ("TimesRoman", Font.PLAIN, 13);
        boldFont = new Font ("TimesRoman", Font.BOLD, 14);

        player1 = new Player ("Player 1");
        player2 = new Player ("Player 2");
        currentPlayer = player1;

	// dice is a random number generator, seeded with current date/time
        dice = new Random ();

	// create a graphics context to use for double buffering
	window = createImage (500, 400);
	buffer = window.getGraphics();

	// some sanity checking on list of special squares and their targets
	if (specialSquares.length % 2 != 0) {
		showStatus ("specialSquares array has odd number of elements");
		System.exit (1);
	}

	// to account for network lag;
	tracker = new MediaTracker (this);
	board = getImage (getCodeBase(), "img/board.gif");
	tracker.addImage (board, 0);
	arrow = getImage (getCodeBase(), "img/arrow.gif");
	tracker.addImage (arrow, 0);
	gameover = getImage (getCodeBase(), "img/gameover.gif");
	tracker.addImage (gameover, 0);

	showStatus ("Loading images, please wait...");

	// wait until applet has all required images
	try {
		tracker.waitForID(0);
	} catch (InterruptedException excep) {}

	// display problems/success
	if (tracker.isErrorAny()) {
		showStatus ("Error loading images.");
	} else if (tracker.checkAll()) {
		showStatus ("Images loaded successfully.");
	}
}

public void start () {

        if (DEBUG) {
                showStatus ("Starting...");
        }
        
	// create the thread that will run the game...
	mainThread = new Thread (this);
	if (mainThread != null) {
		// ...and fire it up!
		mainThread.start();
	}
}

// override inherited update() method to
// implement double buffering - we disable
// screen clearing before calling paint()
public void update (Graphics g) {

	paint (g);
}

// this is where all the fun starts!
public void run () {

	if (DEBUG) {
                showStatus ("Running...");
                try {
                        Thread.sleep(500);
                } catch (Exception excep) {}
        }

	// draw the screen to begin with
        repaint();

	// main game loop - continue until the game is over
        while (!gameOver) {

		// if we caught a valid keypress
                if (OKtoRoll) {
        
                        if (DEBUG) {
                                showStatus ("OKtoRoll unset...");
                                try {
                                        Thread.sleep(500);
                                } catch (Exception excep) {}
                        }                

			// throw the dice and move the player forward
                        diceResult = throwDice();
                        if (DEBUG) {
                                showStatus ("Dice thrown...");
                                try {
                                        Thread.sleep(500);
                                } catch (Exception excep) {}
                        }

			// show their new position
			repaint();

			// Wait
			try {
				Thread.sleep(300);
			} catch (Exception excep) {}

                        currentPlayer.advanceBy(diceResult);
                        if (DEBUG) {
                                showStatus ("Player advanced...");
                                try {
                                        Thread.sleep(500);
                                } catch (Exception excep) {}
                        }

			// show their new position
			repaint();

			// Wait
			try {
				Thread.sleep(300);
			} catch (Exception excep) {}

			// check if they landed on a special square.
			// The method handles moving the player if necessary
			if (querySpecialSquares()) {

				// if so, leave token on current sqaure for a moment
				try {
					Thread.sleep(500);
				} catch (Exception excep) {}

				// then redraw, player will have moved to target square
				repaint();

				// Wait
				try {
					Thread.sleep(300);
				} catch (Exception excep) {}
			}

			// if the current player has reached the last square
                        if (currentPlayer.getLocation() == 24) {

				// display message on status bar and set the gameOver flag
                                showStatus (currentPlayer.getName() + " has won!");
				gameOver = true;
                        } else {

				// otherwise change player and game continues
	                        changePlayer();
			}

			// redraw screen; will display winner or new current player, depending 
			// on gameOver flag
			repaint();
			OKtoRoll = false;

                } // End if (OKtoRoll)

		// Wait - check for valid keystroke five times a second. Otherwise will
		// chew up cycles like you wouldn't believe!
       		try {
              		Thread.sleep(200);
                } catch (Exception excep) {}

        } // End if (!gameOver)

} // End run()

// modified paint() method to implement double buffering
public void paint (Graphics g) {

        if (DEBUG) {
                showStatus ("Painting...");
                try {
                        Thread.sleep(500);
                } catch (Exception excep) {}
        }

	// all operations are carried out on alternative graphics context 
	// called "buffer". Eventually buffer is dumped onto active graphics
	// context, g, for flicker free animation

	// fill the background - to cover previous contents
	buffer.setColor (Color.white);
	buffer.fillRect (0, 0, 500, 400);

	// dump the Snakes and Ladders board
        buffer.drawImage (board, 0, 0, this);

	// Display player and game status info
	buffer.setColor (Color.black);

        buffer.setFont (mainFont);
        buffer.drawString ("Square: " + (player1.getLocation() + 1), 420, 40);

        buffer.drawString ("Square: " + (player2.getLocation() + 1), 420, 110);

        //buffer.drawString ("Name: " + player1.getName(), 420, 40);
        //buffer.drawString ("Square: " + (player1.getLocation() + 1), 420, 55);
        //buffer.drawString ("Name: " + player2.getName(), 420, 110);
        //buffer.drawString ("Square: " + (player2.getLocation() + 1), 420, 125);

        buffer.setFont (boldFont);

	// display roll of the dice
        buffer.drawString ("Dice: " + diceResult, 420, 160);

	if (!gameOver) {

		// while game is running, indicate current player with a small arrow
		if (currentPlayer == player1) {
			buffer.drawImage (arrow, 409, 10, this);
		} else {
			buffer.drawImage (arrow, 409, 80, this);
		}
	} else {
        	buffer.drawString (currentPlayer.getName(), 420, 180);
        	buffer.drawString ("has won!", 420, 195);

		// otherwise display game over & copyright info
		buffer.drawImage (gameover, 405, 300, this);
	}

	// draw player tokens on the board.
	// Determining where to put them is complicated - see documentation for
	// complete explanation
        buffer.setColor (Color.red);
        buffer.fillOval (((player1.getLocation() % SIDESIZE) * SQRSIZEPXLS) + HGAP, 
                    ((SIDESIZE - (player1.getLocation() / SIDESIZE)) * SQRSIZEPXLS) - 65, 20, 20);

        buffer.drawString ("Player 1:", 420, 20);
        
        buffer.setColor (Color.blue);
        buffer.fillOval (((player2.getLocation() % SIDESIZE) * SQRSIZEPXLS) + HGAP, 
                    ((SIDESIZE - (player2.getLocation() / SIDESIZE)) * SQRSIZEPXLS) - 35, 20, 20);

        buffer.drawString ("Player 2:", 420, 90);

	// dump the contents of the buffer
	g.drawImage (window, 0, 0, this);
}

public int throwDice () {

	// generate a pseudo-random number in the range -6 to +6
        int temp = dice.nextInt() % 7;

	// make sure we don't have a zero
        while (temp == 0) {
                temp = dice.nextInt() % 7;
        }

	// return the absolute value (ie positive) of the result
        return Math.abs(temp);
}

public void changePlayer () {

	// switch the object referred to by the currentPlayer varialbe
        if (currentPlayer == player1) {
                currentPlayer = player2;
        } else if (currentPlayer == player2) {
                currentPlayer = player1;
        } else {
		// hopefully unnecessary sanity check
                System.out.println ("Yikes, unknown player object. Failed to change.");
                System.exit (1);
        }

        if (DEBUG) {
                showStatus ("Player changed...");
                try {
                        Thread.sleep(500);
                } catch (Exception excep) {}
        }

	// display confirmation on the status bar
	showStatus ("Current player: " + currentPlayer.getName());        
}

// catch a keyDown event and determine if it's a key we are interested in
public boolean keyDown (Event e, int key) {

        if (DEBUG) {
                showStatus ("keyDown Event caught...");
                try {
                        Thread.sleep(500);
                } catch (Exception excep) {}
        }
        
	// if the game is over, ignore it
	if (gameOver) {
		return false;

	// but if the game's still going and the user typed an "r"
	} else if (key == 'r') {

		// set the OKtoRoll flag
                OKtoRoll = true;

               	if (DEBUG) {
               		showStatus ("OKtoRoll is set...");
			try {
        	        Thread.sleep(500);
                	} catch (Exception excep) {}
		}

		// have to return true to prevent event propogation thru' the AWT
		return true;
	} else {
		// if neither of above conditions, display an error message
		showStatus ("Invalid key, use r to roll the dice.");
               	return false;
	}
}

// determine if current payer is on a "special square"
// and take appropriate action
public boolean querySpecialSquares () {

	// search thru' every second element of special squares array
	for (int i = 0; i < specialSquares.length - 1; i += 2) {

		// if current location matches a special square
		// move player to target square and indicate success
		if (currentPlayer.getLocation() == (specialSquares[i] - 1)) {
			currentPlayer.setLocation (specialSquares[i + 1] - 1);
			return true;
		}
	}

	// otherwise return false to indicate failure
	return false;
}

public void stop () {

        if (DEBUG) {
                showStatus ("Stopping...");
        }

	// stop active thread
	if (mainThread != null) {
		mainThread.stop();
		mainThread = null;
	}
}

public void destroy () {

        if (DEBUG) {
                showStatus ("Destroying...");
        }        
}

}

