In this chapter, you will learn the ins and outs of how to use Ruby to develop scripts that interact with the computer’s file system. In doing so, you will learn how to develop scripts that can create, rename, and delete files and folders. You will learn how to determine a file’s size and to iterate through a folder’s contents. You will also learn how to write data to text files. This will include learning how to write data to new files as well as how to overwrite or append data to existing text files. Finally, you will learn how to read and process data stored in files. On top of all this, you will learn how to create a new Ruby script, the Ruby Blackjack game.
Specifically, you will learn how to:
Create and delete text files
Read from and write to text files
Create and delete folders
Examine and process folder contents
In this chapter, you will learn how to create a new Ruby script called the Ruby Blackjack game. This game will create a virtualized casino blackjack dealer against whom the player will compete in an effort to build a hand that comes as close as possible to 21 without going over. As shown in Figure 9.1, the game begins by displaying a welcome message.
The player is then prompted for permission to begin play, as shown in Figure 9.2.
Once permission has been given, instructions for playing the game are displayed, as shown in Figure 9.3.
Figure 9.3. The object of the game is to get a hand that is as close as possible to 21 without going over.
Both the player and the dealer are dealt an initial card, after which the player is prompted to play out the rest of her hand, as demonstrated in Figure 9.4.
The player may ask for as many additional cards as she wants, as long as the total value of her hand does not exceed 21. If this happens, the player busts and loses the hand, as demonstrated in Figure 9.5.
After each round of play, the player is prompted for permission to start a new hand, as shown in Figure 9.6.
Figure 9.7 shows an example of a game in which the player has won. Here the player’s hand is a perfect 21.
Once the player has decided to stop playing, the game ends after displaying the screen shown in Figure 9.8.
The movement of data from one source to another on a computer is commonly referred to as data input and output. The actual movement of the data occurs as a stream between the sending and the receiving resources. By default, the computer looks to the keyboard for standard input or STDIN and sends standard output or STDOUT to the computer’s monitor.
You can redirect STDIN and STDOUT as necessary to pull data from a different input source and to reroute data to a different output destination. Redirection is accomplished using pipe operators. The > pipe operator allows you to pass the output from one source, such as a Ruby script, to another source, like a text file. For example, suppose you created a Ruby script named Hello.rb that contained the following statement.
puts "Hello World!"
When executed from the command line, the words Hello Word!
are displayed on the computer screen. However, using the >
pipe operator, you could instead redirect the script’s output to a text file, as demonstrated here:
ruby Hello.rb > Hello.txt
When the Hello.rb script is executed this time, its output is redirected to a file named Hello.txt located in the current working directory (i.e., the same directory where the script resides). If the Hello.txt file does not exist, it is created and then written to. If a file of the same name already exists, its contents are overwritten.
In a similar fashion, you can use the <
pipe operator to redirect standard input from one resource to another. For example, suppose you had a Ruby script named DispMsg.rb that consisted of the following statement.
message = STDIN.gets
If you execute this script from the command prompt, the script would immediately pause and wait for input to be provided by the user. Once something is typed and the Enter key is pressed, the script assigns the input to the message
variable and then terminates. However, using the <
pipe operator, you can instead redirect STDIN from a file source, as demonstrated here:
ruby Dispmsg.rb <
Hello.txt
Here, the Dispmsg.rb script reads the first line of text stored in a file named Hello.txt and then assigns it to the message
variable.
If an empty file were used as input in the previous example, a value of nil (Ruby’s way of representing a value of nothing) would be assigned to the message
variable.
Both the >
and the <
pipe operators are provided by the operating system and can be used as an elementary way of writing to and reading from text files. However, Ruby provides a number of more elegant and sophisticated ways of working with and administering files.
Ruby offers a number of different ways to perform file and folder administration. Ruby provides you with everything you need to create, rename, and delete files and folders. This functionality is provided through the Ruby File
and Dir
classes, which supply you with access to various file and folder administration methods.
Before you begin any file or folder administration task, it is a good idea to first check and see if the file or folder with which you intend to work already exists. Files and folders can disappear from a computer for a host of different reasons. They may, for example, be accidentally deleted or moved to a different location. If after checking for the existence of a folder and finding out that it does not exist, you may want to terminate the script’s execution or you may instead want to create a new folder. The same logic can be applied to files.
To determine if a file or folder exists, you need to use the File
class’s exist?
method, which supports the following syntax.
File.exist?(Name)
Name
represents the name and path of the file or directory being looked for. To get a better feel for how to use the exist? method, look at the following example.
puts "Found it!" if File.exist?("Hello.txt")
Here, a statement has been set up to look for a file named Hello.txt located in the current working directory. If the file is found, a message is displayed. A more realistic example might involve modifying this statement to display a message in the event the file is not found. For example, the following statements provide an example that does something similar to this, only this time the focus of attention is on a folder named TestDir instead of a file.
if File.exist?("TestDir") then puts "All is well." else print "The TestDir folder is missing. Enter y to recreate it: " answer = STDIN.gets answer.chop! if answer =~ /y/i then Dir.mkdir("TestDir") end end
Here, if the folder is not found, a message is displayed prompting the user for permission to create a new copy of the folder. If the user agrees, the Dir
class’s mkdir
method is used to create the folder.
The File
and Dir
classes provide you with access to a number of methods that you can use to get information about files and folders. Using these methods, you can determine whether a specified resource is a file, folder, or something else. You can also determine whether a file is empty or how large it is, and you can retrieve a list of all the files and folders stored in a folder.
Using the File
class’s directory?
method, you can determine if a resource is a file or something else (file, socket, pipe, etc.). This method supports the following syntax.
File.directory?(Name)
Name
represents the name and path of the resource to be checked. To get a better feel for how to work with the directory?
method, look at the following example.
if File.directory?("TestDir") then puts "It's a folder." else puts "It is something else." end
Here, the current working directory?
method is used to determine whether TestDir is a file or a folder. The directory?
method returns a value of true
if the specified resource is a folder and value of false
if it is not. Based on the results of the analysis, one of two text messages is displayed.
The File
class also contains a method named file?
, which can be used to determine whether a resource is a file or something else. As you can see from the following syntax, the file?
method is very similar to the directory?
method.
File.file?(Name)
Name
represents the name and path of the resource to be checked. To get a better feel for how to work with the file?
method, look at the following example.
if File.file?("Hello.txt") then puts "It's a file." else puts "It is something else." end
Here, the example looks for a file named Hello.txt located in the current working directory.
Before reading from a file or overwriting an existing file with a new file, you may want to first check to see if the file has anything in it. Based on this analysis, you could avoid trying to read from an empty file or decide to write new data to the end of a file in append mode instead of overwriting any existing data.
To determine if a file has any data in it, you can use the File
class’s size
method, which has the following syntax. This method returns a count of the specified file’s size in bytes.
File.size(Name)
Name represents the name and path of the resource to be checked. To get a better feel for how to work with the size
method, look at the following example.
puts "File Hello.txt is " + File.size("Hello.txt").to_s + " bytes in size."
Here, a text string is displayed that shows the size of a file named Hello.txt located in the current working directory.
In addition to the size
method, you can also use the size?
method to determine the size of a file. The syntax for this method is shown next. This method returns the size of the file, in bytes. However, if the file is empty, a value of nil
is returned instead.
File.size?(Name)
Name
represents the name and path of the resource to be checked. To get a better feel for how to work with the size method, look at the following example.
if File.size?("Hello.txt") > 0 then puts "Processing file" else puts "File processing skipped" end
Here, the size?
method is used to determine whether a file should be processed. If the size of the file is greater than zero bytes, it is processed. Otherwise it is not processed.
Ruby gives you the ability to list the contents in a folder using the Dir
class’s entries
method, which returns the contents of the list as an array. You can then iterate through the array and work with each individual file or folder as necessary. The entries
method has the following syntax.
Dir.entries(Name)
Name
represents the name and path of the directory to be processed. To get a better feel for how to work with the entries
method, look at the following example.
puts Dir.entries(".")
Here, the puts
method is used to display the contents of the current working directory, as provided by the entries
method. When executed, this statement will display output similar to that shown here.
c:Ruby_scripts>test . .. BlackJack.rb Crazy8Ball.rb NumberGuess.rb RPS.rb RubyJoke.rb SupermanQuiz.rb TallTale.rb Test.rb TypingChallenge.rb WordGuess.rb
In Ruby, the . character can be used as a shortcut for representing the current working directory.
You can also produce a list of all the files stored in a folder using the Dir
class’s foreach
method, as demonstrated here:
Dir.foreach(".") do |resource| puts resource end
Here, a loop has been set up that iterates through every file and folder stored in the current working directory. The advantage of using the foreach
method in this manner is that it provides you with the ability to take multiple actions against folder contents by allowing you to place as many statements as you want inside the loop.
As you will see in a few minutes, Ruby allows you to create new files and write any amount of data to them. Ruby also provides you with the ability to create new folders using the Dir
class’s mkdir
method, which has the following syntax.
Dir.mkdir(Name)
Name
is used to specify the name and path of the folder that you want to create. To get a better feel for how to work with the mkdir method, look at the following example.
Dir.mkdir("TestDir")
When executed, this statement creates a new folder named TestDir. However, if a folder of the same name already exists, an error will occur, so you may want to check to see if a folder of the same name already exists, as shown here:
if File.exist?("TestDir") then Dir.mkdir("TestDir") end
Ruby also offers you the ability to delete both files and folders. To do so, you will need to work with the File
and Dir
class’s delete
method. The File
class’s delete
method has the following syntax.
File.delete(Name,... Name)
The Dir
class’s delete
method’s syntax is identical, as shown here:
Dir.delete(Name,... Name)
Name
represents any number of files or folders that you want to delete. To get a better feel for how to work with the delete
method, look at the following example.
Dir.delete("TestDir")
Here, a folder named TestDir
is deleted if it exists.
Ruby also provides you with the ability to rename a file or folder using the File class’s rename
method, which has the following syntax.
File.rename(OldName, NewName)
OldName
represents the current name of the file to be renamed and NewName represents the new file name that is to be assigned to the file. To get a better feel of how to work with the rename
method, look at the following example.
File.rename("Hello.txt", "Greeting.txt")
This example renames a file named Hello.txt to Greeting.txt. If the specified file or folder does not exist, an error will occur.
All of the examples that you have looked at in this chapter have operated based on the assumption that the example scripts were being executed from the same folder where target files and folders resided (e.g., the current working directory). Of course, this will not always be the case. As a result, you need to be able to specify the path to the files and directories you want to work with.
On Microsoft Windows, you can include paths in your script statements, as demonstrated here:
puts File.exists?('C:Test_FilesHello.txt')
The reason that single quotes were used in this example is because they prevent any character interpolation from occurring. As a result, the characters are taken literally and everything works just fine. If you wanted to work with double quotes instead, you would have to escape each instance of the
character to get expected results, as demonstrated here:
puts File.exists?("c:\Ruby_Scripts\xxx.txt")
Here, the File
class’s exists?
method has been instructed to look in the C:Test_Files folder for a file named Hello.txt.
If instead of Microsoft Windows, you were working on a computer running UNIX or Linux, you could rewrite the previous example as shown here:
puts File.exists?('/Test_Files/Hello.txt')
Here, the File
class’s exists?
method has been instructed to look in a folder named Test_Files, which is located at the root of the computer file systems, for a file named Hello.txt.
Note that while Microsoft Windows uses backslashes when specifying path information, UNIX and Linux use forward slashes.
If you need to develop scripts that will run on different operating systems, then you will need a way of determining which type of operating system your script is executing on. One way of addressing this challenge is to take advantage of Ruby’s RUBY_PLATFORM
special variable, as demonstrated here:
if RUBY_PLATFORM =~ /win32/ then puts File.exists?('C:Test_FilesHello.txt') else puts File.exists?('/Test_Files/Hello.txt') end
Here, the value assigned to RUBY_PLATFORM
is checked to see if it contains the characters win32
. If it does, the script is executing on a Windows computer. Otherwise, it is assumed that the script is executing on a computer running some flavor of UNIX or Linux.
RUBY_PLATFORM
is a special Ruby variable. A special variable is a variable that is automatically created and maintained by Ruby and which can be referenced by any Ruby scripts. RUBY_PLATFORM
contains a string that identifies the name of the operating system on which a script is executing. Using the regular expression shown in the previous example, you can easily distinguish between a Windows and a non-Windows computer.
In addition to taking advantage of the operating system’s ability to pipe data to and from text files using the <
and >
pipe operators, Ruby offers a number of different options for writing data to and reading it from files using methods belonging to the File
class.
One way of interacting with files is to use the File
class’s new
method to set up a reference to it. Once this reference has been established, you can refer to the file as necessary to perform read and write operations. One way to set up a file reference is to use the syntax outlined here:
Reference = File.new(Name, Mode)
Reference
is a placeholder for a variable that will be used to refer back to the target file. Name
represents the file that you want to interact with and Mode
represents one of the options listed in Table 9.1 that specify the mode in which you want the file opened.
Table 9.1. File Class Mode Specifications
Mode | Description |
---|---|
| Opens the file in read-only mode, placing the location pointer at the beginning of the file. |
| Opens the file for both reading and writing, placing the location pointer at the beginning of the file. |
| Opens the file in write-only mode, overwriting any existing text by placing the pointer at the beginning of the file. If the specified file does not exist, it is created. |
| Opens the file for both reading and writing, overwriting any existing text by placing the pointer at the beginning of the file. If the specified file does not exist, it is created. |
| Opens the file in append mode, placing the pointer at the end of the file to preserve any pre-existing text. |
| Opens the file in append mode, allowing for both reading and writing, placing the pointer at the end of the file to preserve any pre-existing text. |
One way of writing data to a text file is to use the File
class’s new
method and to specify a write mode operation, as demonstrated here:
outFile = File.new("Demo.txt", "w") outFile.puts "Ho Ho Ho" outFile.puts "Merry Christmas!" outFile.close
In this example, a file reference has been set up to open a file named Demo.txt using write mode. Once established, the file reference (outFile
) can be used to write data to the file using the puts method. In this example, two lines of text were written to the file. If the target file does not exist, it is created and then written to. If, however, it does exist, it is opened and then overwritten. Take special note of the last statement in the example. This is a requirement for any file that is opened by creating a file reference. Failure to explicitly close any open file reference may result in the corruption of the file.
Up to this point in the book, you have used the puts
method exclusively for the purpose of displaying text on the computer screen (e.g., default STDOUT). However, in this example, by pre-appending the file reference to the puts
method using dot notation, you have redirected the puts
method’s output to the specified file.
Figure 9.9 shows the contents of the text file that is created when the example is executed.
Appending data to the end of a file is very similar to writing it except that in append mode, any data already written to the file is preserved. This makes append mode the appropriate option to use when adding to the end of text files, as demonstrated here:
outFile = File.new("Demo.txt", "a") outFile.puts "And a happy new year!" outFile.close
In this example, the Demo.txt file is reopened and an additional line of text is written to it before it is again closed. Figure 9.10 shows how the contents of the text file have been modified once the example has executed.
Reading data stored in text files is no more difficult than writing to text files. For starters, the file must be opened in read mode. You can then read data from the text file, as demonstrated here:
File.new("Demo.txt", "r").each do |line| puts line end
In this example, the Demo.txt file has been opened for reading using the File
class’s new
method. Next, the each
method is used to iterate through and display each line of text that is in the file. When executed, the following output is displayed.
Ho Ho Ho Merry Christmas! And a happy new year!
In addition to processing the contents of a text file using a loop, you can also use the gets
method to retrieve data from the file a line at a time, as demonstrated here:
inputFile = File.new("Demo.txt", "r") puts inputFile.gets inputFile.close
Here, the Demo.txt file has been opened in read mode. Next, the gets
method is used to read the first line of the file, which is then displayed by the puts
method. The last statement closes the open file. When executed, these statements generate the following output.
Ho Ho Ho
As you can see, working with a file reference when reading a file is pretty straightforward. However, every time you open a file to read it, you must remember to close the file to prevent the file from becoming corrupt. To help make things even easier on you, Ruby provides a couple of quick and easy shortcut methods that you can use to read file contents without having to worry about closing the files when you are done. These methods include the read
and readlines
methods.
To use the read
method, all you have to do is pass the method the name of the file that you want read, as demonstrated here:
inputFile = File.read("Demo.txt")
In this example, the Demo.txt file is opened and read and all of its contents are stored in a variable named inputFile
. You can then process the data stored in inputFile
as you see fit. For example, you might manipulate it using regular expression or simply display it as demonstrated here:
puts inputFile
When executed, this statement displays the data stored in the inputFile
variable, as shown here:
Ho Ho Ho Merry Christmas! And a happy new year!
The readlines
method is similar to the read
method, only instead of reading the contents of a file into a single variable, the file’s contents are read line by line into an array, allowing you to reference and manipulate them using any of the Array
class’s methods. For example, the following statement uses the readlines
method to read the Demo.txt file and store its contents in an array named inputArray
.
inputArray = File.readlines("Demo.txt")
Once loaded into the array, you can process the array’s contents as you see fit. For example, the following statements could be used to loop through the array and print out its content, one item at a time.
inputArray.each do |line| puts line end
In addition to opening a file for reading using the File
class’s new
method, you can also use the File
class’s open
method. The advantage of using the open
method over the new
method is that the open
method does not require you to close the file once it has been read. You can use the open
method in conjunction with other methods, including the each
, read
, and readlines
methods. For example, the following statement uses the open
method in conjunction with the readlines
method to read all of the lines stored in Demo.txt and store them in an array named inputArray
.
inputArray = File.open("Demo.txt").readlines
The File
class’s open
method can also be used for write operations, as demonstrated here:
File.open("Demo.txt", "a") do |output| output.puts " The End" end
Here, the open
method is used to open the Demo.txt file in append mode and then to write a single line of text to the end of the file. Once the write operation is completed, the file is automatically closed. The contents of the text file are modified, as shown here:
Ho Ho Ho Merry Christmas! And a happy new year! The End
Okay, now it is time to turn your attention back to the development of this chapter’s game project, the Ruby Blackjack game. As with all previous games, this script will be developed in a modular fashion. As you work your way through this script, take particular note of the manner in which script variables are kept localized and how programming logic is kept separate and organized into distinct methods.
The development of the Ruby Blackjack 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 Blackjack game.
Define the display_greeting
method.
Define the display_instructions
method.
Define the play_game
method.
Define the complete_player_hand
method.
Define the play_dealer_hand
method.
Define the determine_winner
method.
Define the display_credits
method.
Instantiate custom script objects.
Get confirmation before continuing game play.
Control high-level game play.
Remember to follow along carefully and make sure that you do not skip any steps as you work your way through this script.
Let’s begin the creation of the Ruby Blackjack game by starting up your favorite text or script editor and creating a new Ruby script file. Save this file with a file name of Blackjack.rb and store it in the same folder as the rest of your Ruby script files.
Okay, now let’s add the usual list of comment statements to the beginning of the script file to document the script file and explain what it does. To do this, add the following statements to the end of the script file.
#-------------------------------------------------------------------------- # # Script Name: BlackJack.rb # Version: 1.0 # Author: Jerry Lee Ford, Jr. # Date: October 2007 # # Description: This Ruby game is a virtualized casino card game in which # the player competes against the dealer (computer) in an # effort to build a hand that comes as close as possible to 21 # without going over. # #--------------------------------------------------------------------------
The Ruby Blackjack game will make use of two custom classes, which will provide you with access to collections of methods required to control user interaction and the overall execution of the game. The code statements for the script’s first class, the Screen
class, are shown next and should be added to the end of the script file.
#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 defines two methods. The cls
method writes 25 blank lines to the console window and then plays a beep sound. The pause
method pauses script execution whenever it is called and waits for the player to press the Enter key.
The Game
class contains eight methods, which provide you with control over the game’s execution. To begin the creation of the Game
class, append the following statements to the end of the script file.
#Define a class representing the Ruby Blackjack game class Game end
The first method defined in the Game
class is display_greeting
. It is responsible for displaying the game’s welcome message. The statements that make up this method are shown next and should be inserted inside 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 a welcome message print " Welcome to the Ruby Blackjack Game!" + " Press Enter to " + "continue. " Console_Screen.pause #Pause the game end
The next method defined in the Game
class is the display_instructions
method. This method displays the game instructions using a series of text strings. The statements that make up this method are shown next and should be added to the Game
class definition, immediately after the display_greeting
method.
#Define a method to be used to display game instructions def display_instructions Console_Screen.cls #Clear the display area puts "INSTRUCTIONS: " #Display a heading #Display the game's instructions puts "This game is based on the Blackjack card game. In this" puts "game the player and dealer are each dealt an initial card. The" puts "player is then prompted to draw additional cards. The player" puts "may draw as many additional cards as desired, as long as the" puts "player's hand remains less than 21. If the player's hand goes" puts "over 21, the player busts and the dealer automatically" puts "wins. Once the player decides to hold, it becomes the dealer's" puts "turn. The dealer will continue to add new cards to its hand" puts "until the value exceeds 17 or the dealer busts. Once the" puts "dealer's hand is complete, the game analyzes the player's hand" puts "and the dealer's hand to determine the results of the game." puts " " print "Press Enter to continue. " Console_Screen.pause #Pause the game end
The next method defined in the Game
class is the play_game
method, which is responsible for managing an individual round of play. The statements that make up this method are shown next and should be added to the end of the class definition, immediately after the display_instructions
method.
#Define a method to control game play def play_game Console_Screen.cls #Clear the display area #Assist the player and dealer with an initial starting card playerHand = get_new_card dealerHand = get_new_card #Call the method responsible for dealing new cards to the player playerHand = complete_player_hand(playerHand, dealerHand) #If the player has not busted, call the method responsible for managing #dealer's hand if playerHand <= 21 then dealerHand = play_dealer_hand(dealerHand) end #call the method responsible for determining the results of the game determine_winner(playerHand, dealerHand) end
This method begins by calling on the get_new_card
method two times to assign an initial card to both the player’s and dealer’s opening hand. Since the player always goes before the dealer, the complete_player_hand
method is called next and is passed the value of the player’s and dealer’s hand as an argument. The complete_player_hand
method is responsible for adding new cards to the player’s hand until the player busts or decides to stick with the cards currently in her hand, after which it returns a value representing the current value of the player’s hand. This value is then examined to see if it exceeds 21, in which case the player has gone bust. If the player has not gone bust, the play_dealer_hand
method is called and passed the current value of the dealer’s hand as an argument. The play_dealer_hand
method is responsible for playing out the dealer’s hand and then returning the result of that hand. The last statement in the player_game
method calls up on the determine_winner
method, passing it the current value of the player’s and dealer’s hands. The determine_winner
method analyzes these two arguments to determine the result of the game.
The next method to be added to the Game
class is the get_new_card
method, whose statements are shown here:
#Define a method responsible for dealing a new card def get_new_card #Assign a random number from 1 to 13 as the value of the card being #created card = 1 + rand(13) #A value of 1 is an ace, so reassign the card a value of 11 return 11 if card == 1 #A value of 10 or more equals a face card so reassign the card a value #of 10 return 10 if card >= 10 return card #Return the value assigned to the new card end
When called, this method generates a random number from 1 to 13, which it assigns to a variable named card
. If the value of card
is set equal to 1, the card is considered to be an ace. As such, the value of card
is reassigned a value of 11. On the other hand, if the value of card
is greater than or equal to 10, it is assumed that the card is either a 10 or a face card (Jack, Queen, or King) and as such the value of card
is set equal to 10. Once the value assigned to card
is finally established, it is returned back to the statement that called upon the method to execute.
The next method defined in the Game
class is the complete_player_hand
method, which is responsible for assisting the player in completing her hand. The statements that make up this method are shown next and should be added to the end of the class definition, immediately after the get_new_card
method.
#Define a method responsible for dealing the rest of the player's hand def complete_player_hand(playerHand, dealerHand) loop do #Loop forever Console_Screen.cls #Clear the display area #Show the current state of the player's and dealer's hands puts "Player's hand: " + playerHand.to_s + " " puts "Dealer's hand: " + dealerHand.to_s + " " print "Would you like another card? (Y/N) " reply = STDIN.gets #Collect the player's answer reply.chop! #Remove any extra characters appended to the string #See if the player decided to ask for another card if reply =~ /y/i then #Call method responsible for getting a new card and add it to the #player's hand playerHand = playerHand + get_new_card end #See if the player has decided to stick with the current hand if reply =~ /n/i then break #Terminate the execution of the loop end if playerHand > 21 then break #Terminate the execution of the loop end end #Return the value of the player's hand return playerHand end
This method is passed two arguments, playerHand
and dealerHand
, which represent the current value of the player’s and dealer’s hands. The value of both hands is displayed and the player is then asked if she would like another card. If the player elects to add another card to her hand, the value assigned to playerHand
is incremented, adding the result returned by the get_new_card
method to the value of playerHand
. The player may add as many cards as desired to her hand, provided that the total value of her hand does not exceed 21. Once the player busts or decides not to draw more cards, the method ends by returning the current value of the player’s hand back to the statement that called upon the method to execute.
The next method defined in the Game
class is the play_dealer_hand
method, which is responsible for completing the dealer’s hand. The statements that make up this method are shown next and should be added to the end of the class definition, immediately after the complete_player_hand
method.
#Define a method responsible for managing the dealer's hand def play_dealer_hand(dealerHand) loop do #Loop forever #If the value of the dealer's hand is less than 17 then give the #dealer another card if dealerHand < 17 then #Call method responsible for getting a new card and add it to the #dealer's hand dealerHand = dealerHand + get_new_card else break #Terminate the execution of the loop end end #Return the value of the dealer's hand return dealerHand end
This method takes as an argument the current value of the dealer’s hand. The method then repeatedly calls upon the get_new_card
method, adding new cards to the dealer’s hand until the total value of the dealer’s hands exceeds 17, at which time the method returns the current value of the dealer’s hand to the calling statement and then ends.
The next method defined in the Game
class is the determine_winner
method, which is responsible for determining the results of the game. The statements that make up this method are shown next and should be added to the end of the class definition, immediately after the play_dealer_hand
method.
#Define a method responsible for analyzing the player's and dealer's #hands and determining who won def determine_winner(playerHand, dealerHand) Console_Screen.cls #Clear the display area #Show the value of the player's and dealer's hands puts "Player's hand: " + playerHand.to_s + " " puts "Dealer's hand: " + dealerHand.to_s + " " if playerHand > 21 then #See if the player has busted puts "The Player busts! " print "Press Enter to continue." else #See if the player and dealer have tied if playerHand == dealerHand then puts "Tie! " print "Press Enter to continue." end #See if the dealer has busted if dealerHand > 21 then puts "The Dealer busts! " print "Press Enter to continue." else #See if the player's hand beats the dealer's hand if playerHand > dealerHand then puts "The Player wins! " print "Press Enter to continue." end #See if the dealer's hand beats the player's hand if playerHand < dealerHand then puts "The Dealer wins! " print "Press Enter to continue." end end end Console_Screen.pause #Pause the game end
This method is passed two arguments, representing the current value of the player’s and dealer’s hands. It then displays both of these values and, using a series of nested if
statements, determines the overall result of the game. Based on the analysis, the method displays an appropriate message.
The last method to be added in the Game
class is the display_credits
method. This method is responsible for displaying the game’s credits, including the author’s URL. The statements that make up this method are shown next and should be added to the end of the Game
class.
#This method displays information about the Ruby Blackjack game def display_credits Console_Screen.cls #Clear the display area #Thank the player and display game information puts " Thank you for playing the Ruby Blackjack game. " puts " Developed by Jerry Lee Ford, Jr. " puts " Copyright 2007 " puts " URL: http://www.tech-publishing.com " end
Now that both of the script’s custom classes have been defined, it is time to initialize instances of both 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 BJ = Game.new #Instantiate a new Game object #Execute the Game class's display_greeting method BJ.display_greeting answer = "" #Initialize variable and assign it an empty string
In addition to instantiating the Console_Screen
and BJ
objects, these statements call upon the Game
class’s display_greeting
method, which is responsible for prompting the player for permission to start the game, and then define a variable named answer
, which will be used to manage the execution of a loop.
The following script statements are responsible for prompting the player for permission to begin the game and should be added to the end of the script file.
#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 game print "Are you ready to play Ruby Blackjack? (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 are controlled by a loop that runs forever. Upon each iteration of the loop, the player is prompted for permission to start a new round of play. Any input other than a y or n is ignored. Once valid input is provided, a break
command is executed, terminating the loop and allowing the rest of the script to execute.
The rest of the statements that make up the Ruby Blackjack game are shown next and should be appended to the end of the script file. These statements are responsible for managing the overall execution of the game.
#Analyze the player's answer if answer == "n" #See if the player wants to quit Console_Screen.cls #Clear the display area #Invite the player to return and play the game some other time puts "Okay, perhaps another time. " else #The player wants to play the game #Execute the Game class's display_instructions method BJ.display_instructions playAgain = "" #Initialize variable and assign it an empty string loop do #Loop forever #Execute the Game class's play_game method BJ.play_game loop do #Loop forever Console_Screen.cls #Clear the display area #Find out if the player wants to play another round print "Would you like to play another hand? (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|y/i end #Terminate the loop if valid input was provided break if playAgain =~ /n/i end #Call upon the Game class's determine_credits method BJ.display_credits end
These statements are controlled by a large if
code block. The script statements that are executed depend on the player’s input. If the player decides not to play the game, a message is displayed encouraging the player to return and play another time. If, on the other hand, the player decides to play, the Game
class’s display_instructions
method is executed. Next, a loop executes that repeatedly calls upon the Game
class’s play_game
method. Each time the play_game
method finishes executing, control returns to the loop, which prompts the player to play again. The player may play as many times as she wants. Once she decides to stop playing, a break
command is executed, terminating the loop and allowing the display_credits
method to execute.
Okay! You now have everything you need to create and execute the Ruby Blackjack game. As long as you followed along carefully and kept an eye on your typing, everything should work exactly as expected. If you run into any errors, carefully examine the resulting error message to determine where the problem may reside. If necessary, go back and review the script and look for typos or missing scripts statements.
In this chapter, you learned how to interact with the computer’s file system. This included learning how to read from and write to text files. In doing so, you learned how to overwrite existing files or append data to the end of them when writing text. You also learned how to read data from text files a line at a time or all at once. This chapter showed you how to rename and delete files and folders as well as how to determine the size of a file and how to iterate through all the items in a folder.
Now, before you move on to Chapter 10, “Debugging,” I suggest you set aside a little extra time to make a few improvements to the Ruby Blackjack game by implementing the following list of challenges.