// Snakes and Ladders Java Applet // James Raftery - 1BA2, 97750751 // Copyright (c) James Raftery , 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..."); } } }