/**
 * PARCHEESI
 *
 * @version 1.0b1 * @author Jason Bloomberg
 *
 * Java code copyright (c) 1997 Jason Bloomberg
 * Parcheesi brand game copyright (c) Milton Bradley, Inc.
 *
 * This Java code is emailware. You may read it, use it, learn from it,
 * or modify it, as long as you send email to wizard@rhodes.com. Let me
 * know what you think, good or bad!
 *
 * This applet was my first Java project. Version 1 (what you see here) was
 * completed in October 1997. Version 2, if I have the time and inclination,
 * will be the multiplayer version.
 * 
 * Since this was my first Java project, you'll notice that the code has grown
 * organically; early code is very amateurish, while I got the idea as I went.
 * 
 * Why Parcheesi? Well, if you're familiar with The Rhodes Arcade, you know that
 * I like programming games to learn new technologies. I wanted a game that lent
 * itself to Java, that is, one that wouldn't be easy to program in JavaScript. 
 *
 * I also wanted a game that would let me work on all the fun Java stuff -- creating
 * classes, using threads, messing with the AWT, and then working with streams
 * and networking when I get around to the multiplayer version. And then I wanted
 * a game that nobody had programmed in Java, so Parcheesi it was!
 *
 * I tried to comment the code -- over 2500 lines of it -- so that a new Java
 * programmer could follow it and learn from it. So have fun!
 * 
 *****/

import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.net.*;
import java.util.*;
import java.applet.AudioClip;

public class Parcheesi extends Applet implements Runnable
   {
   
/***********
* 
* This program is organized in the following manner:
*
* Parcheesi class
*     Thread methods
*     Computer moves
*     Initialization methods
*     Init painting methods
*     Gameplay methods
*     Static methods
*     Event methods
*     Variables
* Flasher class
*     Pawn class
*     Die class
*
* Because Parcheesi is an applet, program control begins with the init ()
* method. From there, it processes events.
*
************/

/////////////////////////////  THREAD METHODS /////////////////////////   

    public void run ()
      {      
      boolean noMoreDice = false;
      
      // the following loop keeps running until the game stops, usually
      // because the player reloads or leaves the game
      
      while (runner != null)
        {
        //  System.out.println("RUN: " + j);             
        if (!pauseThread & !playAgain)
           {
           
           // gc means garbage collection; since Java garbage collection
           // runs as a low priority thread, I was running out of memory
           // until I forced the system to collect garbage
           
           System.gc ();
           if (readyToStart)
              readyToStart = false;
           else
              if (gameTimer == 0 || readyToForce)
                 forceMove ();
              else   
                 if (noMoreDice || turnIsOver)
                    turn ();
                 else
                    handlePlayerTurn (); 
                    
            // the timer keeps track of quarter seconds, updating the
            // clock every four "ticks". 
            
            try
               {
               runner.sleep (250);
               }
            catch (InterruptedException e) 
               {
               System.out.println("InterruptedException 1");
               }            

            if (timerTick == 3)
               {
               timerTick = 0;
               gameTimer -= 1;
               repaintClock ();
               }
            else
               timerTick++;
        
            noMoreDice = true;
            for (int i = 0; i < 4; i++)
               noMoreDice = noMoreDice && diceArray[i].getValue () == 0;                               
            } 
       }
      readyToStart = true;          // shouldn't get here
      } 
    
    public void start ()
      {
      if (runner == null && readyToStart)
         {
         System.out.println("Starting PARCHEESI!");
         runner = new Thread (this);
         runner.setPriority(Thread.MIN_PRIORITY);
         runner.start ();         
         }
      }
         
    public void stop ()
      {
      if (runner != null)
         {
         System.out.println("Error: PARCHEESI stopped!");
         runner.stop ();
         runner = null;
         }       
      }
   
   public void handlePlayerTurn ()
   
   // handles case where user deselects a die or a pawn
   
      {
      if (selectedPawn != null)
         {
         repaintBoard ();
         if (deselectPawn)
            {
            // System.out.println("run: deselecting pawn");
                  
            selectedPawn.setVisible ();
            selectedPawn = null;
            deselectPawn = false;
            repaintBoard ();
            }
         else
             if (selectedPawn.isSelected ())
                selectedPawn.changeVisibility ();
         }

      if (selectedDie != null)
         {
         repaintDice ();
         if (deselectDie)
            {
            // System.out.println("run: deselecting die") ;
            selectedDie.setVisible ();                    
            selectedDie = null;
            deselectDie = false;
            repaintDice ();
            }                 
         else
            if (selectedDie.isSelected ())
               selectedDie.changeVisibility ();
         }
      }
                                                            
   public void turn ()
   
   // key method that proceeds through each turn and on to the next one
   
      {
      clearBlockades ();
      if (doubletCount == 0 && !newGame)
         {
         currentPlayer = (currentPlayer + 1) % 4;
         repaintBigPawn ();
         repaintMessage (colorName[currentPlayer] + "'s turn!");
         }
      if (newGame)
         {
         repaintBigPawn ();
         repaintMessage (colorName[currentPlayer] + " goes first!");
         System.out.println(colorName[currentPlayer] + " goes first!");
         newGame = false;
         }
      turnIsOver = false;
      for (int i = 0; i < 4; i++)
          diceArray[i].clear ();
      repaintDice ();
      System.gc ();
      System.out.println("-------- " + colorName[currentPlayer] + " --------");
      if (switchPlayerFlag > -1)
         {
         switchPlayer (switchPlayerFlag);
         switchPlayerFlag = -1;
         }
      startBlockades ();
      rollDice ();
      if (turnIsOver)
      try
         {
         runner.sleep (1500);
         }
      catch (InterruptedException e) 
         {
         System.out.println("InterruptedException 9a");
         }            

      gameTimer = maxTime;
      repaintClock ();
      readyToForce = players[currentPlayer] == 1;
      }
   
   public void clearBlockades ()
   
   // Rule: you can't advance a blockade to create a new one. Therefore, I
   // have to keep track of every blockade every pawn of the current player
   // is in during a turn so that they can't create a blockade with pawns
   // that were already in a blockade this turn. This method and the next
   // keep track of that.
   
      {
      for (int i = 0; i < 100; i++)
         for (int j = 0; j < 2; j++)
            if (gameBoard[i][j] != null &&
                  gameBoard[i][j].getColor () == currentPlayer && 
                  !gameBoard[i][j].isHome () )  // not home
               gameBoard[i][j].clearBlockade ();      
      }
      
   public void startBlockades ()
      {
      for (int i = 0; i < 100; i++)
         for (int j = 0; j < 2; j++)
            if (gameBoard[i][j] != null &&
                  gameBoard[i][j].getColor () == currentPlayer && 
                  !gameBoard[i][j].isHome () &&  // not home
                  gameBoard[i][1 - j] != null && // a pawn is next door
                  gameBoard[i][1 - j].getColor () == currentPlayer)
                  
                Pawn.setBlockades (gameBoard[i][j], gameBoard[i][1 - j]);                     
       }
   
   public void switchPlayer (int thisPlayer)
   
   // handles case when player clicks on smiley/computer icon
   
      {
      players[thisPlayer] = 1 - players[thisPlayer];
      selectedPlayer = thisPlayer;
      repaintPlayer ();
      }

////////////////////////// COMPUTER MOVES ///////////////////////////

// I placed computer moves by the thread methods, because they run on the
// thread.
      
   public void forceMove ()
   
   // This method handles the case when player clicks on "Done with turn" or
   // allows timer to expire, as well as when it's the computer's turn. Keep
   // in mind that the computer uses the same strategy in either case.
   
      {
      if (turnIsOver)
         finishTurn ();
      else
         {
         if (players[currentPlayer] == 0)
            {
            repaintMessage ("Computer finishes your move."); 
            System.out.println("Computer finishes your move.");
            }
         readyToForce = false;
      
            if (turnIsOver)
               finishTurn();
            else
               {
               deselectAll ();
               pauseThread = true;
      
               while (computerMove ())
                  {}
                  try
                  {
                  runner.sleep (700);
                  }
                  catch (InterruptedException e)
                     {
                     System.out.println("InterruptedException 2");
                     }            
 
               deselectAll ();
               pauseThread = false; 
               if (!playAgain) 
                  turn ();
            }
         }
      }
      
   public void finishTurn ()
      {
      deselectAll ();
      try
         {
         runner.sleep (700);
         }
      catch (InterruptedException e) 
         {
         System.out.println("InterruptedException 3");
         }            

      turn ();
      }
   
   public void deselectAll ()
      {
      if (selectedPawn != null)
         {
         selectedPawn.setVisible ();
         selectedPawn.deselect ();
         deselectPawn = true;
         }
      if (selectedDie != null)
         {
         selectedDie.setVisible ();
         selectedDie.deselect ();
         deselectDie = true;
         }      
      }
      
   public boolean computerMove ()
   
   // This method is the key to the computer's strategy. It tries to move
   //  pawns in accordance with the order of calls you see here.
   
      {
      if (computerEnter ())
         return true;
         
      if (computerCapture ())
         return true;
         
      if (computerHome ())
         return true;
         
      if (turnIsOver)
         return false;
         
      if (computerBlock ())
         return true;
         
      if (computerSafety ())
         return true;
         
      if (computerOpenEnter ())
         return true;
         
      if (computerDontPass ())
         return true;
         
      if (computerMoveOffWhite ())
         return true;
        
      if (computerMoves ())
         return true;
         
      computerCurses ();
         
      return false;
      }  
      
   public boolean computerEnter ()
   
   // You must enter pawns if you can, so that is top priority
   
      {
      int dieSum = diceArray[0].getValue () + diceArray[1].getValue ();
      if (dieSum == 5 || diceArray[0].getValue () == 5 || diceArray[1].getValue () == 5)
         {
         for (int i = 0; i < 4; i++)
            if (myPawns[currentPlayer][i].isAtStart () &&
                  myPawns[currentPlayer][i].canMove (1) != null)
               {
               System.out.println ("Entering a pawn");
               repaintDice ();
               useBothDice = dieSum == 5;
               if (useBothDice)
                   selectedDie = diceArray[0];
               else
                  for (int j = 0; j < 4; j++)
                     if (diceArray[j].getValue () == 5)
                           selectedDie = diceArray[j];
                movePawn (myPawns[currentPlayer][i], 1);
                if (players[currentPlayer] == 1)
                   chat(currentPlayer, "Yes!!");
                return true;
                }
          }
       return false;      
      }
            
   public boolean computerCapture ()
   
   // Next, the computer tries to capture pawns
   
      {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn neighbor = myPawns[currentPlayer][i].canMove (thisValue);
                if (neighbor != null)
                  if (neighbor.getColor () != currentPlayer)
                     {
                     repaintDice ();
                     System.out.println ("Capturing!");                           
                     selectedDie = diceArray[j];
                     movePawn (myPawns[currentPlayer][i], thisValue); 
                     if (players[currentPlayer] == 1)
                        chat (currentPlayer, "Gotcha! Ha! Ha! Ha!");
                     return true;
                     }
               }
      return false;            
      }
            
   public boolean computerHome ()
   
   // Third, the computer tries to move a pawn Home
   
          {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0)     // for a given die roll
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn neighbor = myPawns[currentPlayer][i].canMove (thisValue);
               if (neighbor != null &&
                     myPawns[currentPlayer][i].getPathPosition () + thisValue == 72)
                     {
                     repaintDice ();
                     System.out.println ("Going home!");                           
                     selectedDie = diceArray[j];
                     movePawn (myPawns[currentPlayer][i], thisValue); 
                     if (players[currentPlayer] == 1)
                        chat (currentPlayer, "What a relief!");
                     return true;
                     }
               }
      return false;            
      }
            
   public boolean computerBlock ()
   
   // Next, computer tries to form a blockade
   
            {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn neighbor = myPawns[currentPlayer][i].canMove (thisValue);
                if (neighbor != null)
                  if (neighbor != myPawns[currentPlayer][i] && neighbor.getColor () == currentPlayer)
                     {
                     repaintDice ();
                     System.out.println ("Creating a block.");                           
                     selectedDie = diceArray[j];
                     movePawn (myPawns[currentPlayer][i], thisValue); 
                     if (players[currentPlayer] == 1)
                        chat (currentPlayer, "Take that!");
                     return true;
                     }
               }
      return false;            
      }
            
   public boolean computerSafety ()
   
   // Next, the computer tries to move a pawn to a safety square
   
      {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn neighbor = myPawns[currentPlayer][i].canMove (thisValue);
                if (neighbor != null)
                  {
                  int newPath = myPawns[currentPlayer][i].getPathPosition () + thisValue; 
                  int newPosition = Pawn.getPawnPath (currentPlayer, newPath);
                  if (newPath > 64 || isSafetySquare (Pawn.getPawnPath (currentPlayer, newPath) ))
                     {
                     for (int k = 0; k < 4; k++)
                        if (k != currentPlayer && isEnter (k, newPosition) && !allPawnsEntered (k) )
                           return false;          // space is enter square of color with pawns in Start
                           
                     repaintDice ();
                     System.out.println ("Entering safety square");                           
                     selectedDie = diceArray[j];
                     movePawn (myPawns[currentPlayer][i], thisValue); 
                     if (players[currentPlayer] == 1)
                        chat (currentPlayer, "Ahhh.");
                     return true;
                     }
                  }
               }
      return false;            
      }
            
   public boolean computerOpenEnter ()
   
   // If there are still pawns to be entered, it's important not to block
   // your own enter space!
   
      {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn thisPawn = myPawns[currentPlayer][i];
               Pawn neighbor = thisPawn.canMove (thisValue);
                if (neighbor != null)
                  if (thisPawn.getPathPosition () == 1 &&            // in Enter space
                        !allPawnsEntered (currentPlayer) &&          // still a pawn in Start
                        gameBoard[thisPawn.getPosition ()][1 - thisPawn.getSpace ()] != null)
                                                                     // currently has a neighbor
                     {
                     repaintDice ();
                     System.out.println ("Moving off enter");                           
                     selectedDie = diceArray[j];
                     movePawn (thisPawn, thisValue); 
                     return true;
                     }
               }
      return false;            
      }
            
   public boolean computerDontPass ()
   
   // I didn't want the computer to be terribly smart, but I wanted it to
   // play a pretty good game. So I figured if it could move a pawn without
   // passing an opponent's pawn, that pawn would be less likely to be 
   // captured. Works pretty well!
   
      {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn neighbor = myPawns[currentPlayer][i].canMove (thisValue);
                if (neighbor != null)
                  if (myPawns[currentPlayer][i].wontPass (thisValue) )
                     {
                     repaintDice ();
                     System.out.println ("Moving without passing opponent's pawn");                           
                     selectedDie = diceArray[j];
                     movePawn (myPawns[currentPlayer][i], thisValue); 
                     return true;
                     }
               }
      return false;            
      }
   
   public boolean computerMoveOffWhite ()
   
   // If all else fails, it's better not to move off of a safety square
   
      {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn thisPawn = myPawns[currentPlayer][i];
               Pawn neighbor = thisPawn.canMove (thisValue);
                if (neighbor != null && !isSafetySquare (thisPawn.getPosition () ))
                  if (neighbor == thisPawn || neighbor.getColor () != thisPawn.getColor () )
                     {   
                     repaintDice ();
                     System.out.println ("Moving off white square, passing opponent's pawn");                           
                     selectedDie = diceArray[j];
                     movePawn (thisPawn, thisValue); 
                     return true;
                     }
               }
      return false;            
      }   
            
   public boolean computerMoves ()
   
   // No good moves left, so it moves what it can
   
      {
      int thisValue = 0;
      for (int i = 0; i < 4; i++)                 // pawns
         for (int j = 0; j < 4; j++)              // dice
            if (diceArray[j].getValue () > 0 &&   // for a given die roll
                  !myPawns[currentPlayer][i].isAtStart () )     // not at Start
               {
               thisValue = adjustValue (diceArray[j].getValue ());
               Pawn neighbor = myPawns[currentPlayer][i].canMove (thisValue);
                if (neighbor != null)
                   {   
                   repaintDice ();
                     System.out.println ("Passing opponent's pawn");                           
                   selectedDie = diceArray[j];
                   movePawn (myPawns[currentPlayer][i], thisValue); 
                   return true;
                   }
               }

      return false;            
      } 
      
      public void computerCurses ()
      
      // Curses! There are dice that cannot be used!
      
         {
         for (int i = 0; i < 4; i++)
            if (diceArray[i].getValue () > 0)
               {
               System.out.println ("Wasted dice!");
               if (players[currentPlayer] == 1)
                  chat (currentPlayer, "Curses!");
               break;
               }
         }

/////////////////////////////  INITIALIZATION METHODS /////////////////////////   
       
   public void init()
   
   // Game begins here! Start by drawing the screen.
   
      { 
      setBackground (Color.white);
      resize (bgW, bgH);
      initGraphics ();
     
      chatField = new TextField (24);
      sendButton = new Button ("Start");
      chatArea = new TextArea (6, 32);
      doneButton = new Button ("Done with turn");
      
      northPanel = new Panel ();
      northPanel.setLayout (new FlowLayout(FlowLayout.CENTER));
      northPanel.add (doneButton);
                  
      messagePanel = new Panel();
      messagePanel.setLayout (new FlowLayout(FlowLayout.CENTER));
      messagePanel.add (chatField);
      messagePanel.add (sendButton);
            
      cornerPanel = new Panel();
      cornerPanel.setLayout(new BorderLayout());
      cornerPanel.add ("North", northPanel);
      cornerPanel.add ("Center", chatArea);
      cornerPanel.add ("South", messagePanel);
      
      blankPanel = new Panel ();
      
      GridBagLayout gbl = new GridBagLayout();
      setLayout (gbl);
      GridBagConstraints gbc = new GridBagConstraints();
      
      gbc.fill = GridBagConstraints.NONE;
      gbc.weightx = 100;
      gbc.weighty = 100;
      gbc.gridx = 1;
      gbc.gridy = 1;
      gbc.gridwidth = 1;
      gbc.gridheight = 1;
      gbc.anchor = GridBagConstraints.SOUTHEAST;
      gbl.setConstraints (cornerPanel, gbc);
      add (cornerPanel);
                                                               
      getMedia();
            
      initGame ();
      }

   public void initGraphics ()
   
   // How to you create an applet that updates different parts of the screen
   // at different times -- efficiently? Maybe you need multiple graphics 
   // contexts! 
   
      {
      bgImage = createImage (bgW, bgH);
      bgG = bgImage.getGraphics ();
      
      boardImage = createImage (boardW, boardH);
      boardG = boardImage.getGraphics ();
      
      diceImage = createImage (diceW, diceH);
      diceG = diceImage.getGraphics ();
      
      clockImage = createImage (clockW, clockH);
      clockG = clockImage.getGraphics ();
      
      messageImage = createImage (messageW, messageH);
      messageG = messageImage.getGraphics ();
      
      playerImage = createImage (playerW, playerH);
      playerG = playerImage.getGraphics ();
      
      activeImage = createImage (activeW, activeH);
      activeG = activeImage.getGraphics ();     
      }

   public void initGame()
      {
      for (int i = 0; i < 4; i++)
         diceArray[i] = new Die (i, 0);
      chatMessage = "";
      chatText = "You!";  
      systemMessage = "Enter your name and click \"Start\" to begin!";  
      currentPlayer = (int) (Math.random() * 4);
      
      for (int i = 0; i < 104; i++)
         for  (int j = 0; j < 4; j++)
            p[i][j] = new Point (0, 0);
            
      initBoard ();
       
      // testPawns ();  // testing only
      initPawns ();
      
      initPaint ();
      
      quickPaint ();
      
      gameTimer = maxTime;

      repaintInit ();
     
      for (int pawn0 = 0; pawn0 < 4; pawn0++)
         for (int die0 = 0; die0 < 2; die0++)
            for (int pawn1 = 0; pawn1 < 4; pawn1++)
               for (int die1 = 0; die1 < 2; die1++)
                  for (int pawn2 = 0; pawn2 < 4; pawn2++)
                     for (int die2 = 0; die2 < 2; die2++)
                        for (int pawn3 = 0; pawn3 < 4; pawn3++)
                           for (int die3 = 0; die3 < 2; die3++)
                              moveArray[pawn0][die0][pawn1][die1][pawn2][die2][pawn3][die3] = false;
      }

   public void initAgain()
   
   // the initialization that needs to be done when a game is finished
   // and the player wants to play again
   
      {
      currentPlayer = (int) (Math.random() * 4);
      gameTimer = maxTime;
      timerTick = 3;
      
      for (int i = 0; i < 4; i++)
         for  (int j = 0; j < 4; j++)
            myPawns[i][j] = null;
            
      for (int i = 0; i < 104; i++)
         for  (int j = 0; j < 4; j++)
            gameBoard[i][j] = null;
       
      initPawns ();
     
      repaintInit ();
      }
   
   public void testPawns ()
   
   // this method is only called when debugging -- it allows me
   // to place the pawns anywhere I want. Essential for testing the
   // end of the game!
   
      {
         // red
       gameBoard[70][0] = new Pawn (0, 70, 0);
       myPawns[0][0] = gameBoard[70][0];
       
       gameBoard[72][1] = new Pawn (0, 72, 1);
       myPawns[0][1] = gameBoard[72][1];  
       
       gameBoard[71][0] = new Pawn (0, 71, 0);
       myPawns[0][2] = gameBoard[71][0];  
       
       gameBoard[73][1] = new Pawn (0, 73, 1);
       myPawns[0][3] = gameBoard[73][1];  
       
       
         // green
       gameBoard[78][0] = new Pawn (1, 78, 0);
       myPawns[1][0] = gameBoard[78][0];  
       
       gameBoard[80][1] = new Pawn (1, 80, 1);
       myPawns[1][1] = gameBoard[80][1];  
       
       gameBoard[79][0] = new Pawn (1, 79, 0);
       myPawns[1][2] = gameBoard[79][0];  
       
       gameBoard[81][1] = new Pawn (1, 81, 1);
       myPawns[1][3] = gameBoard[81][1];  
       
       
         // yellow
       gameBoard[87][0] = new Pawn (2, 87, 0);
       myPawns[2][0] = gameBoard[87][0];  
       
       gameBoard[89][1] = new Pawn (2, 89, 1);
       myPawns[2][1] = gameBoard[89][1];  
       
       gameBoard[88][0] = new Pawn (2, 88, 0);
       myPawns[2][2] = gameBoard[88][0];  
       
       gameBoard[90][1] = new Pawn (2, 90, 1);
       myPawns[2][3] = gameBoard[90][1];  
       
       
         // blue
       gameBoard[95][0] = new Pawn (3, 95, 0);
       myPawns[3][0] = gameBoard[95][0];  
       
       gameBoard[97][1] = new Pawn (3, 97, 1);
       myPawns[3][1] = gameBoard[97][1];  
       
       gameBoard[96][0] = new Pawn (3, 96, 0);
       myPawns[3][2] = gameBoard[96][0];  
       
       gameBoard[98][1] = new Pawn (3, 98, 1); 
       myPawns[3][3] = gameBoard[98][1];  
       
       }
      
   
   public void initPawns ()
   
   // each pawn is kept in two places at all times: on the gameboard,
   // and in the myPawns array. This allows me to index over the board
   // or over every one of a player's pawns as needed. Since pawns
   // are objects, the SAME pawn can be in both arrays! Remember, Java
   // objects retain the best features of pointers.
   
      {
      for (int i = 0; i < 4; i++)
         for  (int j = 0; j < 4; j++)
            myPawns[i][j] = null;
            
      for (int i = 0; i < 104; i++)
         for  (int j = 0; j < 4; j++)
            gameBoard[i][j] = null;
            
      for (int i = 0; i < 4; i++)       // color
         for (int j = 0; j < 4; j++)    // space
            {
            gameBoard[i + 100][j] = new Pawn (i, i + 100, j);
            myPawns[i][j] = gameBoard[i + 100][j];   
            }
      }
           
   public void getMedia ()
   
   // gets the gifs and the sound
   
      {
      MediaTracker tracker;
      tracker = new MediaTracker (this);
                 
      tick = getAudioClip (getCodeBase(), "tick.au");
      
      startcircle = getImage(getCodeBase(), "circle.gif");
      tracker.addImage (startcircle, 0);
      try
         {
         tracker.waitForID (0);
         }
      catch (InterruptedException e)
               {
               System.out.println("InterruptedException 4");
               }            

      
      for (int i = 0; i < 9; i++)
         {
         diceImages [i] = getImage (getCodeBase(), "die" + i + ".gif");
         tracker.addImage (diceImages [i], 1 + i);         
         try
            {
            tracker.waitForID (1 + i);
            }
         catch (InterruptedException e)
               {
               System.out.println("InterruptedException 5");
               }            
         }
         
      for (int i = 0; i < 4; i++)
         {
         pawnImages [i][0] = getImage (getCodeBase(), "pawn" + i + ".gif");
         tracker.addImage (pawnImages [i][0], 10 + i);
         try
            {
            tracker.waitForID (10 + i);
            }
         catch (InterruptedException e)
               {
               System.out.println("InterruptedException 6");
               }            

         pawnImages [i][1] = getImage (getCodeBase(), "pawnm" + i + ".gif");
         tracker.addImage (pawnImages [i][1], 10 + i);
         try
            {
            tracker.waitForID (10 + i);
            }
         catch (InterruptedException e)
               {
               System.out.println("InterruptedException 6a");
               }
                           
         pawnImages [i][2] = getImage (getCodeBase(), "pawnl" + i + ".gif");
         tracker.addImage (pawnImages [i][2], 10 + i);
         try
            {
            tracker.waitForID (10 + i);
            }
         catch (InterruptedException e)
               {
               System.out.println("InterruptedException 6b");
               }            

         }

      playerIcon[1] = getImage(getCodeBase(), "computer.gif");
      tracker.addImage (playerIcon[1], 14);
      try
         {
         tracker.waitForID (14);
         }
      catch (InterruptedException e)
               {
               System.out.println("InterruptedException 7");
               }            
      
      playerIcon[0] = getImage(getCodeBase(), "face.gif");
      tracker.addImage (playerIcon[0], 15);
      try
         {
         tracker.waitForID (15);
         }
      catch (InterruptedException e)
               {
               System.out.println("InterruptedException 8");
               }               
      } 
           
   public void initBoard ()
   
   // This was a fun one to write. There are 104 places pawns can
   // be on the board (allowing for separate Home spaces for each
   // player). How does it know where they are on the board, and 
   // what order they are in? Thank god for the Point object!
   
      {
      for (int i = 4; i <= 11; i++)
         p[i][0].x = 174;
         
      for (int i = 47; i <= 54; i++)
         p[i][0].x = 174;
         
      for (int i = 13; i <= 20; i++)
         p[i][0].x = 108;
         
      for (int i = 38; i <= 45; i++)
         p[i][0].x = 108;
      
      p[12][0].x = 141;
      p[46][0].x = 141;
         
      for (int i = 76; i <= 82; i++)
         p[i][0].x = 141;

      for (int i = 92; i <= 98; i++)
         p[i][0].x = 141;
         
      
      for (int i = 0; i <= 3; i++)
         p[i][0].y = 108;
         
      for (int i = 21; i <= 28; i++)
         p[i][0].y = 108;
         
      for (int i = 64; i <= 67; i++)
         p[i][0].y = 108;
         
      for (int i = 30; i <= 37; i++)
         p[i][0].y = 174;
      
      for (int i = 55; i <= 62; i++)
         p[i][0].y = 174;
         
         p[29][0].y = 141;
         p[63][0].y = 141;       
         
      for (int i = 68; i <= 79; i++)
         p[i][0].y = 141;

      for (int i = 84; i <= 90; i++)
         p[i][0].y = 141;
         
         
      for (int i = 4; i <= 10; i++)
         p[i][0].y = 5 + (11 - i) * 13;
         
      for (int i = 14; i <= 20; i++)
         p[i][0].y = 5 + (i - 13) * 13;
         
      for (int i = 11; i <= 13; i++)
         p[i][0].y = 5;
         
      for (int i = 4; i <= 10; i++)
         p[i][0].y = 5 + (11 - i) * 13;
         
      for (int i = 92; i <= 98; i++)
         p[i][0].y = 5 + (i - 91) * 13;
         
         
      for (int i = 48; i <= 54; i++)
         p[i][0].y = 201 + (54 - i) * 13;
         
      for (int i = 38; i <= 44; i++)
         p[i][0].y = 201 + (i - 38) * 13;
                  
      for (int i = 45; i <= 47; i++)
         p[i][0].y = 292;
         
      for (int i = 76; i <= 82; i++)
         p[i][0].y = 201 + (82 - i) * 13;
         

      for (int i = 0; i <= 3; i++)
         p[i][0].x = 201 + (3 - i) * 13;
         
      for (int i = 65; i <= 67; i++)
         p[i][0].x = 253 + (67 - i) * 13;
         
      for (int i = 55; i <= 61; i++)
         p[i][0].x = 201 + (i - 55) * 13;
         
      for (int i = 62; i <= 64; i++)
         p[i][0].x = 292;
         
      for (int i = 68; i <= 74; i++)
         p[i][0].x = 201 + (74 - i) * 13;
         
         
      for (int i = 21; i <= 27; i++)
         p[i][0].x = 5 + (28 - i) * 13;
         
      for (int i = 31; i <= 37; i++)
         p[i][0].x = 5 + (i - 30) * 13;
         
      for (int i = 28; i <= 30; i++)
         p[i][0].x = 5;
         
      for (int i = 84; i <= 90; i++)
         p[i][0].x = 5 + (i - 83) * 13;
         
         
      p[75][0].x = 115;   p[75][0].y = 115;   
      p[83][0].x = 115;   p[83][0].y = 135;   
      p[91][0].x = 115;   p[91][0].y = 155;   
      p[99][0].x = 115;   p[99][0].y = 175;   
              
      p[100][0].x = 235;  p[100][0].y = 49;   
      p[101][0].x = 235;  p[101][0].y = 249;   
      p[102][0].x = 35;   p[102][0].y = 249;   
      p[103][0].x = 35;   p[103][0].y = 49; 
      
      for (int i = 0; i <= 3; i++)
         incY (i);
     
      for (int i = 21; i <= 37; i++)
         incY (i);
     
      for (int i = 55; i <= 74; i++)
         incY (i);
     
      for (int i = 84; i <= 90; i++)
         incY (i);
         
     
      for (int i = 4; i <= 20; i++)
         incX (i);
     
      for (int i = 38; i <= 54; i++)
         incX (i);
     
      for (int i = 76; i <= 82; i++)
         incX (i);
     
      for (int i = 92; i <= 98; i++)
         incX (i);
         
         
      incX (75);
      incX (83);
      incX (91);
      incX (99);
      
      for (int i = 100; i < 104; i++)                        
         incX (i);           
      }
      
   public void incY (int i)
         {
         for (int j = 1; j < 4; j++)
            {
            p[i][j].x = p[i][0].x;
            p[i][j].y = p[i][0].y + j * 15;
            }
         }

   public void incX (int i)
         {
         for (int j = 1; j < 4; j++)
            {
            p[i][j].y = p[i][0].y;
            p[i][j].x = p[i][0].x + j * 15;
            }
         }

///////////////////////// INIT PAINTING METHODS //////////////////////////

// I grouped all the methods that act on graphics contexts together. Keep 
// in mind that different methods work on different graphics contexts!

   public void initPaint ()
      { 
      drawCircles (boardG);
      
      drawBoard (boardG);
      
      boardG.dispose ();            
      }

   public void drawCircles (Graphics g)
      {     
      g.setColor (Color.white);
      g.fillRect (0, 0, 305, 305);
         
      g.drawImage (startcircle, 6, 6, this);
      g.drawImage (startcircle, 205, 6, this);
      g.drawImage (startcircle, 6, 205, this);
      g.drawImage (startcircle, 205, 205, this);
      }      

   public void drawBoard (Graphics g)
   
   // Yes, I draw all the rectangles on the board one by one!
         {     
         g.setColor (Color.black);
         g.draw3DRect (5, 5, 300, 300, true);
                
         g.setColor (Color.red);
         g.fillRect (138, 18, 33, 7 * 13);
         g.fillRect (138, 201, 33, 7 * 13);
         g.fillRect (18, 138, 7 * 13, 33);
         g.fillRect (201, 138, 7 * 13, 33);
         
         g.setColor (lightBlue);
         g.fillRect (138, 5, 33, 13);
         g.fillRect (105, 5 + 4 * 13, 33, 13);
         g.fillRect (171, 5 + 4 * 13, 33, 13);
         
         g.fillRect (138, 292, 33, 13);
         g.fillRect (105, 305 - 5 * 13, 33, 13);
         g.fillRect (171, 305 - 5 * 13, 33, 13);
         
         g.fillRect (5, 138, 13, 33);
         g.fillRect (5 + 4 * 13, 105, 13, 33);
         g.fillRect (5 + 4 * 13, 171, 13, 33);
         
         g.fillRect (292, 138, 13, 33);
         g.fillRect (305 - 5 * 13, 105, 13, 33);
         g.fillRect (305 - 5 * 13, 171, 13, 33);
         
         g.setColor(medBlue);
         g.fillOval(115, 5 + 4 * 13, 13, 13);
         Polygon bluePoly = new Polygon();
         bluePoly.addPoint(5 + 100 + 33 + 10, 5 + 7 * 13);
         bluePoly.addPoint(5 + 100 + 33 + 20, 5 + 7 * 13);
         bluePoly.addPoint(5 + 100 + 33 + 15, 5 + 8 * 13);
         bluePoly.addPoint(5 + 100 + 33 + 10, 5 + 7 * 13);         
         g.fillPolygon(bluePoly);

         g.setColor(lightGreen);
         g.fillOval(105 + 66 + 10, 305 - 5 * 13, 13, 13);
         Polygon greenPoly = new Polygon();
         greenPoly.addPoint(5 + 100 + 33 + 10, 305 - 7 * 13);
         greenPoly.addPoint(5 + 100 + 33 + 20, 305 - 7 * 13);
         greenPoly.addPoint(5 + 100 + 33 + 15, 305 - 8 * 13);
         greenPoly.addPoint(5 + 100 + 33 + 10, 305 - 7 * 13);
        g.fillPolygon(greenPoly);
         
         g.setColor(lightYellow);
         g.fillOval(5 + 4 * 13, 105 + 66 + 10, 13, 13);
         Polygon yellowPoly = new Polygon();
         yellowPoly.addPoint(5 + 7 * 13, 5 + 100 + 33 + 10);
         yellowPoly.addPoint(5 + 7 * 13, 5 + 100 + 33 + 20);
         yellowPoly.addPoint(5 + 8 * 13, 5 + 100 + 33 + 15);
         yellowPoly.addPoint(5 + 7 * 13, 5 + 100 + 33 + 10);
         g.fillPolygon(yellowPoly);
                  
         g.setColor(pink);
         g.fillOval(305 - 5 * 13, 105 + 10, 13, 13);
         Polygon pinkPoly = new Polygon();
         pinkPoly.addPoint(305 - 7 * 13, 5 + 100 + 33 + 10);
         pinkPoly.addPoint(305 - 7 * 13, 5 + 100 + 33 + 20);
         pinkPoly.addPoint(305 - 8 * 13, 5 + 100 + 33 + 15);
         pinkPoly.addPoint(305 - 7 * 13, 5 + 100 + 33 + 10);
         g.fillPolygon(pinkPoly);
                  
         g.setColor (Color.black);
         
         g.drawOval(115, 5 + 4 * 13, 13, 13);
         g.drawOval(105 + 66 + 10, 305 - 5 * 13, 13, 13);
         g.drawOval(5 + 4 * 13, 105 + 66 + 10, 13, 13);
         g.drawOval(305 - 5 * 13, 105 + 10, 13, 13);
         
         g.drawPolygon(bluePoly);
         g.drawPolygon(greenPoly);
         g.drawPolygon(yellowPoly);
         g.drawPolygon(pinkPoly);
         
         for (int i = 100; i < 199; i += 33)
            for (int j = 0; j < 104; j += 13)
               g.drawRect (i + 5, j + 5, 33, 13);
            
         for (int i = 100; i < 199; i += 33)
            for (int j = 196; j < 300; j += 13)
               g.drawRect (i + 5, j + 5, 33, 13);
         
         for (int i = 100; i < 199; i += 33)
            for (int j = 0; j < 104; j += 13)
               g.drawRect (j + 5, i + 5, 13, 33);
         
         for (int i = 100; i < 199; i += 33)
            for (int j = 196; j < 300; j += 13)
               g.drawRect (j + 5, i + 5, 13, 33);

         g.setFont (new Font ("Helvetica", Font.BOLD, 12));         
         g.drawString("HOME", 137, 160);      
        } 
 
    public void drawPawns (Graphics g)
      {
      for (int i = 0; i < 4; i++)
         for (int j = 0; j < 4; j++)
            drawPawn (myPawns[i][j], activeG);
      }      

   public void drawPlayers (Graphics g)
   
   // draws the players' names and icons
   
      {                   

      int x = 0;
      int y = 0;
      int xDel = 140;
      int yDel = 30;
      g.setFont (smallFont);         
       
      g.setColor (Color.white);
      g.fillRect (0, 0, playerW, playerH);
      g.setColor (Color.black);
      
      g.drawImage (pawnImages[0][1], x, y, this);
      g.drawImage (playerIcon[players[0]], x + 28, y, this);  
      g.drawString(playerName[0], x + 56, y + 16);      

      g.drawImage (pawnImages[1][1], x, y + yDel, this);
      g.drawImage (playerIcon[players[1]], x + 28, y + yDel, this);  
      g.drawString(playerName[1], x + 56, y + yDel + 16);      
         
      g.drawImage (pawnImages[2][1], x + xDel, y, this);
      g.drawImage (playerIcon[players[2]], x + xDel + 28, y, this);  
      g.drawString(playerName[2], x + xDel + 56, y + 16);      

      g.drawImage (pawnImages[3][1], x + xDel, y + yDel, this);
      g.drawImage (playerIcon[players[3]], x + xDel + 28, y + yDel, this);  
      g.drawString(playerName[3], x + xDel + 56, y + yDel + 16);                      
      } 
       
////////////////////// REGULAR PAINTING METHODS /////////////////////
      
   public void paint (Graphics g)
   
   // I write every graphics context to the background image bgImage, 
   // so all paint has to do is draw it.
      {
      // System.out.println("In paint, x=" + clipX + " y=" + clipY + " w=" + clipW + " h=" + clipH);
         
      g.drawImage (bgImage, bgX, bgY, this);
      quickPaintFlag = false;
      }
            
   public void update (Graphics g)
   
   // To improve speed, update only paints the rectangle that needs to be painted
   
      {
      g.clipRect (clipX, clipY, clipW, clipH);
      paint (g);
      }

   public void repaintInit ()
   
   // what I got to paint to start the game.
   
      {
      activeG.drawImage (boardImage, boardX, boardY, this);

      drawClock (clockG);

      drawPawns (activeG);                    
      
      drawPlayers (playerG); 
      
      drawMessage (messageG);

      bgG.setColor (Color.white);
      bgG.fillRect (0, 0, bgW, bgH);
      bgG.drawImage (messageImage, messageX, messageY, this);
      bgG.drawImage (clockImage, clockX, clockY, this);
      bgG.drawImage (activeImage, activeX, activeY, this);
      bgG.drawImage (playerImage, playerX, playerY, this);                      

      clipX = bgX;
      clipY = bgY;
      clipW = bgW;
      clipH = bgH;
      quickPaint ();
      }
 
   public void repaintDice ()
      {
      clipX = diceX;
      clipY = diceY;
      clipW = diceW; 
      clipH = diceH;
      drawDice (diceG);
      bgG.drawImage (diceImage, diceX, diceY, this);       
      quickPaint ();
      }

   public void repaintBoard ()
   
   // The empty board is only drawn to boardImage once; this method
   // puts the pawns on it and repaints it as a unit.
   
      {
      clipX = boardX;
      clipY = boardY;
      clipW = boardW;
      clipH = boardH;  
      activeG.drawImage (boardImage, boardX, boardY, this);
      
      drawPawns (activeG);

      bgG.drawImage (activeImage, activeX, activeY, this); 
         quickPaint ();
      }

  public void repaintClock ()
      {
      clipX = clockX;
      clipY = clockY;
      clipW = clockW;
      clipH = clockH;  
      drawClock (clockG);      
      bgG.drawImage (clockImage, clockX, clockY, this);            
      quickPaint ();
      }

   public void repaintMessage (String str)
      {
      clipX = messageX;
      clipY = messageY;
      clipW = messageW;
      clipH = messageH;   
      systemMessage = str;
      drawMessage (messageG);      
      bgG.drawImage (messageImage, messageX, messageY, this);                  
      quickPaint ();
      }

   public void repaintBigPawn ()
      {
      clipX = bigPawnX;
      clipY = bigPawnY;
      clipW = bigPawnW;
      clipH = bigPawnH;    
      drawBigPawn (bgG);             
      quickPaint ();
      }
      
   public void repaintPlayer ()
      {  
      clipX = playerX;
      clipY = playerY;
      clipW = playerW;
      clipH = playerH;   
      drawPlayers (playerG);      
      bgG.drawImage (playerImage, playerX, playerY, this); 
      quickPaint (); 
      }

   public void quickPaint ()
   
   // Most of the time, I want to draw pawns moving around the board. 
   // Because paint only schedules a screen redraw, I had the problem
   // of pawns disappearing and jumping to their destination, instead of 
   // moving one square at a time. To solve this problem, quickpaint sets
   // a flag, and waits until paint completes for the game to proceed. 
   // Therefore, even on the slowest computers, every pawn moves every
   // square.
   
      {
      quickPaintFlag = true;
      update (this.getGraphics ());
      
      while (quickPaintFlag)
         {
         try
            {
            System.out.println("Waiting...");
            Thread.sleep(10);
            }
         catch (InterruptedException e)
            {
            System.out.println("Interrupted Exception quickPaint");
            }
         }                 
      }
       
   public void drawPawn (Pawn currentPawn, Graphics g)
      {      
      if (currentPawn.getVisible ())
         {
         int col = currentPawn.getColor();
         int pos = currentPawn.getPosition();
         int sp = currentPawn.getSpace();
         g.drawImage (pawnImages[col][0], p[pos][sp].x, p[pos][sp].y, this); 
         }
      }      

   public void drawBigPawn (Graphics g)
      {
      g.drawImage (pawnImages[currentPlayer][2], bigPawnX, bigPawnY, this);      
      }
         
   public void drawClock (Graphics g)
      {
      g.setColor (Color.white);
      g.fillRect (0, 0, clockW, clockH);
      g.setColor (Color.black);
      
      g.setFont (bigFont);       
      g.drawString("Time: " + gameTimer, 0, (int) clockH / 3 + 1);        
      
      }
           
   public void drawDice (Graphics g)
      {
      // System.out.println("In drawdice, ready to draw dice");
      
      g.setColor (Color.white);
      g.fillRect (0, 0, diceW, diceH);
      
      for (int i = 0; i < 4; i++)
         if (diceArray[i].getVisible () && diceArray[i].getValue () > 0)
            g.drawImage (diceImages[diceArray[i].getValue ()], 
                  diceDelta * i, 0, this);
         else
            g.drawImage (diceImages[0], diceDelta * i, 0, this);   
      }
         
   public void drawMessage (Graphics g)
      {
      g.setColor(Color.white);
      g.fillRect (0, 0, messageW, messageH);
      g.setColor(Color.black);
      
      String str = Pawn.getMessage ();
      if (!str.equals (""))
         {
         systemMessage = str;
         Pawn.clearMessage ();
         }
      if (systemMessage.length () > 15)
         g.setFont (smallFont);
      else
         g.setFont (bigFont);       
      g.drawString(systemMessage, 0, (int) messageH / 3 + 1);  
      
      }

////////////////////////// GAMEPLAY METHODS /////////////////////////////
 
   public void checkMove ()
   
   // This method applies when the (human) player attempts to move. The computer
   // will never attempt an illegal move, but that can't be said about humans!
   
      {
      
      int dieSum = diceArray[0].getValue () + diceArray[1].getValue ();
      if (selectedPawn != null && selectedDie != null)
         {
         if (selectedPawn.isAtStart ())
            {
            // System.out.println("Pawn at enter");
            if (selectedDie.getValue () == 5)
               makeMove (true);
            else
               if (dieSum == 5)
                  {
                  //  System.out.println("must use both dice");
                  repaintMessage ("You must use both dice");
                  useBothDice = true;
                  makeMove (true);
                  }
               else
                  pawnStuck ("You can't move that pawn!");
            }
         else
            if ((dieSum == 5 || diceArray[0].getValue () == 5 || 
                  diceArray[1].getValue () == 5) &&
                  (gameBoard[Pawn.getPawnPath(currentPlayer, 1)][0] == null ||
                  gameBoard[Pawn.getPawnPath(currentPlayer, 1)][1] == null) &&
                  !allPawnsEntered (currentPlayer))
               pawnStuck("You must enter a pawn with a 5 first!");
            else
               if (shouldCheckDoublets () && selectedDie.getValue () <= 6)
                  if (canMoveDoublets (selectedPawn, selectedDie.getValue ()) )  
                     makeMove (false);
                  else
                     if (selectedPawn.canMove (selectedDie.getValue ()) != null)
                        // only illegal because of doublet rule
                        pawnStuck("Illegal move! Must move all doublets rolls.");
                     else
                        // illegal for another reason; following will fail
                        makeMove (false);
               else
                  makeMove (false);
          } 
      } 
 
   public void pawnStuck (String str)
      {
       //    System.out.println("Pawn is stuck");
      repaintMessage (str);
      selectedPawn.select ();
      deselectPawn = true;
      }
 
    public void makeMove (boolean entering)
    
    // We know the selected pawn can move, so let's move it
    
      {        
       //     System.out.println("Making move");
         gameTimer = maxTime;
         repaintClock ();
         selectedDie.setVisible ();
         selectedPawn.setVisible ();
         selectedPawn.select ();
         selectedDie.select ();
         pauseThread = true;
         int numSpaces = adjustValue (selectedDie.getValue ());
         if (entering)
            numSpaces = 1; 
         movePawn (selectedPawn, numSpaces);   
         deselectPawn = true;
         deselectDie = true;
         pauseThread = false;      
      }

   public int adjustValue (int val)
   
   // handles bonus roll values of 10 and 20
   
      {
      int numSpaces = val;
      if (numSpaces == 7)
          numSpaces = 10; 
      if (numSpaces == 8)
          numSpaces = 20;
      return  numSpaces;
      }

   public void movePawn (Pawn whichPawn, int numSpaces)
   
   // The player's pawn may move, even if the move turns out to be illegal.
   // If the move is blocked, say, the pawn will advance to the block,
   // get bounced back, and the appropriate message will be displayed.
   
      {
      // System.out.println("Ready to move pawn");
      int hasMoved = 0;
      boolean success;
      whichPawn.startPosition();
      do
         {
         hasMoved++;
         success = whichPawn.advancePawn(hasMoved == numSpaces);
         repaintBoard ();
         tick.play ();
         try
            {
            Thread.sleep (300);
            }
      catch (InterruptedException e)
               {
               System.out.println("InterruptedException 9");
               }                                           
          }
      while (hasMoved < numSpaces && success);
      if (success)
         {
         clearDie ();
         if (!checkHome (whichPawn))
            checkCapture (whichPawn);
            
         for (int i = 0; i < 4; i++)
            if (currentMove [i][0] == -1)
               {
               currentMove[i][0] = getPawnNum (whichPawn);
               currentMove[i][1] = (dv[0] == numSpaces) ? 0 : 1;
               break;
               }                  
         }
      else
         {
         repaintMessage (systemMessage);
         useBothDice = false;
         whichPawn.returnPosition(); 
         }
      }
   
   public void checkCapture (Pawn whichPawn)
   
   // handles the capturing of pawns
   
      {
      int thisPosition = whichPawn.getPosition ();
      int thisSpace = whichPawn.getSpace ();
      int targetColor = -1;
      if  (gameBoard[thisPosition][1 - thisSpace] != null)
         targetColor = gameBoard[thisPosition][1 - thisSpace].getColor ();
         
  //  System.out.println("pos: " + thisPosition + " sp: " + thisSpace + " col: " + targetColor);      
      if (thisPosition < 68)             // not on Home paths
         if (gameBoard[thisPosition][1 - thisSpace] != null &&
               targetColor != currentPlayer)
            {
            repaintMessage (colorName[currentPlayer] + " captured " + 
               colorName[targetColor] + "'s pawn!");
            gameBoard[thisPosition][1 - thisSpace].goStart ();
            gameBoard[thisPosition][1 - thisSpace] = null;
            int i = 0;
            while (diceArray[i].getValue () != 0)
               i++;
            diceArray[i].setValue (8); // really means 20
            repaintBoard ();
            repaintDice ();
            }      
      }
      
   public boolean checkHome (Pawn whichPawn)
   
   // Handles going home. Naturally, also handles the winning condition.
      {
      if (whichPawn.isHome ())
         {
         repaintMessage (colorName[currentPlayer] + " moves a pawn Home!");         
         if (whichPawn.getSpace () == 3)
            {
            repaintMessage (colorName[currentPlayer] + " WINS!! Click \"Start\" to play again.");
            sendButton.setLabel ("Start");
            System.out.println(colorName[currentPlayer] + " WINS!!");
            playAgain = true;
            pauseThread = true;
            doubletCount = 0;
            turnIsOver = true;
            // stop ();
            }
         else
            {
            int i = 0;
            while (diceArray[i].getValue () != 0)
               i++;
            diceArray[i].setValue (7); // really means 10
            repaintDice ();
            }
         }
      return whichPawn.isHome ();
      }            

   public static boolean isSafetySquare (int thisPosition)
      {
      return thisPosition == 7 || thisPosition == 12 || thisPosition == 17 || 
      thisPosition == 24 || thisPosition == 29 || thisPosition == 34 || 
      thisPosition == 41 || thisPosition == 46 || thisPosition == 51 || 
      thisPosition == 58 || thisPosition == 63 || thisPosition == 0;      
      }
         
   public void clearDie()
   
   // Dice aren't actually destroyed when they're used, their values are
   // just set to zero. It's easier to index over all dice without getting
   // null pointer errors this way.
   
      {
      if (useBothDice)
      
      // Rule: if you can enter and you roll a five on both dice (for example,
      // a 3 and a 2), you must use both dice to enter!
      
         {
         diceArray[0].clear ();
         diceArray[1].clear ();
         useBothDice = false;
         }
      else
         if (selectedDie == null)
            System.out.println("ERROR! Please don't click so quickly!");
         else
            selectedDie.clear ();
      repaintDice ();
      }

   public void rollDice()
   
   // Rolls both dice, handles doublets
   
      {
      // System.out.println("In rolldice, ready to paint");
      allPawnsEnteredAtBeginning = allPawnsEntered (currentPlayer);
      diceArray[0].rollDie ();
      diceArray[1].rollDie (); 
      
      // diceArray[1].setValue (diceArray[0].getValue ());   // testing only
      
      //   System.out.println("** rolled " + diceArray[0].getValue () +
      //   " and " + diceArray[1].getValue ());
      if (diceArray[0].getValue () == diceArray[1].getValue ())
         {
         repaintMessage ("Doublets!");
         doubletCount++;
         if (doubletCount == 3)           // actual=3; testing=?
            doubletsPenalty ();
         else
            if (allPawnsEnteredAtBeginning)
               {
               diceArray[2].setValue (7 - diceArray[0].getValue ());
               diceArray[3].setValue (7 - diceArray[0].getValue ());
               
               // Rule: if all your pawns are entered and you roll doublets,
               // you must use all four dice or you can't use any!
               
               if (!passDoublets ())
                  {
                  repaintMessage("Doublets! You can't move all 4 dice!");
                  turnIsOver = true;
                  }
               }
         }
      else
         doubletCount = 0;   
     repaintDice ();   
      if (players[currentPlayer] == 1)
         try
            {
            Thread.sleep (1000);
            }
         catch (InterruptedException e)              
               {
               System.out.println("InterruptedException 10");
               }            
      }
   
   public void doubletsPenalty ()
   
   // Roll doublets three times ends your turn and sends a pawn to Start.
   
      {
      repaintMessage ("Doublets penalty!");
      System.out.println("Doublets penalty!");                  
      Pawn target = null;
      int thePath = 0;
      int test = 0;
      for (int i = 0; i < 4; i++)
         {
         test = myPawns[currentPlayer][i].getPathPosition ();
         if (test != 0 && test != 72 && test > thePath)
            {
            thePath = test;
            target = myPawns[currentPlayer][i];
            }
         }
         
      if (target != null)
         {
         target.goStart ();  
         repaintBoard ();
         }                                           
              
      doubletCount = 0;
      turnIsOver = true;
      }
               
   public boolean passDoublets ()
   
   // This method and its associated methods took over a month to program!
   // If all your pawns are entered and you roll doublets, you must move all
   // four, if possible. Therefore, it is illegal to move a pawn that will
   // prevent you from using all four dice. So I need to know if a move
   // will create this situation. The best way? Calculate each and every 
   // possible move (all 1536 of them!) and designate which ones are legal.
   // Now, all I have to do is look up a move to see if it's okay.
   
   // This method uses all the tricks in the book to avoid extra processing.
   // It still bogs down a slow computer!
   
      {
      /* System.out.println("In passDoublets: " + 
         myPawns[currentPlayer][0].getPosition () + "/" + myPawns[currentPlayer][0].getSpace () + " " +
         myPawns[currentPlayer][1].getPosition () + "/" + myPawns[currentPlayer][1].getSpace () + " " +
         myPawns[currentPlayer][2].getPosition () + "/" + myPawns[currentPlayer][2].getSpace () + " " +
         myPawns[currentPlayer][3].getPosition () + "/" + myPawns[currentPlayer][3].getSpace ()); */
      System.out.println("Checking for legal moves...");
      int die2min, die2max, die3 = 0;
      boolean move0, move1, move2, move3, canmove = false;
      
      // int counter = 0;
      
      for (int i = 0; i < 4; i++)
         {
         for (int j = 0; j < 2; j++)
            currentMove[i][j] = -1;
         myPawns[currentPlayer][i].initialize ();
         }
      
      dv[0] = diceArray[0].getValue ();
      dv[1] = 7 - dv[0];

      
      for (int pawn0 = 0; pawn0 < 4; pawn0++)
        for (int die0 = 0; die0 < 2; die0++)
          {
          /* System.out.println("0: Trying to move pawn #" + pawn0 + " at " + 
               myPawns[currentPlayer][pawn0].getPosition () + " " + dv[die0] + " spaces");  */
          move0 = myPawns[currentPlayer][pawn0].successMove (dv[die0], 0);
          
          for (int pawn1 = 0; pawn1 < 4; pawn1++)
            for (int die1 = 0; die1 < 2; die1++)
              {
              /* System.out.println("1: Trying to move pawn #" + pawn1 + " at " + 
                   myPawns[currentPlayer][pawn1].getPosition () + " " + dv[die1] + " spaces"); */ 
              move1 = (move0 || pawn1 != pawn0) ? 
                  myPawns[currentPlayer][pawn1].successMove (dv[die1], 1) : 
                  myPawns[currentPlayer][pawn1].failMove (1);
              if (die0 == die1)
                {
                die2min = 1 - die0;
                die2max = die2min + 1;
                }
              else
                {
                die2min = 0;
                die2max = 2;
                }
              for (int pawn2 = 0; pawn2 < 4; pawn2++)
                for (int die2 = die2min; die2 < die2max; die2++)
                  {
                  /* System.out.println("2: Trying to move pawn #" + pawn2 + " at " + 
                     myPawns[currentPlayer][pawn2].getPosition () + " " + dv[die2] + " spaces");  */
                 move2 = (move1 || pawn2 != pawn1) ? 
                        myPawns[currentPlayer][pawn2].successMove (dv[die2], 2) : 
                        myPawns[currentPlayer][pawn2].failMove (2);
               
                  for (int pawn3 = 0; pawn3 < 4; pawn3++)
                    {
                    die3 = (die0 + die1 + die2 == 1) ? 1 : 0;
                    /* System.out.println("3: Trying to move pawn #" + pawn3 + " at " + 
                         myPawns[currentPlayer][pawn3].getPosition () + " " + dv[die3] + " spaces"); */
                   move3 = (move2 || pawn2 != pawn1) ? 
                        myPawns[currentPlayer][pawn3].successMove (dv[die3], 3) : 
                        myPawns[currentPlayer][pawn3].failMove (3);
                    moveArray[pawn0][die0][pawn1][die1][pawn2][die2][pawn3][die3] =
                        move0 && move1 && move2 && move3;
                    if (moveArray[pawn0][die0][pawn1][die1][pawn2][die2][pawn3][die3])
                      canmove = true;
                    /* System.out.println(counter + ": Move? " + 
                       moveArray[pawn0][die0][pawn1][die1][pawn2][die2][pawn3][die3]);
                    counter++;  */
                    if (move3)
                      myPawns[currentPlayer][pawn3].restorePawn (3);
                    }
                  if (move2)
                    myPawns[currentPlayer][pawn2].restorePawn (2);
                  }
               if (move1)
                 myPawns[currentPlayer][pawn1].restorePawn (1);
               }
           if (move0)
             myPawns[currentPlayer][pawn0].restorePawn (0);
           }
        /* System.out.println("Leaving passDoublets: " + canmove + " " +
         myPawns[currentPlayer][0].getPosition () + "/" + myPawns[currentPlayer][0].getSpace () + " " +
         myPawns[currentPlayer][1].getPosition () + "/" + myPawns[currentPlayer][1].getSpace () + " " +
         myPawns[currentPlayer][2].getPosition () + "/" + myPawns[currentPlayer][2].getSpace () + " " +
         myPawns[currentPlayer][3].getPosition () + "/" + myPawns[currentPlayer][3].getSpace ());  */
        if (!canmove)
          System.out.println("Can't move all four doublets rolls!");
        Pawn.clearMessage ();
        return canmove;
        }

   public static boolean allPawnsEntered (int player)
   
   // Are all the player's pawns entered?
   
      {
      int st = Pawn.getPawnPath(player, 0);
      boolean okay = true;
      for (int i = 0; i < 4; i++)
         okay = okay && gameBoard[st][i] == null;
      return okay;
      }
      
   public boolean isEnter (int player, int pos)
      {
      if (player == RED && pos == 0)
         return true;
      if (player == GREEN && pos == 51)
         return true;
      if (player == YELLOW && pos == 34)
         return true;
      if (player == BLUE && pos == 17)
         return true;
      return false;
      }

////////////////////////// STATIC METHODS //////////////////////////

// These methods are for use by the other classes (namely, Flasher, Pawn,
// and Die). One tenet of object-oriented programming is never pass values
// between classes directly. Instead, call methods in other classes.
      
   public static void setGameBoard (int pos, int sp, Pawn pn)
      {
      gameBoard[pos][sp] = pn;
      }
      
   public static Pawn getGameBoard (int pos, int sp)
      {
      return gameBoard[pos][sp];
      }
      
   public static Die getDiceArray (int i)
      {
      return diceArray[i];
      }
      
   public static Pawn getMyPawns (int i)
      {
      return myPawns[currentPlayer][i];
      }
      
   public static int getPawnNum (Pawn p)
      {
      for (int i = 0; i < 4; i++)
         if (myPawns[currentPlayer][i] == p)
            return i;
      return -1;
      } 
      
   public static boolean shouldCheckDoublets ()
      {
      return (doubletCount > 0 && allPawnsEnteredAtBeginning);
      }
            
    public static boolean canMoveDoublets (Pawn thisPawn, int numSpaces)
    
    // Here's where I look up whether a pawn can make a doublets move
    
      {
      // System.out.println("In canMoveDoublets, Pawn:" + thisPawn.getPosition () + " #" + numSpaces);
      int whichMove = 0;
      for (int i = 0; i < 4; i++)
         if (currentMove [i][0] > -1)
             whichMove++; 
         
      int thisPawnNum = getPawnNum (thisPawn);            
      int thisDie = (dv[0] == numSpaces) ? 0 : 1;
      
      if (whichMove == 0)
         for (int pawn1 = 0; pawn1 < 4; pawn1++)
            for (int die1 = 0; die1 < 2; die1++)
               for (int pawn2 = 0; pawn2 < 4; pawn2++)
                  for (int die2 = 0; die2 < 2; die2++)
                     for (int pawn3 = 0; pawn3 < 4; pawn3++)
                        for (int die3 = 0; die3 < 2; die3++)
                           if (moveArray[thisPawnNum][thisDie][pawn1][die1][pawn2][die2][pawn3][die3])
                              return true; 
      
      if (whichMove == 1)
         for (int pawn2 = 0; pawn2 < 4; pawn2++)
            for (int die2 = 0; die2 < 2; die2++)
               for (int pawn3 = 0; pawn3 < 4; pawn3++)
                  for (int die3 = 0; die3 < 2; die3++)
                     if (moveArray[currentMove[0][0]][currentMove[0][1]]
                           [thisPawnNum][thisDie][pawn2][die2][pawn3][die3])
                        return true; 
      
      if (whichMove == 2)
         for (int pawn3 = 0; pawn3 < 4; pawn3++)
            for (int die3 = 0; die3 < 2; die3++)
               if (moveArray[currentMove[0][0]][currentMove[0][1]]
                     [currentMove[1][0]][currentMove[1][1]][thisPawnNum][thisDie][pawn3][die3])
                  return true; 
                  
      if (whichMove == 3)
         return true;
      
      return false;   
      }              
//////////////////////////// EVENT METHODS ////////////////////////////////
               
   public boolean action (Event evt, Object arg)
   
   // Click a button. Java 1.0.2 events work fine here.
   
      {
       if (arg.equals ("Chat!"))
         {
         clickSend ();
         return true;
         }
         
      if (arg.equals ("Start"))
         {
         sendButton.setLabel ("Chat!");
         clickSend ();
         return true;
         }
         
      if (arg.equals ("Done with turn"))
         {
         readyToForce = true;
         return true;
         }
         
      return super.action (evt, arg);
      }

   public void clickSend ()
   
   // Let's chat!
   
      {
      chatMessage = chatField.getText();
      chat (0, chatMessage);
      if (newGame)
         {
         readyToStart = true;
         if (chatMessage.length () > 12)
            chatMessage = chatMessage.substring (0, 12);
         playerName[0] = (chatMessage.length () == 0) ? "You" : chatMessage;
         repaintPlayer ();
         start ();
         }
      if (playAgain)
         {
         pauseThread = true;
         playAgain = false;
         initAgain ();
         readyToStart = true;
         newGame = true;
         pauseThread = false;
         }
         
      chatField.setText ("");
      }  

   public void chat (int player, String msg)
      {
      if (msg.length () > 30)
          msg = msg.substring (0, 30);
      if (msg.length () > 0)
         chatArea.appendText (playerName[player] + ": " + msg + "\n");
      }
            
   public boolean mouseUp (Event evt, int x, int y)
   
   // Most of mouseUp handles clicking on pawns and dice. We must
   // handle selecting, deselecting, and making moves.
   
      {
      
      // System.out.println("x:" + x + " y: " + y);


   // pauseThread indicates that a pawn is moving, so the player shouldn't
   // be able to click anything on the board. The player can still chat, though!
         
      if (!pauseThread)
       {
       
      //   check for dice
       for (int i = 0; i < 4; i++)
         if (x >= diceX + i * diceDelta  && x < diceX + i * diceDelta + 32
               && y > diceY  && y < diceY + 32)
           if (diceArray[i] != null && !deselectDie)
            if (diceArray[i].isSelected ())
               {
                  // System.out.println("deselecting die: " + i);
               diceArray[i].select ();
               deselectDie = true;
               return true;
               }
            else
               if (diceArray[i].getValue () != 0)
                  if (checkBonus (i))
                     {
                     if (selectedDie != null) 
                          {
                          selectedDie.select ();
                          selectedDie.setVisible ();                       
                          }                   
                     // System.out.println("selecting die: " + i);
                    diceArray[i].select ();
                    selectedDie = diceArray[i];
                    checkMove ();
                    return true;
                    }
                  else
                    return true; 
       
         
      // check for pawn
      
       if (x < 305 && y < 305)
         for (int i = 0; i < 104; i++)
            for (int j = 0; j < 4; j++)
               if (x >= p[i][j].x && x < p[i][j].x + 12 && 
                     y >= p[i][j].y && y < p[i][j].y + 12)
               if (gameBoard[i][j] != null & !deselectPawn)
                if (gameBoard[i][j].isSelected ())  // this pawn is selected, so deselect it
                    {
                     // System.out.println("deselecting pawn: " + i + ":" + j);
                    gameBoard[i][j].select ();
                    deselectPawn = true;
                    return true;
                    }
                 else
                  if (gameBoard[i][j].getColor () == currentPlayer) // can only select your pawn
                   {  
                   if (selectedPawn != null)    // another pawn is selected, so deselect it
                        {
                        selectedPawn.select ();
                        selectedPawn.setVisible ();
                        }                   
                    //  System.out.println("selecting pawn: " + i + ":" + j);
                   gameBoard[i][j].select ();             // select this pawn
                   selectedPawn = gameBoard[i][j];
                   //   selectedPawn.reportBlockades ();         // debug only
                   checkMove ();
                   return true;
                   }
         }
                    
      // check for player icon
       
      if (x >= 368 && x < 368 + 24 && y >= 115 && y < 115 + 24)
         switchPlayerFlag = 0;
         
      if (x >= 368 && x < 368 + 24 && y >= 145 && y < 145 + 24)
         switchPlayerFlag = 1;
         
      if (x >= 508 && x < 508 + 24 && y >= 115 && y < 115 + 24)
         switchPlayerFlag = 2;         
         
      if (x >= 508 && x < 508 + 24 && y >= 145 && y < 145 + 24)
         switchPlayerFlag = 3;
                
                  
      return false;
      }
         
   public boolean checkBonus (int thisDie)
      {
      if (diceArray[thisDie].getValue () > 6)
         for (int i = 0; i < 4; i++)
            if (thisDie != i && diceArray[i].getValue () > 0 && diceArray[i].getValue () <= 6)
               {
               repaintMessage("You can only use Bonuses at the end of your turn!");
               return false;
               }
      return true;      
      }
                
   public boolean handleEvent(Event evt)
   
   // This method only applies if you are using an applet viewer. It enables the
   // close window box.
   
      { 
      if (evt.id == Event.WINDOW_DESTROY) 
         System.exit(0);
      return super.handleEvent(evt);
      }

////////////////////////////// VARIABLES ///////////////////////////////   

   // General variables
   
   public Thread runner;  
   
   // GUI variables

   private Image boardImage, messageImage, clockImage, diceImage, 
         playerImage, bgImage, activeImage, thisImage;
   Graphics boardG, messageG, clockG, diceG, playerG, bgG, activeG;
   

   private Image startcircle, face, computer;
   private Panel cornerPanel, messagePanel, northPanel, blankPanel;
   
   private TextField chatField;
   private Button sendButton, doneButton, leaveButton;
   private TextArea chatArea;
   
   int clipX = 0;
   int clipY = 0;
   int clipW = 600;
   int clipH = 360;

   boolean quickPaintFlag = false;
   int selectedPlayer = -1;
   
   String [] playerName = {"You", "Larry", "Curly", "Moe"};
   int [] players = {0, 1, 1, 1};
   Image [] playerIcon = new Image [2];
   
   boolean newGame = true;
   boolean readyToStart = false;
   int timerTick = 0;
   boolean pauseThread = false;
   boolean playAgain = false;
   int switchPlayerFlag = -1;
   boolean tickFlag = false;   
   static boolean allPawnsEnteredAtBeginning = false;       

   private AudioClip tick;                                          
    
   private Color lightBlue = new Color(0x99ffff);
   private Color medBlue = new Color(0x66ccff);
   private Color pink = new Color(0xff9999);
   private Color lightYellow = new Color(0xffff00);
   private Color lightGreen = new Color(0x00cc66);
   
   private final int bgX = 0;       
   private final int bgY = 0;
   private final int bgW = 600;       
   private final int bgH = 360;
   
   private final int diceX = 360;       
   private final int diceY = 70;
   private final int diceW = 278;       
   private final int diceH = 32;
   
   private final int boardX = 0;       
   private final int boardY = 0;
   private final int boardW = 306;       
   private final int boardH = 306;
   
   private final int activeX = 0;       
   private final int activeY = 0;
   private final int activeW = 306;       
   private final int activeH = 306;
   
   private final int clockX = 450;       
   private final int clockY = 20;
   private final int clockW = 130;       
   private final int clockH = 48;
   
   private final int messageX = 5;       
   private final int messageY = 340;
   private final int messageW = 300;       
   private final int messageH = 48;
   
   private final int bigPawnX = 325;       
   private final int bigPawnY = 5;
   private final int bigPawnW = 48;       
   private final int bigPawnH = 48;
   
   private final int playerX = 340;       
   private final int playerY = 115;
   private final int playerW = 250;       
   private final int playerH = 54;
   
   private final int diceDelta = 50;   
   
   private Pawn selectedPawn = null;
   private Die selectedDie = null;
   private boolean deselectPawn = false;
   private boolean deselectDie = false;
   private boolean useBothDice = false;
      
   // pawn color/player name designators
   
   private final int RED = 0;
   private final int GREEN = 1;
   private final int YELLOW = 2;
   private final int BLUE = 3;
   
   String [] colorName = {"Red", "Green", "Yellow", "Blue"};
   
   // core game variables

   String systemMessage;
   public static Die [] diceArray = new Die [4];
   private String chatMessage;
   private String chatText;    
   Image [] diceImages = new Image [9];
   Image [][] pawnImages = new Image [4][3];
   final Font bigFont = new Font ("Helvetica", Font.BOLD, 24);
   final Font smallFont = new Font ("Helvetica", Font.BOLD, 12);
   private static int currentPlayer;
   private int gameTimer;
   final int maxTime = 10;
   
   Point [][] p = new Point [104][4];                           // [position][space]
   public static Pawn [][] gameBoard = new Pawn [104][4];       // contains the pawns
   private Pawn currentPawn; 
   public static Pawn [][] myPawns = new Pawn [4][4];
   public static Pawn [][] backup = new Pawn [5][4];
   public static int [] dv = new int [2];
   public static boolean [][][][][][][][] moveArray = new boolean [4][2][4][2][4][2][4][2];
   public static int [][] currentMove = new int [4][2];
      
   boolean turnIsOver = false;
   public static int doubletCount = 0;
   boolean readyToForce = false;
   }

///////////////////////FLASHER CLASS /////////////////////

// Since pawns and dice all flash, and can all be selected and deselected,
// it made sense to have them both extend the same class. This was a good
// exercise in object-oriented programming, as well!

class Flasher extends Object
   {
   public Flasher ()
      {
      }

   public void changeVisibility ()
   {
   // System.out.println("flasher: flash");

   isVisible = !isVisible;
   }

 public void setVisible ()
   {
   isVisible = true;
   }
   
 public boolean getVisible ()
   {
   return isVisible;
   }
   
  public void select ()
   {
   selected = !selected;
   }
   
  public void deselect ()
   {
   selected = false;
   }
   
  public boolean isSelected ()
   {
   return selected;
   }

  public boolean isVisible = true; 
  public boolean selected = false;
  
  }

   
//////////////////////// PAWN CLASS ///////////////////////
                                           
   class Pawn extends Flasher
      {
      public Pawn (int c, int p, int s)
         {
         super ();
         color = c;
         pawnPosition = p;
         spacePosition = s;     
         for (int i = 0; i < 3; i++)
            blockades[i] = null;    
         }

//////////////// PUBLIC METHODS /////////////////////////////
         
      public boolean advancePawn (boolean lastPosition)
      
      // "this" pawn moves one space at a time, but it might have to
      // jump back to its starting point, if the move is illegal.
      // lastPosition indicates that the pawn is moving to its 
      // destination space 
      
         {
         // System.out.println("Color: " + color + " Pos: " + pawnPosition + " Sp: " + spacePosition);
         // System.out.println(pawnPath[0][0]);
         int path = this.getPathPosition ();
         if (path == 72)            // already home!
            {
            newMessage = "You must enter Home with an exact die roll!";
            return false;
            }
         int oldPosition = pawnPath[color][path];
         path++;
         int numSpaces = (path == 72) ? 4 : 2;
         int newPosition = pawnPath[color][path];
         boolean isBlocked = true;
         for (int i = 0; i < numSpaces; i++)
               if (Parcheesi.getGameBoard(newPosition, i) == null)       // found an empty space
                {
                isBlocked = false;
                if (!lastPosition)
                   { 
                   pawnPosition = newPosition;
                   Parcheesi.setGameBoard (newPosition, i, this);                  
                   Parcheesi.setGameBoard (oldPosition, spacePosition, null);
                   spacePosition = i;
                   return true;
                   }
                if (checkSafetySquare (newPosition, 1 - i) && 
                   checkBlockades (newPosition, 1 - i) )
                   { 
                   pawnPosition = newPosition;
                   Parcheesi.setGameBoard (newPosition, i, this);                  
                   Parcheesi.setGameBoard (oldPosition, spacePosition, null);
                   spacePosition = i;
                   return true;
                   }
                } 
               if (isBlocked)
                  newMessage = "That space is blocked!";        
         return false;             // space is full or blocked, or move is invalid
         }
      
      private boolean checkSafetySquare (int newPosition, int occupiedSpace)
      
      /* space is a safety square with another color pawn 
         and this pawn is not entering                            */
         
         {
         if (occupiedSpace < 0)  // only happens at home
            return true;
         if (Parcheesi.isSafetySquare (newPosition))
            if( Parcheesi.getGameBoard (newPosition, occupiedSpace) != null && 
               // space is truly occupied
               Parcheesi.getGameBoard(newPosition, occupiedSpace).getColor () != color &&
               // pawn in space isn't same color as this pawn
               newPosition != pawnPath[color][1])
               // space isn't this pawn's ENTER space
               {
               newMessage = "You can't enter an occupied safety square!";
               return false;
               }
         return true;         
         }
         
      public boolean checkBlockades (int newPosition, int occupiedSpace)
      
      // If a pawn is trying to form a blockade, we must check that it is a
      // legal blockade; if it is, then we must record that each pawn is in
      // a blockade with the other.
      
         {
         if (occupiedSpace < 0)  // only happens at home
            return true;
         // System.out.println("Checking blockade " + newPosition + "/" + occupiedSpace);
         Pawn targetPawn = Parcheesi.getGameBoard (newPosition, occupiedSpace);
         /*    if (targetPawn == null)
               System.out.println("targetPawn is null");
             else
               System.out.println("targetPawn color= " + targetPawn.getColor ()  );    */
         
         if(targetPawn != null && 
               // space is truly occupied
            targetPawn.getColor () == color &&
               // pawn in space is same color as this pawn
            !targetPawn.isHome () )
               { 
               // System.out.println("Checking blockade");
                // first, see if new blockade is legal
               for (int i = 0; i < 3; i++)
                  if (this.blockades[i] != null )  
                     if (this.blockades[i].equals (targetPawn) )
                        {
                        // System.out.println("Bad Blockade");
                        newMessage = "You can't move a blockade forward!";
                        return false;
                        }                                                     
               // second, identify new blockade
               setBlockades (this, targetPawn);                     
               setBlockades (targetPawn, this);
               }                     
         return true;         
         }

      public static String getMessage ()
        {
        return newMessage;
        }
           
      public static void clearMessage ()
         {
         newMessage = "";
         }
         
      public static void setBlockades (Pawn thisPawn, Pawn targetPawn)
         {
         /* System.out.println("In setBlockades: " + thisPawn.getPosition () + "/" +
         thisPawn.getSpace () + " & " + targetPawn.getPosition () + "/" +
         targetPawn.getSpace () );     */                               
         int j = 0;
         while (thisPawn.blockades[j] != null)
            j++;
         thisPawn.blockades[j] = targetPawn;
         // System.out.println("Setting blockade at: " + targetPawn.getPosition ());                                    
         }

      public void clearBlockade ()
         {
         for (int i = 0; i < 3; i++)
            this.blockades[i] = null;         
         }

// The following group of methods allow different classes to obtain
// information about "this" pawn
                              
      public static int getPawnPath (int player, int path)
      
      // pawnPath is a static array that contains the spaces that each of
      // the four players must follow. Remember, each player starts and
      // ends in a different place, so we need to know the order of the
      // spaces for each player.
      
         {
         return pawnPath[player][path];
         }

      public int getPathPosition ()
         {
         for (int i = 0; i < 73; i++)
            if (pawnPath[color][i] == pawnPosition)
               return i;
         System.out.println("ERROR: Path position not found");      
         return -1;
         }
         
      public int getColor ()
         {
         
         return color;
         }
         
      public int getPosition ()
         {
         
         return pawnPosition;
         }

      public int getSpace ()
         {
         
         return spacePosition;
         }
         
      public Pawn getBlockade (int i)
         {
         if (blockades[i] == null)
            return null;
         return (blockades[i]);
         }
         
      public void clearOneBlockade (int i)
         {
         blockades[i] = null;
         }
         
      public void initialize ()
      
      // In order to calculate the possible moves in the case of doublets,
      // we need to keep track of the pawns that might have been captured,
      // so that we can put them back.
      
         {
         for (int i = 0; i < 4; i++)
            prisoners[i] = null;
         }  
         
      public void restorePawn (int move)
      
      // Here's how we put pawns back after calculating possible moves in
      // the case of doublets.
      
         {
         /* System.out.println(move + ": restorePawn from " + pawnPosition + "/" +
            spacePosition + " to " + initialPosition[move] + "/" + initialSpace[move]);  */
         int finalPosition = pawnPosition;
         int finalSpace = spacePosition;
         
         // first, put this pawn back
         Parcheesi.setGameBoard (pawnPosition, spacePosition,  null);
         pawnPosition = initialPosition[move];
         spacePosition = initialSpace[move];
         Parcheesi.setGameBoard (pawnPosition, spacePosition, this);
         
         // second, undo just set blockade
         for (int i = 0; i < 3; i++)
            if (blockades[i] != null)
               if (blockades[i].getPosition () == finalPosition)   
                     // this is now in blockade with blockades[i]
                  {
                  for (int j = 0; j < 3; j++)
                     if (blockades[i].getBlockade (j) == this)
                        blockades[i].clearOneBlockade (j);
                  blockades[i] = null;
                  }
                  
         // third, replace captured pawn
         if (prisoners[move] != null)
            {
            Parcheesi.setGameBoard (finalPosition, 1 - finalSpace, prisoners[move]);
            prisoners[move] = null;
            }

         /* System.out.println(move + ": restorePawn completed at " + pawnPosition + "/" +
            spacePosition);  */
            
         }
 
      public void startPosition()
      
      // startPosition and returnPosition allow a pawn that is trying to make
      // an illegal move to return to its starting point.
      
         {
         currentPosition = pawnPosition;
         currentSpace = spacePosition;
         }
         
      public void returnPosition()
         {
         Parcheesi.setGameBoard (pawnPosition, spacePosition,  null);
         // System.out.println(pawnPosition + "/" + spacePosition + " is null; new = " + 
         //       currentPosition + "/" + currentSpace);
         pawnPosition = currentPosition;
         spacePosition = currentSpace;
         Parcheesi.setGameBoard (pawnPosition, spacePosition, this);
         }
  
     public boolean isAtStart ()
         {
         return (pawnPosition > 99);      
         }
 
      public boolean isHome ()
         {
         // if (pawnPath[color][72] == pawnPosition)
         //    System.out.println("is home");
         return pawnPath[color][72] == pawnPosition;
         }
         
      public void reportBlockades ()     
      
      //  This method is called for debugging only
         {
          for (int i = 0; i < 3; i++)
            if (blockades[i] == null)
               System.out.println("blockade # " + i + " is null");
            else
               System.out.println("blockade #" + i + ": " + 
                  blockades[i].getPosition () + "/" + blockades[i].getSpace ());
         }
         
      public void goStart ()
         {
         int startPosition = pawnPath[this.getColor ()][0];
         for (int i = 0; i < 4; i++)
            if (Parcheesi.getGameBoard (startPosition, i) == null)
               {
               Parcheesi.setGameBoard (pawnPosition, spacePosition, null);
               pawnPosition = startPosition;
               spacePosition = i;
               Parcheesi.setGameBoard (pawnPosition, spacePosition, this);
               }              
         }
          
    public void setBlockade (int i, Pawn blocker)
      {
      blockades[i] = blocker;          
      }

   public boolean successMove (int testvalue, int move)
   
   // This method "pretends" to move a pawn for the case of doublets. It works
   // like advancePawn, except the player doesn't see the pawn move.
   
      {
      initialPosition[move] = pawnPosition;
      initialSpace[move] = spacePosition;
      if (this.isHome ())
         return false;
      int hasMoved = 0;
      this.startPosition ();
      boolean success;
      do
         {
         hasMoved++;
         success = this.advancePawn (hasMoved == testvalue);
         }
      while (hasMoved < testvalue && success);
      if (success)
         {
         if (!this.isHome ())
            this.checkDoubletsCapture (move);  
         }                        
      else
         this.returnPosition ();
      return success;
      }         

   public boolean failMove (int move)
      {
      initialPosition[move] = pawnPosition;
      initialSpace[move] = spacePosition;
      return false;
      }
           
   public void checkDoubletsCapture (int move)
   
   // If a "fake" move results in a "fake" capture, we must keep
   // track of the captured pawns -- the "prisoners."
   
      {
      int targetColor = -1;
      Pawn targetPawn = Parcheesi.getGameBoard (pawnPosition, 1 - spacePosition);
      if (targetPawn == null)
         return;
         
      targetColor = targetPawn.getColor ();
         
      if (pawnPosition < 68)             // not on Home paths
         if (targetColor != color)
            {
            prisoners[move] = targetPawn;
            Parcheesi.setGameBoard(pawnPosition, 1 - spacePosition, null);
            }      
      }
                            
    public Pawn canMove (int numSpaces)
    
    // The computer will never make an illegal move. This method determines
    // whether a move is legal. It returns null if the move is illegal; 
    // returns "this" pawn if the destination space was empty; returns
    // "this" pawn's new neighbor if "this" pawn is moving into a blockade.
    
      {
      // System.out.println("In canMove, numSpaces=" + numSpaces);
      // numSpaces must be adjusted first!
      Pawn neighbor = this;
      if (numSpaces > 6)
         {
         for (int i = 0; i < 4; i++)
            if (Parcheesi.getDiceArray (i).getValue () <= 6 && 
                  Parcheesi.getDiceArray (i).getValue () > 0)
               return null;       // can't use bonus die till end of turn
         }
      else         
         if (Parcheesi.shouldCheckDoublets ())
            if (!Parcheesi.canMoveDoublets (this, numSpaces))
               {
               // System.out.println ("Can't use remaining Doublets rolls!");
               return null;          //   can't use all four Doublets rolls
               }
                                 
      int myPath = this.getPathPosition ();
      int newPath = myPath + numSpaces;
      if (newPath > 72)          // can't move past Home
         return null;

      int target = pawnPosition;
      for (int j = myPath + 1; j <= newPath; j++)
         {
         target = pawnPath[color][j];
         if (Parcheesi.getGameBoard (target, 0) != null && 
               Parcheesi.getGameBoard (target, 1) != null && j != 72)
            {     // can't be blocked at Home           
            // System.out.println("Blocked: roll=" + numSpaces + " at position=" + target);
            return null;        // pawn is blocked;
            }
         }

      target = pawnPath[color][newPath];        
      if (Parcheesi.getGameBoard (target, 0) != null)
         neighbor = Parcheesi.getGameBoard (target, 0);
      if (Parcheesi.getGameBoard (target, 1) != null)
         neighbor = Parcheesi.getGameBoard (target, 1);
              
      if (Parcheesi.isSafetySquare (target) && !isAtStart ())
         {       //  can only move onto occupied safety square if entering
         if (Parcheesi.getGameBoard (target, 0) != null)
             if (neighbor.getColor () != color)
               return null;      // pawn of another color on safety space
         if (Parcheesi.getGameBoard (target, 1) != null)
            if (neighbor.getColor () != color)
               return null;      // pawn of another color on safety space
         }
      
      if (neighbor != this)
         if (neighbor.getColor () == color)
            for (int k = 0; k < 3; k++)
               if (this.blockades[k] == neighbor)
                  return null;     // illegal blockade     
     
      /*
      if (neighbor == this)
         System.out.println("Can move: " + numSpaces + ". No neighbor");
      else
         System.out.println("Can move: " + numSpaces + ". Neighbor is " + neighbor.getColor ()); 
      */
            
     return neighbor;
     } 
     
     public boolean wontPass (int numSpaces)
     
     // If the computer move calls for moving a pawn that won't pass
     // an opponent's pawn, this method determines this.
     
       {
       int startPath = this.getPathPosition ();
       if (startPath + numSpaces > 64)       // entering Home path (even if it'll pass)
         return true;
       int pos = 0;
       for (int i = startPath + 1; i <= startPath + numSpaces; i++)
         {
         pos = pawnPath[color][i];
         if (Parcheesi.getGameBoard (pos, 0) != null)
            if (Parcheesi.getGameBoard (pos, 0).getColor () != color)
               return false;
         if (Parcheesi.getGameBoard (pos, 1) != null)
            if (Parcheesi.getGameBoard (pos, 1).getColor () != color)
               return false;
         }
       return true;       
       }
         
      public static void initPaths ()
      
      // Here is where we define which spaces form each player's path from
      // Start to Home.
      
         {
         int i, j;
         int RED = 0;
         int GREEN = 1;
         int YELLOW = 2;
         int BLUE = 3;

         pawnPath[RED][0] = 100;
         for (i = 1; i <= 64; i++)
            pawnPath[RED][i] = i - 1;
         for (i = 65; i <= 72; i++)
           pawnPath[RED][i] = i + 3;
         
         pawnPath[GREEN][0] = 101;
         for (i = 1; i <= 17; i++)
           pawnPath[GREEN][i] = i + 50;
         for (i = 18; i <= 64; i++)
           pawnPath[GREEN][i] = i - 18;
        for (i = 65; i <= 72; i++)
           pawnPath[GREEN][i] = i + 11;
      
         pawnPath[YELLOW][0] = 102;
         for (i = 1; i <= 34; i++)
           pawnPath[YELLOW][i] = i + 33;
         for (i = 35; i <= 64; i++)
            pawnPath[YELLOW][i] = i - 35;
         for (i = 65; i <= 72; i++)
            pawnPath[YELLOW][i] = i + 19;
      
        pawnPath[BLUE][0] = 103;
        for (i = 1; i <= 51; i++)
           pawnPath[BLUE][i] = i + 16;
        for (i = 52; i <= 64; i++)
           pawnPath[BLUE][i] = i - 52;
         for (i = 65; i <= 72; i++)
            pawnPath[BLUE][i] = i + 27;      
         }

/////////////////////////// VARIABLES /////////////////////////////
      
     private int color, pawnPosition, spacePosition; 
     private int currentPosition;
     private int currentSpace;

     private static int [][] pawnPath = new int [4][73];          // [color][path position]
     static
        {
        initPaths();
        }

     public static String newMessage = "";
     
     Pawn [] blockades = new Pawn [3];
     
     // passDoublets variables
     
     Pawn [] prisoners = new Pawn [4];
     int [] initialPosition = new int [4];
     int [] initialSpace = new int [4];
               
     // pawn color/player name designators
   
     private static int RED = 0;
     private static int GREEN = 1;
     private static int YELLOW = 2;
     private static int BLUE = 3;

}

//////////////////////////// DIE CLASS /////////////////////////////////////

class Die extends Flasher
     
   {
   public Die (int d, int v)
      {
      super ();
      die = d;
      value = v;
      }

////////////////////////// PUBLIC METHODS //////////////////////////////////

// There's not much to the dice! Remember, they inherit from Flasher, too.

 public void rollDie()
    {
    value = (int)(Math.random () * 6) + 1;
    }
     
 public int getValue ()
    {
      
    return value;
    }

 public void setValue (int val)
    {
    value = val;
    }
   
   public void clear ()
      {
      value = 0;
      }
        
  private int die, value;  
  }