Lesson 4: Looping

Computer programs function by doing the same things over and over again at very high speed. This looping behavior enables the user to continue to use the program until the loop is stopped by either the program or the user. In the gaming world the faster a game program can loop the better. Gamers like this because it enables their games to have high frame rates also known as the FPS (frames per second).

The CLS Statement

Before we get into looping let's cover the CLS statement. The CLS statement clears the screen of text, graphics, or both depending on its use. Many times while a program is in a loop it's desirable to clear the screen at the beginning of each loop. Animations need to be redrawn, maps need to be updated showing a player's position, etc. 

The CLS statement by itself will clear anything seen on the screen and home the cursor back to the upper left hand corner of the screen.

PRINT "The rain in Spain" '                           print some text to the screen
PRINT "falls mainly on the plain."
INPUT "Press ENTER to clear the screen ...", Enter$ ' wait for user input
CLS '                                                 clear the screen
PRINT "The screen has been cleared."

After the screen has been cleared by the CLS statement the final PRINT statement once again begins printing in the upper left hand corner because the cursor returned to the home position.

The full syntax for the CLS statement is:

CLS Method%, bgColor&

The optional Method% specifies which parts of the page to clear and can have one of the following values:

Note: text view ports can be created using the VIEW PRINT statement. The VIEW statement can be used to create graphics view ports. You can click on each statement to read more about them in the QB64PE Wiki.

The optional bgColor& specifies a background color to use when clearing the screen.

CLS , 2 ' clear the screen with a green background

We'll cover colors in lessons 5, 9, and 14 in more detail.

The FOR ... NEXT Looping Statement

Remember the program we created in lesson 3 that used GOTO to create a loop that counted from 1 to 24? There is a much better and preferred method to create a loop that does the same thing.

( This code can be found at .\tutorial\Lesson4\CountTo24.bas )

'--------------------------------
'- Variable declaration section -
'--------------------------------

DIM Count% ' used as a counter

'----------------------------
'- Main program begins here -
'----------------------------

FOR Count% = 1 TO 24 ' increment Count% from 1 to 24 in steps of 1
   PRINT Count% '      print current value of Count%
NEXT Count% '          keep looping until Count% equals 24

The FOR...NEXT statement is used to create controlled loops. A controlled loop is one where you explicitly know the start and end values of the loop. FOR...NEXT statements can count forward or backward in any increment you wish. The default behavior for a FOR...NEXT loop is to count forward by 1 (a STEP value of 1) until the end value has been reached.

FOR Count% = 1 TO 24 ' increment Count% from 1 to 24 in steps of 1

The integer variable Count% is used to hold the value of the FOR...NEXT loop. 1 TO 24 indicates the start and end values of the loop counter. The first time the loop executes Count% will contain the value of 1, the second loop execution the value of 2, and then 3, and so on. This continues until Count% finally reaches the end value of 24. This line controls when the loop ends:

NEXT Count% '          keep looping until Count% equals 24

Only when the NEXT statement sees that Count% equals the ending value specified will it end the loop. All code contained within the code block between the FOR and NEXT statements is executed over and over again while the loop is active.

STEP it Up

The STEP statement may be used in conjunction with the FOR...NEXT statement to change the default direction of the count. Type in the next program and execute it.

( This code can be found at .\tutorial\Lesson4\StepUp.bas )

'--------------------------------
'- Variable declaration section -
'--------------------------------

DIM Cstep% '    FOR...NEXT step value
DIM Count% '    FOR...NEXT count value
DIM NextStep$ ' user input for next step

'----------------------------
'- Main program begins here -
'----------------------------

FOR Cstep% = 1 TO 4
   CLS
   PRINT "Counting from 0 to 12 by steps of"; Cstep%
   PRINT
   FOR Count% = 0 TO 12 STEP Cstep%
       PRINT Count%
   NEXT Count%
   PRINT
   IF Cstep% < 4 THEN
       INPUT "Press ENTER for the next STEP value", NextStep$
   END IF
NEXT Cstep%

The example program above uses two FOR...NEXT loops, one inside the other, creating what is known as a nested loop. The outer loop, Cstep%, counts from 1 TO 4 while the inner loop, Count%, counts from 0 TO 12 with a STEP value based on the current value of Cstep%.  It just so happens that counting from 0 TO 12 by any of the Cstep% values allows the loop to end on the number 12 each time because 12 is evenly divisible by the numbers 1 TO 4. However, what would happen if we use a STEP value that mathematically can't reach the ending number? If the next STEP value equals or is greater than the ending loop value, the loop is considered complete. Make the following modifications to the above code to see this in action.

Change line 15 to read:

PRINT "Counting from 1 to 12 by steps of"; Cstep%

Change line 17 to read:

FOR Count% = 1 TO 12 STEP Cstep%

Now, execute the program again.

As you can see STEP will only reach the value of 12 by counting in steps of 1. Counting in steps of 2, 3, or 4 can't mathematically reach the number 12. This is important to remember when writing your programs.

STEP it Down

STEP can also be assigned a negative value to allow FOR...NEXT loops to count in reverse. Type in the following code then execute it to see this in action.

( This code can be found at .\tutorial\Lesson4\Liftoff.bas )

'--------------------------------
'- Variable declaration section -
'--------------------------------

DIM CountDown% ' the count down timer
DIM LiftOff% '   lift off sequence counter

'----------------------------
'- Main program begins here -
'----------------------------

PRINT "Rocket launch in T minus ..."
FOR CountDown% = 10 TO 0 STEP -1
   SLEEP 1
   PRINT
   PRINT CountDown%; "..."
   IF CountDown% = 3 THEN
       PRINT
       PRINT "We have engine ignition ..."
   END IF
NEXT CountDown%
PRINT
PRINT
"and lift-off ... !"
SLEEP 1
PRINT
_DELAY .1
PRINT " /\"
_DELAY .1
PRINT " ||"
_DELAY .1
PRINT " ||"
_DELAY .1
PRINT " ||"
_DELAY .1
PRINT "/||\"
_DELAY .1
PRINT "-||-"
_DELAY .1
PRINT " ^^"
_DELAY .1
FOR LiftOff% = 1 TO 25
   _DELAY .1
   PRINT
NEXT LiftOff%
SYSTEM

Yes, I know, a pretty crude representation of a rocket launch. We'll revisit this code when graphics are introduced and spruce it up a bit.

There are three new commands in the example program above: SLEEP, _DELAY, and SYSTEM.

SLEEP puts the computer into a continuous loop for a given number of seconds which in effect stops program execution for that amount of time. If you do not specify the number of seconds to SLEEP the program will pause until the user presses a key. Also, the user can over-ride the SLEEP time at any point by pressing a key.

_DELAY (yes, the underscore is part of the command, more on that later) performs the same action as SLEEP but gives the programmer finer control over the timing. SLEEP will only work with whole seconds while _DELAY allows the programmer to specify the timing duration down to the millisecond, or thousandths of a second. _DELAY .001 would cause the program pause for one one thousandth of a second. In the program above _DELAY .1 forces the program to pause for 100 milliseconds or one tenth of a second.

SYSTEM forces the text window to close immediately once the program has finished. No "Press any key to continue" message is displayed. The program simply ends, the window is closed, and the operating system is given back control.

The DO...LOOP Looping Statement

Another type of looping construct offered in programming is a conditional loop. Conditional loops continue looping until a certain condition is met. There are a few different methods of creating conditional loops in QB64. The first one we'll discuss is the DO...LOOP UNTIL statement. Type in the following code and then execute it.

( This code can be found at .\tutorial\Lesson4\DoLoop.bas )

'--------------------------------
'- Variable declaration section -
'--------------------------------

DIM Count% ' used as a counter

'----------------------------
'- Main program begins here -
'----------------------------

DO '                      start of loop
    Count% = Count% + 1 ' increment counter
   PRINT Count% '         display value of counter
LOOP UNTIL Count% = 24 '  keep looping until this becomes true

The block of code contained in between the DO and LOOP UNTIL statements is looped until the LOOP UNTIL condition becomes true. Only when Count% contains the value of 24 does the loop exit. It's important to note that the code block with this form of DO...LOOP will always be executed at least one time since the conditional check isn't made until after the code block has been executed.

Here's a little sneak peek of things to come with a graphics program that uses a DO...LOOP construct that becomes true when the user presses the escape (ESC) key. Type in the next example and execute it.

( This code can be found at .\tutorial\Lesson4\Circle.bas )

'--------------------------------
'- Variable declaration section -
'--------------------------------

DIM x% ' x location of circle
DIM y% ' y location of circle

'----------------------------
'- Main program begins here -
'----------------------------

SCREEN _NEWIMAGE(640, 480, 32) '            640x480 graphics screen
x% = 319 '                                  x center of screen
y% = 239 '                                  y center of screen
DO '                                        begin main program loop
   CLS '                                    clear the screen
   PRINT "Arrow keys to move the circle." ' display directions to user
   PRINT "ESC key to exit program."
   CIRCLE (x%, y%), 20, _RGB32(255, 0, 0) ' draw a red circle
   PAINT (x%, y%), _RGB32(255, 0, 0) '      paint the circle red
   IF _KEYDOWN(20480) THEN y% = y% + 1 '    if user pressed down  arrow key increment y coordinate
   IF _KEYDOWN(18432) THEN y% = y% - 1 '    if user pressed up    arrow key decrement y coordinate
   IF _KEYDOWN(19200) THEN x% = x% - 1 '    if user pressed left  arrow key decrement x coordinate
   IF _KEYDOWN(19712) THEN x% = x% + 1 '    if user pressed right arrow key increment x coordinate
   _DISPLAY '                               update screen with changes
   _LIMIT 100 '                             keep loop running at 100 frames per second
LOOP UNTIL _KEYDOWN(27) '                   leave loop when ESC key pressed
SYSTEM '                                    exit and return control to the operating system

The code sample above uses commands that have not been encountered yet. Later lessons will eventually cover these commands but if you're curious beforehand you can view an alphabetical listing of QB64 commands here.

The DO...LOOP statement has a couple of variations that can be used.

DO WHILE <condition is true>
..
.. <code block>
..
LOOP

The code block in this variation may never get executed because the condition is checked before the code block is entered. The condition must be true before the code block is entered.

DO UNTIL <condition is true>
..
.. <code block>
..
LOOP

Again, the code block may never get executed because of the condition being tested before it. The condition must be false before the code block is entered.

DO
..
.. <code block>
..
LOOP WHILE <condition is true>

This variation continues to loop as long as the condition is true. The code block will get executed at least one time since the conditional check is done after the code block.

No DO...LOOP variation is considered better than another. The one you decide to use will depend on your program requirements at the time.

The WHILE...WEND Looping Statement

Another method of implementing a conditional loop is with the use of the WHILE...WEND statement. Type in the following program and execute it.

( This code can be found at .\tutorial\Lesson4\WhileWend.bas )

'--------------------------------
'- Variable declaration section -
'--------------------------------

DIM Count% ' used as a counter

'----------------------------
'- Main program begins here -
'----------------------------

PRINT "This program will count to 100 from the number you supply."
PRINT
INPUT "Enter the number to start counting from >", Count%
WHILE Count% <= 100 '     test condition before loop is entered
   PRINT Count% '         display current count value
    Count% = Count% + 1 ' increment counter
WEND '                    end of loop structure

The code block in between a traditional DO...LOOP structure must be executed at least once before the loop's condition is checked. However, a WHILE...WEND structure allows the programmer to skip a code block completely because the condition is checked before the loop is entered. If the user enters a number below 101 the loop will execute at least once. This behavior is similar to the non-traditional DO WHILE...LOOP and DO UNTIL...LOOP constructs.

WHILE Count% <= 100 '     test condition before loop is entered

As long as Count% is less than or equal to ( <= ) 100 the code block in the loop will be executed. If the user were to enter 110 the WHILE statement's condition would be false resulting in the code block never being executed.

The following relational operators can be used to check for conditions: = (equal to), < (less than), > (greater than), <> (not equal to), <= (less than or equal to), and >= (greater than or equal to).

Looping Speed Considerations

Looping constructs in QB64 do not have equal performance. FOR...NEXT loops are the slowest, followed by DO...LOOP UNTIL, WHILE...WEND, and then the variations that DO...LOOP offers.

FOR...NEXT loops are perfect for uses where speed is not going to affect game play, such as loading the game's assets (graphics, sounds, fonts, etc..) upon program start, or sorting the high score table at game end. However, during game play you're going to want your loops to be as fast as possible and that's where DO...LOOP and WHILE...WEND shine.

Type in the following program that highlights the speed differences between the various looping mechanisms.

( This code can be found at .\tutorial\Lesson4\LoopSpeeds.bas )

' QB64 loop speed comparision
' Terry Ritchie
' 06/07/23

DIM s AS SINGLE '      loop start timer value
DIM e AS SINGLE '      loop end timer value
DIM t AS SINGLE '      loop time to complete
DIM f AS SINGLE '      for...next loop time to complete (baseline)
DIM c AS LONG '        generic counter
DIM max AS LONG '      max value to count to
DIM Format AS STRING ' number formatter

max = 2 ^ 31 - 1 '              calculate max value
Format = "##.########" '        format all timer values the same
PRINT
PRINT
"          ---------------------------------------------------------"
PRINT "               Speed comparision between FOR ... NEXT and the"
PRINT "               various other looping methods offered by QB64."
PRINT "          ---------------------------------------------------------"
PRINT "           Incrementing integer variable c from 0 to 2,147,483,647"
PRINT "          ---------------------------------------------------------"
PRINT
PRINT
"                         Press any key to start ..."
SLEEP
PRINT
PRINT
"    Counting... (first result may take up to 20 seconds depending on CPU)"
PRINT

'+------------------------+
'| FOR ........ NEXT loop |
'+------------------------+

PRINT " FOR ........ NEXT ->";
s = TIMER '                     get start timer value
FOR c = 0 TO max: NEXT c '      increment c from 0 to max
e = TIMER '                     get end timer value
f = e - s '                     calculate time to complete
PRINT USING Format; f '         print result
SOUND 440, 1: SOUND 880, 1 '    audible finished indicator

'+------------------------+     (Much faster than FOR ... NEXT but still )
'| DO ......... LOOP loop |     (slightly slower than the remaining loops)
'+------------------------+

PRINT " DO ......... LOOP ->";
c = 0 '                         reset counter
s = TIMER '                     get start timer value
DO '                            begin loop
    c = c + 1 '                 increment c from 0
LOOP UNTIL c = max '            to max
e = TIMER '                     get end timer value
t = e - s '                     calculate time to complete
PRINT USING Format; t; '        print result
PRINT " -->";
PRINT USING Format; f - t;
PRINT " seconds faster."
SOUND 440, 1: SOUND 880, 1 '    audible finished indicator

'+------------------------+     (The next three looping methods have almost    )
'| WHILE ...... WEND loop |     (identical times and slightly faster than      )
'+------------------------+     (DO ... LOOP. All much faster than FOR ... NEXT)

PRINT " WHILE ...... WEND ->";
c = 0 '                         reset counter
s = TIMER '                     get start timer value
WHILE c < max '                 while c is less then max
    c = c + 1 '                 increment c from 0
WEND '                          end loop
e = TIMER '                     get end timer value
t = e - s '                     calculate time to complete
PRINT USING Format; t; '        print result
PRINT " -->";
PRINT USING Format; f - t;
PRINT " seconds faster."
SOUND 440, 1: SOUND 880, 1 '    audible finished indicator

'+------------------------+
'| DO WHILE ... LOOP loop |
'+------------------------+

PRINT " DO WHILE ... LOOP ->";
c = 0 '                         reset counter
s = TIMER '                     get start timer value
DO WHILE c < max '              while c is less than max
    c = c + 1 '                 increment c from 0
LOOP '                          end loop
e = TIMER '                     get end timer value
t = e - s '                     calculate time to complete
PRINT USING Format; t; '        print result
PRINT " -->";
PRINT USING Format; f - t;
PRINT " seconds faster."
SOUND 440, 1: SOUND 880, 1 '    audible finished indicator

'+------------------------+
'| DO UNTIL ... LOOP loop |
'+------------------------+

PRINT " DO UNTIL ... LOOP ->";
c = 0 '                         reset counter
s = TIMER '                     get start timer value
DO UNTIL c = max '              until c equals max
    c = c + 1 '                 increment c from 0
LOOP '                          end loop
e = TIMER '                     get end timer value
t = e - s '                     calculate time to complete
PRINT USING Format; t; '        print result
PRINT " -->";
PRINT USING Format; f - t;
PRINT " seconds faster."
SOUND 440, 1: SOUND 880, 1 '    audible finished indicator

Loop speed comparisons on an Intel 6th Gen i7 CPU running at 4GHz

Your Turn

Create a program that combines the conversion utilities we created in lesson 2 into a menu driven program. Figures 1 and 2 below show how the output of your program should look.

Figure 1: Fahrenheit to Celsius

Figure 2: Inches to millimeters

- Invalid menu entries should be ignored.

- When the user enters the number 3 to end the program it should immediately terminate back to the operating system.

- The screen should be cleared before the menu is displayed.

- You'll need the following commands to complete this task: DIM, DO...LOOP, CLS, PRINT, IF...THEN, INPUT, SLEEP, and SYSTEM.

- Save the program as MenuLoop.BAS when finished.

Click here to see the solution.

Commands and Concepts Learned

New commands introduced in this lesson:

FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP (and variations)
WHILE...WEND

New concepts introduced in this lesson:

frame rate
controlled loops
conditional loops