Liberty Basic is develeopped by Carl Gundel
Original Newsletter compiled by Alyce Watson
Translation to HTML: Raymond Roumeas

The Liberty Basic Newsletter - Issue #76 - JUL 2000

© 2000, Side by Side Software

http://alyce.50megs.com/sss/

All Rights Reserved
In this issue:

In future issues:


"A life spent making mistakes is not only more honorable, but more useful than a life spent doing nothing."

-- George Bernard Shaw


IF...THEN OVERUSED?

Does this look familiar? Do you ever have similar code routines in your programs?

 
if MouseX>0 and MouseX<=25 then index = 1
if MouseX>26 and MouseX<=50 then index = 2
if MouseX>51 and MouseX<=75 then index = 3
if MouseX>76 and MouseX<=100 then index = 4
if MouseX>101 and MouseX<=125 then index = 5

These banks of conditional statements can go on for pages of code! What a lot of typing! There were only 5 possibilities in the above example, but often there are many, many possibilities with which to deal.

Whenever I see a routine like this developing in my code, I stop in a hurry and try to think of a more efficient way to do it. Avoiding blocks of conditional statements not only saves typing and reduces the code size, but it also makes the code much more readable and easier to modify.


DO IT IN ORDER

Have a look at the routine above. MouseX is evaluated in groups of 25 pixels, with 0 to 25 producing index = 1, 26 to 50 producing index = 2 and so on. The first way to streamline the code would be to consider that it is only necessary to evaluate the greatest possible MouseX, rather than the range for each condition, if the statements are placed in the proper order and end with a GOTO. Below, if MouseX is less than or equal to 25, index is set at 1 and the program continues execution at the [doIndex] branch label. It does not evaluate whether MouseX is less than or equal to 50, 75, etc. This saves a bit of typing, and also may make the program more efficient.

if MouseX<=25 then index = 1 : goto [doIndex]
if MouseX<=50 then index = 2 : goto [doIndex]
if MouseX<=75 then index = 3 : goto [doIndex]
if MouseX<=100 then index = 4 : goto [doIndex]
if MouseX<=125 then index = 5 : goto [doIndex]


DO IT WITH ARITHMETIC

We can do better, though. The method above would still produce many lines of code, especially if there are many possible conditions. If we use a little arithmetic, we can discover a way to associate MouseX with the appropriate index number directly, avoiding all of the If...Then statements!

Since the possible indexes are associated with groups of 25 pixels, we can divide MouseX by 25 and take the integer value, eh? Now, let's see, that would be

index = int(MouseX/25)

Tada! Um, er, wait a minute. If MouseX is 20 (just for example!) then let's plug in that value:

index = int(20/25)

hmmm... that gives us

index = int(0.8)

and THAT gives us an index of 0!! MouseX=20 should give us an index of 1! What did we do wrong? Oh, the int() function actually rounds DOWN. We can fix that by adding 1 to our result:

index = int(MouseX/25) + 1

If you try this for different values of MouseX, you will see that it works just fine. When in doubt, try it out. Plug in some numbers and see if the formula generates the correct result.

Here is a little program that draws some 25 pixel boxes. Click within a box and you will be told the correct index number for it. Note that since values for MouseX over 125, and MouseY over 25 are not within our scope, we don't bother checking for an index value if the mouse is outside of our graphic:

if MouseY>25 or MouseX > 125 then [loop] 'not on buttons

The code:

'try the following program both ways...
'the if...then way, and the math way!
'switch between these two commands:
'print #1, "setfocus; when leftButtonDown [easyway]"
'print #1, "setfocus; when leftButtonDown [hardway]"
'
'code released to the public domain
'
nomainwin
open "Math 1" for graphics_nsb as #1
print #1, "trapclose [quit]"
 
print #1, "down;backcolor red;place 0 0"
print #1, "boxfilled 25 25"
 
print #1, "backcolor green;place 25 0"
print #1, "boxfilled 50 25"
 
print #1, "backcolor blue;place 50 0"
print #1, "boxfilled 75 25"
 
print #1, "backcolor lightgray;place 75 0"
print #1, "boxfilled 100 25"
 
print #1, "backcolor pink;place 100 0"
print #1, "boxfilled 125 25"
 
 
[loop]
print #1, "setfocus; when leftButtonDown [easyway]"
'print #1, "setfocus; when leftButtonDown [hardway]"
input a$ 
 
[hardway]
if MouseY>25 or MouseX > 125 then [loop] 'not on buttons
if MouseX>0 and MouseX<=25 then index = 1
if MouseX>26 and MouseX<=50 then index = 2
if MouseX>51 and MouseX<=75 then index = 3
if MouseX>76 and MouseX<=100 then index = 4
if MouseX>101 and MouseX<=125 then index = 5
notice "You clicked box #";index
goto [loop]
 
 
[easyway]
if MouseY>25 or MouseX > 125 then [loop] 'not on buttons
index = int(MouseX/25) + 1
notice "You clicked box #";index
goto [loop]
 
[quit]
close #1:end


DO IT WITH ARRAYS

Depending upon the information required, it might be best to include the possibilities in arrays. The index returned can then be used to indicate the proper array element.

Now, instead of just the box 'number', we want to know the color of the box we clicked. Here it is:

if MouseX>0 and MouseX<=25 then colorname$ = "red"
if MouseX>26 and MouseX<=50 then colorname$ = "green"
if MouseX>51 and MouseX<=75 then colorname$ = "blue"
if MouseX>76 and MouseX<=100 then colorname$ = "gray"
if MouseX>101 and MouseX<=125 then colorname$ = "pink"
notice "You clicked color ";colorname$

Okay, this isn't too bad. If there were 100 boxes, instead of 5, the code could get pretty long and repetitive. Let's see what happens if we set up an array to hold the color names.

color$(1)="red"
color$(2)="green"
color$(3)="blue"
color$(4)="gray"
color$(5)="pink"

Although it is true that it takes a bit of typing to set up the array, once it is filled it can be used in many routines in the program. This saves us from the bank of conditional (if...then) statements in each routine that accesses the color names. Here is the streamlined routine:

index = int(MouseX/25) + 1
notice "You clicked color ";color$(index)

The code:

'switch between these two commands to
'observe the two methods for determining
'condtions:
'print #1, "setfocus; when leftButtonDown [arrayway]"
'print #1, "setfocus; when leftButtonDown [hardway]"
'
'code released to the public domain
 
nomainwin
color$(1)="red"
color$(2)="green"
color$(3)="blue"
color$(4)="gray"
color$(5)="pink"
 
open "Math 1" for graphics_nsb as #1
print #1, "trapclose [quit]"
 
print #1, "down;backcolor red;place 0 0"
print #1, "boxfilled 25 25"
 
print #1, "backcolor green;place 25 0"
print #1, "boxfilled 50 25"
 
print #1, "backcolor blue;place 50 0"
print #1, "boxfilled 75 25"
 
print #1, "backcolor lightgray;place 75 0"
print #1, "boxfilled 100 25"
 
print #1, "backcolor pink;place 100 0"
print #1, "boxfilled 125 25"
 
 
[loop]
'print #1, "setfocus; when leftButtonDown [arrayway]"
print #1, "setfocus; when leftButtonDown [hardway]"
input a$ 
 
[hardway]
if MouseY>25 or MouseX > 125 then [loop] 'not on buttons
 
if MouseX>0 and MouseX<=25 then colorname$ = "red"
if MouseX>26 and MouseX<=50 then colorname$ = "green"
if MouseX>51 and MouseX<=75 then colorname$ = "blue"
if MouseX>76 and MouseX<=100 then colorname$ = "gray"
if MouseX>101 and MouseX<=125 then colorname$ = "pink"
notice "You clicked color ";colorname$
goto [loop]
 
 
[arrayway]
if MouseY>25 or MouseX > 125 then [loop] 'not on buttons
 
index = int(MouseX/25) + 1
notice "You clicked color ";color$(index)
goto [loop]
 
[quit]
close #1:end


TOGGLING WITH MATH

Toggling refers to setting a flag on or off, or to give it a value of 1 or 0. If we toggle a lightswitch, we turn it off if it was ON and we turn it on if it was OFF. We can do the same for flags we've set. We can do it with conditional statements, like this:

if flag <= 0 then flag = 1:goto [doneflag]
if flag >  0 then flag = 0:goto [doneflag]

Okay, that does what we want. Well, we could do it even more easily with if/then/else, like this:

if flag <= 0 then flag = 1 else flag = 0

Now it is in one statement, rather than two. We can't get much more efficient than that, can we? Well, yes we can! Look at the following expression. The variable 'flag' has an initial value of 0. Plug in that value and see the result.

flag = 1 - flag

Okay, if flag = 0:

flag = 1 - 0 means that
flag = 1

So, if flag starts out with a value of 0, it is given a new value of 1.

What happens if flag has an initial value of 1?

flag = 1 - 1
flag = 0

So, if flag starts with a value of 1, it is given a value of 0! Here is the magic expression again:

flag = 1 - flag

And here is a small demo of this toggling effect in action:

'code released to the public domain
nomainwin
 
statictext #1.s, "Flag is now 0",12,50,120,30
 
'button #1.b, "Toggle Flag",[toggleeasy],UL,10,10
button #1.b, "Toggle Flag",[togglehard],UL,10,10
 
open "Math 3" for window as #1
print #1, "trapclose [quit]"
 
 
[loop]
input a$ 
 
[toggleeasy]
flag = 1 - flag
print #1.s, "Flag is now ";flag
goto [loop]
 
[togglehard]
if flag <= 0 then flag = 1:goto [doneflag]
if flag >  0 then flag = 0:goto [doneflag]
'if flag <= 0 then flag = 1 else flag = 0
[doneflag]
print #1.s, "Flag is now ";str$(flag)
goto [loop]
 
[quit]
close #1:end


DON'T DUPLICATE ROUTINES

If you scroll though a program's code and notice a similar chunk of code repeated again and again, there is probably a way to pull it out into a gosub, a sub, or a function.

Which one would be best?

GOSUB

Remember that variables within subs and functions are local. This means that variables in other parts of a program will have no value within subs and functions. If you have a variable called city$ in your program, and it has a value of "New York", it will have a value of "" within all subs and functions! It is possible to pass information from global variables into subs and functions as parameters, but it is not possible to change the values of these variables from within subs and functions. If you need to work on global variables, then the best routine to use to avoid code replication is the GOSUB.

How does GOSUB work in a program? To call a GOSUB routine, use the GOSUB command:

GOSUB [branchlabel]

The program will execute all code following the specified branch label until it hits a RETURN command. When it comes to the RETURN, it returns to the line following the original GOSUB commmand.

 
[branchlabel]
	(BASIC code goes here!)
	...
	...
	...
RETURN

Because it returns execution to the line following the GOSUB command, this routine can be called from a number of different places in a program.

After execution, it always returns to the line following the specific GOSUB command that called it.

Here is an example. The GOSUB routine changes the value of the global variable city$ to all uppercase and appends the state abbreviation, " NY":

Here is a sample program:

city$ = "New York"
gosub [cityname]
 
city$ = "Albany"
gosub [cityname]
 
 
city$ = "Buffalo"
gosub [cityname]
 
 
end
[cityname]
print "city$ was first called "+ city$ 
print "but it has been changed to:"
city$=upper$(city$)
city$=city$+" NY"
print city$
print
RETURN


SUB

Place replicated code into a SUB when it does not need to act on global variables, or return a value, but some parameters change each time it is needed. To access this type of sub, use the CALL command, like this:

call subname expr1, expr2$

The word CALL (or "call", it is case insensitive!) is followed by the name of the sub, then a list of the parameters to pass, separated by commas.

The program will go to to the named SUB, execute the code until it reaches the END SUB command, then return to the line after the CALL SUB was issued.

    sub subname var1, var2$
	(BASIC code goes here!)
	...
	...
	...
    end sub

Notice that the parameters are sent to the sub, either as string and numeric literals or variables, and they are given an alias by the sub. In the above example, the program sends a string variable called expr2$. The sub gives it the name var2$ and acts on that variable. It CANNOT change the value of expr2$, because it doesn't have access to the actual variable, but to a copy of its contents.

The following example shows a good use of the sub. The sub contains a routine to call a Windows message beep and a message box with the title "Error!" and the red X, or win3.1 stop sign icon, and an OK button. If a program contained a number of routines that trapped errors, it would only need to make a simple call statement that includes the error each time it wanted to give an error message.

call ErrorMessage error$

You can see how putting the messagebeep/box calls into a sub to be called from many places within a program saves a lot of redundant code. It isn't necessary to access global variables, or to get a return, so the SUB works great here. The sub is called three times, and each time the message is different. Once it is the string variable, error$, once it is the string variable, goof$ and once it is the string literal , "This means you!" In all cases, the value sent to the sub is given the variable name msg$ and used within the routine.

Here is a small sample program.

nomainwin
    error$ = "You made a mistake!"
    call ErrorMessage error$
 
    goof$ = "Hey, cut that out!"
    call ErrorMessage goof$
 
    call ErrorMessage "This means you!"
 
    end
 
'***************
sub ErrorMessage msg$
    open "user.dll" for dll as #user
 
    calldll #user, "MessageBeep", _
    _MB_ICONHAND as word, _
    result as void
 
    wtype = _MB_ICONHAND or _MB_OK
    calldll #user, "Messagebox", _
    0 as word, _
    msg$ as ptr, _
    "Error!" as ptr, _
    wtype as word, _
    result as short
 
    close #user
    end sub
'***************


FUNCTION

If a program has a redunant code routine (one that is coded in several places within the same program) and it doesn't need to work on global variables, it does need to have differing parameters, and it also need to return a value, then a FUNCTION is the perfect solution.

To use a function, we must either place its return into a variable, or use it in an action such as PRINT. We call it by its name, then we enclose a list of appropriate parameters within parenthesis:

newvar = FunctionName(var1,var2...)

or

PRINT FunctionName(var1,var2...)

The actual code for the function begins with the word FUNCTION (again, case insensitive!) followed by the name of the function, then a parameter list, enclosed in parenthesis. A function always returns a value, so before the END FUNCTION command that signals the end of the code for a function, we must set the name of the function equal to a value. This value is the one that will be contained in the "newvar" variable above, or it will be printed in the print statement. Here is the syntax:

function FunctionName(parameter1,parameter2...)
	(BASIC code goes here!)
	...
	...
	...
      FunctionName = (value)
  end function

Notice that the calling command gives the parameters as var1 and var2, but the function places the values of those variables into variables called parameter1 and parameter2, for use within the function routine.

In the very first sample program above, we reduced a large bank of if...then statements to two lines:

index = int(MouseX/25) + 1
notice "You clicked color ";color$(index)

By placing our arithmetic into a function, we can condense ir further, to just one line!

notice "You clicked box #";Index(MouseX)

Here is the function, that contains the "math". The MouseX value sent in by the program is placed into the variable "mx" and used to calculate and return the proper index number.

function Index(mx)
    Index = int(mx/25) + 1
    end function

Here is the sample code:

'using a function to 'do the math'
'
'code released to the public domain
'
nomainwin
open "Math 1" for graphics_nsb as #1
print #1, "trapclose [quit]"
 
print #1, "down;backcolor red;place 0 0"
print #1, "boxfilled 25 25"
 
print #1, "backcolor green;place 25 0"
print #1, "boxfilled 50 25"
 
print #1, "backcolor blue;place 50 0"
print #1, "boxfilled 75 25"
 
print #1, "backcolor lightgray;place 75 0"
print #1, "boxfilled 100 25"
 
print #1, "backcolor pink;place 100 0"
print #1, "boxfilled 125 25"
 
 
[loop]
print #1, "setfocus; when leftButtonDown [easyway]"
'print #1, "setfocus; when leftButtonDown [hardway]"
input a$ 
 
[hardway]
if MouseY>25 or MouseX > 125 then [loop] 'not on buttons
if MouseX>0 and MouseX<=25 then index = 1
if MouseX>26 and MouseX<=50 then index = 2
if MouseX>51 and MouseX<=75 then index = 3
if MouseX>76 and MouseX<=100 then index = 4
if MouseX>101 and MouseX<=125 then index = 5
notice "You clicked box #";index
goto [loop]
 
 
[easyway]
if MouseY>25 or MouseX > 125 then [loop] 'not on buttons
notice "You clicked box #";Index(MouseX)
goto [loop]
 
[quit]
close #1:end
 
function Index(mx)
    Index = int(mx/25) + 1
    end function

 


Comments, requests or corrections: Hit 'REPLY' now! Dean Hodgson -- mailto:Hodgson.Dean@saugov.sa.gov.au Alyce Watson -- mailto:awatson@wctc.net