In previous chapters, you learned how to create an assortment of different computer games. In most of these games, you more or less accepted whatever input the player provided, with little or no validation. In this chapter, this changes because you will learn how to use regular expressions. Using regular expressions, you can perform a detailed analysis of user input to determine if it meets criteria you specify. Using regular expressions, you will also be able to pick apart and process the contents of data regardless of its source, including text files and databases. You will also learn how to perform text-substitution operations and to deal with differences in case. On top of all this, this chapter will show how to create a new Ruby script, the Word Guessing game.
Specifically, you will learn how to:
Set up basic regular expression patterns
Set up regular expression patterns to match groups of characters
Set up regular expression patterns to match individual or multiple instances of characters
Use metacharacters when setting up regular expression patterns
Perform text substitution and to ignore differences in case
In this chapter, you will learn how to create a new computer script called the Word Guessing game. This game challenges the player to try to guess a secret word in three or fewer guesses. Before making any guesses, the player is allowed to specify five consonants and one vowel, which, if present in the word, are revealed, providing the player with a clue as to what the word might be.
The Word Guessing game begins by displaying the message shown in Figure 7.1, welcoming the player to the game.
The player is then prompted for permission to start a round of play, as demonstrated in Figure 7.2.
Before the first round of play begins, instructions for playing the game are presented as shown in Figure 7.3.
Next, the player is asked to specify five consonants, as demonstrated in Figure 7.4.
Figure 7.4. To make things easier, the player gets to provide a list of consonants that will be revealed if they are part of the secret word.
Consonants are collected one at a time, as shown in Figure 7.5. If the player attempts to enter a vowel, number, special character, or more than one character at a time, her input is rejected, and she will be prompted to try again.
Figure 7.5. Using regular expressions, the player’s input is carefully evaluated before being accepted.
Once all five consonants have been collected, the player is prompted to specify a vowel. The game then displays a clue to its secret word and challenges the player to try to guess it, as demonstrated in Figure 7.6.
Figure 7.7 shows the message that is displayed if the player guesses the secret word.
If the player’s first guess is incorrect, two more chances are given to guess the secret word. Figure 7.8 shows the message that is presented before the player is permitted to make a final guess.
If the player is unable to guess the game’s secret word, the screen shown in Figure 7.9 is displayed letting the user know that the current round of play is over and revealing the secret word to the player.
At the end of each round of play, the game prompts the player for permission to start another round. Depending on the player’s response, the game is either terminated or a new secret word is selected, and play begins again.
Data processed in computer scripts can come from many different sources, including databases, files, as input passed from other scripts and programs, and from the users, as is the case with computer games. While you can certainly trust in the source of the data to provide your scripts with valid data, you often do so at your own peril. In most cases, you will want to build data-validation logic into your own scripts, rejecting any data that is not in the proper format.
You can perform a certain amount of data validation using conditional logic and methods belonging to Ruby classes. However, to create really granular data-validation routines, you will need to take things a step further and use regular expressions. A regular expression is a pattern used to identify matching character data.
In previous chapter projects, you performed limited amounts of data validation. In these scripts, you were able to simplify data validation by restricting valid data to very limited and specific sets of characters, rejecting any data provided by the player that did not match the script’s precise requirements. For example, in the Superman Movie Trivia Quiz, you rejected as invalid any player input that was not an a, b, c, or d when processing quiz questions, as shown here:
#Analyze the player to determine if it was valid if reply == "a" or reply == "b" or reply == "c" or reply == "d" then break #Terminate the execution of the loop end
When processing this type of restricted input, you can use basic conditional logic to process the player input. The following statements, taken from the Ruby Number Guessing game, provide another example of how input data validation was implemented.
#Validate the player's input only allowing guesses from 1 to 100 if reply < 1 or reply > 100 then redo #Redo the current iteration of the loop end
Here, numeric data is validated using conditional logic, and any value that is below 1 or greater than 100 is rejected. This type of data validation worked well in this example because the input allowed by the script was restricted to a very specific range (1 to 100). Things become a lot trickier when you begin writing scripts that can accept and process many different types of input. For example, this chapter’s game project is the Word Guessing game. This game will accept any alphabetic characters as input. Given the number of letters in the alphabet, it is not practical to use either of the two previous data-validation examples as models for solving this problem. Instead, as you will see, this script will validate incoming data using several different validation techniques, including regular expressions.
Regular expressions are not just limited to data validation. Regular expressions can also be used to search for data within strings and to perform string substitutions. For example, using regular expressions, you can define patterns that:
Perform search and replace operations on strings
Keep counts of instances of patterns
Extract a substring from a larger string
In most cases, regular expression patterns are processed from left to right. Matches occur when patterns are found anywhere within a source string. By default, matches are case sensitive, although you can override this behavior. Also, every character inside a pattern is taken literally, with the exception of metacharacters, to which regular expressions assign a special meaning.
The most basic type of regular expression is one that matches up against a specific pattern or a set of characters. This type of pattern can be set up using the //
match operator, which has the following syntax.
/pattern/
For example, take a look at the following pattern.
/USA/
This pattern will match the occurrence of the characters USA
if found anywhere in a string. If found, a value of true
is returned. Otherwise, a value of false
is returned. To see how this pattern might be used, take a look at the following set of statements.
if "Welcome to New York Harbor, USA." =~ /USA/ then puts "Welcome to America!" end
Here, the source string “Welcome to New York Harbor, USA.
” is searched using a pattern of /USA/
. Since the string contains the words USA
, a match occurs and the following output is displayed when the statements are executed.
"Welcome to America!"
Take note of the use of the =~
operator, which is the equivalent of the equals operation in a regular expression.
In addition to matching basic patterns as discussed in the previous section, you can set up regular expression patterns that can look for any of a possible set of matches. To set up this type of pattern, you need to use the |
character to separate each possible match, as demonstrated here:
if "Welcome the USA!." =~ /USA|America/ then puts "We have a match!" end
Here, a search pattern has been set up that looks for either the string USA
or the string America
. If either string is found in the source string, the match is successful.
Using this alternate pattern-matching approach, you can set up a pattern that searches for as many different alternate patterns as you want, as demonstrated here:
if "Remember to call your mother." =~ /tall|call|wall|ball|fall|mall/ then puts "We have a match!" end
As you can see, alternate pattern matches can be very convenient. However, they can quickly grow a little long-winded. To make things easier, you can shorten things up using parentheses to separate unique parts of the search pattern from common parts, as demonstrated here:
if "Remember to call your mother." =~ /(t|c|w|b|f|m)all/ then puts "We have a match!" end
Here, /(t|c|w|b|f|m)all/
is just a shorthand way of writing /tall|call|wall|ball|fall|mall/
.
Normally, any character that you include in a regular expression pattern will match the same character if found in the source string. However, metacharacters are an exception to this rule. A metacharacter is a character that alters the way a pattern match occurs, as demonstrated in the following example.
if "My name is Jerry. My father's name is Mr. Ford." =~ /Mr./ then print "We have a match!" end
Here, the source string is searched using the pattern /Mr./
. The result is a match. However, the match has occurred for a reason that may not be immediately obvious. On the surface, it appears that the pattern matched because the string Mr.
is equal to the string Mr.
in the source string. However, something different is actually happening here. Specifically, when used in a regular expression, the .
character is viewed as a metacharacter. Metacharacters have a special meaning. In particular, the .
metacharacter is used to match up against any individual character. As a result, a pattern of /Mr./
results in a match with Mr.
, Mrs
, and Mrx
, etc., which is clearly not what was intended in the previous example.
While metacharacters can be extremely useful, sometimes they can get in the way. In these situations, you can escape the metacharacter by preceding it with a character. When escaped, the metacharacter is taken literally. Therefore, using the
escape character, you can force a regular expression to treat the . character like a period and not like a metacharacter, as demonstrated here:
if "My name is Jerry. My father's name is Mr. Ford." =~ /Mr./ then print "We have a match!" end
Table 7.1 lists a number of metacharacters that you will want to become familiar with.
Table 7.1. Regular Expression Metacharacters
Character | Description |
---|---|
. | Matches any character |
^ | Looks for a match at the beginning of a line |
$ | Looks for a match at the end of a line |
A | Looks for a match at the beginning of a string |
Looks for a match at the end of a string | |
d | Matches any numeric character |
w | Matches any alphabetic, numeric, or underscore character |
D | Matches any non-numeric character |
W | Matches any non-alphabetic, non-numeric, or non-underscore character |
s | Matches white space |
S | Matches non-white space |
You will learn more about how to work with metacharacters as you read through the rest of this chapter.
As you just learned, you can use the .
metacharacter as part of a pattern to match any individual character (except for the new line character). By using multiple instances of the .
metacharacter, you can create patterns that match more than one character, as demonstrated here:
if "My name is Jerry. My father's name is Mr. Ford." =~ /f...er/ then print "We have a match!" end
Here, a pattern has been set up to match the lowercase letter f
followed by any two characters and the letters er
.
By default, a regular expression looks anywhere within a string for a match. However, using the ^
metacharacter, you can create regular expression patterns that only look at the beginning of a line for a match. For example, a pattern of /^My name/
will result in a match only if it is found at the beginning of the source, as demonstrated here:
if "My name is Jerry. My father's name is Mr. Ford." =~ /^My name/ then print "We have a match!" end
The $ metacharacter is the opposite of the ^
metacharacter, performing a match only if the pattern is found at the end of the line, as demonstrated here:
if "My name is Jerry. My father's name is Mr. Ford." =~ /Ford.$/ then print "We have a match!" end
There may be some situations in which you need to look for a character or a group of characters that occur either once or not at all, with either possibility resulting in a match. You can set this up using the ?
metacharacter modifier, which matches zero or none of the preceding characters, as demonstrated here:
if "My name is Jerry. My father's name is Mr. Ford." =~ /Mrs?/ then print "We have a match!" end
Here, a pattern of /Mrs?/
has been specified. This pattern will match a string of Mrs
, and if the s
is not present, it will also match Mr
. You can use the ?
metacharacter modifier on groups of characters just as easily, as shown here:
if "My father's name is Mr. Ford." =~ /father('s name)?/ then print "We have a match!" end
As you can see, the trick here is to use parentheses to enclose the group of characters. In this example, a match will occur if either father or father's name
is found in the source string.
Another metacharacter modifier that you need to know about is the *
character. This character is similar to the . metacharacter except that the *
metacharacter matches zero or more instances of the preceding character. For example, a pattern of /f*r/
would match any of the following set of characters:
father
friendlier
for
r
Another type of pattern that you may need to work with is one that searches for a range of characters. This can be set up using character classes, which are enclosed inside a matching pair of square brackets ([]
). Any characters placed inside the []
characters are regarded as a single character. Ruby’s support for regular expressions includes support for the character class patterns shown in Figure 7.2.
Table 7.2. Character Class Patterns
Pattern | Description |
---|---|
| Matches any specified lowercase letter (a, b, c) |
| Matches any lowercase letter |
| Matches any number between 0 and 9 |
| Shorthand option for matching any number between 0 and 9 |
| Shorthand option for matching any of the lowercase letters |
| Shorthand option for matching any of the uppercase letters |
To see an example of how to work with character class patterns, look at the following example.
print "Please enter a vowel and press Enter: " input = STDIN.gets input.chop! if input =~ /[aeiou]/ then puts "A vowel has been submitted." end
Here, the user is prompted to enter a vowel (i.e., a, e, i, o, or u). A conditional check is then performed using a regular expression of /[aeiou]/
. As a result, a match occurs if the user enters a response that includes at least one vowel.
Ruby’s support for regular expressions is quite extensive. As a result, there is a lot more that you can do with regular expressions than what has been discussed so far in this chapter. For example, using regular expressions, you can perform case-insensitive pattern searches. In addition, you can also perform complex string substitutions, where one or more instances of a search pattern are used to replace characters in a source string.
Regular expressions represent an enormous topic. So much so that entire books have been dedicated to this one topic. If you are interested in learning more about regular expressions, check out Mastering Regular Expressions, Second Edition (ISBN: 0596002890). You might also want to visit http://en.wikipedia.org/wiki/Regular_expression.
So far, all of the regular expression patterns that you have used have worked because the case used inside the pattern matched the case used in the source string. It won’t always work out this well. To eliminate case as an issue, you can disable case-sensitivity using the optional i
modifier, as demonstrated here:
if "Welcome to New York Harbor, USA." =~ /usa/i then puts "Welcome to America!" end
When used, the i
modifier allows the pattern to match characters in the source string, regardless of the case that is in use. So this example results in a match, even though lowercase characters have been specified in the pattern and uppercase characters are used in the source string. If you were to remove the i
modifier from the above example, a case-sensitive match would be attempted and the result would be no match.
In addition to finding and validating string contents, you can modify strings using character substitution with the String
class’s sub
method. This method accepts as an argument a search pattern, which can be any regular expression pattern or string, and a replacement pattern. The sub
method has the following syntax.
string.sub(search, replace)
This method searches string
and substitutes the first instance of search
that it finds with replace
. To get a feel for how this method works, take a look at the following example.
x = "Once upon a time there was a small boy who climbed a small tree." puts x.sub("small", "big")
Here, a string is assigned to a variable named x
. Next, the sub
method is called and passed "small"
as the search pattern and "big"
as the replacement pattern. As a result, the following output is displayed when these statements are executed.
Once upon a time there was a big boy who climbed a small tree.
Here, the first instance of the word small
has been replaced with the word big
. However, the source string contained two instances of the word small
and the second instance was not replaced. If you want to replace all instances of the search pattern, you need to work with the String
class’s gsub
method instead. Like the sub
method, the gsub method accepts as an argument a search pattern, which can be any regular expression pattern or string, and a replacement pattern. Using gsub
, you could rewrite the previous example as shown here:
x = "Once upon a time there was a small boy who climbed a small tree." puts x.gsub("small", "big")
When executed, this example displays the following output.
Once upon a time there was a big boy who climbed a big tree.
All right, that’s enough about regular expressions for now. It is time to turn your attention back to the creation of this chapter’s game project, the Word Guessing game. Remember to follow along carefully, watch for typos, and not omit any steps.
The development of the Word Guessing game will be completed in 15 steps as outlined here:
Open your text or script editor and create a new file.
Add comment statements to the beginning of the script file to document the script and its purpose.
Define a class representing the terminal window.
Define a class representing the Number Guessing game.
Add a display_greeting
method to the Game
class.
Add a display_instructions
method to the Game
class.
Add a select_word
method to the Game
class.
Add a get_consonants
method to the Game
class.
Add a get_vowel
method to the Game
class.
Add a prompt_for_guess
method to the Game
class.
Add a play_game
method to the Game
class.
Add a display_credits
method to the Game
class.
Instantiate script objects.
Prompt the player for permission to begin the game.
Set up the game’s controlling logic.
The first step in developing the Word Guessing game is to start your preferred text or code editor, create a new Ruby script file, and save the script file with a name of WordGuess.rb.
Now let’s add a few comment statements to the beginning of the script file to provide a high-level overview of the game and to explain what it does. To do this, add the following statements to the end of the script file.
#-------------------------------------------------------------------------- # # Script Name: WordGuess.rb # Version: 1.0 # Author: Jerry Lee Ford, Jr. # Date: October 2007 # # Description: This Ruby script demonstrates how to work with regular # expressions through the development of a computer game # that challenges the player to guess a mystery word after # being first allowed to guess 5 consonants and 1 vowel. # #--------------------------------------------------------------------------
The Word Guessing game will utilize two custom classes that will provide it with a collection of methods required to control user interaction and the execution of the game. The code statements for the script’s first custom class, the Screen
class, are shown next and should be added to the end of the script file.
# Define custom classes --------------------------------------------------- #Define a class representing the console window class Screen def cls #Define a method that clears the display area puts (" " * 25) #Scroll the screen 25 times puts "a" #Make a little noise to get the player's attention end def pause #Define a method that pauses the display area STDIN.gets #Execute the STDIN class's gets method to pause script #execution until the player presses the Enter key end end
The Screen class contains two methods. The cls
method writes 25 blank lines to the console window to clear the screen and then makes an audible beep sound. The pause
method uses the STDIN
class’s gets
method to pause script execution until the player presses the Enter key.
The code statements that make up the Game
class are shown next and should be added to the end of the script file. This class contains eight methods that are needed to control the game’s execution. To begin the creation of the Game
class, add the following statements to the end of the script file.
#Define a class representing the Word Guessing Game class Game end
The first of the methods defined in the Game
class is display_greeting
and as the name implies, it is responsible for displaying the game’s welcome message. The statements that make up this method are shown next and should be inserted between the game class’s opening and closing statements.
#This method displays the game's opening message def display_greeting Console_Screen.cls #Clear the display area #Display welcome message print " Welcome to the Word Guessing Game!" + " Press Enter to " + "continue." Console_Screen.pause #Pause the game end
The next method to be added to the Game
class is the display_instructions
method. The script statements for this method are shown next and should be added to the class definition, immediately after the previously defined method.
#Define a method to be used to present game instructions def display_instructions Console_Screen.cls #Clear the display area puts "INSTRUCTIONS: " #Display a heading #Display the game's instructions puts "At the start of each new round of play, the game will randomly" puts "select a word that is between 5 and 10 characters long" puts "and challenge you to guess it. Before submitting your guess, you" puts "are allowed to provide the game with 5 consonants and 1 vowel to" puts "determine if they are used in the secret word. " puts "Good luck! " print "Press Enter to continue." Console_Screen.pause #Pause the game end
This method displays a series of text strings that provide the player with instructions for playing the game.
The next method to be added to the Game
class is the select_word
method. This method’s script statements are shown next and should be added to the end of the class definition, immediately after the display_instructions
method.
#Define a method that generates the secret word def select_word #Define an array of 20 words from which the game will randomly select words = ["W I N D O W", "S T A T I O N", "H A M B U R G E R", "E X P R E S S I O N", "W A L L E T", "C A M E R A", "A I R P L A N E", "C A N D L E", "C O M P U T E R", "P I C T U R E", "F R A M E", "S H E L F", "B O W L I N G", "P O L I T E", "S T A T E M E N T", "N E G A T I V E", "M E T H O D", "F I S H I N G", "C O M P E N S A T E", "H A P P Y"] #Generate and return a random number between 0 and 19 randomNo = rand(19) #Return a randomly selected word to the calling statement return words[randomNo] end
This method begins by defining an array named words
and populates the array with 20 text strings representing game words. Spaces have been added between each letter in each word to facilitate the eventual splitting up of the characters that make up each word into an array later in the script file.
Next, a random number between 0 and 19 is generated. The final statement in the method returns a word extracted from the words
array back to the statement that called upon the method to execute.
The code statements for the get_consonants
method are shown next and should be added to the end of the class definition, immediately after the select_word
method. This method is responsible for prompting the player to provide a list of five consonants, which the game will use to disclose matching letters in its secret word prior to prompting the player to try to guess it.
#Define a method that collects the player's consonant guesses def get_consonants list = Array.new #define an array in which to store the consonants #Give the player an idea of what is coming puts "Before you try to guess the secret word, you must specify " + "5 consonants. " print "Press Enter to continue." Console_Screen.pause #Pause the game 5.times do #Iterate 5 times Console_Screen.cls #Clear the display area #Prompt the player to enter a consonant print " Please enter a consonant and press Enter: " input = STDIN.gets #Collect the player's input input.chop! #Remove the end of line marker #Only accept consonant characters if input !~ /[bcdfghjklmnpqrstvwxyz]/i then Console_Screen.cls #Clear the display area print "Error: " + input + " is not a consonant. Press Enter to " + "continue." Console_Screen.pause #Pause the game redo #Repeat the current execution of the loop end #Only accept one character of input per guess if input.length > 1 then # !~ /./ then Console_Screen.cls #Clear the display area print "Error: You may only enter one character at a time. Press " + "Enter to continue." Console_Screen.pause #Pause the game redo #Repeat the current execution of the loop end #Do not allow the player to submit the same guess twice if list.include?(input.upcase) == true then Console_Screen.cls #Clear the display area print "Error: You have already guessed " + input + ". Press " + "Enter to continue." Console_Screen.pause #Pause the game redo #Repeat the current execution of the loop else list.push(input.upcase) #Convert the consonant to uppercase and end #add it to the list of consonants end return list #Return the list of consonants to the calling statement end
This method begins by defining a new array named list
, which will be used to store a list of consonants supplied by the player. Next, a message is displayed that explains to the player that she is about to be prompted to provide five consonants. A loop is then set up that executes five times (once for each consonant that is collected).
Within the loop, the player is prompted to enter a constant. The user’s input is then validated in a series of three conditional checks. The first conditional check sets up a regular expression that performs a case-insensitive comparison of the player’s input against a list of all the consonants in the alphabet. The player’s input is rejected if it does not match one of the consonants listed in the regular expression.
The second conditional check uses the String
class’s length method to determine whether the player entered more than one character and rejects the input if she did. The third conditional check uses the String
class’s include?
method to determine if the player’s input (converted to uppercase) has already been submitted (e.g., has been added to the list
array) and rejects it if this is the case. However, if the player’s input is valid at the end of the third conditional check, it is added to the end of the list
array using the push
method (after being converted to all uppercase).
There are two important points to note in the previous set of conditional checks. First, any time one of the conditional checks rejects the player’s input, the redo
command is executed. As a result, the current execution of the loop runs again, preventing the loop from iterating. Second, only valid input that has been converted to uppercase is added to the list
array. The contents of this array will be used later in the script to disclose any matching letters in the game’s secret word, thus helping the player to figure it out.
The last statement in the get_consonants
method returns a copy of the contents of the list
array to the statement that invoked the method.
The code statements for the get_vowel
method are shown next and should be added to the end of the class definition, immediately after the get_consonants
method. This method is responsible for prompting the player to provide a vowel, which the game will use to disclose matching letters in its secret word prior to prompting the player to try to guess it.
#Define a method that collects the player's vowel guess def get_vowel #Give the player an idea of what is coming puts "Before you try to guess the secret word, you must specify " + "1 vowel. " 1.times do #Iterate 1 time Console_Screen.cls #Clear the display area #Prompt the player to enter a vowel print " Please enter a vowel and press Enter: " input = STDIN.gets #Collect the player's input input.chop! #Remove the end of line marker #Only accept vowel characters if input !~ /[aeiou]/i then Console_Screen.cls #Clear the display area print "Error: " + input + " is not a vowel. Press Enter to " + "continue." Console_Screen.pause #Pause the game redo #Repeat the current execution of the loop end #Only accept one character of input per guess if input.length > 1 then # !~ /./ then Console_Screen.cls #Clear the display area print "Error: You may only enter one character at a time. Press " + "Enter to continue." Console_Screen.pause #Pause the game redo end input = input.upcase #Convert the vowel to uppercase return input #Return the vowel to the calling statement end end
As you can see, the program statements that make up this method are very similar to those in the previous method, except only two conditional validation checks are performed on the user’s input and the user’s input is returned to the calling statement as a individual value and not as a list.
The code statements for the prompt_for_guess
method are shown next and should be added to the end of the class definition, immediately after the get_vowel
method. This method is responsible for formatting the display of the secret word and then managing the game’s interaction with the player as the player attempts to guess the word.
#Define a method that collects player guesses def prompt_for_guess(shortWord, word, consonants, vowel) Console_Screen.cls #Clear the display area consonants.push(vowel) #To make things easy, add the vowel to the #list of consonants wordArray = word.split(" ") #Split the secret word into an array i = 0 #Initial the variable with a starting value of zero #Loop once for each letter in the word (stored in an array) wordArray.each do |letter| match = false #Initial the variable with a starting value of false #Loop once for each consonant stored in the consonants array consonants.each do |character| #Compare the current character from the consonants array to the #current letters in the wordArray array if character == letter then match = true #Set variable value to indicate a match break #Terminate loop execution when a match occurs end end #If there is no matching character in the consonants array for the #current letter in the wordArray array, replace that letter in the #wordArray with an underscore character if match == false then wordArray[i] = "_" #Replace the current character with an end #underscore match = false #Reset the value of the match variable i = i + 1 #Increment the variable's value by 1 end #Once the contents of the array have been formatted with underscores, #convert the contents of the array back into a word word = wordArray.join(" ") #Allow the player up to three guesses 3.times do |i| #i equals 0 on the first iteration of the loop Console_Screen.cls #Clear the display area #Prompt the player to try to guess the secret word puts "I am thinking of a word. " print "Here is your clue: " + word + " " print "What do you think this word is? " reply = STDIN.gets #Collect the player's reply reply.chop! #Remove the end of line marker reply = reply.upcase #Convert the reply to all uppercase #Analyze the player's guess if reply == shortWord then #The player guessed the secret word Console_Screen.cls #Clear the display area print "Correct! Press Enter to continue." Console_Screen.pause #Pause the game break #Terminate the execution of the loop else #The player did not guess the secret word Console_Screen.cls #Clear the display area #Display a message based on how many turns remain if i == 1 then print "Wrong! You have one guess left. Press Enter to " + "try again." elsif i == 2 print "Sorry, you lose. " print "The word was " + shortWord + ". Press Enter to continue." else print "Wrong! Press Enter to try again." end Console_Screen.pause #Pause the game end end end
The prompt_for_guess
method processes four arguments. shortWord
is a copy of the game’s secret word with no spaces in it. word
is a copy of the game’s secret word with spaces inserted between each letter. consonants
is a list of five consonants previously supplied by the player, and vowel
is a string representing the vowel specified by the player when the get_vowel
method was executed.
The prompt_for_guess
method begins by adding vowel
to the consonants
array. This is done to simplify things by grouping all of the player’s input into a single array, making the data easy to process. Next a new array named wordArray
is created and assigned a list of letters extracted from word
.
In order to extract each letter from the string stored in word and assign it as an item in the wordArray
array, the String
class’s split
method was used. This method splits the contents of string
into an array using a specified delimiter. In the case of the word
variable, the delimiter was the blank space located between each letter.
Next, a value of zero is assigned to a variable named i
. This variable will be used later in the method to keep track of which item (letter) is being examined in the wordArray
array when it is being processed by a loop and compared to each of the six letters provided by the player (five consonants and a vowel).
A loop is then set up that loops though each item stored in the wordArray
array. Within this loop a second loop has been set up that loops through each item stored in the consonants array. The inner loop compares the currently selected item (letter) from the wordArray
array against each of the items (letters) in the consonants
array. If a match is found, the inner loop is terminated using the break
command and a value of true
is assigned to a variable named match
.
Next, a conditional statement is executed that replaces the current character in the wordArray
array with an underscore if no match was found in the consonants
array. The value of match
is then set back to false
and the value of i
is incremented by one. Once every item in wordArray
has been compared to every item in consonants
, the contents of wordArray
are converted back into a string again using the Array
class’s join
method.
The join
method takes as an argument a delimiter that is then used to pad array items to create a string. In the previous statement, the join
method is passed a single blank space.
Next, a loop is set up that provides the player with three chances to guess the game’s secret word. Each time the loop iterates, it displays a copy of the secret word. Any letters in the word that match up against the consonants and vowel that the player specified are revealed and all other letters are hidden (represented by underscore characters). The player is then prompted to guess the secret word.
The player’s guess is converted to all uppercase characters and compared against the value of shortWord
to see if there is a match, in which case the player has successfully guessed the word. If this happens, the break
command is executed, terminating the execution of the loop. Otherwise, the player is informed of her error and given another chance to make a guess. After using up all three chances without correctly guessing the secret word, the loop terminates.
The next method to be added to the Game
class is the play_game
method. The statements belonging to this method are shown next and should be added to the end of the class definition, immediately after the prompt_for_guess
method.
#Define a method to control game play def play_game word = select_word #Call on the method that retrieves a random word Console_Screen.cls #Clear the display area consonants = get_consonants #Call on the method that prompts the player #to enter a list of consonants Console_Screen.cls #Clear the display area #Call on the method that prompts the player to enter a vowel vowel = get_vowel #Remove blank spaces from the word to create a short version of the word shortWord = word.gsub(" ", "") #Call the method that processes player guesses prompt_for_guess(shortWord, word, consonants, vowel) Console_Screen.cls #Clear the display area end
This method begins by calling on the select_word
method to retrieve a word for the player to guess. Next, the get_consonants
method is called to retrieve a list of five consonants, and the get_vowel
method is called to prompt the player to identify a vowel. Next, the String
class’s gsub
method is used to generate a short version of the secret word (without spaces) and the prompt_for_guess
method is executed. This method is passed shortWord
, word
, consonants
, and vowel
as arguments and uses these objects to prompt the player to try to guess the secret word.
The last method to be added to the Game
class is the display_credits
method. This method displays the game’s credits, including the author’s URL. The statements that make up this method are shown next and should be appended to the end of the Game
class.
#This method displays the information about the Word Guessing game def display_credits Console_Screen.cls #Clear the display area #Thank the player and display game information puts " Thank you for playing the Word Guessing Game. " puts " Developed by Jerry Lee Ford, Jr. " puts " Copyright 2008 " puts " URL: http://www.tech-publishing.com " end
Now it is time to initialize instances of the Screen
and the Game
classes. This is done by appending the following statements to the end of the script file.
# Main Script Logic ------------------------------------------------------- Console_Screen = Screen.new #Instantiate a new Screen object WordGuess = Game.new #Instantiate a new Game object #Execute the Game class's display_greeting method WordGuess.display_greeting answer = "" #Initialize variable and assign it an empty string
In addition to instantiating the Console_Screen
and SQ
objects, these statements define a variable named answer
, which will be used to control the execution of the loop that prompts the player for permission to begin a new round of play.
The script statements responsible for prompting the player for permission to start a new round of play are outlined here:
#Loop until the player enters y or n and do not accept any other input loop do Console_Screen.cls #Clear the display area #Prompt the player for permission to start the quiz print "Are you ready to play the Word Guessing Game? (y/n): " answer = STDIN.gets #Collect the player's answer answer.chop! #Remove any extra characters appended to the string #Terminate the loop if valid input was provided break if answer =~ /y|n/i end
These statements, which should be added to the end of the script file, are controlled by a loop that has been set up to run forever. Each time the loop iterates, the player is prompted to enter a value of y or n, to tell the game whether a new round of play should be initiated or the game should be terminated. Any input other than a y or n is ignored. As soon as valid input has been provided, the break
command is executed and the loop terminates, allowing the rest of the script to execute.
The rest of the statements that make up the Word Guessing game are shown next and should be added to the end of the script file. These statements are responsible for controlling the overall execution of the game.
#Analyze the player's input if answer == "n" #See if the player elected not to take the quiz Console_Screen.cls #Clear the display area #Invite the player to return and take the quiz some other time puts "Okay, perhaps another time. " else #The player wants to play the game #Execute the game class's display_instructions method WordGuess.display_instructions loop do #Loop forever #Execute the Game class's play_game method WordGuess.play_game #Find out if the player wants to play another round print "Would you like to play again? (y/n): " playAgain = STDIN.gets #Collect the player's response playAgain.chop! #Remove any extra characters appended to the string #Terminate the loop if valid input was provided break if playAgain =~ /n/i end #Call upon the Game class's determine_credits method WordGuess.display_credits end
As you can see, these statements are controlled by a large if
code block. The script statements that it executes depend on whether the player decides to terminate the game or play another round. If the player elects not to play, a message is displayed that encourages her to return and play another time. If the player elects to play, the Game
class’s display_instructions
method is executed. Next, a loop executes the Game
class’s play_game
method, initiating a new round of play. Once the current round of play has finished, control returns to the loop, which prompts the player to play again. If the player decides to play again, the loop iterates. Otherwise the break
command is executed, terminating the loop and allowing the display_credits
method to execute.
Okay, you now have everything needed to create and execute the Word Guessing game. Assuming that you did not make any typos along the way and that you did not accidentally skip any steps, the script should run as described at the beginning of this chapter. If, however, you should run into an error or two, be sure to carefully read the text of the error messages to get an idea of what and where the problems reside. If all else fails, go back and review the script and look for typos and missing script statements.
In this chapter, you learned how to use regular expressions as a tool for analyzing data. You learned how to create regular expressions that can match simple character patterns and groups of characters. You learned how to incorporate metacharacters to create more streamlined and powerful regular expressions. You also learned how to use regular expressions to extract data from a string and to perform string substitution. Finally, you learned how to use regular expressions to negate differences in case.
Now, before you move on to Chapter 8, “Object-Oriented Programming,” I suggest you set aside a little extra time to make a few improvements to the Word Guessing game by implementing the following list of challenges.