// file: Hanoi.java // author: Robert M. Keller // purpose: A java version of Towers of Hanoi // $Id: Hanoi.java,v 1.30 1996/09/30 01:37:41 keller Exp keller $ import java.lang.Thread; import java.applet.*; import java.awt.*; import java.util.*; public class Hanoi extends Applet implements Runnable { static public Color backgroundColor = Color.white, outlineColor = Color.black, spindleColor = Color.black, baseColor = Color.black; static public Color diskColor[] = {Color.green, Color.orange}; static public Font font = new Font("Helvetica", Font.BOLD, 18); static public int widthInc = 30; // incremental width of disks static public int loopDelay = 10; static public int raiseDelay = 5; static public int lowerDelay = 5; static public int horizontalDelay = 5; static public int spindleSpacing = 200, spindleWidth = 5, spindleHeight = 300, baseHeight = 5, baseWidth = 800; String Demo = "Demo"; String Manual = "Manual"; String Restack = "Restack"; Button demo_button = new Button(Demo); Button restack_button = new Button(Restack); int demo_button_width = 100, demo_button_height = 40; int demo_button_x = 300, demo_button_y = 320; int restack_button_width = 100, restack_button_height = 40; int restack_button_x = 200, restack_button_y = 320; public Image image; int disks = 7; // maximum number of disks int spindles = 3; Disk disk[]; static public Spindle spindle[]; static public Graphics graphics; Graphics imageGraphics; Thread mythread; Disk selected; public static int tolerance = 30; Spindle current_spindle; // spindle of current disk boolean on_spindle; // whether disk is actually on boolean demo_mode, restack_request; /** * Initialize the Hanoi applet. **/ public void init() { setBackground(backgroundColor); graphics = getGraphics(); // remember graphics throughout demo_button.setFont(font); restack_button.setFont(font); image = createImage(size().width, size().height); imageGraphics = image.getGraphics(); spindle = new Spindle[spindles]; for( int i = 0; i < spindles; i++ ) { spindle[i] = new Spindle(i, (i+1)*spindleSpacing, spindleHeight, spindleColor, imageGraphics); } disk = new Disk[disks]; for( int i = disks-1; i >= 0; i-- ) { disk[i] = new Disk(diskColor[i%2], widthInc*(i+1), this, backgroundColor, outlineColor, 0); } } /** * Run the Hanoi applet. **/ public void run() { add(restack_button); add(demo_button); restack(disks); demo_mode = false; restack_request = false; while( true ) { Hanoi.sleep(loopDelay); repaint(); if( demo_mode ) demo(disks); if( restack_request ) { restack(disks); restack_request = false; } } } public boolean mouseDown(Event e, int x, int y) { if( selected != null ) return true; // already selected, not replaced selected = locate_disk(x, y); // get disk identity if( selected != null ) { if( current_spindle == null ) { on_spindle = false; } else { on_spindle = true; } } return true; } boolean defaultDrag(int x, int y) { selected.erase(); redraw(); selected.position(x, y).draw(); repaint(); return true; // too low, still off } public boolean mouseDrag(Event e, int x, int y) { if( selected == null ) return true; // no disk selected if( on_spindle ) { // can't drag below top disk or base if( y + Disk.height > current_spindle.top_surface() ) return true; if( y >= Spindle.clearance - Disk.height ) { // disk is not in the clearance area // stay on the spindle, with same x return defaultDrag(selected.x, y); } else { // disk is in the clearance area Spindle s = locate_spindle(x); if( s == null ) { // disk has moved off spindle on_spindle = false; current_spindle = null; return defaultDrag(x, y); } else { // disk has moved to a different spindle current_spindle = s; return defaultDrag(s.x, y); } } } else { // some disk is selected, was not on spindle current_spindle = locate_spindle(x); if( current_spindle == null ) { on_spindle = false; // no spindle is selected return defaultDrag(x, y); } else { if( y >= Spindle.clearance - Disk.height ) { // disk must be in the clearance area to go on the spindle // currently it is not current_spindle = null; on_spindle = false; return defaultDrag(x, y); } else { Spindle target = spindle[current_spindle.number]; // check for not placing larger over smaller if( target.numDisks() > 0 ) { if( ((Disk)target.peek()).width < selected.width ) return defaultDrag(x, y); // can't lower } // count disk as on spindle now on_spindle = true; return defaultDrag(x, y); } } } } public boolean mouseUp(Event e, int x, int y) { if( selected == null ) return true; // no disk current_spindle = locate_spindle(x); // find spindle if( current_spindle == null ) { on_spindle = false; return true; // no spindle = noop } if( on_spindle ) { Spindle target = spindle[current_spindle.number]; selected.transfer(current_spindle.number); // let go over spindle // check for not placing larger over smaller if( target.numDisks() > 0 ) { if( ((Disk)target.peek()).width < selected.width ) return true; // can't drop here } selected.lower(target); current_spindle.add(selected); selected = null; return true; } return true; // no action (below clearance) } Spindle locate_spindle(int x) { for( int i = 0; i < spindles; i++ ) { if( Math.abs(x - spindle[i].x) <= tolerance ) return spindle[i]; } return null; } Disk locate_disk(int x, int y) { current_spindle = locate_spindle(x); if( current_spindle == null ) return null; // no spindle found if( current_spindle.numDisks() == 0 ) return null; // spindle is empty // can only move top disk if( Math.abs(y - current_spindle.top_surface()) <= tolerance ) { return (Disk)current_spindle.pop(); } return null; } void redraw() { for( int i = 0; i < spindles; i++ ) spindle[i].draw(); drawBase(); } void restack(int N) { disks = N; drawBase(); int i; for( i = 0; i < spindles; i++ ) { spindle[i].clear(); } for( i = disks-1; i >= 0; i-- ) { spindle[0].add(disk[i]); disk[i].my_spindle = 0; } for( i = 0; i < spindles; i++ ) { spindle[i].draw(); } repaint(); } void demo(int N) { try { moveall(disks-1, 0, 2); } catch(AbortDemo e) // ends demo { } } // moveall moves disks from 0 through N Hanoi moveall(int N, int From, int To) throws AbortDemo { if( N >= 0 ) { int Other = find_target(To, disk[N].my_spindle); moveall(N-1, From, Other); Move(disk[N].my_spindle, To); repaint(); moveall(N-1, Other, To); } return this; } int find_target(int To, int Base) { if( To == Base ) return To; else return other(To, Base); } int other(int A, int B) { switch( A ) { case 0: return B == 1 ? 2 : 1; case 1: return B == 0 ? 2 : 0; case 2: return B == 1 ? 0 : 1; default: return 0; } } Hanoi drawBase() { imageGraphics.setColor(baseColor); imageGraphics.fillRect(0, spindleHeight, baseWidth, baseHeight); return this; } Hanoi Move(int i, int j) throws AbortDemo { if( demo_mode == false ) throw new AbortDemo(); if( i == j ) return this; try { Disk d = spindle[i].remove(); d.raise(spindle[i]); d.transfer(j); d.lower(spindle[j]); spindle[j].add(d); } catch(EmptyStackException e) { // can't happen } return this; } // repaint implicitly calls update // update calls paint public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.drawImage(image, 0, 0, null); demo_button.move(demo_button_x, demo_button_y); demo_button.resize(demo_button_width, demo_button_height); restack_button.move(restack_button_x, restack_button_y); restack_button.resize(restack_button_width, restack_button_height); } public void start() { if( mythread == null ) { mythread = new Thread(this); mythread.start(); } else mythread.resume(); } public void stop() { mythread.suspend(); } public static void sleep(int delay) { try { Thread.sleep(delay); } catch(InterruptedException e) { } } public boolean action(Event event, Object arg) { if( event.target == demo_button ) { return demo_toggled(); } if( event.target == restack_button ) { if( demo_mode ) return true; demo_mode = false; restack_request = true; return true; } return super.action(event, arg); // Delegate all other actions to super. } boolean demo_toggled() { if( demo_mode ) { // switch to manual mode demo_mode = false; demo_button.setLabel(Demo); } else { // switch to demo mode demo_mode = true; demo_button.setLabel(Manual); } return true; } } // Hanoi class Disk { Color color; static public int height = 20; static public int v_inc = 5, h_inc = 5; static int gap = 10; // gap left at top when lifting int width; int my_spindle; int x, y; Hanoi game; Graphics graphics; Color backgroundColor; Color outlineColor; Disk(Color color, int width, Hanoi game, Color backgroundColor, Color outlineColor, int my_spindle) { this.color = color; this.width = width; this.backgroundColor = backgroundColor; this.outlineColor = outlineColor; this.my_spindle = my_spindle; this.game = game; graphics = game.imageGraphics; } Disk position(int x, int y) { this.x = x; this.y = y; return this; } Disk raise(Spindle spindle) { while( !at_top() ) { erase(); y -= v_inc; spindle.draw(); draw(); game.repaint(); Hanoi.sleep(Hanoi.raiseDelay); } return this; } Disk lower(Spindle spindle) { y = (y/v_inc)*v_inc; while( !on_disk() ) { erase(); y += v_inc; spindle.draw(); // draw spindle with other disks draw(); // draw this disk game.repaint(); Hanoi.sleep(Hanoi.lowerDelay); } return this; } Disk left(int j) { while( x > Hanoi.spindle[j].x ) { erase(); x -= h_inc; draw(); game.repaint(); Hanoi.sleep(Hanoi.horizontalDelay); } erase(); x = Hanoi.spindle[j].x; draw(); game.repaint(); return this; } Disk right(int j) { while( x < Hanoi.spindle[j].x ) { erase(); x += h_inc; draw(); game.repaint(); Hanoi.sleep(Hanoi.horizontalDelay); } erase(); x = Hanoi.spindle[j].x; draw(); game.repaint(); return this; } boolean at_top() { return y <= gap; } boolean on_disk() { return y >= Spindle.height - ((Hanoi.spindle[my_spindle].numDisks())*height + height); } // move from clearance in spindle i to spindle j Disk transfer(int j) { if( my_spindle == j ) return this; if( my_spindle < j ) right(j); else left(j); my_spindle = j; return this; } Disk draw(Color color) { graphics.setColor(outlineColor); graphics.fillRect(x-width/2, y, width, height); graphics.setColor(color); graphics.fillRect(x-width/2+1, y+1, width-2, height-2); return this; } Disk draw() { return draw(color); } Disk erase() { graphics.setColor(backgroundColor); graphics.fillRect(x-width/2, y, width, height); return this; } } // Disk // Spindle knows the disks stacked on it class Spindle extends Stack { static public int clearance = 50; // clearance at top of spindle public int number; public int x; // x of the center of the spindle int base; // y of the base of the spindle static int width = 5; static public int height = 300; Color color; Graphics graphics; Spindle(int number, int x, int base, Color color, Graphics graphics) { this.number = number; this.x = x; this.base = base; this.width = width; this.height = height; this.color = color; this.graphics = graphics; } int numDisks() { return elementCount; } Spindle add(Disk disk) { int fromBase = numDisks()*disk.height; disk.position(x, base - fromBase - disk.height).draw(); push(disk); return this; } Disk remove() throws EmptyStackException { Disk result = (Disk)pop(); result.erase(); return result; } Spindle clear() // clear the spindle of all disks { Enumeration disks = elements(); while( disks.hasMoreElements() ) { remove(); } return this; } Spindle draw() { // first draw disks Enumeration disks = elements(); while( disks.hasMoreElements() ) { ((Disk)disks.nextElement()).draw(); } // then draw rest of spindle graphics.setColor(color); graphics.fillRect(x-width/2, clearance, width, height-clearance-Disk.height*numDisks()); return this; } int top_surface() { if( numDisks() > 0 ) return ((Disk)peek()).y; else return base; } } // Spindle class AbortDemo extends Exception { }