LEARN TO PROGRAM WITH SMALL BASIC: An Introduction to Programming with Games, Art, Science, and Math (2016)

18. ADVANCED TEXT MAGIC

Although pictures of blue skies and green fields are prettier to look at than a screen full of words, many useful programs, such as Facebook, Twitter, and Words with Friends, work with text. That’s why Small Basic provides the Text object for working with text. In this chapter, you’ll learn how to use the Text object to find the length of a string, extract a small portion of a string, and perform many other advanced string-processing tasks. You’ll also write your own string-processing subroutines and apply what you learn to create some interesting applications, like a pig latin translator and a word scramble game!

The Text Object

You’ve been working with strings throughout this book. To recap, a string is a sequence of characters that is enclosed between double quotes, such as "stringY strinGy striNg strIng stRing". These characters can include letters (both uppercase and lowercase), digits (0 to 9), and other symbols on your keyboard (such as +, –, &, @, and so on). You can use strings in your programs to store names, addresses, phone numbers, book titles, names of Star Trek episodes, and more. The Text object contains many useful methods for working with strings.

Figure 18-1 shows the complete list of the Text object’s methods. We’ve divided these methods into four groups that we’ll discuss in the following sections.

image

Figure 18-1: The Text object’s methods

Appending Strings and Getting Their Length

Combining strings and finding their length is a common task in programming. Let’s look at how the Text object can help you out.

Appending Strings

The Append() method can join (or append) two strings together, as shown in the following example:

str = Text.Append("He-", "Man")
TextWindow.WriteLine(str) ' Displays: He-Man

Earlier in the book, you learned how to join strings using the + sign. But the Append() method is useful when you have text that the + sign treats as numbers, as shown in the following example:

res = Text.Append("1", "5")
TextWindow.WriteLine(res)       ' Output: 15 (1 followed by 5)
TextWindow.WriteLine("1" + "5") ' Output: 6

The first statement appends the two strings ("1" and "5") and assigns the result to the variable res (short for result). The output of the second statement shows that the string "5" was appended to the string "1", resulting in a new string "15". The third statement shows that you can’t do this concatenation using the + sign. The + operator interprets its two operands as numbers (1 and 5) and adds these numbers together, which is why the third statement displays 6.

Using Append() is the only way to concatenate numbers in Small Basic.

Getting the Length of a String

The number of characters in a string makes up its length. To find the length of a string, you can use the GetLength() method, as in the following example:

1 res = Text.GetLength("")               ' res = 0 (empty string)
2 res = Text.GetLength("Careless Bears") ' res = 14 (the space counts!)
3 res = Text.GetLength(1023)             ' res = 4
4 res = Text.GetLength(-101.5)           ' res = 6

GetLength() treats its argument as a string and returns the number of characters in that string. Line 1 shows that an empty string has zero length. Line 2 shows that the length of the string "Careless Bears" is 14, because this string contains 14 characters (spaces are characters too). Line 3 calls GetLength() using the number 1023 as an argument. GetLength() treats this number as a string ("1023") and returns 4 as the length of this string. A similar process happens in line 4 for the number –101.5, where GetLength() returns 6 (four digits, the minus sign, and the decimal point).

TRY IT OUT 18-1

Write a program that prompts the user to enter an adjective. Have the program display the corresponding adverb by appending ly to the input. For example, if the user enters mad, the program displays madly. Will this program work for all adjectives? (Hint: consider adjectives ending in y, such as happy, or adjectives ending in ic, such as heroic.)

Taking Strings Apart: Substrings

Just as you can join strings to create longer ones, you can also separate strings into smaller strings, which are called substrings. A substring is just a portion of a larger string. The Text object has six methods that let you work with substrings. Let’s look at these methods.

The IsSubText() Method

You can use IsSubText() to find out if one string is part of another. This method takes two arguments: the string you want to search through and the substring you want to search for. It returns "True" or "False" depending on whether the substring is in the source string. Here are some examples:

1 myString = "The quick brown fox"
2 res = Text.IsSubText(myString, "brown") ' res = "True"
3 res = Text.IsSubText(myString, "BROWN") ' res = "False"
4 res = Text.IsSubText(myString, "dog")   ' res = "False"

As these examples show, IsSubText() is case sensitive when it searches for substrings. This is why searching for "BROWN" in line 3 returns "False".

The EndsWith() Method

Use EndsWith() to find out if a string ends with a given substring. Here are some examples:

1 myString = "The quick brown fox"
2 res = Text.EndsWith(myString, "fox") ' res = "True"
3 res = Text.EndsWith(myString, "x")   ' res = "True"
4 res = Text.EndsWith(myString, "FOX") ' res = "False"
5 res = Text.EndsWith(myString, "dog") ' res = "False"

Again, the string’s case matters: the search for "FOX" in line 4 returns "False".

The StartsWith() Method

Use StartsWith() to find out if a string starts with a given substring. Here are some examples:

1 myString = "The quick brown fox"
2 res = Text.StartsWith(myString, "The") ' res = "True"
3 res = Text.StartsWith(myString, "T")   ' res = "True"
4 res = Text.StartsWith(myString, "the") ' res = "False"

Similarly, the search for "the" in line 4 returns "False".

The GetSubText() Method

To extract text from any position in a string, you can use GetSubText(). This method takes three arguments: the source string to get your substring from, the starting position of the substring, and the length of the substring you want. To understand how this method works, look at Figure 18-2.

image

Figure 18-2: Illustrating character positions in strings

The first character has a position of 1, the second character has a position of 2, and so on. Now consider the following examples:

1 myString = "The quick brown fox"
2 res = Text.GetSubText(myString, 1, 3)  ' res = "The"
3 res = Text.GetSubText(myString, 0, 3)  ' res = ""
4 res = Text.GetSubText(myString, 17, 3) ' res = "fox"
5 res = Text.GetSubText(myString, 17, 4) ' res = "fox"

Line 2 gets a substring of length 3 starting at position 1, which returns the string "The". Line 3 fails to get a substring that starts at position 0, because the first valid position is 1. Instead, it returns an empty string. Line 4 gets the three-letter substring that starts at position 17, which returns"fox". Line 5 requests a substring of length 4 starting at position 17. Because that substring extends beyond the end of the string, the length is cut short, and the method returns "fox", whose length is 3.

You can use GetSubText() inside a For loop to access the individual characters of a string. For example, the following code writes each character of strIn on a new line. Enter and run this code to make sure you understand how it works:

strIn = "Pirate squids hate hot dogs."
For N = 1 To Text.GetLength(strIn)  ' For each character
  ch = Text.GetSubText(strIn, N, 1) ' Gets the character at position N
  TextWindow.WriteLine(ch)          ' Displays it on a new line
EndFor

The loop counter, N, runs from 1 to the end of the string. Each iteration requests a substring of length 1 (a single character) that starts at position N and displays that character.

The GetSubTextToEnd() Method

The GetSubTextToEnd() method is similar to GetSubText(), except it returns a substring from one position all the way to the end of the string. It takes two arguments: the source string that you want to get your substrings from and the starting position of the substring. Here are some examples (refer to Figure 18-2 for context):

1 myString = "The quick brown fox"
2 res = Text.GetSubTextToEnd(myString, 13) ' res = "own fox"
3 res = Text.GetSubTextToEnd(myString, 19) ' res = "x"
4 res = Text.GetSubTextToEnd(myString, 20) ' res = ""

Line 2 gets the substring starting at position 17, which returns "own fox". Line 3 gets the substring starting at position 19, which returns "x". Line 4 requests the substring starting at position 20. Because the source string contains only 19 characters, this method returns an empty string.

The GetIndexOf() Method

You pass the GetIndexOf() method the substring you want to search for, and it returns the index position of that substring in the source text. Here are some examples:

1 myString = "The quick brown fox"
2 res = Text.GetIndexOf(myString, "The")   ' res = 1
3 res = Text.GetIndexOf(myString, "quick") ' res = 5
4 res = Text.GetIndexOf(myString, "QUICK") ' res = 0
5 res = Text.GetIndexOf(myString, "o")     ' res = 13
6 res = Text.GetIndexOf(myString, "dog")   ' res = 0

The search is case sensitive, so line 4 returns 0 because "QUICK" isn’t found in the source string. Line 5 requests the index of the letter o, but because there are two, it gives you the index of the first one it finds. The last line returns 0 because it doesn’t find "dog" in the source string.

TRY IT OUT 18-2

A young boy named Franklin Roosevelt once signed letters to his mother backward: Tlevesoor Nilknarf. Write a program that displays the characters of an input string in reverse order. (Hint: start a loop that counts from the string’s length down to 1, and use GetSubText() to extract each character.)

Changing Case

Sometimes you might want to display strings in uppercase or lowercase letters. The ConvertToLowerCase() and ConvertToUpperCase() methods can do that for you. Run the example in Listing 18-1.

' ChangeCase.sb
2 var1 = "Ewok"
3 lwrCase = Text.ConvertToLowerCase(var1)   ' lwrCase = "ewok"
4 TextWindow.WriteLine(lwrCase)             ' Displays: ewok
5 TextWindow.WriteLine(var1)                ' Displays: Ewok
6 uprCase = Text.ConvertToUpperCase(var1)   ' uprCase = "EWOK"
7 TextWindow.WriteLine(uprCase)             ' Displays: EWOK
8 TextWindow.WriteLine(var1)                ' Displays: Ewok

Listing 18-1: Changing the case of a string

The call to ConvertToLowerCase() on line 3 returns the lowercase string "ewok", which is displayed on line 4. The statement on line 5 shows that the original string isn’t affected by the lowercase conversion; calling ConvertToLowerCase() returns a brand-new string whose characters are lowercase. The ConvertToUpperCase() method on line 6 returns the uppercase version of "EWOK", which is displayed on line 7. And line 8 also shows that the original string isn’t affected by the conversion.

You can use these methods to make case-insensitive string comparisons. For example, let’s say your program asks a user about their favorite Shrek character. If the user likes Donkey, they win 200 points; otherwise, they win 100 points. The user can enter donkey, DONKEY, Donkey, DOnkey, or any other combination of cases in response to the question. Rather than checking for all the possible combinations, you can convert the user’s response to uppercase (or lowercase) and compare the result with that new string "DONKEY" (or "donkey" if you’re using lowercase). Run the program in Listing 18-2.

 1 ' StringMatch.sb
 2 While ("True")
 3   TextWindow.Write("Who's your favorite Shrek character? ")
 4   name = Text.ConvertToUpperCase(TextWindow.Read())
 5   If (name = "DONKEY") Then
 6     TextWindow.WriteLine("You won 200 ogre points!")
 7   Else
 8     TextWindow.WriteLine("You won 100 ogre points!")
 9   EndIf
10 EndWhile

Listing 18-2: Case-insensitive string matching

The Read() method on line 4 reads the text entered by the user. The user’s text is then converted to uppercase, and the result is stored in the name variable. Note how we used the Read() method directly as an argument to ConvertToUpperCase(); this is equivalent to the following two statements:

name = TextWindow.Read()
name = Text.ConvertToUpperCase(name)

The If statement on line 5 compares the uppercase version of the user’s input with the literal string "DONKEY" and awards the user accordingly.

Here’s an output example:

Who's your favorite Shrek character? dOnkey
You won 200 ogre points!

TRY IT OUT 18-3

Write a program that prompts the user with a yes/no question, such as “Can you paint with all the colors of the wind?” Create a program that accepts yyesn, or no using any casing as valid answers. If the answer is invalid, ask the user to re-enter their answer.

Character Coding with Unicode

All computer data (including text) is stored as binary sequences of 0s and 1s. The letter A for example is 01000001. The mapping between a character and its binary representation is called encoding.

Unicode is a universal encoding scheme that lets you encode more than a million characters from many languages. Each character is assigned a unique number (called a code point). For example, the code point for the character A is 65, and the code point for the dollar sign ($) is 36. TheGetCharacterCode() method returns a character’s code point. But the GetCharacter() method does the opposite; when you give it a character’s code point, it returns the corresponding character.

Run the program in Listing 18-3.

' CharCode.sb
2 str = "ABab12"
For N = 1 To Text.GetLength(str)
4   ch = Text.GetSubText(str, N, 1)         ' Gets the Nth character
5   code = Text.GetCharacterCode(ch)        ' Gets its code point
6   TextWindow.WriteLine(ch + ": " + code)  ' Displays ch and its code point
EndFor

Listing 18-3: Demonstrating the GetCharacterCode() method

Line 2 defines a string that contains six characters. Line 3 starts a For loop that accesses each of these characters; GetLength() sets the upper limit of the loop. Each iteration of the loop reads one character from the string and saves it in a variable named ch (line 4). Then the loop gets the Unicode code point for that character and saves it in the code variable (line 5). Line 6 displays the character and its code point. When you run this program, you’ll see the following output:

A: 65
B: 66
a: 97
b: 98
1: 49
2: 50

Fancy Characters

Let’s explore some characters not used in English. Listing 18-4 shows a simple program that displays the symbols for 140 Unicode characters, starting with the character whose code point is 9728. You can change this number to explore other Unicode symbols.

 1 ' UnicodeDemo.sb
 2 GraphicsWindow.BrushColor = "Black"
 3 GraphicsWindow.FontSize = 30  ' Makes the font larger
 4
 5 code = 9728           ' Code point for the first symbol
 6 xPos = 0              ' Horizontal position for drawing a symbol
 7 yPos = 0              ' Vertical position for drawing a symbol
 8 For row = 1 To 7      ' Draws 7 rows
 9   xPos = 0            ' For each new row, start at the left edge
10   For col = 1 To 20   ' 20 columns for each row
11     ch = Text.GetCharacter(code)              ' Gets a character
12     GraphicsWindow.DrawText(xPos, yPos, ch)   ' Draws it
13     code = code + 1                           ' Sets to next code point
14     xPos = xPos + 30                          ' Leaves a horizontal space
15   EndFor
16   yPos = yPos + 30                            ' Moves to the next row
17 EndFor

Listing 18-4: Demonstrating Unicode characters

The outer For loop runs seven times (line 8). Each time the outer loop runs, the inner loop displays 20 symbols that are placed 30 pixels apart (lines 10–15). After drawing a complete row of symbols, we move the vertical drawing position down 30 pixels to draw the next row (line 16).Figure 18-3 shows the output of this program.

image

Figure 18-3: The output of UnicodeDemo.sb

More on Code Points

The Unicode code points for lowercase letters are consecutive integers from 97 (a) to 122 (z). Similarly, the code points for uppercase letters range from 65 (A) to 90 (Z). The code point for a lowercase a is greater than the code point for an uppercase A, and the difference between the code points for a and A (97 – 65 = 32) is the same as the difference between the code points for b and B (98 – 66 = 32), and so on. When given the code point for a lowercase letter, which we’ll represent as ch, the code point for its corresponding uppercase letter is 65 + (ch – 97). Here’s the formula:

code for uppercase ch = code(A) + (code for lowercase ch – code(a))

Now that you know that each character in a string is identified by a code point, you can perform many useful operations on strings. The following examples show what you can do.

Displaying a Quotation Mark

Let’s say you want to display the string "Bazinga" with the double quotes included in the output. If you write TextWindow.WriteLine("Bazinga"), Small Basic displays Bazinga without the quotation marks because the quotation marks identify the start and end of a string. But Small Basic returns a syntax error if you write TextWindow.WriteLine(""Bazinga""). So how do you display the quotation marks? By using the quotation mark’s code point, you can append the quotation mark characters to the string, as shown in the following code snippet:

QUO = Text.GetCharacter(34)                 ' Gets the double quotation mark
TextWindow.WriteLine(QUO + "Bazinga" + QUO) ' Output: "Bazinga"

The first statement gets the quotation mark character from its Unicode code point (34) and assigns it to the variable QUO. The second statement inserts the string "Bazinga" between two QUO characters to output the desired result.

Creating a Multiline String

You can create a multiline string by embedding the line feed character (code point 10) into a string. Enter the following code snippet as an example:

LF = Text.GetCharacter(10)                     ' Code for line feed
TextWindow.WriteLine("Line1" + LF + "Line2")   ' Displays two lines

When you run this code, the two strings, "Line1" and "Line2", are displayed on two lines. The result is identical to what you get when you use the following two statements:

TextWindow.WriteLine("Line1")
TextWindow.WriteLine("Line2")

Armed with the knowledge you’ve gained so far, you’re ready to create full-sized programs that use strings in all kinds of fancy ways!

TRY IT OUT 18-4

The following program displays the letters of the English alphabet. Explain how the program works.

For code = 65 To 90
  ch = Text.GetCharacter(code)
  TextWindow.WriteLine(ch)
EndFor

Practical Examples with Strings

Earlier you learned how to use GetLength() to get a string’s length and GetSubText() to access individual characters in a string. When you use these two methods with a For loop, you can count special characters, examine multiple characters, and do several other useful tasks with strings. Let’s explore some examples!

Counting Special Characters

Listing 18-5 shows a program that counts the number of vowels in a string. It asks the user to enter a string and then counts and displays the number of vowels in that string.

 1 ' VowelCount.sb
 2 TextWindow.Write("Enter a sentence: ")  ' Prompts the user for text
 3 str = TextWindow.Read() ' Reads text entered by the user
 4
 5 count = 0               ' Sets vowel count to 0 (so far)
 6 For N = 1 To Text.GetLength(str)        ' Checks all characters
 7   ch = Text.GetSubText(str, N, 1)       ' Gets Nth character
 8   ch = Text.ConvertToUpperCase(ch)      ' Makes it uppercase
 9   If ((ch = "A") Or (ch = "E") Or (ch = "I") Or (ch = "O") Or (ch = "U")) Then
10     count = count + 1  ' If it finds a vowel, increments count
11   EndIf
12 EndFor
13 TextWindow.Write("Your sentence contains [") ' Shows result
14 TextWindow.WriteLine(count + "] vowels.")

Listing 18-5: Counting the number of vowels in a string

After getting the user’s input (lines 2–3), the program initializes the count variable to 0, because no vowels have been found so far (line 5). Then a loop starts to check the characters of the input string one by one (line 6). The loop counter N points to the Nth character of the string.

Line 7 gets the Nth character of the input string using GetSubText() and assigns it to the variable ch (short for character). The code then converts the character to uppercase (line 8) and compares that uppercase letter with the vowel characters (line 9). If the character is a vowel, countincreases by 1 (line 10). When the loop ends, the program displays the number of vowels counted (lines 13–14). Here’s a sample output from this program:

Enter a sentence: Small Basic is fun
Your sentence contains [5] vowels.

Enter a sentence: Giants leave nasty diapers.
Your sentence contains [9] vowels.

TRY IT OUT 18-5

Convert the code in Listing 18-5 into a game for two players. The first player enters a word, and the second player has to guess the number of vowels in the word. Then the players alternate turns. A player scores one point for each correct guess. End the game after 10 rounds and display the winner.

Palindrome Number Checker

In this section, we’ll write a program that checks whether an integer entered by a user is a palindrome. A palindrome is a number, word, or phrase that reads the same backward and forward. For example, 1234321 and 1122332211 are palindromes. Likewise, racecarHannah, and Bob are also palindromes.

Let’s look at the input number 12344321 shown in Figure 18-4.

image

Figure 18-4: Using two variables to check whether a number is a palindrome

To check whether this number is a palindrome, you need to compare the first and eighth digits, the second and seventh digits, the third and sixth digits, and so on. If any two digits in the comparison aren’t equal, the number isn’t a palindrome. As the figure illustrates, you can access the digits you want to compare by using two variables (pos1 and pos2), which move in opposite directions. The first variable (pos1) starts at the first digit and moves forward, and the second variable (pos2) starts at the last digit and moves backward. The number of required comparisons is at most one-half the number of digits in the input number. In this example, you need at most four comparisons because the input number has eight digits. The same logic applies if the input integer has an odd number of digits, because the digit in the middle of the number doesn’t need to be compared.

Listing 18-6 shows the complete program. The comments should help you understand how the program works.

 1 ' Palindrome.sb
 2 Again:
 3 TextWindow.WriteLine("")
 4 TextWindow.Write("Enter a number: ")
 5 ans = TextWindow.ReadNumber()          ' Saves user's input in ans
 6
 7 length = Text.GetLength(ans)           ' Number of digits of input number
 8 pos1 = 1                               ' Sets pos1 to read first digit
 9 pos2 = length                          ' Sets pos2 to read last digit
10 For N = 1 To (length / 2)              ' Performs (length/2) comparisons
11   ch1 = Text.GetSubText(ans, pos1, 1)  ' Reads digit at position pos1
12   ch2 = Text.GetSubText(ans, pos2, 1)  ' Reads digit at position pos2
13   If (ch1 <> ch2) Then                 ' If not equal, no need to continue
14     TextWindow.WriteLine(ans + " isn't a palindrome.")  ' Shows result
15     Goto Again
16   EndIf
17 EndFor
18
19 TextWindow.WriteLine(ans + " is a palindrome.")
20 Goto Again

Listing 18-6: Testing whether a number input by the user is a palindrome

Here’s a sample run of this program:

Enter a number: 1234321
1234321 is a palindrome.

Enter a number: 12345678
12345678 isn't a palindrome.

TRY IT OUT 18-6

Another way to create the program in Listing 18-6 is to reverse the input string and then compare the reversed string with the original. Create a new palindromechecker program using this method.

Igpay Atinlay

Let’s teach the computer a language game called pig latin. The rules for creating pig latin words are simple. To convert a word into pig latin, move the first letter to the end and add the letters ay after it. So, the word talk becomes alktayfun becomes unfay, and so on. Can you decipher the original title of this section?

Figure 18-5 shows the strategy you’ll use to convert a word into pig latin, using the word basic.

image

Figure 18-5: Translating an English word into pig latin

You first extract the substring from the second character to the end and assign it to the output string. You then add the first letter in the input string to the output, followed by ay. Enter the code in Listing 18-7 to implement these steps.

 1 ' PigLatin.sb
 2 TextWindow.Title = "Pig Latin"
 3
 4 While ("True")
 5   TextWindow.Write("Enter a word: ")
 6   word = TextWindow.Read()
 7
 8   pigLatin = Text.GetSubTextToEnd(word, 2)          ' Gets characters 2 to end
 9   pigLatin = pigLatin + Text.GetSubText(word, 1, 1) ' Appends first character
10   pigLatin = pigLatin + "ay"                        ' Appends "ay"
11   TextWindow.WriteLine(pigLatin)                    ' Displays the output
12   TextWindow.WriteLine("")
13 EndWhile

Listing 18-7: Converting a word entered by the user into pig latin

The program runs an infinite loop to allow the user to try different words (line 4). After reading the input word from the user (line 6), we extract the substring that starts at position 2 (that is, from the second character to the end of the input word) and assign it to pigLatin. Then we extract the first letter from word and append it to pigLatin (line 9), followed by ay (line 10). We display the pig latin word (line 11), followed by an empty line (line 12) and go for another round. Ongratulationscay! Ouyay inishedfay ouryay rogrampay!

TRY IT OUT 18-7

Write a program that takes a pig latin word as input and shows its original English word.

Fix My Spelling

Now we’ll develop a game that displays misspelled words and asks the player to enter the correct spelling. The game creates misspelled words by inserting a random letter at a random position in an English word. There could be more than one correct spelling of misspelled simple words. For example, if the game displays mwall, either mall or wall could be correct. To keep the game simple, we’ll ignore that possibility and insist on a particular spelling for the correct answer.

First, we select the word to be misspelled from a predefined array of words and save the selected word in a variable named strIn. We then pick a random character randChar to insert into strIn. The insertion position charPos is a random number between 1 and the length of strIn. Figure 18-6 shows the process of generating the misspelled word hewlp.

image

Figure 18-6: Illustrating the process of generating misspelled words

We first extract the substring from letter 1 to the letter at position charPos – 1 and assign it to strOut (because charPos is 3, this makes strOut = "he"). We then append randChar to strOut (this makes strOut = "hew"). We extract the substring from position charPos to the end ("lp" in this case) and append it to strOut (this makes strOut = "hewlp"). Listing 18-8 shows the complete program. Make sure you download and open FixMySpelling.sb from this chapter’s folder to get the full list of the words we wrote for this program.

 1 ' FixMySpelling.sb
 2 words = "1=mountain;2=valley;...;22=animation;" ' See file for full list
 3
 4 While ("True")      ' Runs forever
 5   strIn = words[Math.GetRandomNumber(Array.GetItemCount(words))]
 6   randChar = Text.GetCharacter(96 + Math.GetRandomNumber(26))
 7   charPos = Math.GetRandomNumber(Text.GetLength(strIn))
 8
 9   strOut = Text.GetSubText(strIn, 1, charPos - 1)
10   strOut = strOut + randChar
11   strOut = strOut + Text.GetSubTextToEnd(strIn, charPos)
12
13   TextWindow.Write("Enter correct spelling for [" + strOut + "]: ")
14   ans = TextWindow.Read()
15   ans = Text.ConvertToLowerCase(ans)
16   If (ans = strIn) Then
17     TextWindow.WriteLine("Good Job!")
18   Else
19     TextWindow.WriteLine("Incorrect. It is " + strIn + ".")
20   EndIf
21   TextWindow.WriteLine("")
22 EndWhile

Listing 18-8: Creating misspelled words and asking the player to fix them

The words array contains the words for this game (line 2). The program randomly picks a word from the words array and saves that word as strIn (line 5). Note how we used the array’s item count to set the upper limit of the random number. The program then selects a random letter,randChar, from the alphabet (line 6). It does that by getting a random number from 1 to 26 and adding 96 to it; this gives you a random number between 97 (the code point for letter a) and 122 (the code point for letter z). Next, the program picks a random position, charPos, in strIn (line 7): this is the position where the random character is inserted. Then the program creates the misspelled word and stores it in strOut (lines 9–11).

In line 13, the program asks the player to enter the correct spelling. It reads the user’s answer (line 14) and converts it to lowercase (line 15). It then compares the answer with the correct word (line 16). If the player’s answer matches the original word, the game displays Good Job! (line 17). Otherwise, the game displays an error message and shows the correct spelling (line 19). In both cases, the program ends by displaying an empty line (line 21), and the loop repeats to give the user a new misspelled word.

Here’s a sample run of this program:

Enter correct spelling for [mairror]: miror
Incorrect. It is mirror.

Enter correct spelling for [inteorface]: interface
Good Job!

TRY IT OUT 18-8

Update the program in Listing 18-8 so the misspelled word contains two additional random letters instead of just one random letter. Also, add more words to the list for more variety.

Unscramble

Now we’ll create a word scramble game. The program starts with an English word, scrambles the letters, displays the scrambled word to the player, and asks them to guess the original word.

Listing 18-9 shows the main part of the program. Open Unscramble.sb from this chapter’s folder for the full list of words.

 1 ' Unscramble.sb
 2 words = "1=mountain;2=valley;...;22=animation;" ' See file for full list
 3
 4 While ("True")
 5   strIn = words[Math.GetRandomNumber(Array.GetItemCount(words))]
 6   Scramble()  ' Returns strOut (a scrambled version of strIn)
 7
 8   TextWindow.Write("Unscramble [" + strOut + "]: ")
 9   ans = TextWindow.Read()
10   ans = Text.ConvertToLowerCase(ans)
11
12   If (ans = strIn) Then
13     TextWindow.WriteLine("Good Job!")
14   Else
15     TextWindow.WriteLine("No. It is " + strIn + ".")
16   EndIf
17   TextWindow.WriteLine("")
18 EndWhile

Listing 18-9: Scrambling words and asking the player to unscramble them

The words array contains the words for this game (line 2). The program randomly picks a word from this array and saves that word as strIn (line 5). It then makes a call to Scramble() to produce strOut, a scrambled version of strIn (line 6): we’ll add the Scramble() subroutine in a moment. Next, the program asks the player to unscramble strOut (line 8). It reads their answer (line 9) and converts it to lowercase (line 10). It then compares the player’s answer with the correct word (line 12). If the player’s answer matches the original word, the game displays Good Job!(line 13). Otherwise, the game displays the correct word (line 15). In both cases, the program ends by displaying an empty line (line 17) to separate the rounds and the loop repeats.

Now let’s look at the Scramble() subroutine, which shuffles the characters of a string into a random order. The caller sets the input string (strIn), and the subroutine returns a new string (strOut) that contains the characters of strIn shuffled around. Listing 18-10 shows this subroutine.

 1 Sub Scramble                                ' Scramble subroutine
 2   len = Text.GetLength(strIn)
 3   For N = 1 To len                          ' Loops up to length of word
 4     char[N] = Text.GetSubText(strIn, N, 1)  ' Saves each letter into an array
 5   EndFor
 6
 7   strout = ""                               ' Empties the output string
 8   While (Text.GetLength(strout)< len)
 9     pos = Math.GetRandomNumber(len)         ' Picks where to place the letter
10     If (char[pos] <> "") Then
11       strout = strout + char[pos]           ' Adds in the extra letter
12       char[pos] = ""                        ' Empties the element
13     EndIf
14   EndWhile
15 EndSub

Listing 18-10: Word-scrambling subroutine

The subroutine saves the length of the input string into len (line 2). It then uses a For loop to save the individual letters of strIn into an array named char (lines 3–5). It empties the output string, strOut, and starts a While loop to assemble strOut letter by letter (lines 7–14). The While loop runs until strOut has the same length as strIn (which means that we’ve added all the letters of strIn). Each iteration of the loop picks a random element from the char array (line 9). If that element is empty, we loop again to pick another one. Otherwise, we append the selected letter to strOut(line 11) and empty that element to indicate that we’ve used it (to prevent using it again) in line 12. Ouy fishendi eth egma!

Here’s a sample run of this program:

Unscramble [lalvey]: lovely
No. It is valley.

TRY IT OUT 18-9

Try to update the word-scrambling game using the skills you learned in previous chapters. Make the game last 10 rounds and then display the user’s score: how many words were unscrambled correctly out of the 10? Next, add 28 more words to unscramble so you have a total of 50. Then show the game to your friends and see who can get the best score!

Rhyme Time: The House That Jack Built

Let’s finish this chapter with a program that displays a popular British nursery rhyme and cumulative tale. In a cumulative tale, an action repeats and builds up as the tale progresses. Figure 18-7 shows this program in progress; more rhyme lines appear each time a user clicks the Next button.

image

Figure 18-7: The House That Jack Built rhyme

Examine this rhyme closely, and you’ll notice the common strings among the story pages. Study Figure 18-8 to understand how to create this rhyme by appending short strings at each stage.

image

Figure 18-8: The strings that make up the rhyme

For example, let’s trace the third row in this figure. Following the third arrow, you’ll get the following:

This is the Rat,
That ate

When you continue with the second arrow, you’ll get the following:

This is the Rat,
That ate the Malt,
That lay in

And, when you follow the first arrow, you’ll get the full rhyme that will appear on the third page:

This is the Rat,
That ate the Malt,
That lay in the House that Jack built.

Open the file JackHouse_Incomplete.sb from this chapter’s folder. The file contains the main program in Listing 18-11 and a placeholder for the OnButtonClicked() subroutine, which we’ll add in a moment. The folder also contains the 11 background images (Page1.pngPage2.png, ...,Page11.png) that we’ll display for each page of the rhyme.

 1 ' JackHouse.sb
 2 GraphicsWindow.Title = "The House That Jack Built"
 3 GraphicsWindow.CanResize = "False"
 4 GraphicsWindow.Width = 480
 5 GraphicsWindow.Height = 360
 6 GraphicsWindow.FontBold = "False"
 7 GraphicsWindow.FontSize = 20
 8 GraphicsWindow.FontName = "Times New Roman"
 9
10 LF = Text.GetCharacter(10)  ' Code for line feed
11
12 rhyme[1] = "the Farmer who sowed the corn," + LF + "That fed "
13 rhyme[2] = "the Cock that crowed in the morn," + LF + "That waked "
14 rhyme[3] = "the Priest all shaven and shorn," + LF + "That married "
15 rhyme[4] = "the Man all tattered and torn," + LF + "That kissed "
16 rhyme[5] = "the Maiden all forlorn," + LF + "That milked "
17 rhyme[6] = "the Cow with the crumpled horn," + LF + "That tossed "
18 rhyme[7] = "the Dog," + LF + "That worried "
19 rhyme[8] = "the Cat," + LF + "That killed "
20 rhyme[9] = "the Rat," + LF + "That ate "
21 rhyme[10] = "the Malt," + LF + "That lay in "
22 rhyme[11] = "the House that Jack built."
23
24 Controls.AddButton("Next", 420, 320)
25 Controls.ButtonClicked = OnButtonClicked
26 nextLine = 11
27 OnButtonClicked()

Listing 18-11: The main part of the House That Jack Built program

Lines 2–8 set up the GraphicsWindow object. Line 10 defines the line feed character (for appending new lines to the strings). Lines 12–22 define the rhyme array, which contains the strings for this rhyme. Note how the elements of this array relate to the boxes in Figure 18-8. Line 24 creates the Next button, and line 25 registers the handler for the ButtonClicked event. Then the nextLine variable is set to 11 to point to the 11th element of the rhyme array, which is the first page of the story (line 26), and OnButtonClicked() is called to show the first page of the rhyme (line 27).

Now we’ll add the OnButtonClicked() subroutine in Listing 18-12. This subroutine is called when the user clicks the Next button.

 1 Sub OnButtonClicked
 2   img = Program.Directory + "\Page" + (12 - nextLine) + ".png"
 3   GraphicsWindow.DrawImage(img, 0, 0)
 4
 5   strOut = "This is "
 6   For N = nextLine To 11
 7     strOut = Text.Append(strOut, rhyme[N])
 8   EndFor
 9   GraphicsWindow.DrawText(10, 10, strOut)
10
11   nextLine = nextLine - 1
12   If (nextLine = 0) Then
13     nextLine = 11
14   EndIf
15 EndSub

Listing 18-12: The OnButtonClicked() subroutine

Line 2 fills img with the name of the image for the current page of the rhyme. When nextLine is 11, we’ll show Page1.png (which is 12 minus 11). When nextLine is 10, we’ll show Page2.png (12 minus 10), and when nextLine is 9, we’ll show Page3.png (12 minus 9), and so on. Line 3 draws the image on the graphics window. We then build up the output string (lines 5–8). We set strOut to "This is " (line 5) and then start a loop that goes from nextLine to 11 (lines 6–8). When nextLine is 11, the loop runs one time and appends rhyme[11] to strOut. When nextLine is 10, the loop runs from 10 to 11 and appends rhyme[10] and then rhyme[11] to strOut. Similarly, when nextLine is 9, the loop runs from 9 to 11 and appends rhyme[9], rhyme[10], and then rhyme[11] to strOut.

When the loop ends, strOut contains the entire string for the rhyme at this stage of the story. We display this string using DrawText() in line 9.

Then we decrease nextLine by 1 to point to the previous element in the rhyme array (line 11). If nextLine becomes 0 (line 12), the story is done, so we set it back to 11 to start over (line 13). As a result, when the user clicks the Next button at the last page of the story, the program goes back to displaying the first page. We’ve finished the tale before it got stale!

TRY IT OUT 18-10

Use the techniques you learned in the House That Jack Built example to write a program that tells your favorite story. Don’t have one? Make one about an alien rat trapped in a tower with nothing but JELL-O, a slingshot, and an advanced chemistry set. Explain how the rat got there and how it escapes!

Programming Challenges

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.    Open the file Shoot_Incomplete.sb from this chapter’s folder. Run the program to see the following interface.

image

The goal of this game is to estimate the turn angle and moving distance between the turtle and the target. When a player enters their input, it is saved in a variable named strIn. Your task is to split strIn into two parts: assign the substring before the comma to angle, and assign the substring after the comma to dist. The comments in the file tell you where to add your code. If you get stuck, see the file Shoot.sb, which contains the completed program.

2.    Open the file BinaryToDecimal_Incomplete.sb from this chapter’s folder. This program converts binary numbers to decimal numbers and then asks the user to input an 8-bit binary number. It then shows the input number in the graphics window, computes its decimal number, and displays the result of the conversion, as shown in the following figure.

image

Complete the GetInput() subroutine, which prompts the user to enter an 8-bit binary number. You need to verify that the user’s input isn’t empty and has at most eight binary digits (so it contains only 1s and 0s). When the user enters a valid input, save it in strIn and return from the subroutine. The comments in the file tell you what to do. If you get stuck, see the file BinaryToDecimal.sb, which contains the completed code.