This site requires JavaScript, please enable it in your browser!
Greenfoot back
danpost
danpost wrote ...

2013/12/9

On 'Tower of Hanoi' puzzle algorithm

danpost danpost

2013/12/9

#
No help is needed with this code. It was just the only way to share it without uploading the scenario itself. The following is the world class the generates the solutions to the puzzle. It does not use recursion or iterating of any kind to solve the puzzle. It uses its current state and move count to determine the move that is to be made next, provided the puzzle has not yet been completed.
import greenfoot.*;

public class Hanoi extends World
{
    private int[] cts = new int[3]; // track the number of rings on each of the 3 pegs
    private static int ringCount = 8; // holds the total number of rings in the puzzle
    private Ring[] order = new Ring[ringCount]; // lists order of rings by size
    private Ring[][] rings = new Ring[3][ringCount]; // tracks the arrangement of rings on each of the 3 pegs
    Counter counter; // to track and display move count
    
    public Hanoi()
    {
        super(800, 400, 1);
        prepareBackground();
        addActors();
    }
    
    private void addActors()
    {
        // the rings (added to arrays as needed)
        for (int i=0; i<ringCount; i++)
        {
            rings[0][i] = new Ring(ringCount-1-i);
            addObject(rings[0][i], 200, 300-30*i);
            addRing(0, rings[0][i]);
            order[ringCount-1-i] = rings[0][i];
        }
        // the move counter
        counter = new Counter("Moves: ");
        addObject(counter, 80, 25);
    }
    
    private void prepareBackground()
    {
        GreenfootImage bg = getBackground();
        // the board with pegs
        bg.setColor(new java.awt.Color(128, 50, 50));
        bg.fillRect(50, 316, 700, 30);
        bg.fillRect(195, 51, 10, 265);
        bg.fillRect(395, 51, 10, 265);
        bg.fillRect(595, 51, 10, 265);
        // the instructions and ring count text
        GreenfootImage text = new GreenfootImage("Use 'space' to execute a move", 24, java.awt.Color.black, null);
        bg.drawImage(text, 100, 360);
        text = new GreenfootImage("Use '3' through '8' to adjust ring count", 24, java.awt.Color.black, null);
        bg.drawImage(text, 400, 360);
        text = new GreenfootImage("Rings: "+ringCount, 24, java.awt.Color.black, null);
        bg.drawImage(text, 700, 15);
    }
    
    /**
     * removes the top ring from the array that tracks the arrangement of the rings on the pegs
     * and returns the ring removed from the array
     */
    private Ring removeRing(int pegNum)
    {
        Ring ring = rings[pegNum][cts[pegNum]-1];
        cts[pegNum]--;
        return ring;
    }
    
    /**
     * adds a ring to a peg in the array that tracks the arrangement of the rings on the pegs
     */
    private void addRing(int pegNum, Ring ring)
    {
        rings[pegNum][cts[pegNum]] = ring;
        cts[pegNum]++;
    }
    
    /**
     * checks for keystrokes for move execution and setting ring count
     */
    public void act()
    {
        String key = Greenfoot.getKey();
        if ("space".equals(key)) executeMove();
        if (key != null && "345678".indexOf(key) >= 0)
        {
            ringCount = "345678".indexOf(key)+3;
            Greenfoot.setWorld(new Hanoi());
        }
    }
    
    /**
     * executes the next move in solving the puzzle if not solved yet
     */
    private void executeMove()
    {
        // check if all moves have been executed
        if (counter.getValue() == (int)Math.pow(2, ringCount)-1) return;  // puzzle completed
        int moves = counter.increment(); // get incremented move count
        // determine 'from' peg
        int bit = ringCount-1;
        while (moves%(int)Math.pow(2, bit) != 0) bit--;
        int from = (order[bit].getX()-200)/200;
        // remove ring from 'from' peg
        Ring ring = removeRing(from);
        // determine 'to' peg
        int to = from;
        // first possible 'to' peg with rings
        if (cts[(from+1)%3] != 0 && // rings on 'to' peg
             ring.getIndex() < rings[(from+1)%3][cts[(from+1)%3]-1].getIndex() && // top ring on 'to' peg is larger
            (rings[(from+1)%3][cts[(from+1)%3]-1].getIndex()+ring.getIndex())%2 == 1) // top ring on 'to' peg is legal
        {
            to = (from+1)%3;
        }
        // other possible 'to' peg with rings
        else if (cts[(from+2)%3] != 0 && // rings on 'to' peg
            ring.getIndex() < rings[(from+2)%3][cts[(from+2)%3]-1].getIndex() && // top ring on 'to' peg is larger
            (rings[(from+2)%3][cts[(from+2)%3]-1].getIndex()+ring.getIndex())%2 == 1) // top ring on 'to' peg is legal
        {
            to = (from+2)%3;
        }
        // empty 'to' pegs
        else if (cts[(from+1)%3] == 0) to = (from+1)%3; else to = (from+2)%3; // 'to' peg is empty
        moveRing(ring, from, to);
        addRing(to, ring);
    }
    
    /**
     * animates the movement of a ring from one peg to another
     */
    private void moveRing(Ring ring, int from, int to)
    {
        while (ring.getY() != 30)
        {
            ring.setLocation(ring.getX(), ring.getY()-10);
            Greenfoot.delay(1);
        }
        int dir = (int)Math.signum(to-from);
        while (ring.getX() != 200+200*to)
        {
            ring.setLocation(ring.getX()+dir*10, ring.getY());
            Greenfoot.delay(1);
        }
        int targetY = 300-30*(cts[to]);
        while (ring.getY() != targetY)
        {
            ring.setLocation(ring.getX(), ring.getY()+10);
            Greenfoot.delay(1);
        }
    }
}
shrucis1 shrucis1

2013/12/10

#
Wow, this is really nice. The methods you used for animating are slightly different from mine, and the algorithim is really interesting. I have yet to fully understand your code, but from these two lines: to = (from+1)%3; to = (from+2)%3; , It seems like your algorithm is similar to the method of either moving a ring clockwise or counterclockwise based on the current move.
danpost danpost

2013/12/10

#
shrucis1 wrote...
I have yet to fully understand your code, but from these two lines: to = (from+1)%3; to = (from+2)%3; , It seems like your algorithm is similar to the method of either moving a ring clockwise or counterclockwise based on the current move.
I guess you could look at it that way. '(from+1)%3' refers to the peg number of the next peg (if 'from' refers to the last peg, then this would refer to the first), and '(from+2)%3' refers to the peg after that. This was an easy way to refer to the pegs that the ring was not being removed from. It would matter not which peg was referred to by which expression as long as both possible 'to' pegs were referred to by both expressions.
shrucis1 shrucis1

2013/12/10

#
Ah, I guess I had that method of looking at it in my head after doing some research on the iterative solution. It seemed similar to this: http://www.ecse.rpi.edu/~wrf/p/28-sigplan84-hanoi.pdf
danpost danpost

2013/12/10

#
shrucis1 wrote...
It seemed similar to this: http://www.ecse.rpi.edu/~wrf/p/28-sigplan84-hanoi.pdf
I did not in any way research the possible ways to code the generating of solutions and unless you provide here what that link is about, I will not know how to compare it to mine (I refuse to go to any site I am not familiar with).
shrucis1 shrucis1

2013/12/10

#
Sorry for the confusion, I wasn't suggesting you copied your idea from anyone else, I was simply remarking that it seemed similar to a solution I had come across while doing research on the problem, and was pointing to it because I believe the explanation for that method is simpler, and if it is similar to yours, it is an easier way of understanding it. As for the content of that link, it was a two-page pdf document. Here's a screenshot:
danpost danpost

2013/12/11

#
Alright. I see what you were saying. It is a bit different however. I was no considering any 'clockwise/counterclockwise' referencing and determining the disk to move was absolute in my case (because I utilized the value of the move count for that). The way I did it, if you had to move a disk on top of another disk, it not only had to be smaller, but the number of the disks (as numbered in the 'pdf' you have shown) had to be different as far as odd/even. My method could be written so: 1. Number the disks from 1 (the smallest to n (the largest) 2. On any move, the disk to move is the first power of 2 (starting from the highest -- '2^(n-1)') that evenly go into the current move number. 3. Never put a higher numbered disk on a smaller numbered disk 4. Never put an even numbered disk on an odd numbered disk or an odd numbered disk on an even numbered disk 5. Move to a stick that currently has disks on it, if possible
shrucis1 shrucis1

2013/12/11

#
Ah, I see. Thank you for the clarification!
danpost danpost

2013/12/11

#
Sorry, I stated rule 4 incorrectly. It should read: 4. Never put an even numbered disk on an even numbered disk or an odd numbered disk on an odd numbered disk
shrucis1 shrucis1

2013/12/11

#
That makes more sense! Otherwise you wouldn't be able to complete the puzzle. Once again, thank you for this explanation.
danpost danpost

2013/12/11

#
shrucis1 wrote...
That makes more sense! Otherwise you wouldn't be able to complete the puzzle. Once again, thank you for this explanation.
I was going to mention that; but decided it went without saying. That, and I did have it explained correctly in the summary before the giving of the rules.
danpost danpost

2013/12/21

#
I finally uploaded mine. Since others decided to get into this and since ten days have passed, I felt it would not hurt much at this point to go ahead and get mine in. It has been upgraded to include both auto and manual modes and now includes a separate 'Rules' world.
danpost danpost

2013/12/22

#
I simplified the algorithm to this : 1. Number the disks from 1 (the smallest) to n (the largest) 2. On any move, the disk to move is the first power of 2 (starting from the highest -- '2^(n-1)') that evenly go into the current move number. 3. Move all even numbered disks one way and odd numbered disks the other (clockwise or counter-clockwise). Believe it or not, rule 3, here, will not break any of the rules 3, 4, or 5 of my previous rules.
You need to login to post a reply.