Sunday, 27 May 2018

Rise of the Bike-sharing Phenomenon

With mobile technology now invading just about every aspect of our lives, it should come as little surprise that bike-sharing, which has been a thing since the 60s, is now available via apps. Dockless bicycle sharing has been in Singapore since last year with moderate success. Today, no less than six different companies vie for their share of the consumer pie.



Basically, you download the appropriate app on your mobile phone, tie your credit card account to it, and use the scanner function in the app (Bluetooth required) to unlock the bike.

The timer starts, and does not stop until you park and lock the bike.

Here's the low-down on the bike-sharing phenomenon...


Conveniently available.

The Good

Generally cheap. Upon signing up, most bike-sharing operators will give you some sort of special trial period offer. Competition is intense on this tiny island.

Perks for responsible use. Parking the bicycle in an approved spot or reporting damaged bikes nets you credit. This credit may offset the cost of your next bike-share.

Convenience. Being able to get from Point A to Point B is a perk that just cannot be understated. If I fall asleep on the bus and miss my stop, having a bicycle at my disposal will ensure I still make it to the office on time. Being able to use a bicycle without the hassle of actual ownership, is great.

Mobile-enabled. Having these services available via mobile is a huge step in the right direction. Yes, I'm aware this started way before last year, but still...


Anyone feel like riding
on this rustbucket?

The Bad

Uneven distribution. If you only use one bike-share app, finding an appropriate bike for your use from that one operator, is a bit of a crapshoot. Certain bikes from certain vendors are simply more plentiful at certain places.

Damaged bikes. There have been times when I found a bike, and the handlebars came off in my hand. Or those whose brakes have been shot to hell. Or those that have been out in the rain so long, the frame has corroded and riding on them is pretty much taking your life in your grubby little hands.

Finding a good fit. The bikes cater to the average build. If you're exceptionally fat, tall or lightweight, there just aren't that many bikes you can simply pick up and ride without a certain amount of discomfort.

Language. Some of the apps are almost incomprehensible. Ofo, for instance, comes from China and the English they use in their app... horrible does not begin to describe it.


Randomly dumped.

The Downright Ugly


Bike-hogging. I've seen these bikes being parked outside someone's front door, as if they own them. Worse - I've actually seen personal bicycle locks on the wheels of these bikes.

Dumping. Some people just toss the bikes when they're done. In the drain, in the grass, in the middle of the road... seriously, guys?

Vandalism. I can understand wear and tear. I can understand a few knocks here and there. What I can't condone is the seat of a bike being knifed, or your initials being scraped into the body. Or pedals missing. That's just being a douche.

Operator names. Can I just say that the names of the operators are really terrible? Firstly, they have very similar names to each other. And then there's Ofo, which rhymes with the short-form for an impolite term describing an incestuous dude. And then there's GBikes. When you say it fast, it sounds like a Hokkien expletive. The only possible exception is Baicycle, which is a pun on the Chinese word for "white". And damn, I love puns. The lamer, the better.

The Future

Bike-sharing's here to stay, that's for sure. However, with the rising costs of a bike-share, along with up-front payment implemented by many vendors, casual users are being put off, while serious users will probably buy their own. Kind of like the rising costs of ride-sharing - it was a good thing while it lasted.

Singapore's a tiny island, and over time it's my belief that one or two operators will trump the rest. There's just not enough space for that many bike-sharing operators here. Something has to give, eventually.


You don't GBike!
T___T 

Wednesday, 23 May 2018

The WSQ Way to Self-improvement

Identifying and addressing knowledge gaps is part and parcel of being a web developer; indeed just about any technical field. I remarked on this last month, and now here's a concrete suggestion. Today, I'd like to cover one of the ways in which developers may find out exactly what knowledge they lack.

In a field as wide and varied as software development, there is a very real danger of specializing too much, or worse; not realizing where the knowledge gaps are in the first place, and what's required by the industry. Because the answer to that question varies depending on who you ask. In cases like these, we need a singularly reliable source of truth - the Government. I'm not saying that I agree with everything the Government pushes, but certification from the Government does mean that if you have such a certification and seek employment in Singapore, there are very few organizations that can argue against your certification. After all, this spec was developed by industry experts and endorsed by the highest authority in the land.

Cover

The Workforce Skills Qualification credential system was set up by the Singapore Government to set up competency frameworks across a variety of industries, so as to improve labor movement and quality control. A few of these frameworks are pertinent to web development - InfoComm and Creative Industries. For today's example, I downloaded a copy of a Competency Unit - Code Scripts to Provide Front-End Functionality for Websites - from the Creative Industries WSQ framework at https://www.skillsconnect.gov.sg/sop/portal/. By referring to this document, if you're a front-end developer, even (or especially) if you're an aspiring one, you may be able to identify areas in which you can shore up your skills and knowledge to meet industry standards.

Here's a breakdown of the spec...

Relevant Job Roles/Occupations
Assumed Skills and Knowledge
Performance Statements

Relevant Job Roles/Occupations

This basically lets you know what roles or occupations would find this relevant. Pretty straightforward there... though I can't say I agree with the term "HTML Programmer". What is this, 1995?!

Assumed Skills and Knowledge

This is a list of ideal prior knowledge for taking up training in this Competency Unit. Meaning, it'll be a whole lot easier for you to learn this Competency Unit if you know this stuff. I'm not sure that these should be optional, though. I mean, if you don't know HTML, CSS or even basic programming, what the hell are you doing trying to code scripts? Most, if not all, scripts manipulate the Document Object Model, which is the product of HTML. Honestly, having no knowledge of HTML while trying to learn scripting, is the software equivalent of attempting the hundred meter dash without learning to walk first.

Performance Statements

A list of items, in no particular order of importance, that someone should be able to demonstrate in order to pass this Competency Unit.

Pay attention to this one. Be honest with yourself as you assess your ability to perform each of these tasks. Anything you're not so great at? Anything you've never heard of at all? These are excellent starting points.


Underpinning Knowledge

Underpinning Knowledge

This is all the knowledge that should be behind the items you have performed above. There is, for example, no sense in writing a HTML page and claiming you know how to write a HTML page when you don't even know what "HTML" stands for. Or being able to write scripts that do stuff, without knowing what the lines in the script do and why they are required.

We've all dealt with those devs before, from time to time we may even be them - they're called "copy-paste programmers".

As in the Performance Statements, what's needed is for you to go through this list and see if there's anything you don't know, then start rectifying your shortfalls from there.

Range of Application

Range of Application

This is about contextualization. Not every tech job requires you to know everything under the Performance Statements and Underpinning Knowledge. For example, not every tech context requires objects... or even loops (though that would be pretty strange). So there are some examples of each Performance Statement and Underpinning Knowledge, of which a subset has to be fulfilled.

Research your competencies!

A tech career is an ongoing progress. The day you stop learning, the day you stop wanting to learn, your career is already dead - you just don't know it yet.

Use the (work)Force, Luke!
T___T


Friday, 18 May 2018

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

The final part of this web tutorial is here, and it's perhaps the most important thing we'll be doing.

Comment out this part. Or erase it.
'openpiece = pop(1)
'push 2, openpiece
'openpiece = pop(1)


Declare the variable operation. It's not "shared" because it isn't referenced in any subroutine or function.
DIM SHARED stack(3, 4) AS INTEGER
DIM SHARED top(3) AS INTEGER
DIM SHARED piece(4) AS STRING
DIM SHARED openpiece AS INTEGER
DIM operation AS INTEGER


Now, let's wrap the call to drawstack() in a While loop. As long as the third rod does not have four disks, the user has not won the game.
'openpiece = pop(1)
'push 2, openpiece
'openpiece = pop(1)

WHILE top(3) < 4
    drawstack
WEND


Now, set operation to 0. And create a While loop within the current While loop, that will run for as long as operation is 0.
WHILE top(3) < 4
    drawstack

    operation = 0

    WHILE operation = 0

    WEND
WEND


Move the cursor to below the drawn setup, and proceed to ask for input from the user. If openpiece is 0, that means all disks are on rods. So you have to ask which piece to remove. If it's not 0, then that means there is an open, unassigned piece and you need to place it on a rod. Whatever the user input it, assign the value to the variable selected.
WHILE top(3) < 4
    drawstack

    operation = 0

    WHILE operation = 0
        LOCATE 12, 2
        IF openpiece = 0 THEN
            INPUT "Remove piece  "; selected
        ELSE
            INPUT "Place onto rod"; selected
        END IF
    WEND
WEND


Next, set the value of operation to the value returned by the function doable(), passing in selected as an argument.
WHILE top(3) < 4
    drawstack

    operation = 0

    WHILE operation = 0
        LOCATE 12, 2
        IF openpiece = 0 THEN
            INPUT "Remove piece    "; selected
        ELSE
            INPUT "Place onto stack"; selected
        END IF

        operation = doable(selected)
    WEND
WEND


And then let's create the doable() function. First, set the function to return 0 by default.
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 (stack(j, 6 - i))
            END IF
        END IF
    NEXT j
NEXT i

COLOR 15, 0
LOCATE 9, 2
PRINT "Open ";
IF openpiece = 0 THEN
    PRINT "(none)         ";
ELSE
    drawpiece openpiece
END IF
END SUB

FUNCTION doable (userinput)
doable = 0
END FUNCTION


Again, we set  a conditional revolving around whether openpiece is 0 or not.
FUNCTION doable (userinput)
doable = 0

IF openpiece = 0 THEN

ELSE

END IF
END FUNCTION


So if there is no disk currently unassigned to any rod, iterate from the first rod to the third. And if the appropriate element of the top array is greater than zero (which means that particular rod is not empty)...
FUNCTION doable (userinput)
doable = 0
IF openpiece = 0 THEN
    FOR i = 1 TO 3
        IF top(i) > 0 THEN

        END IF
    NEXT
ELSE

END IF
END FUNCTION


...and if the number of the topmost disk on that rod matches userinput, then set doable to 1 and openpiece to the value returned by pop(), with the number of that rod passed in as an argument. It's not the most efficient way ever, but it works. This also means that if the user enters in some funny number, it won't work.
FUNCTION doable (userinput)
doable = 0
IF openpiece = 0 THEN
    FOR i = 1 TO 3
        IF top(i) > 0 THEN
            IF stack(i, top(i)) = userinput THEN
                doable = 1
                openpiece = pop(i)
            END IF
        END IF
    NEXT
ELSE

END IF
END FUNCTION


Now, if openpiece isn't 0, that means you need to "push" the disk on the indicated rod. First, we need to ascertain that userinput is between 1 and 3. Seems a bit defensive, but it's necessary.
FUNCTION doable (userinput)
doable = 0
IF openpiece = 0 THEN
    FOR i = 1 TO 3
        IF top(i) > 0 THEN
            IF stack(i, top(i)) = userinput THEN
                doable = 1
                openpiece = pop(i)
            END IF
        END IF
    NEXT
ELSE
    IF userinput >= 1 AND userinput <= 3 THEN

    END IF
END IF
END FUNCTION


Now, you can place the disk on the rod only if the topmost disk on the rod is larger than the disk you're trying to "push", or if that rod is empty anyway.
FUNCTION doable (userinput)
doable = 0
IF openpiece = 0 THEN
    FOR i = 1 TO 3
        IF top(i) > 0 THEN
            IF stack(i, top(i)) = userinput THEN
                doable = 1
                openpiece = pop(i)
            END IF
        END IF
    NEXT
ELSE
    IF userinput >= 1 AND userinput <= 3 THEN
        IF stack(userinput, top(userinput)) > openpiece OR top(userinput) = 0 THEN

        END IF
    END IF
END IF
END FUNCTION


Set doable to 1 and execute the appropriate push().
FUNCTION doable (userinput)
doable = 0
IF openpiece = 0 THEN
    FOR i = 1 TO 3
        IF top(i) > 0 THEN
            IF stack(i, top(i)) = userinput THEN
                doable = 1
                openpiece = pop(i)
            END IF
        END IF
    NEXT
ELSE
    IF userinput >= 1 AND userinput <= 3 THEN
        IF stack(userinput, top(userinput)) > openpiece OR top(userinput) = 0 THEN
            doable = 1
            push userinput, openpiece
        END IF
    END IF
END IF
END FUNCTION


Now start the program and try to place the rods!









Some improvements

We need to keep track of the number of moves! So let's create a variable, moves. Set it to 0.
DIM SHARED stack(3, 4) AS INTEGER
DIM SHARED top(3) AS INTEGER
DIM SHARED piece(4) AS STRING
DIM SHARED openpiece AS INTEGER
DIM operation AS INTEGER
DIM SHARED moves AS INTEGER

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

FOR i = 1 TO 3
    FOR j = 1 TO 4
        stack(i, j) = 0
    NEXT j

    top(i) = 0
NEXT i

push 1, 4
push 1, 3
push 1, 2
push 1, 1
moves = 0


In the push() subroutine, increment moves at the end. This means that every "push" is counted as one move. Yes, that means after all the initial calls to push(), openpiece should be 4. Now see why you set moves to 0 after the push() calls?
SUB push (stackno, value)
top(stackno) = top(stackno) + 1
stack(stackno, top(stackno)) = value
moves = moves + 1
openpiece = 0
END SUB


Now, at the end of the drawstack() subroutine, after printing out openpiece, print out the moves used so far!
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 (stack(j, 6 - i))
            END IF
        END IF
    NEXT j
NEXT i

COLOR 15, 0
LOCATE 9, 2
PRINT "Open ";
IF openpiece = 0 THEN
    PRINT "(none)         ";
ELSE
    drawpiece openpiece
END IF

LOCATE 10, 2
PRINT "Moves ";
PRINT moves;
END SUB


Go ahead, run the program again. Does the game keep track of the number of moves you have made?








Now, for one more improvement. Tell the user when they have won the game. After that While loop, call drawstack() again and move the cursor, then print the congratulatory message. This is logical because they can only have won if they've exited the While loop, meaning that top(3) is 4, which in turn means that all four disks are on the third rod.

WHILE top(3) < 4
    drawstack

    operation = 0

    WHILE operation = 0
        LOCATE 12, 2
        IF openpiece = 0 THEN
            INPUT "Remove piece  "; selected
        ELSE
            INPUT "Place onto rod"; selected
        END IF

        operation = doable(selected)
    WEND
WEND

drawstack
LOCATE 12, 2
PRINT "Congratulations! You have solved the Tower of Hanoi!"


Try it! I used 26 moves to solve the game. You can (and should) do a lot better.


Notes

This code hasn't been fully abstracted, which is geek-speak to say, if you want to modify the code to incorporate more disks or change the background color and such, there are a few places you have to change. Still, this is just an exercise in programming logic. Don't worry too much about it. I certainly didn't.

Sure, take the code and make it better. That's what sharing is for.

Many towering thanks,
T___T

Wednesday, 16 May 2018

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

Hello, and welcome back. This part will be a little bit technical, but duh, it's a programming tutorial.

Let's declare another array, stack. This will be a two-dimensional array. It represents your Tower of Hanoi setup - there are three bases and rods, and each one has four slots. Four slots because we have four disks, right? Then create another array, top. Each element represents the current height of the stacked disks on each rod.

DIM SHARED stack(3, 4) AS INTEGER
DIM SHARED top(3) AS INTEGER 
DIM SHARED piece(4) AS STRING


Now, let's initialize the stack array. Use a nested For loop to ensure that every slot in each base and rod, is set to 0. And make sure that all elements of the top array are set to 0.

DIM SHARED stack(3, 4) AS INTEGER
DIM SHARED top(3) AS INTEGER
DIM SHARED piece(4) AS STRING

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

FOR i = 1 TO 3
    FOR j = 1 TO 4
        stack(i, j) = 0
    NEXT j

    top(i) = 0
NEXT i

drawstack

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


Up to now, we've been using the word "stack" to mean "base and rod", but today we'll go a bit further and implement a data structure stack. For that to happen, we must have a pop() function and a push() subroutine. First, let's create with the push() subroutine. It will accept two parameters - stackno, and value.

DIM SHARED stack(3, 4) AS INTEGER
DIM SHARED top(3) AS INTEGER
DIM SHARED piece(4) AS STRING

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

FOR i = 1 TO 3
    FOR j = 1 TO 4
        stack(i, j) = 0
    NEXT j

    top(i) = 0
NEXT i

drawstack

SUB push (stackno, value)

END SUB

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


stackno is the base and rod you are currently referencing. value is the number of the disk you are "pushing". Remember that in a stack, it's Last In First Out. When you "push", you are stacking a disk onto a rod, on top of any other disks that may be already there. When you "pop", you remove the disk at the top, that is, the most recently "pushed" disk.

So first, increment the element of the top array referenced by stackno.

SUB push (stackno, value)
top(stackno) = top(stackno) + 1
END SUB


Then set the element of stack, referenced by stackno and the current value of the element of top you just incremented, to value. In layman's terms, that means you set the current topmost slot of the base and rod of stackno, to the disk you are "pushing".

SUB push (stackno, value)
top(stackno) = top(stackno) + 1
stack(stackno, top(stackno)) = value
END SUB


Now, in your drawstack() subroutine, alter this line to draw the pieces in that stack instead of just the rod.

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 (stack(j, 6 - i))
            END IF
        END IF
    NEXT j
NEXT i
END SUB


Now try this. "Push" Disk 4 onto Stack 1, starting with the largest stack, just before the calling drawstack() subroutine.

push 1, 4

drawstack

SUB push (stackno, value)
top(stackno) = top(stackno) + 1
stack(stackno, top(stackno)) = value
END SUB


You just put the largest disk onto the first rod!



Now try this...

push 1, 4
push 1, 3

drawstack

SUB push (stackno, value)
top(stackno) = top(stackno) + 1
stack(stackno, top(stackno)) = value
END SUB


Here, you've stacked the second largest disk onto the first rod, right on top of the largest disk.



push 1, 4
push 1, 3
push 1, 2
push 1, 1

drawstack

SUB push (stackno, value)
top(stackno) = top(stackno) + 1
stack(stackno, top(stackno)) = value
END SUB


And so on. This, coincidentally, is the starting configuration of the Tower of Hanoi.



Now, let's do the "pop". For that, we'll need to declare another variable, openpiece. It holds the number of the disk that is currently not assigned to any rod. At any time, there can be only one of these.  Set the value to 0.

DIM SHARED stack(3, 4) AS INTEGER
DIM SHARED top(3) AS INTEGER
DIM SHARED piece(4) AS STRING
DIM SHARED openpiece AS INTEGER

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


In the push() subroutine, set openpiece to 0 after pushing. This indicates that no disks are unassigned to rods. It's not important now, but later it will be.

Now, let's create a function, pop(). It will accept a parameter stackno, which is the number of the rod you're trying to remove a disk from.

SUB push (stackno, value)
top(stackno) = top(stackno) + 1
stack(stackno, top(stackno)) = value
openpiece = 0
END SUB

FUNCTION pop (stackno)

END FUNCTION

Hold on... why is push() a subroutine and pop() a function?

Because when you pop a stack, you want to know the number of the disk you removed. Therefore the function will return that number. With push(), you don't need to return anything.

So yeah, first you assign the returned value to the number of the most recently "pushed" disk onto the rod (the stack array) indicated by stackno.

FUNCTION pop (stackno)
pop = stack(stackno, top(stackno))
END FUNCTION


Then you assign that slot in the stack array to 0, since that slot is no longer occupied. And decrement the appropriate element in the top array to say that there is one less disk in that rod now.

FUNCTION pop (stackno)
pop = stack(stackno, top(stackno))
stack(stackno, top(stackno)) = 0
top(stackno) = top(stackno) - 1
END FUNCTION


Let's modify the drawstack() subroutine to display the piece that is currently open. First, reset the color to white on black, and then move the cursor to below the drawn stack. And print the word "Open".

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 (stack(j, 6 - i))
            END IF
        END IF
    NEXT j
NEXT i

COLOR 15, 0
LOCATE 9, 2
PRINT "Open ";
END SUB


Now, if the value of openpiece is 0, print something to indicate so. Otherwise, invoke the drawpiece() subroutine, passing in the value of openpiece as an argument.

COLOR 15, 0
LOCATE 9, 2
PRINT "Open ";

IF openpiece = 0 THEN
    PRINT "(none)         ";
ELSE
    drawpiece openpiece
END IF


Now, try this. After pushing all four disks onto the first rod, "pop" the first rod. And then call the drawstack() subroutine.

push 1, 4
push 1, 3
push 1, 2
push 1, 1
openpiece = pop(1)

drawstack


You just removed Disk 1 (the blue and smallest one) from the first rod.



Now try this... it pushes the unassigned disk (Disk 1) to the second rod, then removes the next disk from the first rod.

push 1, 4
push 1, 3
push 1, 2
push 1, 1
openpiece = pop(1)
push 2, openpiece
openpiece = pop(1)

drawstack


You're getting the hang of this...



Next

Game logic will tie everything up nicely, now that you've gotten the drawing subroutines and "pop" and "push" functionality out of the way.

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.