Saturday, 21 January 2017

Web Tutorial: Year of the Rooster Song and Dance

A blessed and fortune-filled Chinese New Year to all! It's the Year of the Rooster and to mark the occasion, I'd like to walk you through a little fun assignment - a QBasic animation and song.

Sound in QBasic is pretty much a function of supplying different tones at different frequencies. For me, it was a whole mess of trial and error, but once you hit the right first few notes, the rest just sort of fall into place.

In fact, we're going to start off with this...
DIM basetune$
basetune$ = "C-6 C+6 D6 E6 G3 F+4 F+6 B6 B6 F+6 F+3 F-4 F-6 G6 G-6 E6 E3 D4 D6 C+6 C-6 <B-6 >C-3 C-4 E4 G-8 D4 G-8 D-6 G-8 C-6 G-8 E4 G-8 D4 G-8 D-6 G-8 C-6"

PLAY basetune$


When you compile and run, your computer should emit that lovely Chinese New Year song.

Now make the following changes, creating bgtune as a new string variable. This plays the previous tune, then replays it in a higher pitch courtesy of the ">" symbol.

DIM basetune$
basetune$ = "C-6 C+6 D6 E6 G3 F+4 F+6 B6 B6 F+6 F+3 F-4 F-6 G6 G-6 E6 E3 D4 D6 C+6 C-6 <B-6 >C-3 C-4 E4 G-8 D4 G-8 D-6 G-8 C-6 G-8 E4 G-8 D4 G-8 D-6 G-8 C-6"

DIM bgtune$
bgtune$ = basetune$ + " > " + basetune$

PLAY bgtune$


And again!

DIM basetune$
basetune$ = "C-6 C+6 D6 E6 G3 F+4 F+6 B6 B6 F+6 F+3 F-4 F-6 G6 G-6 E6 E3 D4 D6 C+6 C-6 <B-6 >C-3 C-4 E4 G-8 D4 G-8 D-6 G-8 C-6 G-8 E4 G-8 D4 G-8 D-6 G-8 C-6"

DIM bgtune$
bgtune$ = basetune$ + " > " + basetune$ + " >> " + basetune$

PLAY bgtune$


It may all be a little too high pitched, so just add this to the beginning...
bgtune$ = "<<<" + basetune$ + " > " + basetune$ + " >> " + basetune$

PLAY bgtune$


But what's music without a little dance, right? Let's make a rooster dance right here, on screen. To do that, we'll need to draw a rooster on screen. We'll draw different parts of the rooster and animate those parts separately. And, to make our life easier, we'll rely on some good old-fashioned abstraction.

First, we'll need a delay subroutine. Let's create one, and call it, very imaginatively, delay(). A variable Value is passed in, and that is the number of milliseconds you want the function to delay by.

bgtune$ = "<<<" + basetune$ + " > " + basetune$ + " >> " + basetune$

PLAY bgtune$

SUB delay (Value!)

END SUB


The variable T is set to the number of milliseconds since midnight.
SUB delay (Value!)
    T! = TIMER
END SUB


And after that, we use a DO: LOOP UNTIL loop that does nothing until the T subtracted from the current number of milliseconds since midnight, equals or exceeds the value of parameter Value.
SUB delay (Value!)
    T! = TIMER
    DO: LOOP UNTIL (TIMER - T!) >= Value!
END SUB


Then we'll need another subroutine, drawstr(). Here, we pass in the parameters row, col, fg, bg and text. text is the string you want to print, while the rest are integers.
SUB delay (Value!)
    T! = TIMER
    DO: LOOP UNTIL (TIMER - T!) >= Value!
END SUB

SUB drawstr (row, col, fg, bg, text$)
    PRINT text$
END SUB


row and col define where your cursor is going to start drawing.
SUB drawstr (row, col, fg, bg, text$)
    LOCATE row, col
    PRINT text$
END SUB


fg is the foreground color of your string, and bg is the background color.
SUB drawstr (row, col, fg, bg, text$)
    COLOR fg, bg
    LOCATE row, col
    PRINT text$
END SUB


Now we begin drawing the head of the rooster. It's the subroutine draw_head1(), and here you pass in a variable bg which will be used as the background color. Then it's a simple matter of using the drawstr()  subroutine to draw each line of the rooster's head.
SUB drawstr (row, col, fg, bg, text$)
    COLOR fg, bg
    LOCATE row, col
    PRINT text$
END SUB

SUB draw_head1 (bg)
    CALL drawstr(2, 29, 0, bg, " ")
    CALL drawstr(2, 30, 0, 4, "    ")
    CALL drawstr(2, 34, 0, bg, " ")
    CALL drawstr(3, 29, 0, bg, " ")
    CALL drawstr(3, 30, 0, 15, " o  ")
    CALL drawstr(3, 34, 0, 4, " ")
    CALL drawstr(3, 35, 0, bg, " ")
    CALL drawstr(4, 28, 0, bg, " ")
    CALL drawstr(4, 29, 14, bg, "<")
    CALL drawstr(4, 30, 0, 15, "    ")
    CALL drawstr(4, 34, 0, 4, " ")
    CALL drawstr(4, 35, 0, bg, " ")
    CALL drawstr(5, 29, 0, bg, " ")
    CALL drawstr(5, 30, 0, 4, " ")
    CALL drawstr(5, 31, 0, 15, "   ")
    CALL drawstr(5, 34, 0, bg, " ")
END SUB


Let's test this subroutine out. declare a variable, bg, in the main body. We'll set bg to 6, which is a lovely orange. Then we cover the entire screen in this color using the CLS function.
PLAY bgtune$

DIM bg
bg = 6

COLOR 0, bg
CLS


Then call the draw_head1() subroutine passing in the 2 as a test argument, which is green.
PLAY bgtune$

DIM bg
bg = 6

COLOR 0, bg
CLS

CALL draw_head1(2)


This is what you have! Don't worry about the jarring green background. This is a test, remember?



But you'll notice that the song has to finish playing before your code will run. Add this to your bgtune variable. Now your tune runs in the background!
bgtune$ = "mb <<<" + basetune$ + " > " + basetune$ + " >> " + basetune$


Write the draw_head2() subroutine. It's almost a mirror of the draw_head1() subroutine, with a few differences.
SUB draw_head1 (bg)
    CALL drawstr(2, 29, 0, bg, " ")
    CALL drawstr(2, 30, 0, 4, "    ")
    CALL drawstr(2, 34, 0, bg, " ")
    CALL drawstr(3, 29, 0, bg, " ")
    CALL drawstr(3, 30, 0, 15, " o  ")
    CALL drawstr(3, 34, 0, 4, " ")
    CALL drawstr(3, 35, 0, bg, " ")
    CALL drawstr(4, 28, 0, bg, " ")
    CALL drawstr(4, 29, 14, bg, "<")
    CALL drawstr(4, 30, 0, 15, "    ")
    CALL drawstr(4, 34, 0, 4, " ")
    CALL drawstr(4, 35, 0, bg, " ")
    CALL drawstr(5, 29, 0, bg, " ")
    CALL drawstr(5, 30, 0, 4, " ")
    CALL drawstr(5, 31, 0, 15, "   ")
    CALL drawstr(5, 34, 0, bg, " ")
END SUB

SUB draw_head2 (bg)
    CALL drawstr(2, 28, 0, bg, " ")
    CALL drawstr(2, 29, 0, 4, "    ")
    CALL drawstr(2, 33, 0, bg, " ")
    CALL drawstr(3, 28, 0, bg, " ")
    CALL drawstr(3, 29, 0, 15, " u  ")
    CALL drawstr(3, 33, 0, 4, " ")
    CALL drawstr(3, 34, 0, bg, " ")
    CALL drawstr(4, 27, 0, bg, " ")
    CALL drawstr(4, 28, 14, bg, "<")
    CALL drawstr(4, 29, 0, 15, "    ")
    CALL drawstr(4, 33, 0, 4, " ")
    CALL drawstr(4, 34, 0, bg, " ")
    CALL drawstr(5, 28, 0, bg, " ")
    CALL drawstr(5, 29, 0, 4, " ")
    CALL drawstr(5, 30, 0, 15, "   ")
    CALL drawstr(5, 33, 0, bg, " ")
END SUB


Now test using a FOR loop and the delay() subroutine. Does the head move?
COLOR 0, bg
CLS

FOR i = 0 TO 10 STEP 1
    CALL draw_head1(2)
    CALL delay(0.5)
    CALL draw_head2(2)
    CALL delay(0.5)
NEXT i


Change the code by passing in the actual background color, bg. Compile and run again. Is the animation right?
FOR i = 0 TO 10 STEP 1
    CALL draw_head1(bg)
    CALL delay(0.5)
    CALL draw_head2(bg)
    CALL delay(0.5)
NEXT i


In the same vein, write the subroutines draw_legs1(), draw_legs2(), draw_body1(), draw_body2(), draw_tail1() and draw_tail2(). You may notice that the draw_body1() and draw_body2() subroutines do not require parameters. That's because the animation does not change the shape of the body at all.
SUB draw_head2 (bg)
    CALL drawstr(2, 28, 0, bg, " ")
    CALL drawstr(2, 29, 0, 4, "    ")
    CALL drawstr(2, 33, 0, bg, " ")
    CALL drawstr(3, 28, 0, bg, " ")
    CALL drawstr(3, 29, 0, 15, " u  ")
    CALL drawstr(3, 33, 0, 4, " ")
    CALL drawstr(3, 34, 0, bg, " ")
    CALL drawstr(4, 27, 0, bg, " ")
    CALL drawstr(4, 28, 14, bg, "<")
    CALL drawstr(4, 29, 0, 15, "    ")
    CALL drawstr(4, 33, 0, 4, " ")
    CALL drawstr(4, 34, 0, bg, " ")
    CALL drawstr(5, 28, 0, bg, " ")
    CALL drawstr(5, 29, 0, 4, " ")
    CALL drawstr(5, 30, 0, 15, "   ")
    CALL drawstr(5, 33, 0, bg, " ")
END SUB

SUB draw_tail1 (bg)
    CALL drawstr(5, 45, 0, bg, " ")
    CALL drawstr(5, 46, 0, 4, "      ")
    CALL drawstr(5, 52, 0, bg, "  ")
    CALL drawstr(6, 44, 0, bg, " ")
    CALL drawstr(6, 45, 0, 4, "     ")
    CALL drawstr(6, 50, 0, bg, "    ")
    CALL drawstr(7, 44, 0, bg, " ")
    CALL drawstr(7, 45, 0, 4, "  ")
    CALL drawstr(7, 47, 0, bg, "       ")
    CALL drawstr(8, 44, 0, bg, " ")
    CALL drawstr(8, 45, 0, 4, " ")
    CALL drawstr(8, 46, 0, bg, "        ")
    CALL drawstr(9, 44, 0, bg, " ")
    CALL drawstr(9, 45, 0, 4, " ")
    CALL drawstr(9, 46, 0, bg, "        ")
END SUB

SUB draw_tail2 (bg)
    CALL drawstr(5, 46, 0, bg, " ")
    CALL drawstr(5, 47, 0, 4, "     ")
    CALL drawstr(5, 52, 0, bg, "  ")
    CALL drawstr(6, 45, 0, bg, " ")
    CALL drawstr(6, 46, 0, 4, "     ")
    CALL drawstr(6, 51, 0, bg, "    ")
    CALL drawstr(7, 44, 0, bg, " ")
    CALL drawstr(7, 45, 0, 4, "  ")
    CALL drawstr(7, 47, 0, bg, "       ")
    CALL drawstr(8, 44, 0, bg, " ")
    CALL drawstr(8, 45, 0, 4, "  ")
    CALL drawstr(8, 47, 0, bg, "        ")
    CALL drawstr(9, 44, 0, bg, " ")
    CALL drawstr(9, 45, 0, 4, " ")
    CALL drawstr(9, 46, 0, bg, "        ")
END SUB

SUB draw_body1 ()
    CALL drawstr(6, 30, 0, 15, "    ")
    CALL drawstr(7, 29, 0, 15, "     ")
    CALL drawstr(8, 28, 0, 15, "      ")
    CALL drawstr(9, 27, 0, 15, "       ")
    CALL drawstr(10, 27, 4, 15, "                   ")
    CALL drawstr(11, 28, 4, 15, "   .    .  )      ")
    CALL drawstr(12, 29, 4, 15, "  .    . )      ")
    CALL drawstr(13, 30, 4, 15, "   ..   )    ")
    CALL drawstr(14, 31, 4, 15, "           ")
END SUB

SUB draw_body2 ()
    CALL drawstr(6, 30, 0, 15, "    ")
    CALL drawstr(7, 29, 0, 15, "     ")
    CALL drawstr(8, 28, 0, 15, "      ")
    CALL drawstr(9, 27, 0, 15, "       ")
    CALL drawstr(10, 27, 4, 15, "                   ")
    CALL drawstr(11, 28, 4, 15, "         .    )   ")
    CALL drawstr(12, 29, 4, 15, "    .  ..    )  ")
    CALL drawstr(13, 30, 4, 15, "   .   .   ) ")
    CALL drawstr(14, 31, 4, 15, "           ")
END SUB

SUB draw_legs1 (bg)
    CALL drawstr(15, 1, 14, bg, "                                  |   |")
    CALL drawstr(16, 1, 14, bg, "                                  |   |")
    CALL drawstr(17, 1, 14, bg, "                                  |   |")
    CALL drawstr(18, 1, 14, bg, "                                  |   |")
    CALL drawstr(19, 1, 14, bg, "                                  ^   ^")
END SUB

SUB draw_legs2 (bg)
    CALL drawstr(15, 1, 14, bg, "                                  |   |")
    CALL drawstr(16, 1, 14, bg, "                             >----|   |")
    CALL drawstr(17, 1, 14, bg, "                                      |")
    CALL drawstr(18, 1, 14, bg, "                                      |")
    CALL drawstr(19, 1, 14, bg, "                                      ^")
END SUB


Now, we'll use the first FOR loop we wrote as a test, and expand by adding calls to the subroutines for the rest of the rooster. Also, you may want to initialize the drawing of the rooster first.
DIM bg
bg = 6

COLOR 0, bg
CLS

CALL draw_head1(bg)
CALL draw_body1
CALL draw_legs1(bg)
CALL draw_tail1(bg)

FOR i = 0 TO 10 STEP 1
    CALL draw_head1(bg)
    CALL draw_body1
    CALL delay(0.5)
    CALL draw_head2(bg)
    CALL draw_body2
    CALL delay(0.5)
NEXT i


We have a dancing rooster!



Vary it up. Make different parts move using different FOR loops.
FOR i = 0 TO 10 STEP 1
    CALL draw_head1(bg)
    CALL draw_body1
    CALL delay(0.5)
    CALL draw_head2(bg)
    CALL draw_body2
    CALL delay(0.5)
NEXT i

FOR i = 0 TO 10 STEP 1
    CALL draw_legs1(bg)
    CALL draw_body1
    CALL delay(0.5)
    CALL draw_legs2(bg)
    CALL draw_body2
    CALL delay(0.5)
NEXT i

FOR i = 0 TO 10 STEP 1
    CALL draw_tail1(bg)
    CALL draw_body1
    CALL delay(0.5)
    CALL draw_tail2(bg)
    CALL draw_body2
    CALL delay(0.5)
NEXT i

FOR i = 0 TO 10 STEP 1
    CALL draw_head1(bg)
    CALL draw_legs1(bg)
    CALL draw_tail1(bg)
    CALL draw_body1
    CALL delay(0.5)
    CALL draw_head2(bg)
    CALL draw_legs2(bg)
    CALL draw_tail2(bg)
    CALL draw_body2
    CALL delay(0.5)
NEXT i

And there you go...

Dancing rooster. Clunky graphics, very retro. But still awesome McCool.

Once again, Gong Ji Fa Cai!
T___T

No comments:

Post a Comment