As programs become longer and more complicated, a common problem occurs. Regardless of programming language or computer system, there is a point at which logic commands reach a roadblock and the program cannot grow any larger without being broken into smaller parts. When the main procedure needs to do something specific, such as check the position of the mouse pointer on the screen, it is better to jump to another part of the program that specifically handles mouse input than to write the mouse code directly in the main procedure's source code. That way other parts of the program can use the mouse, and you don't have to rewrite the mouse commands every time.
This chapter will teach you how to break up a program into smaller parts, called subroutines, and how to use branching statements to call upon those subroutines when needed. Branching gives a program the logic it needs to perform more than one task based on certain conditions. There are times when you can write small portions of code directly inside a branching statement, but there are other times when that code is too lengthy and requires its own space. I'll show you how to create subroutines that perform specific processes in a game, and you will put this new information to use in later chapters. Basically, this is one of the most important chapters in the book!
First I would like to talk about program logic, because you should understand it before you get into subroutines. DarkBASIC calls all subroutines functions, but I'll get to that in the second half of the chapter.
What is logic and how does it relate to programming? When I hear the word logic, I think of several descriptions—analysis, deductive reasoning, processing—the rival of intuition.
Computers are great at performing logical commands, but how does logic work in DarkBASIC? Most programming languages have a standard set of branching statements that you can use to create the logic in a program. The two branching statements in DarkBASIC are IF…THEN and IF…ELSE…ENDIF. These statements can be more formally described as conditional statements. Most programmers will know right away what you are talking about if you mention an IF statement, strange as that may sound at first.
Branching statements provide the program with a means to apply rules and tests to variables and then take action based on the results. They are built into programming languages so programmers can process the input and provide the output for a program. Without branching statements, the program would only be able to forward the input directly to the output without any processing. Although this might be something that you want the program to do (perhaps to display a text file on the screen or print a document), it is far more likely that you will need to do something with data that has been entered into the program. Branching statements allow you to create complex program logic. You might not be able to create something as complicated as a pattern-recognition neural network like the human brain (as described back in Chapter 2), but even the human brain works by simple rules. It is just that billions of those small rules are being followed synchronously.
Conditions are factors that limit something within a specific boundary. For example, a football field is a rectangle bordered by white lines that delineate the area in which legal plays can be made. The rules of the game dictate how the game is played, and these rules form a set of conditions that must be met in order to play the game correctly. Who enforces the rules of the game? The referees (and in some cases, the fans!).
Program logic and logical decisions are at the core of how computers work, providing a means to process data. At the highest level, programs should be designed to accomplish the following three tasks.
The goal of any programmer should be to write programs that revolve around these three basic operations. Input, process, and output help to break down a problem into manageable pieces, and you should be able to clearly see how these three concepts apply to every program. When a program doesn't receive input, process data, or output something, it really isn't a useful program.
Obviously, these operations have a wide range of applications. Input could be from the keyboard, mouse, or joystick, but it could also be from a database, text file, serial or infrared port, network connection, or even a scanner or digital camera. Processing might involve translating, combining, or transforming the data to suit a specific purpose. In the case of a digital camera, processing might involve adjusting the brightness and cropping the photo. Output is the result of the processing, which might involve displaying something on the screen, in a printed report, or possibly to an output file or database. As you can see, input-process-output can be interpreted to mean many things. The important thing is that every program accomplishes at least this basic level of functionality.
In a computer program, you define a set of conditional statements (or branching commands) that enforce the rules of the program. There are usually many different areas of the program that perform these commands, depending on its state. For instance, a game might check the status of a joystick button. The condition in this case is a rule that if the button is pressed, something will happen (for example, the spaceship will fire a weapon or a player will shoot a gun). A more complicated example is reading the keyboard. There are approximately 100 keys on a typical AT-101 keyboard. Checking the scan codes (the special codes for each key) involves some logic, as does checking the mouse for input. The point is, without the ability to process input and provide a result, computer programs are not very useful. Imagine a car game in which you just watch the computer drive the cars around the screen. Sound like fun? Obviously, a game needs to interact with the user, and that is my point.
There are two specific branching statements in DarkBASIC: IF…THEN and IF…ELSE…ENDIF. In this section, I'll describe both statements and show you how to use them.
The most common branching statement used in programming languages is the IF…THEN statement. Following is the general syntax of the statement as it is used in DarkBASIC.
IF <condition is true> THEN <do something>
Do you see how the entire statement occurs on a single line? This is a simple statement. There is also a compound version of the IF…THEN statement, which is called IF…ELSE…ENDIF.
What happens when you need to test more than one condition at a time? You could use multiple IF…THEN statements, but there are times when it is easier just to include an ELSE block within the statement. Following is the general format of the IF…ELSE…ENDIF statement.
IF <condition is true> <do something> ELSE <do something else> ENDIF
There is one important distinction between the simple and compound statements. The compound IF does not have a trailing THEN when an ENDIF is also used. DarkBASIC identifies the THEN keyword to indicate that the whole statement occurs on a single line, while an IF without a THEN indicates a compound statement.
The use of the ELSE keyword is equivalent to the following two individual branching statements. Note the use of ENDIF to end each statement.
IF <condition is true> <perform primary commands> ENDIF IF <condition is false> <perform alternative commands> ENDIF
As I mentioned, these two branching statements are equivalent to the IF…ELSE…ENDIF statement. The ELSE saves a lot of time! Figure 6.1 shows an
Figure 6.1: Illustration of a branching statement, showing the true or false result
illustration of the IF…ELSE…ENDIF statement, demonstrating how input is tested for a true or false condition and the program execution is directed down a specific path.
Using Branching Statements
Okay, I don't want to lose you! If you are new to branching statements or to programming in general, this discussion might not have sunk in yet. How about a realistic example? Here is how you might code the branching statement to determine which mouse button has been clicked.
IF MouseButton = 1 PRINT "Left click" ELSE PRINT "Right click" ENDIF
You could have just as easily written this code using two IF…THEN statements instead of a single IF…ELSE…ENDIF, and the result would have been the same.
IF MouseButton = 1 PRINT "Left click" ENDIF IF MouseButton = 2 PRINT "Right click" ENDIF
The better solution, of course, is to use the ELSE section instead of the second IF statement, which is relevant when there are one or two possible values to be tested. When there are more than two values that you need to check, you can use compound IF statements, as follows.
IF MouseButton = 1 PRINT "Left click" ELSE IF MouseButton = 2 PRINT "Right click" ENDIF ENDIF
Tip |
Chapter 8, "Number Crunching: Mathematical and Relational Operators and Statements," covers the common relational operators, such as greater than (>), less than (<), and equal to (=), along with mathematical operators such as multiply (*) and divide (/). You can combine this information with what you learn in this chapter and the one that follows to have some very useful techniques available for writing programs. |
To demonstrate branching statements and program logic in DarkBASIC, I have written a program called Conditions that moves a ball around the screen. Any time the ball nears the edge of the screen, which is drawn with a border, the program will reverse the direction of the ball. The logic in this program determines when the ball is nearing the edge, and then deflects the ball so it doesn't go off the edge of the screen.
Running the Program
The Conditions program is located on the CD-ROM under SourcesDarkBASICCH06Conditions (for standard DarkBASIC) and under SourcesDBProCH06Conditions (for DarkBASIC Pro). You can run the program directly off the CD if you want, although you will need to copy the files to your hard drive to make any changes to the source code. Remember that you will need to turn off the read-only attribute for any files copied from a CD-ROM. (You can do this by right-clicking on a file in Windows Explorer and selecting Properties from the drop-down menu.) Figure 6.2 shows the Conditions program right after it has started.
Figure 6.2: The Conditions program tests program logic with a bouncing ball.
Figure 6.3 shows the ball right at the left edge of the screen, from where it will bounce back. The border around the edge shows the point on the screen from where the ball is bounced.
Figure 6.3: The ball is bounced off the left wall in this shot of the Conditions program.
Conditions Source Code
Following is the source code for the Conditions program. The important sections of code related to the program's logic are set in bold text. This program uses an advanced feature called SYNC that dramatically speeds up the program and eliminates flicker. To see the difference that the SYNC command makes, comment out the SYNC ON command in the initialization section. I'll explain the SYNC command and the other graphics commands used in this program in more detail in Chapter 9, "Basic Graphics Commands."
Now for the code listing. You can create a new project in DarkBASIC and type in the following code, or you can load this project off the CD-ROM (as described earlier). If you type in the program, remember to save the source code to a file called Conditions.dba after you're done. This is one of the most complex programs in the book so far! Trust me, it is worth the effort to type it in because you will learn a lot about logic statements and get a sneak peek at some graphics commands.
REMSTART --------------------------------- Beginner's Guide To DarkBASIC Game Programming Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith Chapter 6 - Conditions Program --------------------------------- REMEND REM Create some variables BallX = 320 BallY = 240 BallSize = 20 SpeedX = 4 Speedy = -5 Border = 25 REM Initialize the program SYNC ON REM Set the color color = RGB(0, 200, 255) INK color, 0 REM Start the main loop DO REM Clear the screen CLS REM Draw the screen border LINE 0, 0, 639, 0 LINE 639, 0, 639, 479 LINE 639, 479, 0, 479 LINE 0, 479, 0, 0 REM Move the ball BallX = BallX + SpeedX BallY = BallY + SpeedY REM Check conditions for the BallX IF BallX > 640 - Border BallX = 640 - Border SpeedX = SpeedX * -1 ELSE IF BallX < Border BallX = Border SpeedX = SpeedX * -1 ENDIF ENDIF REM Check conditions for BallY IF BallY > 480 - Border BallY = 480 - Border SpeedY = SpeedY * -1 ELSE IF BallY < Border BallY = Border SpeedY = SpeedY * -1 ENDIF ENDIF REM Draw the ball CIRCLE BallX, BallY, BallSize REM Redraw the screen SYNC LOOP
Subroutines are important for breaking up a large program into smaller, more manageable pieces, leading to better code reuse and legibility. They are also important for creating program logic. Quite often, the conditional statements in a branching statement point to a subroutine to keep the branching statement short. If each case in a branching statement included a page of source code, it would be easy to lose track of the cases! Therefore, subroutines are essential parts of a programming language. Figure 6.4 illustrates the relationships between the main program and all of its functions, which are broken down into more detail at each level.
Figure 6.4: Structured programs have a hierarchy of functions from high levels to lower levels of detail.
First, let me clear up something regarding subroutines. A subroutine is a separate sequence of source code within a program that performs a recurring or distinct task. By recurring, I mean something that is repeated over and over. By distinct, I mean something that might be used only once, but for which it makes more sense to put the code in its own subroutine.
The next thing I want to make clear is that you must use the FUNCTION statement to create custom subroutines. For the sake of clarity, I will refer to subroutines and functions interchangeably; they are the same thing. I will use the uppercase FUNCTION when referring to source code and the lowercase function when talking about functions in the general sense.
Following is the basic syntax of a function in DarkBASIC.
FUNCTION FunctionName([Parameters]) ENDFUNCTION [ReturnValue]
Let me explain how this works. You declare a function with a FUNCTION…ENDFUNCTION block of code. You give it a name and include any parameters that you want to be able to pass to the function, and then you provide an optional return value. Since there really is no easy way to explain this, let me show you a few examples. This first example is a function that displays a message on the screen.
FUNCTION PrintHello() PRINT "Hello!" ENDFUNCTION
You can simply call this function by name anywhere in the program. The function prints out "Hello!" on the screen any time it is called. That is great as a first example, but it isn't very useful. A far more useful function would be one that lets you print out any message you want. Although the PRINT command does this already, it is helpful to demonstrate how parameters work. Here is an example of a function that includes a parameter.
FUNCTION PrintHello(Name$) PRINT "Hello, "; Name$; "!" ENDFUNCTION
The parameter, Name$, includes a dollar sign at the end because that is how you pass a text message to a function in DarkBASIC. Number parameters don't need the dollar sign, only text messages (which are called strings) do. In fact, it is a good idea to refer to text variables in that manner, for instance by calling the parameter "Name string"—just how it sounds.
Using Functions
I want you to remember something very important about dealing with string variables. Any time you declare a string parameter, you must use the dollar sign along with the variable name everywhere in the function. If you were to write the previous line without the dollar sign, like the following line of code (note the variable name in bold), the output of the program would be very strange.
PRINT "Hello, "; Name; "!"
No matter what text message you send to the PrintHello function, it would always print 0 instead of the text you intended. Why is that, do you suppose?
Let me show you a complete program that uses the PrintHello function so you can see for yourself what happens when you leave off the dollar sign. Type the following code into a new project in DarkBASIC.
REMSTART --------------------------------- Beginner's Guide To DarkBASIC Game Programming Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith Chapter 6 - PrintHello Program --------------------------------- REMEND CLS PRINT PrintHello("DarkBASIC") END FUNCTION PrintHello(Name$) PRINT "Hello, "; Name$; "!" ENDFUNCTION
After typing in the program (or loading it off the CD-ROM from the SourcesCH06PrintHello folder), save it and then run it. The display will look like Figure 6.5.
Figure 6.5: The PrintHello program demonstrates how to pass parameters to a function.
Now change the program as I explained by removing the dollar sign from the Name$ variable inside the PrintHello function. The result is a 0 instead of the string "DarkBASIC."
As Austin Powers would say, "Positively smashing, baby!" Okay, maybe it's not that big of a deal, but I just want to make it clear that a lot of programming problems in DarkBASIC are a result of an error involving parameters. DarkBASIC is a very lenient programming language, allowing you to declare variables anywhere! That's why Name printed out 0 instead of the intended string—because DarkBASIC created a new variable called Name on the fly with a default value of 0.
How about another example to give you a little more practice using parameters? I've written a program called RandomText that uses a custom function called PrintAt. This function prints a string on the screen at a specific location using parameters. Figure 6.6 shows the output of the program.
Figure 6.6: The RandomText program demonstrates how to use multiple parameters.
Here's the code listing for the RandomText program. There's one new command in this program that I haven't explained yet—RANDOMIZE TIMER(). The RANDOMIZE command initializes the random-number seed in DarkBASIC, which causes your program to generate random numbers each time it is run. Without RANDOMIZE, the program will generate the same numbers each time, even when you re-run the program several times. TIMER is another command; in this case it is passed as a parameter to RANDOMIZE. TIMER returns a value that represents the current system timer in milliseconds.
REMSTART --------------------------------- Beginner's Guide To DarkBASIC Game Programming Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith Chapter 6 - RandomText Program --------------------------------- REMEND REM Initialize the program CLS PRINT RANDOMIZE TIMER() DO REM Select a random color color = RGB(RND(256), RND(256), RND(256)) INK color, 0 REM Print the message PrintAt(RND(640), RND(480), "I Love DarkBASIC!!") LOOP FUNCTION PrintAt(X, Y, Message$) SET CURSOR X, Y PRINT Message$ ENDFUNCTION
You can use functions not only to break up lengthy programs with many lines of code and to make code reusable, but also to perform calculations or logic and return a value. This is an important aspect of any programming language. Without the ability to return values from functions, a program would have to use variables for everything, including calculations, and the source code would quickly become unmanageable.
Remember the syntax of a function, which allowed you to return a value? Here's the definition again for reference.
FUNCTION FunctionName([Parameters]) ENDFUNCTION [ReturnValue]
The return value is added after the ENDFUNCTION keyword when you want the function to return some value. This value can be a string or a number (integer or decimal), derived from a calculation, variable, or by any other means. Following is an example of a function that returns a value.
FUNCTION RandomColor() Color = RGB(RND(256), RND(256), RND(256)) ENDFUNCTION Color
Pay close attention to the use of parentheses after the function name: RandomColor(). When you want to use this function in your own program, you must include the parentheses at the end, or else DarkBASIC will give you an error message. Some commands do not need the parentheses. Can you think of why this might be the case? The answer is related to the return value. Some commands return a value, and some do not. You can spot such commands in the source code because they include parentheses at the end. In general, a command that returns a value is called a function. Some languages even use the words "procedure" and "function" so it is easier to tell them apart. DarkBASIC is more flexible, allowing you to decide whether or not one of your custom functions will return a value. Sometimes it is helpful to think of it this way: Commands do something, while functions return something.
To demonstrate how to return values from functions, I've written a program called ReturnValue (see Figure 6.7), located on the CD-ROM in the folder for this chapter. Type in the following code and run the program to see how it works. This program features three functions, two of which were designed to return a value and make the main program easier to read.
Figure 6.7: The ReturnValue program demonstrates how to return values from functions.
For example, the custom SetColor function that I wrote is easier to use than the built-in INK command.
REMSTART --------------------------------- Beginner's Guide To DarkBASIC Game Programming Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith Chapter 6 - ReturnValue Program --------------------------------- REMEND REM Initialize the program CLS PRINT REM Print a message N times FOR N = 1 TO 20 REM Select a random color color = RandomColor() SetColor(color) REM Display message PRINT Spaces(N); "Line "; N; " - "; PRINT "Three cheers for the conquering heroes!" NEXT N WAIT KEY REM End the program END REM The Spaces function returns a specified number of blanks FUNCTION Spaces(Length) Temp$ = "" FOR A = 1 TO Length Temp$ = Temp$ + " " NEXT A ENDFUNCTION Temp$ REM The RandomColor function returns a random color FUNCTION RandomColor() Color = RGB(RND(256), RND(256), RND(256)) ENDFUNCTION Color REM The SetColor function sets the foreground color FUNCTION SetColor(Forecolor) INK Forecolor, 0 ENDFUNCTION
You can create any custom function for your programs, with your own parameters and return values. As such, you can rewrite much of the DarkBASIC language to fit in with your own ideas of how programs should be written, or you can just combine DarkBASIC commands to perform higher-level processes. For instance, instead of just tracking the mouse, you might write your own function that moves a walking character on the screen based on where the mouse is clicked. Or you might move a spaceship around the screen using the mouse with a custom MoveShip function that you have created. The possibilities are endless, which is what makes programming so much fun! Moreover, once you have a library of functions, you can use them for other programs and cut down on your programming time.
This chapter covered the extremely important subjects of branching and subroutines, which are both related in functionality. Branching statements allow you to apply rules to a program to keep the program running in a predetermined manner and behaving correctly. In addition, you learned how to use the IF…ELSE…ENDIF statement to apply logic and rule enforcement to your programs. This chapter also showed you how to create and use your own functions, complete with parameters and return values. Functions greatly enhance the capabilities of your games, allowing you to extend DarkBASIC beyond what the designers imagined.
The chapter quiz will help you to retain the information that was covered in this chapter, as well as give you an idea about how well you're doing at understanding the subjects. You can find the answers for this quiz in Appendix A, "Answers to the Chapter Quizzes."
1. |
Which command does DarkBASIC use for single-line conditional statements?
|
|
2. |
What is the purpose of a conditional statement?
|
|
3. |
What is the purpose of a subroutine?
|
|
4. |
How many programmers does it take to screw in a light bulb?
|
|
5. |
Which character suffix do you use to declare a string variable?
|
|
6. |
Which statement do you use to declare a custom subroutine in DarkBASIC?
|
|
7. |
All functions must return a value, even if that value is null.
|
|
8. |
Which statement is used to mark the end of a function?
|
|
9. |
What is the common synonym for a conditional statement?
|
|
10. |
Which at phrase best describes the activities of the sample Conditions program?
|
Answers
1. |
A |
2. |
C |
3. |
B |
4. |
D |
5. |
B |
6. |
D |
7. |
B |
8. |
B |
9. |
C |
10. |
B |
Part I - The Basics of Computer Programming
Part II - Game Fundamentals Graphics, Sound, Input Devices, and File Access
Part III - Advanced Topics 3D Graphics and Multiplayer Programming
Epilogue
Part IV - Appendixes