LEARN TO PROGRAM WITH SMALL BASIC: An Introduction to Programming with Games, Art, Science, and Math (2016)
10. SOLVING PROBLEMS WITH SUBROUTINES
The programs you’ve written so far are short and easy to understand. But as you start dealing with more complex problems, you’ll need to write longer programs. Understanding long programs can be a challenge, because you’ll need to keep track of many different parts of the program. In this chapter, you’ll learn how to organize your programs into smaller pieces.
An approach known as structured programming started in the mid-1960s to simplify the process of writing, understanding, and maintaining computer programs. Instead of writing a single, large program, you divide your program into smaller pieces. Each piece solves one part of the overall task, and subroutines implement these smaller pieces as part of a long program.
Subroutines are basic building blocks for creating large programs (see Figure 10-1). In this chapter, you’ll delve into the wild world of subroutines, learn how to move data in and out of them, and use them to build large programs and fun games!
Figure 10-1: Subroutines are the building blocks of larger programs
Why Use Subroutines?
Let’s say you run a construction company. Your job is to coordinate the work among your contractors and build houses. As a manager, you don’t have to know all the nitty-gritty details of building a home: the plumber handles the plumbing, the roofer shingles the roof, and the electrician runs all the wires. Each contractor knows their job and is always ready to work when they receive your call.
That’s very similar to how subroutines work! Each subroutine has its own name, like how the plumber’s name is Mario. Each subroutine does something different, just like how the plumber and the roofer have different jobs, but all are needed to build the house. As the programmer, you’re the manager, and your job is to solve problems as you build your program. You call your contractors (that is, your subroutines) and let them know when you need them to work (see Figure 10-2). You start writing the program by typing statements in the editor. When you need to perform a job that a subroutine handles, you just call that subroutine and wait. When the subroutine completes its task, you move on to the next step in your program.
Figure 10-2: The boss (main program) calling the Bob subroutine
There’s nothing new about this call-and-wait strategy; you’ve been doing it since Chapter 1. When you call an object’s method, you’re actually giving the work to that object in the Small Basic library. Subroutines are like methods, but you have to write all the statements in the subroutines. Subroutines help you organize your thinking process and make it easier to fix errors.
Let’s use a fun example to learn how to write a subroutine: in his travels, Gulliver had dinner with the king and queen of Lilliput (the land of tiny people). During dinner, the king explained that he was 8.5 glum-gluffs tall. Gulliver later learned that 1 glum-gluff is about 0.75 inches. To find out how the sizes of items in Lilliput compare to sizes in our land, write the program in Listing 10-1, which converts glum-gluffs to inches.
1 ' GlumGluff.sb
2 TextWindow.Write("How many glum-gluffs? ")
3 glumGluffs = TextWindow.ReadNumber()
5 inches = 0.75 * glumGluffs ' Converts to inches
6 inches = Math.Round(inches * 100) / 100 ' Rounds to 2 decimal places
7 TextWindow.WriteLine("That's about " + inches + " inches.")
Listing 10-1: Converting measurements
This program looks just like the ones you’re already used to! You prompt the user to enter the glum-gluff measurement (line 2), read the input into the glumGluffs variable (line 3), convert the input number to inches (line 5), round the answer to two decimal places (line 6), and then display the result (line 7). Run the program to figure out how tall the king is in inches; remember that he’s 8.5 glum-gluffs tall.
Next, let’s rewrite this program and put the conversion statements (lines 5–6) in a subroutine named GlumGluffToInch(). Enter the code in Listing 10-2.
1 ' GlumGluff2.sb
2 TextWindow.Write("How many glum-gluffs? ")
3 glumGluffs = TextWindow.ReadNumber()
5 GlumGluffToInch() ' Calls the subroutine
6 TextWindow.WriteLine("That's about " + inches + " inches.")
8 ' This subroutine converts from glum-gluffs to inches
9 ' Input: glumGluff; the size in glum-gluff units
10 ' Output: inches; the size in inches rounded to 2 decimal places
11 Sub GlumGluffToInch
12 inches = 0.75 * glumGluffs
13 inches = Math.Round(inches * 100) / 100
Listing 10-2: Calling a subroutine
This code does the same thing as the code in Listing 10-1, but it uses a subroutine. A subroutine is a collection of statements that do a specific job (just like hiring Mario the plumber to build a fancy toilet). In this case, your subroutine converts glum-gluffs to inches. The statements that make up the subroutine are sandwiched between the Sub and EndSub keywords (lines 11–14). The subroutine’s name comes after the Sub keyword (line 11). When you define the subroutine, don’t put parentheses after its name.
But just because you define a subroutine doesn’t mean your program will run it. To run a subroutine, you need to call (or invoke) it! To call a subroutine, you type its name followed by parentheses (line 5). The statement on line 5 means “run the subroutine named GlumGluffToInch(), and then return to the line that comes after this subroutine call” (which is line 6 in this example). It’s like taking a break from cleaning your room to go watch some TV and then coming back to pick up where you left off. Figure 10-3 shows how a subroutine works in a program.
Figure 10-3: Showing how GlumGuff2.sb calls the GlumGluffToInch() subroutine
Here is one example of output from this program:
How many glum-gluffs? 8.5
That's about 6.38 inches.
A subroutine can access all the variables in the main program, and the main program can access all the variables in a subroutine. The variable glumGluffs was created and assigned a value in the main program (line 3), but it was used by the subroutine to know how many glum-gluffs it needs to convert (line 12). And the variable inches was created inside the subroutine (line 12), but the main program reads it and displays its value to the user (line 6).
Here are some good reasons to put the unit conversion code into a subroutine:
1. You isolate (or separate) the unit conversion details from the main program. The main program now doesn’t have to worry about how the conversion is done. This makes your code easier to read and maintain.
2. If errors occur, you know where to look, which makes debugging much easier to do.
3. You don’t have to write the same code over and over again! Without using subroutines, if a program needs to run the same set of statements more than once, you have to duplicate these statements in your code. But if you put those statements in a subroutine, you can call it from any point in your program (code reuse). You’ll practice this in the next section.
In this book, we’ll start the name of a subroutine with a capital letter. We’ll also write all the subroutines at the bottom of every main program. We recommend you follow the same practice in your own programs: it’ll help keep you organized!
TRY IT OUT 10-1
When Gulliver asked what a glum-gluff was, he was told it was 1/20 of a mumgluff. Write a subroutine named MumGluffToFoot() that converts mum-gluffs to feet. Write a program that prompts the user for a mum-gluff measurement, calls the subroutine, and then displays the result.
Subroutine Input and Output
You can think of a subroutine as a small program that provides a service to the main program. When the main program needs that service, it prepares the inputs that the subroutine needs and then calls the subroutine to start its job. The subroutine runs, saves its output(s) in some variables, and returns to the main program. When the main program continues, it looks at any new information from the subroutine and then uses that data to decide what to do next.
Small Basic doesn’t let you pass arguments to subroutines between parentheses (like you do with an object’s method, such as the DrawLine() method of GraphicsWindow). And it doesn’t define subroutines that directly return a value (like the Math.Round() method does). So you need to use variables to pass data between the main program and your subroutines. Let’s see how that works.
Great news! You inherited some land (Figure 10-4) from Uncle Moneybags. But you need to know the area of the land before you can sell it. The figure also shows Heron’s formula, which computes the area of a triangle given the lengths of its three sides. Don’t worry if you’re not familiar with this formula; you don’t need to fully understand something in order to use it (or most people wouldn’t be allowed to use the toilet).
Figure 10-4: Calculating the area of the piece of land you inherited
Because the land is made up of two triangles, you can compute the area of these triangles and then add them together. Follow Listing 10-3 and note how we put the code for calculating the triangle’s area (Heron’s formula) in a subroutine.
1 ' LandArea.sb
2 ' Calculates the area of the first triangle
3 side1 = 7
4 side2 = 20.6
5 side3 = 25
7 totalArea = area ' Saves the result from the subroutine call
9 ' Calculates the area of the second triangle
10 side1 = 30
11 side2 = 14
12 side3 = 22.3
14 totalArea = totalArea + area ' Adds the new area
16 totalArea = Math.Round(totalArea * 100) / 100 ' Rounds the answer
17 TextWindow.WriteLine("Area = " + totalArea + " square meters")
19 ' Subroutine: computes the area of a triangle given its three sides
20 ' Inputs: side1, side2, and side3; the length of the three sides
21 ' Outputs: area; the area of the triangle
22 ' Temporary variables: s; the semiperimeter
23 Sub TriangleArea
24 s = 0.5 * (side1 + side2 + side3)
25 area = Math.SquareRoot(s * (s - side1) * (s - side2) * (s - side3))
Listing 10-3: Calling a subroutine multiple times
Here’s the output of this program:
Area = 208.63 square meters
The main program sets the lengths of the three sides of the first triangle (lines 3–5) and then calls the TriangleArea() subroutine (line 6). The subroutine (lines 23–26) saves the computed area in a variable named area. After the subroutine call, the main program stores this first area in thetotalArea variable (line 7). Without this, the value stored in area will be lost the next time we call the TriangleArea() subroutine. Then the main program sets the values to compute the area of the second triangle (lines 10–12) and calls the subroutine again (line 13). When the subroutine ends, the main program adds the new area to totalArea (line 14). The main program then rounds the answer (line 16) and displays it (line 17).
The TriangleArea() subroutine uses a temporary variable named s to store the semiperimeter, one-half of the perimeter of the current shape (line 24). Note how this variable is used to compute the area in line 25. This variable isn’t intended to be used by the main program, which just cares about the area variable. But the main program knows about it (for example, it can display the variable). Because your subroutines can change variables that belong to the main program, be sure to name your variables carefully and clearly. For example, if the s variable seems confusing, rename it to semiperimeter so you’ll remember what it does.
TRY IT OUT 10-2
Uncle Moneybags left you another piece of land (Figure 10-5)! Update the program in Listing 10-3 to compute its area (all dimensions are in meters).
Figure 10-5: Your new piece of land
If your chore is to clean the house, you might get help by making a deal with your sister to clean the windows and asking your dog to clean the floor under the table. Similarly, a subroutine might call other subroutines to help it do part of a larger job. In Figure 10-6, the main program calls a subroutine, SubA(), which then calls another subroutine, SubC(). Subroutines called from other subroutines are nested subroutines.
Figure 10-6: Illustrating nested subroutines
If your program contains many subroutines, you can place these subroutines at the end of your program in any order you like. For example, it doesn’t matter if you put the code for SubA() before or after SubB(). What matters is the order in which you call these subroutines, not where you place them in your code!
To try out this concept, you’ll play Pepper Dare, an exciting game of chance, against the computer. When the game starts, the player is handed 10 imaginary cards face down. One of those cards has a jalapeño pepper on it; the rest are blank. The player picks a card and hopes for a blank one. If the player picks the card with the jalapeño, the player has to eat a hot pepper and the computer wins! If the player doesn’t get the pepper card, the computer takes a turn. The game ends when either the player or computer eats the pepper and runs for a drink of water. Enter the main program in Listing 10-4 into Small Basic. You’ll add the subroutines in a moment.
1 ' PepperDare.sb
2 player = 1 ' 1 for player, 2 for computer
3 pepper = Math.GetRandomNumber(10) ' Which card has the pepper
6 Pick() ' Updates the two variables: card and name
7 If (card = pepper) Then
8 TextWindow.Write("Hot tamale, it's a pepper! ")
9 TextWindow.WriteLine(name + " wins!")
12 TextWindow.Write("The card is blank. ")
13 TextWindow.WriteLine("You put it back in and shuffle the deck.")
15 player = 3 - player ' Switches the player
16 Goto Again
Listing 10-4: Setting up Pepper Dare
The game starts by setting the player variable to 1 to give you the first turn (line 2). It then randomly picks 1 of the 10 cards to be the card that has the jalapeño pepper (line 3). Then it starts a loop (lines 5–17) to take turns. In each round, the game picks one card at random for the player (or the computer) by calling the Pick() subroutine (line 6). If the picked card has a pepper on it (line 7), the game displays the winner’s name (line 9), and the game ends because the program moves out of the If loop and jumps from line 10 to line 17, bypassing the Goto loop on line 16.
Otherwise, it displays The card is blank. You put it back in and shuffle the deck. (lines 12–13) to indicate that the player (or the computer) picked a blank card. The game then switches to the next player (line 15) and goes back to start a new round (line 16). This is how the statement on line 15 works: if player is 1 (you, the user), then 3 – 1 is 2 (switching to the computer’s turn), and if player is 2 (the computer), then 3 – 2 is 1 (switching back to the user’s turn).
Next, you’ll add the Pick() subroutine in Listing 10-5 to the bottom of your program.
1 Sub Pick
2 If (player = 1) Then
3 name = "The computer"
4 TextWindow.WriteLine("Your turn. Pick a card.")
6 name = "The player"
7 TextWindow.WriteLine("The computer picks a card.")
10 TextWindow.Write("[Press any key...]")
14 card = Math.GetRandomNumber(10) ' Picks a random card
15 Animate() ' Animates the delay in picking a card
Listing 10-5: The Pick() subroutine for Pepper Dare
The subroutine starts by checking the current player (either you or the computer) and then sets the name variable (lines 3 and 6). Next, it asks you to press any key to have you or the computer pick a card (lines 10–12). Then it randomly picks a card (line 14) and calls the nested Animate()subroutine to animate an arrow in the text window.
Now add the Animate() subroutine in Listing 10-6 to the bottom of your program.
1 Sub Animate
2 For N = 1 To card
6 TextWindow.Write("-> ")
Listing 10-6: Subroutine to animate the delay
Don’t worry about the For loop here. You’ll learn about it in depth in Chapter 13. For now, this code just slowly displays a variable-length arrow. Here’s a sample run of the completed Pepper Dare program:
Your turn. Pick a card.
[Press any key...]
--> The card is blank. You put it back in and shuffle the deck.
The computer picks a card.
[Press any key...]
--------> The card is blank. You put it back in and shuffle the deck.
Your turn. Pick a card.
[Press any key...]
---------> Hot tamale, it's a pepper! The computer wins!
Not only can a subroutine call other subroutines, but it can also call itself (this is called recursion)! See the online resources to learn more.
TRY IT OUT 10-3
Play the Pepper Dare game several times to understand how it works. Come up with some ideas to improve it, and then try to implement those ideas.
Create a Dragon Game
The previous example showed you how subroutines can add structure and clarity to your programs. You break your program into smaller pieces and tackle them one at a time. Although every problem is different and there’s no one-size-fits-all solution, we recommend a few ways to think through any problem.
First, spend some time trying to fully understand the problem. You wouldn’t dive into a pool without looking at it first, right?! (What if it was filled with pudding?) When you have a good idea of the problem you need to solve, plan a general solution. Then divide it into major tasks. As the solution planner, you decide what those tasks are. There’s no right or wrong answer; with practice you’ll get better at making these choices. But if you start with the general solution and break it down into smaller tasks, the logic of your program will be in good shape.
To show you this problem-solving strategy, let’s make the dragon game shown in Figure 10-7.
Figure 10-7: The dragon game’s user interface
In this game, you control the knight, and it’s your job to slay the dragon. On the screen you can see which variables we’ll display to keep score and where the player makes a choice of three actions to play.
When the game starts, Good Knight is on the right, some distance from Draggy the dragon. Good Knight has a bow and some arrows, and his shield has a certain strength level (the program picks these values at random). The knight makes the first move. He can move 1 step forward, shoot an arrow at the dragon, or stab the dragon with his sword (but only if he’s 1 step away). If the arrow hits the dragon, it’ll slay him instantly! With the sword, the knight has a 50-50 chance of slaying the dragon (but only when he’s close enough). If Good Knight slays Draggy, he’ll become Knight of the Year, win his very own dance party, and get his picture on the castle wall.
Once Good Knight makes his move, Draggy breathes his flames at the knight. If he hits the knight, he’ll weaken the knight’s shield. When the shield loses its strength, the knight is defenseless. After this point, if the dragon’s fire hits the knight, it’ll burninate him! The entire city will be under the attack of the merciless, ferocious dragon. Game over!
The game uses five images that you can find in this chapter’s folder: the background image (your battlefield), two images for the dragon (one image shows the dragon’s fire), the knight’s image, and an image of an arrow. Follow steps 1–10 to make a fun dragon game!
Step 1: Open the Startup File
Open the Dragon_Incomplete.sb file from the code folder for this chapter. This file contains the code in Listing 10-7 and has empty placeholders for your subroutines. You’ll add the code for these subroutines one step at a time. The program’s folder has all the images you need as well. It also has the complete game, Dragon.sb, in case you get stuck.
1 ' Dragon_Incomplete.sb
2 SetUp() ' Does one-time set up
4 NewGame() ' Sets the parameters for a new game
6 UpdateUserInterface() ' Shows values on background image
9 GetChoice() ' Displays options and gets the knight's choice
11 ProcessChoice() ' Processes the user's choice
13 DragonFire() ' Now it's the dragon's turn
14 Goto NextMove
Listing 10-7: High-level structure of the dragon game
First, you call the SetUp() subroutine (line 2) to draw the background image, create text shapes (for displaying the distance, number of arrows, and so on), and load the game’s images (dragon, knight, and arrow). Line 4 calls NewGame() to set the parameters for a new game, including the knight’s arrows, shield strength, and distance from the dragon. In line 6, you call UpdateUserInterface() to update the game’s user interface (UI). Then the code goes into a loop (lines 8–14) to manage the game. Each round, you ask the knight for his next move (line 9), process his move by calling ProcessChoice() on line 11, and then give the dragon a turn (line 13). As you’ll see in a moment, these subroutines will keep track of the game’s status and end the game when there’s a winner!
Next, you’ll work on the subroutines one by one.
Step 2: Write the SetUp() Subroutine
You’ll start by writing the SetUp() subroutine, which creates the scenario for your game. Add the code in Listing 10-8 to your program.
1 Sub SetUp
2 GraphicsWindow.Title = "Slay the Dragon"
3 TextWindow.Title = GraphicsWindow.Title
5 GraphicsWindow.Width = 480
6 GraphicsWindow.Height = 380
7 GraphicsWindow.CanResize = 0
8 GraphicsWindow.FontSize = 14
9 GraphicsWindow.Left = 40
10 ' Positions the text window
11 TextWindow.Left = GraphicsWindow.Left + GraphicsWindow.Width + 20
12 TextWindow.Top = GraphicsWindow.Top
14 path = Program.Directory
15 GraphicsWindow.DrawImage(path + "\bkgnd.png", 0, 0)
17 ' Creates text objects to show distance, arrows,
18 ' shield strength, and message
19 distText = Shapes.AddText("")
20 arrowsText = Shapes.AddText("")
21 shieldText = Shapes.AddText("")
22 msgText = Shapes.AddText("Draggy VS Good Knight")
23 Shapes.Move(distText, 60, 30)
24 Shapes.Move(arrowsText, 200, 30)
25 Shapes.Move(shieldText, 370, 30)
26 Shapes.Move(msgText, 5, 362)
28 ' Loads the images for the knight, dragon, and arrow
29 knightImg = Shapes.AddImage(path + "\knight.png")
30 dragon1Img = Shapes.AddImage(path + "\dragon1.png")
31 dragon2Img = Shapes.AddImage(path + "\dragon2.png")
32 arrowImg = Shapes.AddImage(path + "\arrow.png")
33 Shapes.Move(dragon1Img, 0, 250)
34 Shapes.Move(dragon2Img, 0, 250)
35 Shapes.Move(knightImg, 380, 250)
Listing 10-8: Setting up the windows and properties
This code contains all the one-time setup for your game; it’s a little long, but we’ll talk you through it. You set the titles for the graphics and text windows (lines 2–3). These are displayed in the title bars for these windows when the game is played (see Figure 10-7).
Then you set the graphics window’s size (lines 5–7), font size (line 8), and position (line 9). Next, you position the text window to appear to the right of the graphics window (lines 11–12). After drawing the background image (lines 14–15), you create and position the text shapes that you’ll use to show all the numbers on the game’s UI (lines 19–26). Then you load and position the images for the knight, dragon, and arrow (lines 29–35). Finally, you hide the images for the firing dragon and the arrow because they aren’t needed at this time (lines 37–38): you’ll show these images when Draggy breathes fire and Good Knight shoots the arrow.
When we built this program, we figured out where to place the text and images (with the numbers we’re using) on the background’s image by using a trial-and-error method (we guessed and tweaked it until we got it right). You’ll likely need to do that when designing your own UIs for your awesome future games.
Step 3: Add a Bit of Chance
Next, you need to add some luck to the game. Each time we run the game, we want Good Knight to get a different number of arrows, be a random distance away from the dragon, and have a different shield strength. To do this, add the NewGame() subroutine in Listing 10-9 to your program.
1 Sub NewGame
2 dist = 9 + Math.GetRandomNumber(10) ' 10 to 19
3 arrows = Math.Floor(0.4 * dist) ' 4 to 8
4 shield = Math.Floor(0.4 * dist) ' 4 to 8
5 moveStep = 280 / dist ' Knight's move in pixels
Listing 10-9: Setting up a new game
In line 2, you add 9 to a random number between 1 and 10, which sets the distance, dist, between 10 and 19. This is the number of steps Good Knight has to take to get to Draggy. Next, you set the number of arrows as 40 percent of the distance (line 3). The farther the knight is from the dragon, the more arrows he’ll have. In line 4, you set the strength of the knight’s shield—again, as a fraction of his distance.
Let’s think about the moveStep line a little. The width of the background image is 480 pixels. The width of the dragon is 100 pixels, and the width of the knight is 100 pixels. When we place the dragon and the knight on the background, the distance from the dragon’s right edge to the knight’s left edge is 280 pixels. So every time Good Knight moves forward, we’ll move his image to the left by 280 / dist pixels.
You can change the fraction in lines 3 and 4 from 0.4 to a different value to make the game easier or harder. After you complete the game, try changing the fraction and play the game a couple of times!
Step 4: Let the Player Know What’s Going On
After you set the game’s parameters, you’ll need to show them to the user. Add the UpdateUserInterface() subroutine in Listing 10-10.
1 Sub UpdateUserInterface
2 Shapes.SetText(distText, dist)
3 Shapes.SetText(arrowsText, arrows)
4 Shapes.SetText(shieldText, shield)
Listing 10-10: Subroutine that updates the text
This subroutine is pretty basic (and small!). You just use the SetText() method of the Shapes object and pass the identifier of the text shape and the number you want to display. Recall that we saved these identifiers when we created these text shapes in the SetUp() subroutine (lines 19–21 in Listing 10-8).
Step 5: Get the Player in the Game with GetChoice()
If you run the game now, you should see all the images and numbers in place, but nothing will happen yet. You need to start taking the knight’s orders, so it’s time to add the GetChoice() subroutine in Listing 10-11.
1 Sub GetChoice
4 TextWindow.WriteLine("  Move 1 step forward")
5 TextWindow.WriteLine("  Shoot an arrow")
6 TextWindow.WriteLine("  Stab the dragon (you have to be 1 step away)")
7 TextWindow.Write(" Your choice [1-3]: ")
9 choice = TextWindow.ReadNumber()
10 If((choice <> 1) And (choice <> 2) And (choice <> 3)) Then
11 Goto AskAgain
14 If ((choice = 2) And (arrows = 0)) Then
15 Shapes.SetText(msgText, "You ran out of arrows! Borrow some from Link.")
16 Goto AskAgain
19 If ((choice = 3) And (dist > 1)) Then
20 Shapes.SetText(msgText, "You're too far to use your sword. Too bad you can't train dragons.")
21 Goto AskAgain
24 Shapes.SetText(msgText, "")
Listing 10-11: Getting the user’s choice and displaying any errors
You start by displaying the options to the user (lines 3–7). You read the user’s choice for Good Knight (line 9) and make sure it’s valid. If your user enters any number other than 1, 2, or 3, you ask them to enter a number again (lines 10–12). If the user chooses to shoot an arrow but doesn’t have any arrows, you tell them they’re out of arrows and ask them again (lines 14–17). If they want to stab the dragon but are too far away, you tell them they’re too far away and ask them to choose again (lines 19–22). Otherwise, the choice the user makes is acceptable. You clear the message text in line 24, add an empty line to the text window in line 25 to prepare for the next prompt, and return to the main program (line 26).
Step 6: Process the Player’s Choice
Now that the user has made their choice, you need to examine the choice variable to decide what to do next. Add the ProcessChoice() subroutine in Listing 10-12 to your program.
1 Sub ProcessChoice
2 If (choice = 1) Then ' Move-forward subroutine
4 ElseIf (choice = 2) Then ' Shoot-arrow subroutine
6 Else ' Stab subroutine
Listing 10-12: Jumping to the choice’s subroutine
You use an If/Else ladder on the choice variable and call a different subroutine for each choice. Next, you’ll write these three subroutines!
Step 7: Add Motion with MoveKnight()
Add the MoveKnight() subroutine in Listing 10-13 to breathe some life into Good Knight and get him moving.
1 Sub MoveKnight
2 dist = dist - 1
3 Shapes.SetText(distText, dist)
5 Shapes.Move(knightImg, 100 + dist * moveStep, 250)
7 If (dist = 0) Then ' Checks whether the knight touched the dragon
8 Shapes.SetText(msgText, "The dragon swallowed you! You taste like chicken.")
Listing 10-13: The subroutine that moves Good Knight
You start by reducing the knight’s distance from the dragon by 1 step (line 2), and then you show that new distance on the game’s UI (line 3). You then move the knight’s image to the left (line 5).
To understand how this works, let’s assume that the knight’s initial distance from the dragon, dist, is 10, which makes moveStep = 28, as illustrated in Figure 10-7. When the knight is 10 steps away from the dragon, the upper-left corner of the knight’s image is at (100 + (10 × 28), 250). When the knight is 9 steps away from the dragon, the upper-left corner of the knight’s image is at (100 + (9 × 28), 250), and when he’s 8 steps away, the image’s upper-left corner is at (100 + (8 × 28), 250), and so on. To move the knight, you set the image’s horizontal position to 100 plus the current distance, dist, times the moveStep, and you set the image’s vertical position to 250 (see Figure 10-8).
Figure 10-8: Illustrating the knight’s motion
After moving the knight, you check whether he touched the dragon (line 7). If he did, you tell Good Knight how great the dragon thinks he tastes and call the GameOver() subroutine. This subroutine is in Listing 10-14; add it to your program now.
1 Sub GameOver
Listing 10-14: Running the GameOver() subroutine
This subroutine calls Pause() to give your user a chance to read the message (line 2). When the user presses any key, the Pause() method ends, and you call the End() method to exit your program (line 3).
Step 8: Shoot Arrows with ShootArrow()
Add the ShootArrow() subroutine in Listing 10-15 to make the Good Knight a master archer who puts Hawkeye to shame.
1 Sub ShootArrow
2 arrows = arrows - 1
3 Shapes.SetText(arrowsText, arrows)
5 range = Math.GetRandomNumber(dist)
7 ' Animates the arrow
8 pos1X = 100 + dist * moveStep
9 pos2X = 100 + (dist - range)* moveStep
10 Shapes.Move(arrowImg, pos1X, 280)
12 Shapes.Animate(arrowImg, pos2X, 280, 2000)
16 If (range = dist) Then ' You hit the dragon right on
17 Shapes.SetText(msgText, "Perfect shot. The dragon's dead! You kiss the princess's frog.")
20 Shapes.SetText(msgText, "Your arrow missed! Robin Hood is giving lessons.")
21 Program.Delay(2000) ' To read the message
Listing 10-15: Shooting the arrow
You start by using one arrow (line 2) and show the remaining arrows on the UI (line 3). You then set the arrow’s range randomly to a number between 1 and the distance to the dragon (line 5). The closer the knight is to the dragon, the better his chances are that he’ll hit his target. The next block of code (lines 8–14) animates the arrow. The horizontal start position, pos1X, is the same as the knight’s position (line 8), and the end position, pos2X, is based on the selected range (line 9). You then move the arrow to its start position (line 10), show it (line 11), animate it to its final position (line 12), wait for it to reach its target (line 13), and then hide it (line 14). You can change the value 2000 in lines 12 and 13 to make the animation shorter or longer.
Once the animation is complete, you check whether the arrow hit the dragon (line 16). If it did, the game is over (lines 17–18) and the dance party is yours! Otherwise, you tell Good Knight that his arrow missed (line 20), delay the program for your user to read the message (line 21), and return to the ProcessChoice() subroutine, which returns to the main program to give the dragon his turn.
Step 9: Swing the Sword with StabDragon()
Now, add the last subroutine for the knight in Listing 10-16.
1 Sub StabDragon
2 If (Math.GetRandomNumber(2) = 1) Then
3 Shapes.SetText(msgText, "You killed the dragon! You marry the princess and 7 dwarves.")
6 Shapes.SetText(msgText, "Your sword missed! Good one, Lance-a-Little!")
7 Program.Delay(2000) ' To read the message
Listing 10-16: Stabbing the dragon
You randomly pick the number 1 or 2. If the number is 1 (line 2), the knight hits the dragon and the game ends (lines 3–4). If the knight misses, you tell the knight that he missed (line 6), delay the program for your user to read the message (line 7), and return to the ProcessChoice()subroutine.
Step 10: Breathe Fire
If the knight didn’t kill Draggy and end the game, the main program calls DragonFire() to give the dragon a fair fight. Add Listing 10-17 to your program.
1 Sub DragonFire
2 Shapes.SetText(msgText, "The dragon ignited his fire. The Pokemon run.")
9 If (Math.GetRandomNumber(2) = 1) Then ' Knight is hit
10 If (shield = 0) Then ' Shield is damaged
11 Shapes.SetText(msgText, "The dragon's fire BURNINATED you!")
14 shield = shield - 1
15 Shapes.SetText(shieldText, shield)
16 Shapes.SetText(msgText, "You're hit! Your shield became weaker. Use the force!")
19 Shapes.SetText(msgText, "The fire missed you! Aunt Mildred could've used your luck.")
Listing 10-17: The dragon breathing fire on Good Knight
Lines 3–7 animate the dragon’s fire. You hide the normal dragon image (line 3) and show the one spitting fire (line 4). You wait 1 second (line 5) and switch the images back (lines 6–7). After that, the dragon has a 50-50 chance to hit the knight with his fire. You pick a random number that’s either a 1 or a 2. A value of 1 means the dragon has hit the knight (line 9). In this case, you check the shield’s strength (line 10); if it’s 0, the game is over (lines 11–12). But if it isn’t 0, you reduce the shield’s strength by 1 (line 14), display the new value (line 15), tell the knight that he was hit (line 16), and return to the main program. If the random number is 2 (line 18), you tell the knight that the dragon’s fire missed him (line 19) and return to the main program.
Your game is done! Play it several times and enjoy your creation!
TRY IT OUT 10-4
The dragon game is fun, but it isn’t perfect. When you play the game several times, you’ll notice some issues that you either don’t like or can improve. It’s now your game; make any changes you think will make the game better. You can even change the messages and the graphics. Head to http://tiny.cc/dragongame/ to share your game in the gallery and see what others did!
If you get stuck, check out http://nostarch.com/smallbasic/ for the solutions and for more resources and review questions for teachers and students.
1. The folder for this challenge has images for the head, eyes, mouth, and body of an alien creature (see the following figure).
Write a program that prompts a user to enter the number of eyes (2, 4, or 6) and the number of mouths (1 or 2) of the alien. Then have your main program call DrawHead(), DrawEyes(), DrawMouths(), and DrawBody() to draw the alien! For example, here’s an alien with six eyes and two mouths:
2. In this challenge, you’ll develop the game Ghost Hunt (see the following figure). Open the file GhostHunt_Incomplete.sb from this chapter’s folder (which has all the images you need for this game). A ghost is hiding in 1 of the 12 rooms. To find the ghost, the user picks a room. If the user finds the ghost in that room, they win! Otherwise, the ghost tries to find the user (by selecting a room number at random). If the ghost finds the user, the game ends. Otherwise, the ghost moves to a different room, and the user tries again.