Sunday 13 May 2018

Web Tutorial: The Tower of Hanoi (Part 1/3)

It's time for more QBasic! It'll be something fun today. (Isn't it always?)

The Tower of Hanoi is a puzzle game where you have to move all the disks from one rod to another, stacked in descending order of size (largest at the bottom, smallest on top) using as few moves as possible. Taking out one disk from a rod and placing it on another rod counts as one move. There are, in the basic configuration, three rods and four disks. Additionally,  larger disks cannot be stacked on top of smaller disks.

For the first part of this tutorial, let's focus on drawing the rods and disks. Honestly, that's the easy part. First, let's assume that the background is black. If you really want, you can use a variable for that, but I'm just going to go with black.

Let's start with drawing the bases. The bases will be a nice brown color. That's 6 in QBasic. Here, we implement the action as a subroutine drawbase(), that accepts a parameter stackno. We'll put in a call to drawbase(), adding an argument of "1", to test.
drawbase 1

SUB drawbase (stackno)

END SUB


Set the foreground to black and the background to brown. Then print three brown spaces, followed by stackno, followed by another three brown spaces. Reset the colors at the end of the subroutine.
SUB drawbase (stackno)
COLOR 0, 6
FOR i = 1 TO 3
    PRINT " ";
NEXT

PRINT stackno;

FOR i = 1 TO 3
    PRINT " ";
NEXT

COLOR 15, 0


END SUB


This is what you should have. Simple enough, right?

EDITOR'S NOTE: The following screenshot is incorrect because I neglected to reset the colors originally. You should really be seeing "Press any key to continue" in white text on black background.


Now, delete the call to drawbase(), create a call to drawpiece() instead (using the same argument of "1") and create another subroutine, drawpiece(). This will accept the parameter pieceno.
drawpiece 1

SUB drawpiece (pieceno)

END SUB

SUB drawbase (stackno)
COLOR 0, 6
FOR i = 1 TO 3
    PRINT " ";
NEXT

PRINT stackno;

FOR i = 1 TO 3
    PRINT " ";
NEXT

COLOR 15, 0
END SUB


Also, define the array piece. Then initialize the values. Each element in the piece array represents a disk. See how the smallest one is 1, followed by 2, and so on? The number of spaces also corresponds to the number of the disk. For instance, the first element of piece, piece(1), has one space on either side of the "1". For piece(2), there are two spaces on either side of the "2". This can be done programmatically, but I don't see the point since we're not reusing this anywhere.

DIM SHARED piece(4) AS STRING

piece(1) = " 1 "
piece(2) = "  2  "
piece(3) = "   3   "
piece(4) = "    4    "

drawpiece 1

SUB drawpiece (pieceno)

END SUB


Now drawpiece() will print black spaces in front and back, just like in drawbase(). However, unlike drawbase(), the size of the piece varies according to the number. The bigger the number, the bigger the piece, and therefore the smaller the black spaces. The largest piece is piece(4), so piece(4) will have  (5 - 4 = 1) black space preceding and after it. The smallest piece, piece(1), will have (5 - 1 = 4) black spaces.
SUB drawpiece (pieceno)
COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i
END SUB


Next, we cater for a case of pieceno being 0. When that happens, you print a single brown space, to indicate that this section of rod has no disks, and it's all rod.
SUB drawpiece (pieceno)
COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

IF pieceno = 0 THEN
    COLOR 0, 6
    PRINT " ";
ELSE

END IF

COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i
END SUB


And if pieceno is not 0, which means there is a disk on that section of rod, you draw the element of the piece array referenced by pieceno. For the color of the disk, we'll just use the color that pieceno represents in the QBasic color table. piece(1) will be blue, piece(2) will be green, piece(3) will be yellow and piece(4) will be red.
SUB drawpiece (pieceno)
COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

IF pieceno = 0 THEN
    COLOR 0, 6
    PRINT " ";
ELSE
    COLOR 0, pieceno
    PRINT piece(pieceno);
END IF

COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

END SUB


After that, reset the colors.
SUB drawpiece (pieceno)
COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

IF pieceno = 0 THEN
    COLOR 0, 6
    PRINT " ";
ELSE
    COLOR 0, pieceno
    PRINT piece(pieceno);
END IF

COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

COLOR 15, 0
END SUB


Now let's test the subroutine!
drawpiece 1
drawpiece 2
drawpiece 3
drawpiece 4
drawpiece 0


As expected.


Now delete the test statements. And write a new subroutine, drawstack(). This one will leverage off the subroutine, drawbase(), that we created earlier.
SUB drawbase (stackno)
COLOR 0, 6
FOR i = 1 TO 3
    PRINT " ";
NEXT

PRINT stackno;

FOR i = 1 TO 3
    PRINT " ";
NEXT

COLOR 15, 0
END SUB

SUB drawstack ()

END SUB


The entire stack contains three bases and three rods. Counting the bases, it will be 6 spaces high. So create a For loop iterating from 1 to 6. Within it, send the cursor to a y value of (i + 1) and an x value of 2. The x value is to allow some space at the left side of the screen.
SUB drawstack ()
FOR i = 1 TO 6
    LOCATE i + 1, 2
NEXT i
END SUB


Define another For loop within it, iterating from 1 to 3. j will represent each base and rod.
SUB drawstack ()
FOR i = 1 TO 6
    LOCATE i + 1, 2

    FOR j = 1 TO 3

    NEXT j
NEXT i
END SUB


If we're at the top of the stack (i.e, i is 1), whether or not there are disks, there will be only the rod showing. So call drawpiece(), and use 0 as an argument.
SUB drawstack ()
FOR i = 1 TO 6
    LOCATE i + 1, 2

    FOR j = 1 TO 3
        IF i = 1 THEN
            drawpiece 0
        ELSE

        END IF
    NEXT j
NEXT i
END SUB


If i is not 1, then check if i is 6, which means you're now drawing the bottom of the stack. If so, draw the base by calling drawbase() and pass in j as an argument.
SUB drawstack ()
FOR i = 1 TO 6
    LOCATE i + 1, 2

    FOR j = 1 TO 3
        IF i = 1 THEN
            drawpiece 0
        ELSE
            IF i = 6 THEN
                drawbase j
            ELSE

            END IF
        END IF
    NEXT j
NEXT i
END SUB


If i is not 1 or 6, then... well, for now let's just use drawpiece() with an argument of 0 to show what the entire setup would look like without disks.
SUB drawstack ()
FOR i = 1 TO 6
    LOCATE i + 1, 2

    FOR j = 1 TO 3
        IF i = 1 THEN
            drawpiece 0
        ELSE
            IF i = 6 THEN
                drawbase j
            ELSE
                drawpiece 0
            END IF
        END IF
    NEXT j
NEXT i
END SUB


Now call drawstack().
drawstack

SUB drawpiece (pieceno)
COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i


OK, this is a little off. Let's fix it...


In drawpiece(), add a black space after drawing.
SUB drawpiece (pieceno)
COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

IF pieceno = 0 THEN
    COLOR 0, 6
    PRINT " ";
ELSE
    COLOR 0, pieceno
    PRINT piece(pieceno);
END IF

COLOR 0, 0
FOR i = 1 TO (5 - pieceno)
    PRINT " ";
NEXT i

COLOR 0, 0
PRINT " ";

COLOR 15, 0
END SUB


Do the same for drawbase().
SUB drawbase (stackno)
COLOR 0, 6
FOR i = 1 TO 4
    PRINT " ";
NEXT

PRINT stackno;

FOR i = 1 TO 4
    PRINT " ";
NEXT

COLOR 0, 0
PRINT " ";

COLOR 15, 0
END SUB


Ladies and gentlemen, your rods and bases!


Next

We need to put those disks on the first rod. We already created the piece array, and the drawpiece() subroutine. We'll take care of placement in the next part of this tutorial.

No comments:

Post a Comment