In the previous lesson we covered how to display messages, get input from the user, and store values in variables. While this seems like all you would need to make a text-based game, you may have realized very quickly that there's not very much you can do with a program that just carries out some steps in order and then ends. In this two-part lesson, we will learn how to have a program respond to conditions and make decisions, and go over several methods to repeat or skip code.
IF statements and decision-makingEdit
Almost every program will need to make decisions or respond in a certain way when a certain condition is true. The core decision making tool in BASIC is the IF statement, which is written like a mixture of English and algebra:
- IF HEALTH < 1 THEN PRINT "You died!"
This code should be easy to understand - the program checks if the player has less than one health, and prints that the player died if so. IF allows our program to respond according to certain conditions, so an IF statement is known as a conditional statement.
Basic IF statements come in the form
IF condition THEN statement. The condition is usually a comparison:
- IF A == B .. 'equal to
- IF A != B .. 'not equal to
- IF A < B ... 'less than
- IF A > B ... 'greater than
- IF A <= B .. 'less than or equal to
- IF A >= B .. 'greater than or equal to
The different symbols we can use to compare values are called Relational Operators. Notice that equality is tested with a double equals == to distinguish it from assigning a value like X = 5 + 5. Also observe that you can add a comment to a line by adding an apostrophe; anything after it is just a note and Petit ignores it. In these lessons I will often use comments to clarify examples. Also note ! is shorthand for "not".
When a condition is met, the condition is said to be true, and the code after THEN will be executed. Otherwise the code after THEN is ignored. This is a good time to introduce two new types of values: TRUE and FALSE, which are called booleans (see notes). We can assign TRUE or FALSE to a variable like a number or string value:
- IF X == 6 THEN XIS6 = TRUE
- IF XIS6 == TRUE THEN PRINT "X is equal to 6"
That example is unnecessarily complicated but it demonstrates how TRUE can be used. An interesting note: if you know that the variable is a boolean,
== TRUE is optional; you could rewrite the second line to start
IF XIS6 THEN. There is so much to IF that it's worthy of its own article.
Let's look at example of IF with user input:
- INPUT "Enter your age"; AGE
- IF AGE < 18 THEN PRINT "I swear officer, I didn't know!"
- IF AGE >= 21 THEN PRINT "You're old enough to drink in most places!"
We're beginning to see how IF is the keystone of interactivity. We'll get back to IF in a bit, but let's examine some new commands first.
Branching with GOTO and GOSUBEdit
So far we've only seen code that is followed line by line until the end of the program. In many cases we need more flexibility than that.
GOTO is best explained by example:
- IF HEALTH < 1 THEN GOTO @DEATHSCENE
- PRINT "You survived!"
- END 'ends your program
- CLS 'clear screen
- PRINT "GAME OVER!"
- PRINT "You died! The world is lost!"
GOTO is called a jump command because it allows us to jump directly to another place in code. In Petit, GOTO jumps to a label, which starts with @. You can choose your own label names (between 1 and 16 characters, no spaces). In the above example, if the player has no health left, "You survived!" is never printed because the code jumps to the death scene. Note that I have also introduced the END command, which simply ends the program immediately.
In most cases, GOTO is used with an IF statement, but GOTO can have other uses:
- PRINT "This is the code that never ends"
- PRINT "It just goes on and on my friends"
- GOTO @REPEAT
This is a way to implement a loop - a section of code that is repeated multiple times. This particular example would run forever (until you pressed SELECT to quit the program), which is called an infinite loop. In some cases, infinite loops are bad, but in other cases they are necessary:
- PRINT "Sum is " X
- PRINT "Enter a number to add,"
- PRINT "or -1 to quit:"
- INPUT VAL
- IF VAL < 0 THEN GOTO @QUIT
- X = X + VAL
- GOTO @START
GOTO lets you jump somewhere else in the code, but doesn't provide any way to get back to where you came from. You could go back with another GOTO, but that becomes messy very quickly. Let's examine a related command, GOSUB.
GOSUB and RETURNEdit
GOSUB behaves similarly to GOTO, but with an important exception: it lets you return to where you came from.
- PRINT "You are standing in a " ROOM$
- IF SAWENEMY == TRUE THEN GOSUB @PRINTENEMY
- PRINT "What do you want to do?" 'returns here
- PRINT "You see a " ENEMYNAME$ "!"
- PRINT "It has " ENEMYHP " HP"
- IF ENEMYSAWYOU THEN PRINT "It sees you!"
In this example, if an enemy is present, the program will jump to the @PRINTENEMY label, display the information, and then return to the PRINT line after the GOSUB. It uses GOSUB to perform several actions from a SINGLE IF statement, and makes the code more organized. A section of code executed by a GOSUB is called a subroutine; you can think of it like a custom command that you wrote yourself.
Another important way to use GOSUB is when you might need to reuse some code repeatedly:
- 'calculate player damage
- DEF = PLAYERDEF
- ATK = ENEMYATK
- GOSUB @CALCDAMAGE
- PLAYERHP = PLAYERHP - DMG
- 'calculate enemy damage
- DEF = ENEMYDEF
- ATK = PLAYERATK
- GOSUB @CALCDAMAGE
- ENEMYHP = ENEMYHP - DMG
- 'Takes ATK, DEF
- 'Returns DMG
- DMG= 3 * ATK - 2 * DEF
- IF DMG < 0 THEN DMG== 0 'don't do negative damage!
- PRINT "The attack did " DMG " damage!"
This example is more complicated. It uses the same subroutine to calculate the damage done to both the player and the enemy, which saves us from copying and pasting the code twice. To make this possible, we have to decide on some variables that we will use like the arguments we pass to commands. So we could say that this subroutine's "arguments" are ATTACK and DEFENSE. We have to set those values before we call the subroutine so it can perform the proper calculation. After calculating the damage, we have to subtract it from either the player or enemy's health. To do this, we also have to set aside a variable that we can call a "return" value; it stores the results of the operation. It's important to double check you set the arguments before calling a subroutine and that you are using the correct return variable afterward, or your results might not make sense!
Using a subroutine instead of copying and pasting code has many advantages. If the subroutine is long or used often, your program will be shorter than it would without it. If you decide later that you need to use the subroutine in other places, its easy to just add another GOSUB. The above example isn't the most efficient because the subroutine is only 3 lines long, but this way you would only need a single copy of the damage formula and it's easy to edit. If you didn't use a subroutine, every time you changed the formula you'd have to carefully look through your code and make sure you changed every copy of it.
Subroutines help us write better code, and make it easier to do more complex and powerful things with the code we write.
In the next lesson, we will cover making FOR loops, and go over some more information about IF.
- Tip: Explain what variables a subroutine takes and returns in a comment above the label for easy reference later
- Tip: In most cases you might want to use short and unique variable names for your arguments and returns so it's less work to set up the arguments and use the results.
- 99 Bottles of Beer - make a program that prints out all the lyrics to 99 Bottles of Beer. Try it with a FOR loop and then with GOTOs and IFs!
- High-low game - write a program that picks a random number between 0 and 99 and then asks the user to guess it. If they get it wrong, the computer tells them whether the number is higher or lower than the number they guessed. You can make a random number like this: X=RND(100)
- Reverse high-low - the player thinks of a number and the computer guesses
- In some programming languages, Booleans are a completely separate type of value. Petit doesn't actually have another data type for Booleans, so they follow a common programming technique: using 1 to represent TRUE and 0 to represent FALSE (based on binary). When you write X = TRUE, it actually gives X the value of 1, and X = FALSE sets X to 0. You can use this feature for some programming shortcuts, as well as for writing bizarre but correct statements like IF (HEALTH == FALSE) THEN PRINT "You are dead!"
- Note that using
IF HEALTH < 1 THEN GOTO @DIEwill kill the player if they have 0.9 health. It can be easier for a new programmer to think in integers, but Petit doesn't distinguish between integers and fractions. You can write
IF HEALTH <= 0, or use rounding (which we'll go over in a later lesson)
- What kind of sociopath would release a programming language that didn't allow multiline IF statements?
- Thanks to SquareFingers for corrections and feedback