The Liberty Basic Newsletter - Issue #39 - JUN 1999

© 2003, http://groups.yahoo.com/group/lbnews/

All Rights Reserved

Individual authors retain copyrights to their works.

"Knowledge is a gift we receive from others." - Michael T. Rankin

In This Issue:

Using Arrays with For-Next Loops:

In Future Issues:


Using Arrays with For-Next Loops: A Sample Program

Please download the sample source code and associated files at: http://alyce.webjump.com/fishsim.zip

This little program is a simple aquarium simulation. The methods used within the program can be applied to any type of application, not just a graphics-oriented program.


1. LAYOUT

The layout of this program is done according to Brosco's teachings. First of all, it makes use of white space to make the source code more readable. Branch labels appear at the margins, but most other code is indented. There are blank lines between routines.

Secondly, branch labels and variables are named according to their functions. This cuts down on the need for comments. For instance, the number of Fish is held in a variable called numberFish. There is no doubt about the use of that variable! In the olden days of programming, everything was kept as small as possible because of severe restrictions on usable memory. We really don't need to concern ourselves with this limitation any more, so feel free to use descriptive names.

Third, the source IS commented so that others can understand it, and the author can return to it after some time has passed and still understand it. (We ALL forget!) Try this experiment sometime: take a heavily commented BAS file and tokenize it. Then compare the number of KB in each. You will see that the TKN is a smaller file. The comments are ignored in the tokenizing process, so you needn't worry that comments will make your code too large.

Fourth, routines are kept small enough to be contained on one screen for editing. It is far too confusing to scroll up and down and back and forth through a routine! You will soon lose the sense of it! It is possible, and even preferable to make extensive use of GOSUBS. And, of course the subroutines themselves should be kept to a reasonable length. Remember that you can call a subroutine from within a subroutine! By putting often-used routines into subroutines to be called with GOSUB, you are streamlining your code and making it much easier to read AND debug. Here is the opening section, from just after the program window is opened, until the main input loop is reached:

    GOSUB [load]
    GOSUB [init]
    GOSUB [sim.setup]

And here is the main input loop:

[loop]
    print #1, "setfocus; when characterInput [quit]"
    print #1, "when mouseMove [quit]"  'stop program at keystroke or mouse move
    scan
    GOSUB [move]
    GOSUB [draw.screen]

    if Count<1500 then
        for i = 1 to pause: next i  'generate a delay on fast cpu's
    end if
    goto [loop]

THAT's ALL! Notice that it is a small routine, uses white space at the margins and between parts of the routine, it makes use of GOSUBs, which are named to suit their functions, and it is commented.


2. FOR-NEXT LOOP: AN EASY ONE

Here is the subroutine that draws the screen. The window is filled by tiling a bitmap that is 200x200 in a nested loop:

[init] 'initialize dll and draw water background
    for x=0 to 1200 step 200
      for y=0 to 1000 step 200
        print #1, "drawbmp water ";x;" ";y
      next y
    next x

    open "lbsprite.dll" for dll as #s
    'load background from screen:
    CallDll #s, "Init",h as short, r as short
    RETURN

The bitmap, called 'water' was loaded in the [load] routine. Rather than make several calls to draw the bitmap across the width and height of the screen, the command is put within a FOR-NEXT LOOP which increments by 200 each time for the x and y placement, since 200 is the width and height of the bitmap. If the bitmap were 300 wide, then that loop would say STEP 300 instead.


3. EASY - FILLING AN ARRAY WITH A FOR-NEXT LOOP:

At the very top of the source code, the numberFish variable is set and the fishes( array is dimensioned:

    numberFish=5  'pick 1-10 here.  Slower pc's should choose fewer fish
    DIM fishes(10,5)

Since the array can contain a maximum of 10 fish, according to our command to dimension it, we must trap the possibility of setting numberFish too high. It is also a good idea to make sure that the minimum value of numberFish is at least 1:

[sim.setup]
    if numberFish>10 then numberFish=10  'trap error - too many fish
    if numberFish < 1 then numberFish=1    'trap error - no fish

The fishes( array is double-dimensioned. That means that each index in the first dimension is associated with (in our case) 5 elements in the second dimension. For each fish, the element fishes(i,1) will contain its x-location. The element fishes(i,2) will hold its current y-location. Because we are keeping these variables in an array, it only takes a few lines of code to set the initial x, y location of all of our fish:

    'x,y location to start each fish:
    for i=1 to numberFish
        fishes(i,1)=int(rnd(1)*600)  'x location
        fishes(i,2)=int(rnd(1)*380)  'y location
    next i

This routine will start with fishes(1,1) and pick a random x location that does not exceed 600, then it will pick a random y location that does not exceed 380. After finishing the first fish, it moves on to fishes(2,1) and fishes(2,2), setting the x, y location for the second fish. It continues until it reaches the maximum value set by numberFish.


4. A DIFFERENT METHOD OF FILLING AN ARRAY WITH A FOR-NEXT LOOP:

In the [load] routine, we loaded the bitmaps of the five fishes, and got their handles:

    loadbmp "f1", "fish1.bmp"
    hf1=hbmp("f1")
    loadbmp "f2", "fish2.bmp"
    hf2=hbmp("f2")
    loadbmp "f3", "fish3.bmp"
    hf3=hbmp("f3")
    loadbmp "f4", "fish4.bmp"
    hf4=hbmp("f4")
    loadbmp "f5", "fish5.bmp"
    hf5=hbmp("f5")

Since there are five different handles to assign, it might seem that this is not a job for a FOR-NEXT LOOP, but it is!

    for i=1 to numberFish STEP 5
        if i <=numberFish then fishes(i,3)=hf1
        if i+1 <=numberFish then fishes(i+1,3)=hf2
        if i+2 <=numberFish then fishes(i+2,3)=hf3
        if i+3 <=numberFish then fishes(i+3,3)=hf4
        if i+4 <=numberFish then fishes(i+4,3)=hf5
    next i

This same few lines of code will assign a bitmap handle to as many fish as there are in numberFish. Since there are five bitmaps in use, STEP 5 is set as the increment. Then, notice that the counter variable, i, is used within the loop. The first index is i, the second is i+1, the third is i+2 and so on. You must also be sure that i+? is no larger than the DIM of the fishes( array, with a conditional statement like the following example:

if i+2 < numberFish then fishes(i+2,3)=hf2


5. EVEN MORE CUTE TRICKS TO FILL ARRAYS:

As the fish swim about in their aquarium, they should move up and down a bit, as well as across the screen. (Think about it - fish don't swim in the same straight line, back and forth!) We can easily add variation to the movement of our fish by assigning the increment of change in Y direction to be the index of the fish. Fish 1 will move 1 pixel in Y each frame, while fish 2 moves 2 pixels in Y each frame and fish 3 moves 3 pixels in the Y direction for each frame of animation. This works for us because we won't have too many fish. An aquarium with 50 fish would need another method, or fish #50 would move in huge jerks of 50 pixels Y each time!

fish(i,5) will be the change in y value:

    'set y-speed
    for i=1 to numberFish
        fishes(i,5)=i
    next i

Most of the movement of the fish will be in the x-direction. This value will be contained in the array fishes(i,4). They will certainly move more than one pixel at a time! We'll choose 2 possible speeds for our fish: 10 and 14. Half of the fish will move ten pixels in the x-direction for each frame of animation, and the other half will move 14 pixels. We'll make the first half of the fish the 'slow' fish. The value for half of the fish would be numberFish/2. Since we cannot use just half of one fish, should numberFish be an odd number, we'll use LB's INT to trap that error:

    for i=1 to int(numberFish/2)
        fishes(i,4)=10   'speed of first half of fish
    next i

Now, we'll set the x increment for the second half of the fish. To count the upper half, we add one to the number we found for the last fish in the first half, or (int(numberFish/2) +1):

    for i = (int(numberFish/2) +1) to numberFish
        fishes(i,4)=14  'speed of last half of fish
    next i

The code so far is fine (as far as it goes) in setting up the simulation. Let's picture it: we start our aquarium simulation and our fish begin swimming. Wait a minute! The are all swimming to the right and downward! That's not very realistic! We'll need to change the x-increment and the y-increment to a negative number in half of our fish. A handy way to express the opposite of ANY number is to write it as: 0 - number. In just that way, we can change the x-direction to moving left in some of the fish with this expression: fishes(i,4)=0-fishes(i,4) Let's add as much variability as we can and do it by STEP 2 in the x-direction:

    'change direction of half of fish:
    for i=1 to numberFish STEP 2
        fishes(i,4)=0-fishes(i,4)
    next i

Again, to add a more natural look to the aquarium, lets change DIFFERENT fish so that they are traveling in a negative y-direction. The code is almost identical to the previous routine, but instead of starting at index i=1, we'll START with the second fish, i=2:

    for i=2 to numberFish STEP 2
        fishes(i,5)=0-fishes(i,5)
    next i


6. MOVING THOSE FISH WITH FOR-NEXT LOOPS:

Now we will see the advantage of holding our fish values in arrays! If we did not, we would need to perform the following routine once for EACH fish, for EACH frame of animation! Remember that fishes(i,1) holds the x-position and fishes(i,4) holds the amount of change in x for this particular fish with each frame. Easy! All we need is the following routine within a FOR-NEXT LOOP to move all of our fish:

fishes(i,1)=fishes(i,1)+fishes(i,4)

NOPE! Close, though. We also need to make a check to see if the fish is about to go off of the screen on the right or left side. If the fish is about to exit, we need to keep him on screen AND modify the value for change in x for the next frame. Remember, earlier we changed the value of some elements by subtracting from 0. That method will work here also. A negative number will become positive when subtracted from 0, just as a positive number will become negative. In other words, subtraction from 0 serves to change the SIGN of the number. So, a fish who gets to the left side of the screen has had a negative value in fishes(i,4) which we will now change to a positive value, so he swims towards the right. We'll need a separate conditional statment to trap the left and right sides of the screen:

    for i=1 to numberFish
        fishes(i,1)=fishes(i,1)+fishes(i,4)
            if fishes(i,1) < 0 then
                fishes(i,4)=0-fishes(i,4)   'reverse direction
                fishes(i,1)=0
            end if

            if fishes(i,1)>(DisplayWidth-100) then
                fishes(i,4)=0-fishes(i,4)    'reverse direction
                fishes(i,1)=(DisplayWidth-100)
            end if
    next i

And, OF COURSE, it works exactly the same way in the y-direction:

    for i=1 to numberFish
        fishes(i,2)=fishes(i,2)+fishes(i,5)
            if fishes(i,2) < 0 then
                fishes(i,5)=0-fishes(i,5)    'reverse direction
                fishes(i,2)=0
            end if

            if fishes(i,2)>(DisplayHeight-40) then
                fishes(i,5)=0-fishes(i,5)     'reverse direction
                fishes(i,2)=(DisplayHeight-40)
            end if
    next i


7. YET ANOTHER USE OF FOR-NEXT LOOPS IN THIS PROGRAM:

We have now determined WHERE each fish should be for the next frame of animation, so all that is left is to actually draw the fish.

To do the drawing, we'll set the variable xfish equal to fishes(i,1), the variable yfish will equal fishes(i,2) and the variable for the proper bitmap handle, hFish will be set to fishes(i,3). We can now make the call to draw the fish, but we need to know which direction he is facing, so we know whether the call should be to DrawSprite, or to DrawSpriteMirror. Since fishes(i,4) contains the x- increment, we'll check that. If it is positive (more than zero) then we'll need to call DrawSprite, but if it is negative (less than zero) then he is heading in the other direction and we will need to DrawSpriteMirror. Here is the entire routine:

[draw.screen]
    for i=1 to numberFish
        hFish=fishes(i,3)  'proper bmp for this fish
        xfish=fishes(i,1)  'current x location for this fish
        yfish=fishes(i,2)  'current y location for this fish
        if fishes(i,4)>0 then
            CallDll #s, "DrawSprite",h as short, hFish as short,_
		 xfish as short, yfish as short,r as short
          else
            CallDll #s, "DrawSpriteMirror",h as short, hFish as short,_
		 xfish as short, yfish as short,r as short
        end if
     next i


    CallDll #s, "UpdateScreen",h as short, r as short,
    RETURN


8. TOM'S TURN

Whenever Tom sees his mom writing an interesting bit of code, he can't help but elbow her out of the way, so he can modify it. (Or, as he says, 'Do it the RIGHT way!' The shark.bas program in the packet is his version, and yes - you guessed it - he has added a shark to the tank, with predictable consequences!

He has made further use of FOR-NEXT LOOPS in his routine and you might want to check out his gradiant fill for the water in the aquarium.

Tom also suggests that there are many other modifications that could be made to these simulations. If anyone does write a variation, why not post it (just the code!) here on the lbnews list? Thanks.


Newsletter compiled and edited by: Brosco and Alyce.

Comments, requests or corrections: Hit 'REPLY' now!

mailto:brosco@orac.net.au

or

mailto:awatson@mail.wctc.net