/** * The TowersOfHanoi class is designed to create an Applet illustrating * the solution to the popular Towers of Hanoi problem. * <br> * Date: 2/6/08 * * @author Mr. Dietzler * */ import java.applet.Applet; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.KeyListener; import java.awt.event.KeyEvent; import java.awt.geom.Rectangle2D; import java.util.ArrayList; public class TowersOfHanoi extends Applet implements Runnable, KeyListener { //These variables represent the sizes and locations of the key elements public static final double TOWER_WIDTH = 10; public static final double TOWER_HEIGHT = 200; public static final double TOWER_Y = 150; public static final double DISC_WIDTH = 30.0; public static final double DISC_HEIGHT = 20.0; public static final double DISC_WIDTH_FACTOR = 10.0; public static final double BASE_X = 100; public static final double BASE_Y = 350; public static final double BASE_WIDTH = 400; public static final double BASE_HEIGHT = 30; //We use this array to ensure that the discs are different colors. public static final Color[] COLORS = {Color.RED, Color.ORANGE}; //This ArrayList will store the moves in chronological order to solve // the puzzle. private ArrayList<DiscMove> moves; //This ArrayList will store the 3 Tower objects private ArrayList<Tower> towers; //The base of the 3 towers. private Rectangle2D.Double base; //When it comes time to moves the discs, this variable identifies // which disc to move. private Disc discToMove; //numDiscs tells how many discs to have in the puzzle and move tells us // the current move we are on to figure out how many moves it takes to // solve. private int numDiscs, move; //This thread controls the animation. private Thread hanoiAnimator; //delay(in milliseconds) will control the step by step // animation delay. The value can be altered by hitting // the up and down arrows on the keyboard. private int delay; /** * This method initializes the applet to its beginning state. */ public void init() { moves = new ArrayList<DiscMove>(); towers = new ArrayList<Tower>(); move = 0; //so we start on move # 1 numDiscs = 15; //CHANGE THIS FOR DIFFERENT PUZZLE SIZES delay = 10; //milliseconds discToMove = null; //this is because we haven't begun moving the discs //Loop through and initialize the 3 towers for (int i = 0; i < 3; i++) { towers.add (new Tower (i, ((this.getWidth() * ((i + 1)/4.0)) - (TOWER_WIDTH/2.0)), TOWER_Y, TOWER_WIDTH, TOWER_HEIGHT)); }//end for //Initialize the base base = new Rectangle2D.Double (BASE_X, BASE_Y, BASE_WIDTH, BASE_HEIGHT); //The start tower is always index 0 Tower startTower = towers.get(0); //Loop through and initialize the discs on the leftmost tower //Note: Discs are stored biggest to smallest for (int i = (numDiscs - 1); i >= 0; i--) { startTower.addDisc (new Disc (i, (startTower.getX() + (startTower.getWidth()/2.0)), ((startTower.getY() + startTower.getHeight()) - (DISC_HEIGHT * (numDiscs - i))), DISC_WIDTH + (i * DISC_WIDTH_FACTOR), DISC_HEIGHT, COLORS[i % COLORS.length])); }//end for //Set the Applet up to detect keyboard input this.setFocusable(true); this.addKeyListener(this); }//end init method /** * The start method is called after the init method is complete */ public void start() { //begin the thread hanoiAnimator = new Thread(this); hanoiAnimator.start(); }//end start method /** * This method is called after the applet window is closed. */ public void stop() { hanoiAnimator = null; }//end stop method /** * The paint method displays everything on the screen in its * current state * * @param this Graphics object is used to display objects in * the applet. */ public void paint(Graphics g) { //recover Graphics2D Graphics2D g2 =(Graphics2D)g; g2.setColor(Color.BLACK); g2.drawString(new String("Move # " + move),2,20); g2.fill (base); //loop through each Tower to display it and its discs (if any) for (int i = 0; i < towers.size(); i++) { g2.setColor(Color.BLACK); Tower curTower = towers.get(i); g2.fill(curTower); //loop through and display all the discs on this Tower (if any) for (int j = 0; j < curTower.getNumDiscs (); j++) { Disc curDisc = curTower.peekDisc (j); g2.setColor(Color.BLACK); g2.draw (curDisc); g2.setColor (curDisc.getColor()); g2.fill (curDisc); }//end inner for to display discs }//end outer for to display towers //display the disc to move if the move process has begun if (discToMove != null) { g2.setColor(Color.BLACK); g2.draw (discToMove); g2.setColor (discToMove.getColor()); g2.fill (discToMove); }//end if }//end paint method /** * The run method handles the solving of the Towers of Hanoi * puzzle and animation of the solution. */ public void run() { //All the variables help in the animation process DiscMove nextMove; Tower from, to; boolean complete = false; boolean moveLeft = false; /* * HERE IS WHERE THE RECURSION TAKES PLACE TO SOLVE THE PUZZLE */ DiscMover mover = new DiscMover(0, 2, numDiscs); while (mover.hasMoreMoves()) moves.add (mover.nextMove()); //Check that the current thread is still our hanoiAnimator and // that the puzzle is not complete. while(Thread.currentThread() == hanoiAnimator && !complete) { for (int m = 0; m < moves.size(); m++) { move++; //increment the move number nextMove = moves.get(m); //get the next move to make from = towers.get (nextMove.getTowerFrom()); to = towers.get (nextMove.getTowerTo()); //We will be moving the top-most disc from the 'from' Tower // so remove it from that Tower accordingly discToMove = from.removeDisc (from.getNumDiscs() - 1); //Animate the disc up for (int i = (int)discToMove.getY(); i >= ((int)from.getY() - (DISC_HEIGHT * 2.0)); i--) { discToMove.setFrame (discToMove.getX (), discToMove.getY () - 1, discToMove.getWidth(), discToMove.getHeight()); repaint(); try { Thread.sleep(delay); } catch(InterruptedException e) { break; } }//end for to move up //Check if the 'to' Tower is to to the right of left of the // 'from' Tower. if (nextMove.getTowerTo() < nextMove.getTowerFrom ()) moveLeft = true; else moveLeft = false; //Animate the disc left or right accordingly if (moveLeft) { for (int i = (int)discToMove.getX(); i >= (((int)to.getX() + (int)to.getWidth()/2) - ((int)discToMove.getWidth() / 2)); i--) { discToMove.setFrame (discToMove.getX () - 1, discToMove.getY (), discToMove.getWidth(), discToMove.getHeight()); repaint(); try { Thread.sleep(delay); } catch(InterruptedException e) { break; } }//end for to move left }//end if else { for (int i = (int)discToMove.getX(); i <= (((int)to.getX() + (int)to.getWidth()/2) - ((int)discToMove.getWidth() / 2)); i++) { discToMove.setFrame (discToMove.getX () + 1, discToMove.getY (), discToMove.getWidth(), discToMove.getHeight()); repaint(); try { Thread.sleep(delay); } catch(InterruptedException e) { break; } }//end for to move right }//end else //Animate the disc back down for (int i = (int)discToMove.getY(); i <= (((int)to.getY() + (int)to.getHeight() - (to.getNumDiscs ()+1)* DISC_HEIGHT)); i++) { discToMove.setFrame (discToMove.getX (), discToMove.getY () + 1, discToMove.getWidth(), discToMove.getHeight()); repaint(); try { Thread.sleep(delay); } catch(InterruptedException e) { break; } }//end for to move down //Add the disc that was moved to the Towed it moved 'to'. to.addDisc (discToMove); //Have the Thread sleep for 100 milliseconds. try { Thread.sleep(100); } catch(InterruptedException e) { break; } }//end BIG for to loop through the moves complete = true; }//end while }//end run method //The following methods control the keyboard input. public void keyPressed(KeyEvent e) { //This code checks a key was pressed was one of // the up or down arrow keys. if(e.getKeyCode() == KeyEvent.VK_UP) { delay --; while (delay < 0) { delay = 0; } } else if(e.getKeyCode() == KeyEvent.VK_DOWN) { delay ++; if (delay > 10) { delay = 10; } } } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} }//end TowersOfHanoi class