/******************************************************************************************

   Hanoi.java - Tower of Hanoi puzzle
   Version 2.0
   www.mazeworks.com
   
   Copyright (c) 2002 Robert Kirkland
   All Rights Reserved.

   Permission is hereby granted, free of charge, to any person obtaining a copy 
   of this software and associated documentation files (the "Software"), to
   deal in the Software without restriction, including without limitation the
   rights to use, copy, modify, merge, publish, distribute, and/or sell copies
   of the Software, and to permit persons to whom the Software is furnished to
   do so, provided that the above copyright notice and this permission notice
   appear in all copies of the Software and that both the above copyright
   notice and this permission notice appear in supporting documentation.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
   IN NO EVENT SHALL THE COPYRIGHT HOLDER INCLUDED IN THIS NOTICE BE LIABLE FOR
   ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
   OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
   CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

   Except as contained in this notice, the name of the copyright holder shall
   not be used in advertising or otherwise to promote the sale, use or other
   dealings in this Software without prior written authorization of the
   copyright holder.
 
    Added 1/29/2014 By Jerrold Siegel: This is an application version of my 
    applet code which, in turn, was just  an update of the original applet code with only one runtime bug found and fixed.
    One example of a change is BoardCanvas extends Canvas becomes BoardCanvas extends JPanel and paint becomes
    paintComponent. There is also some relativization eg.
 
     bufferImage=Toolkit.getDefaultToolkit().getImage(System.getProperty("user.dir").replace('\\','/')+"/"+"blue.gif");
    
   I also used "disk" rather than "disc" 
******************************************************************************************/
import java.io.*;
import java.awt.* ;
import java.awt.event.*;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

/******** HANOI ********
   main class: initializes  panel, starts games,
   runs autosolve thread, handles disc moving events
*/  

public class Hanoi  extends JPanel   implements Runnable, Serializable {
    public static final long serialVersionUID = 42123L;
    public int[][] diskArray=new int[3][50];  //peg - disk
    public int [][] theDisks= new int[50][3]; //x-y-witdth
    public int[] dragDisk =new int[4];
    public int[] present_peg_count=new int[3];
    public String getAppletInfo() { 
        return "Tower of Hanoi 2.0 - Copyright (c) 2002 Robert Kirkland";}
    String parameterInfo[][] = {
        {"bgColor",      "integer", "Applet background color (24-bit RGB hex int)"},
        {"boardBgColor", "integer", "Board background color (24-bit RGB hex int)"},
        {"title",        "string",  "Applet title text"},
        {"discs",        "string",  "Discs label text"},
        {"plus",         "string",  "Discs Plus button text"},
        {"minus",        "string",  "Discs Minus button text"},
        {"restart",      "string",  "Restart button text"},
        {"solution",     "string",  "Solution button text"},
        {"speed",        "string",  "Speed label text"},
        {"timer",        "string",  "Timer label text"},
        {"moves",        "string",  "Moves label text"},
        {"instruct",     "string",  "Instructions status text"},
        {"solving",      "string",  "Solve in progress status text"},
        {"finished",     "string",  "Solve finished status text"},
        {"minimum",      "string",  "Minimum moves status text"},
        {"win",          "string",  "Puzzle solved status text"},
        {"perfect",      "string",  "Minimum moves solution status text"}} ;
    public String[][] getParameterInfo() { return parameterInfo ;}
    public Graphics graphics;
    //Hanoi newContentPane;
    JFrame frame;
    Color  DF_BG_COLOR=Color.white ;      // default background color
    Color  DF_BOARD_BG_COLOR=new Color(0xFFCC99) ;      // default background color FFCC99
    String DF_TITLE  ="Tower of Hanoi";   // default text strings
    int DF_WIDTH  =550;   // default text strings
    int DF_HEIGHT  =310;   // default text strings
    String DF_DISCS  ="DISCS",
    DF_PLUS   ="+",
    DF_MINUS  ="-",
    DF_RESTART="RESTART",
    DF_SOLUTION="SOLUTION",
    DF_SPEED  ="SPEED",
    DF_TIMER  ="TIMER",
    DF_MOVES  ="MOVES",
    DF_INSTRUCT="Move all discs to the rightmost peg.",
    DF_SOLVING ="Solving ...",
    DF_FINISHED="Finished !",
    DF_MINIMUM ="The minimum number of moves required is",
    DF_WIN     ="Done !  Now try for the minimum number of moves.",
    DF_PERFECT ="Congratulations !" ;
    static final Font titleFont=new Font("Helvetica", Font.BOLD, 12),
                                textFont=new Font("Helvetica", Font.PLAIN, 11),
                                         monoFont=new Font("Courier", Font.BOLD, 12),
                                                  statusFont=new Font("Dialog", Font.PLAIN, 12) ;
    static final int CANVAS_WIDTH=450, CANVAS_HEIGHT=250, TABLE_TOP=225,
    PEG1=0, PEG2=1, PEG3=2, MIN_DISCS=3, MAX_DISCS=12 ;
    protected Color BG_COLOR, BOARD_BG_COLOR ;
    protected String TITLE, DISCS, PLUS, MINUS, RESTART, SOLUTION, SPEED, TIMER, MOVES,
    INSTRUCT, SOLVING, FINISHED, MINIMUM, WIN, PERFECT ;
    private boolean gameOver ;
    private int sourceDisc, sourcePeg, targetPeg ;
    private String gameStatus ;
    private Panel cpBack, titlePanel ;
    private Label titleLabel ;
    public Board bd ;
    public BoardCanvas bc ;
    private StatusPanel sp ;
    private ControlPanel cp ;
    private Image boardImage ;
    private Thread solveThread;
    private Timer timer ;
    public void mouseExited(MouseEvent e) { return ;}
    public void resettimer(){
        timer.isRunning=false;
    }
    public static void main(String[] args) {
        new Hanoi();
    }   
    public Hanoi(){
        super(new BorderLayout());
        frame = new JFrame("Tower of Hanoi");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        init();   //The actual Puzzle.
        frame.setContentPane(this);
        // frame.setSize(450,250);
        frame.getContentPane().setBackground(Color.BLUE);
        //Display the window.
        frame.pack();
        frame.setVisible(true);  
    }

    public void init() {
        BG_COLOR = DF_BG_COLOR ;
        BOARD_BG_COLOR = DF_BOARD_BG_COLOR  ;
        TITLE = DF_TITLE ;
        DISCS = DF_DISCS;
        PLUS = DF_PLUS;
        MINUS = DF_MINUS ;
        RESTART = DF_RESTART;
        SOLUTION = DF_SOLUTION;
        SPEED = DF_SPEED;
        TIMER = DF_TIMER;
        MOVES = DF_MOVES;
        INSTRUCT = DF_INSTRUCT ;
        SOLVING = DF_SOLVING ;
        FINISHED = DF_FINISHED ;
        MINIMUM = DF_MINIMUM ;
        WIN = DF_WIN;
        PERFECT = DF_PERFECT ;
        // load board image
        MediaTracker tracker = new MediaTracker(this) ;
        boardImage = Toolkit.getDefaultToolkit().getImage(getClass().getResource("blue.gif")) ;
        tracker.addImage(boardImage,0) ;
        try {
            tracker.waitForID(0);
        } catch (InterruptedException e) {
        }
        setBackground(BG_COLOR) ;
        setOpaque(true); //content panes must be opaque
        Panel mainPanel = new Panel() ;
        mainPanel.setLayout(new BorderLayout(0,0)) ;
        // BoardCanvas
        bc = new BoardCanvas(this) ;
        bc.setSize(new Dimension(CANVAS_WIDTH,CANVAS_HEIGHT)) ;
        // Title panel
        titlePanel = new Panel() ;
        titlePanel.setLayout(new GridLayout(1,1)) ;
        titlePanel.setBackground(Color.lightGray) ;
        titlePanel.setFont(titleFont) ;
        titlePanel.add(titleLabel=new Label("   "+TITLE,Label.LEFT)) ;
        titleLabel.setForeground(Color.black) ;
        // Control Panel
        cpBack = new Panel() ;
        cpBack.setLayout(new FlowLayout(FlowLayout.CENTER,10,5)) ;
        cpBack.setBackground(Color.lightGray) ;
        cp = new ControlPanel(this) ;
        cpBack.add(cp) ;
        // Status panel
        sp = new StatusPanel(this) ;
        sp.setBackground(Color.lightGray) ;
        // construct applet panel
        Panel p1 = new Panel() ;
        p1.setLayout(new FlowLayout(FlowLayout.CENTER,10,10)) ;
        mainPanel.add("Center",bc) ;
        mainPanel.add("North",titlePanel) ;
        mainPanel.add("East",cpBack) ;
        mainPanel.add("South",sp) ;
        p1.add(mainPanel) ;
        add(p1) ;

        validate() ;

        newGame() ;
    }

    // startup
    void newGame() {
        int discs = cp.getDiscs() ;
        System.gc() ;
        gameOver = false ; 
        bd = new Board(discs,this) ;
        present_peg_count=new int[3];
        diskArray=new int [3][50];
        theDisks=new int[50][3];
        bc.drawBoard(bd,boardImage,0,0,0) ;
        sp.setMoveCount(0) ;
        timer=null;
        timer = new Timer(cp) ;
        cp.startTime = System.currentTimeMillis() ;
        if (solveThread == null)
            sp.setStatus(INSTRUCT) ;
        bc.requestFocus() ;
    }        
    // handle Reset event
    void restartGame() { 
        stop() ; 
        cp.setAutoSolveEnable(true) ;
        newGame() ;
    }
    // kill all threads
    public void stop() {
        if (solveThread!=null) {
            //solveThread.stop() ;
            solveThread = null ;
        }
        if (timer!=null) {
            timer.isRunning=false;
            timer = null ;
        }
    }
    // spawn Autosolve thread
    public void startSolveThread() {
        stop() ;
        solveThread = new Thread(this) ;
        solveThread.start() ;
    }
    // run Autosolve thread
    public void run() {
        newGame() ;
        sp.setStatus(SOLVING) ;
        solve(cp.getDiscs(),PEG1,PEG2,PEG3) ;
        sp.setStatus(FINISHED) ;
        gameOver = true ;
        solveThread = null ;
        cp.setAutoSolveEnable(true) ;
    }
    // here's the famous algorithm
    void solve(int discs,int source,int aux,int target) {
        if (discs==0) return ;                      // base to end recursion
        solve(discs-1,source,target,aux) ;          // recursive call #1
        bd.moveDisc(source,target) ;                // move disc
        sp.setMoveCount(bd.getMoveCount()) ;        // update display
        bc.drawBoard(bd,boardImage,0,0,0) ;
        try {
            solveThread.sleep(cp.getDelay()) ;
        }  // Autosolve delay
        catch (InterruptedException e) {
        }
        solve(discs-1,aux,source,target) ;          // recursive call #2
    }
    // handle mouse drag event
    void dragDisc(int x,int y) {
        if (!gameOver&&(sourceDisc!=0)) {
            dragDisk[0]=sourceDisc;dragDisk[1]=x;dragDisk[2]=y;dragDisk[3]=theDisks[sourceDisc][2];
            //System.out.println(dragDisk[0]+" "+dragDisk[1]+" "+dragDisk[2]+" "+dragDisk[3]);
            bc.drawBoard(bd,boardImage,0,0,0) ;
        }
    }
    // handle mouse down event
    void selectDisc(int x,int y) {
        if (!gameOver&&(solveThread==null)) {
            if ((timer!=null)&&(!timer.isAlive())) timer.start() ;
            sourcePeg = pixelToPeg(x,y) ;
            if (bd.isStartPeg(sourcePeg)) {
                sourceDisc = bd.getTopDisc(sourcePeg) ;
                dragDisk[0]=sourceDisc;dragDisk[1]=theDisks[sourceDisc][0];dragDisk[2]=theDisks[sourceDisc][1];dragDisk[3]=theDisks[sourceDisc][2];
                //System.out.println(dragDisk[0]+" "+dragDisk[1]+" "+dragDisk[2]+" "+dragDisk[3]);
                bc.drawBoard(bd,boardImage,0,0,0) ;
                //   bc.drawBoard(bd,boardImage,sourceDisc,x,y) ; 
            }
        }
    }
    // handle mouse up event
    void dropDisc(int x,int y) {
        dragDisk[0]=0;
        if (!gameOver&&(sourceDisc!=0)) {
            targetPeg = pixelToPeg(x,y) ;
            if (bd.moveDisc(sourceDisc,sourcePeg,targetPeg)) {
                gameStatus = bd.getBoardStatus() ;
                sp.setMoveCount(bd.getMoveCount()) ;
                if (gameStatus==null)
                    sp.setStatus(MINIMUM + " " + bd.getMinMoves() + ".") ;
                else {
                    gameOver = true ;                                    
                    stop() ;
                    sp.setStatus(gameStatus) ;
                }
            }
            bc.drawBoard(bd,boardImage,0,0,0) ;
            sourceDisc = 0 ;
        }
    }
    // conversion for mouse down/up events
    int pixelToPeg(int x,int y) {
        int peg = -1 ;
        if ((y>40)&&(y<TABLE_TOP)) {
            if ((x>50)&&(x<100)) peg = PEG1 ;
            else if ((x>200)&&(x<250)) peg = PEG2 ;
            else if ((x>350)&&(x<400)) peg = PEG3 ;
        }
        return peg ;
    }
}
/******** TIMER ********
    controls Timer thread
*/  
class Timer extends Thread {
    static final int ONE_SECOND=1000 ;
    //  public long startTime;
    private long  rem ;
    private int hours, min, sec ;
    private String sMin, sSec, sTime ;
    private ControlPanel cp ;
    public boolean isRunning=true;

    Timer(ControlPanel cp) {
        this.cp = cp ;
        cp.setTimer(setTime(0)) ;
    }
    // run thread
    public void run() {
        cp.startTime = System.currentTimeMillis() ;
        while (isRunning) {
            try {
                Thread.sleep(ONE_SECOND) ;
            } catch (InterruptedException e) {
            }
            if ( cp.isTimerOn()) {
                cp.setTimer(setTime(System.currentTimeMillis() - cp.startTime)) ;
            }
        }
    }
    // return h:mm:ss string from milliseconds
    String setTime(long millisec) { 
        hours = (int)(millisec/3600000) ;
        rem = millisec - (hours*3600000) ;
        min = (int)(rem/60000) ;
        rem = rem - (min*60000) ;
        sec = (int)(rem/1000) ;      

        sMin = Integer.toString(min) ;
        if (sMin.length()==1) sMin = "0" + sMin ;
        sSec = Integer.toString(sec) ;
        if (sSec.length()==1) sSec = "0" + sSec ;
        sTime = "  " + Integer.toString(hours) + ":" + sMin + ":" + sSec ;
        return sTime ;
    }
}           
/******** BOARD ********
   controls disc positions, rules for moving discs
*/   
final class Board {
    public static final long serialVersionUID = 421233L;
    int PEGS=3, 
    DISC_SIZES[][]={ {68,18},{76,16},{84,14},{92,13},{100,12},
        {108,12},{112,11},{116,10},{120,9},{124,9}} ;
    private int peg[][], pegTop[]=new int[PEGS], discWidth[] ;
    private int discs, moveCount, minMoves ;
    private Hanoi main ;

    // constructor
    Board(int discs,Hanoi main) {
        this.discs = discs ;
        this.main = main ;
        peg = new int[discs][PEGS] ;
        // put all the disks on the first peg
        for (int i=0; i<discs; i++) peg[i][Hanoi.PEG1] = discs-i ;
        pegTop[Hanoi.PEG1] = discs-1 ;
        for (int i=1; i<PEGS; i++) pegTop[i] = -1 ;
        // calculate disc widths
        discWidth = new int[discs] ;
        for (int i=discs-1; i>=0; i--)
            discWidth[i] = DISC_SIZES[discs-Hanoi.MIN_DISCS][0] - 
                           (DISC_SIZES[discs-Hanoi.MIN_DISCS][1] * 
                            (discs-1-i)) ;
        moveCount = 0 ;
        // minimum moves is (2**discs)-1
        minMoves = ((int)Math.pow(2.0,discs)) - 1 ;
    }
    void setDisc(int d,int p) { peg[++pegTop[p]][p] = d ;}
    int getDisc(int d,int p) { return peg[d][p] ;}
    int getTopDisc(int p) { return peg[pegTop[p]--][p] ;}
    int getPegTop(int p) { return pegTop[p] ;}
    int getMoveCount() {return moveCount ;}
    int getMinMoves() {return minMoves ;}
    int getDiscWidth(int d) { return discWidth[d-1] ;}
    boolean isStartPeg(int i) { 
        if ((i >= 0)&&(pegTop[i]>=0)) return true ;
        else return false ;
    }
    String getBoardStatus() {
        String status = null ;
        if (pegTop[PEGS-1]==(discs - 1)) {
            status = (moveCount==minMoves) ? main.PERFECT : main.WIN ;
        }
        return status ;
    }
    // manual move
    boolean moveDisc(int d,int p1,int p2) {
        if ((p1>=0)&&(p2>=0)) {
            // to different peg which is empty or has larger disc
            if ( (p1!=p2)&&((pegTop[p2]<0)||(peg[pegTop[p2]][p2]>d)) ) {
                setDisc(d,p2) ;
                for (int imoveit=0;imoveit<3;imoveit++) {
                    //    main.diskArray[p2][main.present_peg_count[p2]][imoveit]= main.diskArray[p1][main.present_peg_count[p1]-1][imoveit];
                    //    main.diskArray[p1][main.present_peg_count[p1]-1][imoveit]=0;
                }
                main.present_peg_count[p1]--; main.present_peg_count[p2]++;
                moveCount++ ;
                return true ;
            }
        }
        setDisc(d,p1) ;
        return false ;
    }            
    // AutoSolve move
    void moveDisc(int p1,int p2) {
        setDisc(getTopDisc(p1),p2) ;
        moveCount++ ;
    }            
}
/******** BOARD CANVAS ********
  
*/  
final class BoardCanvas extends JPanel implements MouseListener,MouseMotionListener {
    public static final long serialVersionUID = 421232L;
    static final int PEG_SPACE=75, DISC_HEIGHT=15 ;
    static final Color COLOR_1=new Color(102,51,0), 
                               COLOR_2=new Color(153,102,0),
                                       COLOR_3=new Color(204,153,51),
                                               COLOR_4=new Color(255,204,0),
                                                       COLOR_5=new Color(255,255,204) ;
    private Image bufferImage ;
    private Graphics buffer ;
    private Hanoi main ;

    // constructor
    BoardCanvas(Hanoi main) { 
        this.main = main ;
        this.addMouseListener(this);
        this.addMouseMotionListener(this);

    }    
    void drawBoard(Board b,Image boardImage,int dragDisc,int dragX,int dragY) {
        int width=0, disc=0 ;
        if (buffer==null) {
            //  bufferImage=Toolkit.getDefaultToolkit().getImage("C:/WSWeb/computation/newsrc/src/board.gif");
        }
        // draw board

        // draw discs
        for (int p=main.PEG1; p<=main.PEG3; p++) {
            for (int d=0; d<=b.getPegTop(p); d++) {
                main.diskArray[p][d]=b.getDisc(d, p);
                disc = b.getDisc(d,p) ;
                if (disc!=0) {
                    width = b.getDiscWidth(disc) ;
                    main.theDisks[disc][0]=(((2*p)+1)*PEG_SPACE)-((width/2));
                    main.theDisks[disc][1]=(main.TABLE_TOP-((d+1)*DISC_HEIGHT));
                    main.theDisks[disc][2]=width;
                    main.present_peg_count[p]++;
                }
            }
        }

        if (dragDisc!=0) {
            width = b.getDiscWidth(dragDisc) ;
        }
        repaint() ; 
    }

    void drawADisc(int x,int y,int width,Graphics g) {
        g.setColor(COLOR_3) ;
        g.drawLine(x+4,y,x+width-4,y) ;         // 1
        g.drawLine(x+2,y+1,x+width-2,y+1) ;     // 2
        g.drawRect(x,y+7,width,1) ;             // 8,9
        g.setColor(COLOR_4) ;
        g.drawLine(x+1,y+2,x+width-1,y+2) ;     // 3
        g.drawRect(x,y+5,width,1) ;             // 6,7
        g.setColor(COLOR_5) ;
        g.drawLine(x+1,y+3,x+width-1,y+3) ;     // 4
        g.drawLine(x,y+4,x+width,y+4) ;         // 5
        g.setColor(COLOR_2) ;
        g.drawRect(x,y+9,width,1) ;             // 10,11
        g.drawLine(x+1,y+11,x+width-1,y+11) ;   // 12
        g.setColor(COLOR_1) ;
        g.drawLine(x+1,y+12,x+width-1,y+12) ;   // 13
        g.drawLine(x+2,y+13,x+width-2,y+13) ;   // 14
        g.drawLine(x+4,y+14,x+width-4,y+14) ;   // 15

    }
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        update(g) ;}
    public void update(Graphics g) { 
        bufferImage = Toolkit.getDefaultToolkit().getImage(getClass().getResource("blue.gif")) ;
        g.drawImage(bufferImage,0,0,this);
        for (int p=main.PEG1; p<=main.PEG3; p++) {
            for (int d=0; d<=main.bd.getPegTop(p); d++) {
                main.bc.drawADisc(main.theDisks[main.diskArray[p][d]][0],
                                  main.theDisks[main.diskArray[p][d]][1],main.theDisks[main.diskArray[p][d]][2],g);
            }
            if (main.dragDisk[0]!=0) {
                main.bc.drawADisc(main.dragDisk[1], main.dragDisk[2], main.dragDisk[3], g);
            }
        }
    }

    // mouse event handlers
    public void mousePressed(MouseEvent e) { main.selectDisc(e.getX(),e.getY()) ; return  ;}
    public void mouseDragged(MouseEvent e) { main.dragDisc(e.getX(),e.getY());  return ;}
    public void mouseMoved(MouseEvent e){return;}
    public void mouseReleased(MouseEvent e) { main.dropDisc(e.getX(),e.getY()) ; return ;}
    public void mouseExited(MouseEvent e) { return ;}
    public void mouseEntered(MouseEvent e) { return ;}
    public void mouseClicked(MouseEvent e) {  return;}
}
/******** CONTROL PANEL ********
   main UI components
*/  
final class ControlPanel extends Panel implements ActionListener {
    public static final long serialVersionUID = 421231L;
    static final int MAX_DELAY=1000 ;
    public long startTime = System.currentTimeMillis() ;
    private Panel discsPanel ;
    private Button bDiscsMinus, bDiscsPlus, bRestart, bSolve ;
    private Checkbox cbTimer ;
    private TextField tfDiscs, tfTimer ;
    private Scrollbar sbSpeed ;
    private Hanoi main ;
    private int discs=Hanoi.MIN_DISCS, delay=200 ;

    // constructor
    ControlPanel(Hanoi main) {
        this.main = main ;
        setLayout(new GridLayout(9,2,0,3)) ;
        setFont(Hanoi.textFont) ;
        // DISCS
        discsPanel = new Panel() ;
        discsPanel.setLayout(new BorderLayout(2,0)) ;
        tfDiscs = new TextField(3) ;
        tfDiscs.setFont(Hanoi.monoFont) ;
        tfDiscs.setForeground(Color.black) ;
        tfDiscs.setBackground(Color.lightGray) ;
        tfDiscs.setEditable(false) ;
        setDiscs(discs) ;
        discsPanel.add("West",bDiscsMinus=new Button(main.MINUS)) ;
        bDiscsMinus.addActionListener(this);  //new1
        bDiscsMinus.setFont(Hanoi.monoFont) ;
        discsPanel.add("Center",tfDiscs) ;
        discsPanel.add("East",bDiscsPlus=new Button(main.PLUS)) ;
        bDiscsPlus.addActionListener(this);  //new1
        bDiscsPlus.setFont(Hanoi.monoFont) ;
        discsPanel.validate() ;
        // SPEED
        sbSpeed = new Scrollbar(Scrollbar.HORIZONTAL,
                                (MAX_DELAY-delay),0,0,(MAX_DELAY-9)) ;
        sbSpeed.setBackground(Color.lightGray) ;
        sbSpeed.setBlockIncrement(MAX_DELAY/10) ;
        sbSpeed.setUnitIncrement(MAX_DELAY/100) ;
        bSolve = new Button(main.SOLUTION) ;
        bSolve.addActionListener(this);  //new1
        bRestart=new Button(main.RESTART);
        bRestart.addActionListener(this);  //new1
        // TIMER
        cbTimer = new Checkbox("   "+main.TIMER) ;
        cbTimer.setBackground(Color.lightGray) ;
        tfTimer = new TextField(10) ;
        tfTimer.setFont(Hanoi.monoFont) ;
        tfTimer.setForeground(Color.white) ;
        tfTimer.setBackground(Color.black) ;
        tfTimer.setEditable(false) ;
        // construct panel
        add(new Label(main.DISCS,Label.CENTER)) ;
        add(discsPanel) ; 
        add(bRestart) ;
        add(bSolve) ; 
        add(new Label(main.SPEED,Label.CENTER)) ;
        add(sbSpeed) ;
        add(cbTimer) ;
        add(tfTimer) ;
        validate() ;
        setPlusMinusEnable() ;
        cbTimer.setState(true) ;
    }
    void setTimerEnable(boolean b) { cbTimer.setEnabled(true) ;}
    void setAutoSolveEnable(boolean b) { bSolve.setEnabled(true) ;}
    void setPlusMinusEnable() { 
        if (discs<Hanoi.MAX_DISCS) {
            bDiscsPlus.setEnabled(true);
        }
        if (discs>Hanoi.MIN_DISCS) {
            bDiscsMinus.setEnabled(true);
        }
    }
    void setDiscs(int i) { 
        String s = Integer.toString(i) ;
        if (s.length()==1) s = "  " + s ;
        else if (s.length()==2) s = " " + s ;
        tfDiscs.setText(s) ;
    }
    void setTimer(String s) { tfTimer.setText(s) ;}
    int getDiscs() { return discs ;} 
    int getDelay() { return  MAX_DELAY-sbSpeed.getValue();} 
    boolean isTimerOn() { return cbTimer.getState() ;}

    // handle button clicks
    public void actionPerformed(ActionEvent e) {
        if (e.getSource()==bSolve) {
            setAutoSolveEnable(false) ;
            main.startSolveThread() ;
        } else {
            if ((e.getSource()==bDiscsPlus)&&(discs<Hanoi.MAX_DISCS)) setDiscs(++discs) ;  //The one bug Part 1.
            else if ((e.getSource()==bDiscsMinus)&&(discs>Hanoi.MIN_DISCS)) setDiscs(--discs) ; //The one bug Part 2.
            setPlusMinusEnable() ;
            main.restartGame() ;
        }
        return ;
    }
    // handle scrollbar
    public void processEvent(AWTEvent e) {
        System.out.println(e.getSource());
        if (e.getSource()==sbSpeed) {
            System.out.println(delay);
            delay = MAX_DELAY - sbSpeed.getValue() ;
            return  ;
        }
        super.processEvent(e) ;
        return;
    }
}
/******** STATUS PANEL ********
   UI components for status messages and move counter
*/   
final class StatusPanel extends Panel {
    public static final long serialVersionUID = 421234L;
    private TextField tfStatus, tfMoveCount ;
    private Hanoi main ;

    // constructor
    StatusPanel(Hanoi main) {
        this.main = main ;
        setFont(main.statusFont) ;
        tfStatus = new TextField(60) ;
        tfStatus.setForeground(Color.white) ;
        tfStatus.setBackground(Color.black) ;
        tfStatus.setEditable(false) ;

        tfMoveCount = new TextField(6) ;
        tfMoveCount.setFont(main.monoFont) ;
        tfMoveCount.setForeground(Color.white) ;
        tfMoveCount.setBackground(Color.black) ;
        tfMoveCount.setEditable(false) ;

        add(tfStatus) ;
        add(tfMoveCount) ;
        add(new Label(main.MOVES)) ;
        validate() ;
    }
    void setMoveCount(int i) { 
        String s=Integer.toString(i) ;

        switch (s.length()) {
        case 1: s = "    " + s ; break ;
        case 2: s = "   " + s ; break ;
        case 3: s = "  " + s ; break ;
        case 4: s = " " + s ; break ;
        }
        tfMoveCount.setText(s) ;
    }
    void setStatus(String s) { tfStatus.setText(s) ;} 
}