In the first two chapters of this book, you focused on learning about Ruby’s overall capabilities, how to experiment with Ruby using the irb, and how to create and execute Ruby scripts. Now it is time to begin digging into the language and seeing how things work. In this chapter, you will learn a number of fundamental object-oriented programming techniques, including how to define custom classes, which you can use as a template for instantiating new objects. You will also learn how to create and work with strings and to store and retrieve object data using variables. On top of all this, you will learn how to create a new Ruby script, the Ruby Virtual Crazy 8 Ball game.
Specifically, you will learn how to:
Create and work with text strings
Assign and retrieve object data using variables
Define custom classes and use them to instantiate objects
Define class properties and methods
In this chapter, you will learn how to create a new computer game called the Ruby Virtual Crazy 8 Ball game. In developing this Ruby script, you will learn how to work with text strings, objects, and variables. You will also learn how to generate random numbers, which will be used as the basis for creating a virtual crazy 8 ball game that provides randomly selected answers to player questions.
The game begins by displaying a welcome screen as shown in Figure 3.1. To begin the game, the player must press the Enter key.
Next, the game prompts the player for permission to play the game, instructing the player to respond with a y or an n, as shown in Figure 3.2.
If the player responds with anything other than y or n, the game will redisplay the screen shown in Figure 3.2. If the player responds by typing n, the game responds by displaying the screen shown in Figure 3.3.
If, on the other hand, the player responds with a y, the game prompts the player to ask it a question and then press the Enter key (see Figure 3.4).
The game then answers the player’s questions by displaying one of six randomly selected answers, as demonstrated in Figure 3.5.
Once the player dismisses the game’s answer by pressing the Enter key, the game responds by asking the player if she would like to ask a new question, as demonstrated in Figure 3.6.
The last screen displayed thanks the player for taking time to play the game, as shown in Figure 3.7.
As you are no doubt aware by this point in the book, text plays a big role in most Ruby scripts, especially when communicating with users. Text can consist of letters, numbers, special characters, or even blank spaces. So far, all of the text strings that you have worked with in this book have been enclosed inside matching pairs of double quotation marks, as demonstrated here:
"Sample text string"
However, there are other ways of creating text strings within Ruby scripts. Strings can be created by enclosing them within matching single quotation marks, as demonstrated here:
'Sample test string'
Deciding whether to use matching double or single-quoted strings is more than a matter of personal preference. Ruby allows you to do things to text strings enclosed inside matching double quotes that it does not allow within matching single quotes. For example, when working with double-quoted strings, Ruby is able to perform escaping and variable substitution operations using #{}
. Single-quoted strings do not support either of these operations.
When working with double-quoted strings, Ruby recognizes a number of escape characters that, when found, are automatically replaced with the appropriate corresponding operation. For example, Ruby supports escape characters that execute tab and new line operations. By embedding these escape characters within double-quoted strings, you can exercise detailed control over the manner in which Ruby renders the strings when it displays them. Table 3.1 provides a list of escape characters that you can use to exercise control over text formatting within strings.
Table 3.1. String Escape Substitution Characters
Option | Description |
---|---|
Backspace | |
| Formfeed |
| New line |
| Return |
| Space |
| Tab |
To get a good understanding of how to work with escape characters, it helps to see a few examples.
puts "1 2 3 4 5"
When executed, this statement results in the following output.
1 2 3 4 5
Now, let’s reformat this example by embedding a series of
escape characters.
puts " 1 2 3 4 5"
This time when you execute the statement, the following output is displayed.
1 2 3 4 5
As you can see, by embedding the
escape character, you can perform tab operations that affect how the string is displayed. Now, look at this next example.
puts " 1 2 3 4 5"
This time the
escape character has been placed before each number in the text string. Since this escape character is the equivalent of a new line operation, when executed this statement produces the following output.
1 2 3 4 5
String interpolation, also referred to as variable substitution, is performed by embedding a text string inside other strings using the #{}
characters. Interpolation is the process of substituting the value of an expression or variable inside a string. To see how this works, look at the following statements.
totalScore = 100 puts "Game over. Your score is #{totalScore}."
When executed, these two statements produce the following output.
Game over. Your score is 100.
As you can see, the value stored in the totalScore
variable has been substituted into a pre-specified location in the text string. As has been stated, interpolation also works with expressions.
totalScore = 100 bonusPoints = 50 puts "Game over. Your score is #{totalScore + bonusPoints}."
When executed, these statements generate the following output.
Game over. Your score is 150.
As you can see, Ruby added together the values of totalScore
and bonusPoints
and then substituted the resulting value into the text string in place of the specified expression.
Ruby provides you with many different ways of working with strings. For starters, you can compare two different strings to see if they are the same, concatenate them together to create new strings, and multiply them together. You can also compare one string to another to see if they match or spread text strings out over multiple lines. In addition, you can use Ruby’s support for regular expressions to perform a host of other string-manipulation techniques.
Regular expressions are covered in Chapter 7, “Working with Regular Expressions.”
Earlier in the chapter, you learned how to perform string interpolation. As an alternative to string interpolation, you could achieve the same results using string concatenation. Concatenation is the process of joining two strings together to form a new string. Concatenation is performed using the + string method. To see how concatenation works, consider the following example.
totalScore = 100 puts "Game over. Your score is " + totalScore.to_s + "."
Here, three strings have been concatenated together. The first string is "Game over. Your score is "
. The second string was created by converting the numeric value of totalScore
to a string using the to_s
method, and the third string is "."
. When executed, this statement generates the following output:
Game over. Your score is 100.
Here is another example of how to concatenate strings together. This time, in addition to concatenating the two strings, the
escape character has been included to control the format of the resulting new string.
story = "Welcome to Ruby Programming for the Absolute Beginner" author = "by Jerry Lee Ford, Jr." puts story + " " + author
When executed, these statements generate the following output.
Welcome to Ruby Programming for the Absolute Beginner by Jerry Lee Ford, Jr.
In addition to creating new strings by concatenating existing strings together, you can also create new strings by multiplying existing strings. This is accomplished using the String
class’s * method.
x = "Happy birthday to you. " * 3 puts x
Here, the text string "Happy birthday to you. "
is repeated three times as shown here:
Happy birthday to you. Happy birthday to you. Happy birthday to you.
Another commonly performed string operation is to compare two strings to see whether they are equal. This is accomplished using the equality operator (= =)
. You have already seen examples of string comparisons numerous times in this book. For example, look at the following statements.
puts "Would you like to hear a few funny jokes? (y/n) " answer = STDIN.gets answer.chop! if answer = = "n" #See if the player elected not to play
Here, the user is prompted to enter a value of y or n. The user’s input is then captured as answer
, which is then used in the last statement to determine whether the value of answer
is equal to the value of "n"
.
String comparisons are performed using the ==
operator and not the =
operator. Because they are similar, it is easy to get them mixed up. To see what I mean, start up a new irb session, and execute the statements shown here:
irb(main):001:0> x = 1 => 1 irb(main):002:0> y = 2 => 2 irb(main):003:0> puts "x and y are equal" if x = y x and y are equal => nil
Here, x
has been set equal to 1
and y
has been set equal to 2
. The puts
statement displays a text string message only in the event that the values of x
and y
are equal. Therefore, when the puts
statement is executed, it would seem to make sense that nothing would be displayed. However, this is not the case. Instead, the test string "x and y are equal"
is displayed. To find out why, execute the commands shown here:
irb(main):004:0> x => 2 irb(main):005:0> y => 2
As you can see, the value of x
is set equal to 2
as expected. However, the value of y
has also been set to 2
and not to 1. The reason is because there is an error in the puts
statement that was previously executed. It used the equals assignment operator (=)
and not the equals comparison operator (==)
. As a result, instead of comparing x
and y
, the puts
statements assigned the value of y
to x
.
Now, to prove that the above explanation is accurate, execute the following statements using the irb.
irb(main):001:0> x = 1 => 1 irb(main):002:0> y = 2 => 2 irb(main):003:0> puts "x and y are equal" if x == y => nil
This time, things went as expected and the test string was not displayed because the values of x
and y
were different.
In addition to creating text strings by embedding them inside matching quotation marks, you can also create text strings by embedding text inside the %q{
and }
characters or the %Q {
and }
characters. Embedding characters inside the %q{
and }
characters creates a string that is equivalent to a single-quoted string. Embedding characters inside the %Q{
and }
characters creates a string that is equivalent to a double-quoted string.
The advantage of using the %q{
and }
and %Q{
and }
characters in place of quotation marks is that these characters allow you to create strings that span multiple lines, as demonstrated here:
story = %Q{Once upon a time there were three children named Alexander, William, and Molly.}
Using the %q{
and }
and %Q{
and }
characters to define strings, there is no practical limit as to the length of your text strings. In addition, strings created using the %Q{
and }
characters provide the same support for escape characters and interpolation as do double-quoted strings.
When creating multiline text strings, you can replace the opening and closing brackets with any matching set of characters you want. For example, the following example uses the %q[
and ]
characters to define a string.
story = %q[Once upon a time there were three children named Alexander, William, and Molly.]
The important thing to remember when deciding which characters you want to substitute when creating a string is to make sure that those characters do not also appear inside the string text; otherwise, Ruby will get confused and things will not work correctly.
In addition to the previously discussed options for manipulating text strings, Ruby’s String
class provides you with access to a number of additional string manipulation methods, as shown in Table 3.2.
Table 3.2. A Listing of Some of the Methods Belonging to the String Class
Method | Description |
---|---|
| Capitalizes the first letter of a string |
| Converts a string to all lowercase letters |
| Removes the last character from a string |
| Returns an integer representing the number of characters in a string |
| Replaces the next letter in a string with the next letter in the alphabet |
| Reverses the spelling of a string |
| Reverses the case of each letter in a string |
| Converts a string to all uppercase letters |
Let’s look at a few examples that demonstrate how to work with these String
class methods. For starters, look at the following example.
irb(main):028:0> story = "Once upon a time" => "Once upon a time" irb(main):029:0> puts story.length 16 => nil irb(main):030:0>
Here, the String
class’s length
method was used to display the number of characters that make up the string stored in the story
variable. In this next example, the upcase
method is used to convert the content of the string to all uppercase characters.
The irb(main):030:0> story = "Once upon a time" => "Once upon a time" irb(main):031:0> puts story.upcase ONCE UPON A TIME => nil irb(main):032:0>
As this last example demonstrates, Ruby allows you to combine different string methods.
irb(main):001:0> story = "Once upon a time" => "Once upon a time" irb(main):002:0> puts story.reverse.upcase EMIT A NOPU ECNO => nil irb(main):003:0>
Here, the reverse
and upcase
methods were chained together and executed. The reverse
method reversed the order of all the characters in the string and the upcase
method converted them to all uppercase.
Ruby is an object-oriented programming language. It sees everything that it interacts with, including data, as an object. Objects are self-contained entities, meaning that they include information about themselves in the form of properties and provide program code stored as methods for interacting with and manipulating themselves.
Objects are defined in what is referred to as a class. Using the information defined within a given class, you can create or instantiate new object instances based on that class. So for an automobile
class, you could create specific object instances, each of which would represent an individual automobile. You could then assign each automobile instance a different color by assigning a color to the appropriate object property and controlling the operation of the object by executing its methods.
In addition to its name, an object may be assigned any number of properties. Object properties are implemented as named variables. An object representing an automobile might have properties representing the model and color of the vehicle. To store data for each of these properties, object properties are stored in variables, which are pointers to locations in memory where data is stored. The code defined within an object definition that you use in your Ruby scripts to interact with and control an object is referred to as object methods. Returning to the automobile example, you might define methods for starting and stopping the car and for turning on and off different features like headlights.
Ruby scripts use this object-orientedness to define real-world concepts like files, folders, and network resources, or perhaps things like people, automobiles, and animals. Once they’re defined, you can interact with and control objects and define relationships between objects.
Objects are defined as classes using the syntax outlined here:
class ClassName statements end
class
is a keyword that tells Ruby that a new class is being defined. ClassName
is the name that is being assigned to the new class. You must assign a capital letter as the first character of every class definition. statements
is a placeholder for one or more script statements that define class attributes (properties) and methods. end
is a keyword that identifies the end of the class definition. For example, the following statements begin the outline of a class named Automobile
.
class Automobile end
A Class definition represents a template that can be used to create or instantiate individual objects. In the case of the preceding example, the class represents the logical definition of an automobile. The end
keyword marks the end of the class definition.
Within the class definition, you can define one or more attributes that describe characteristics associated with the class. For example, you might want to define class attributes such as model and color to the Automobile
class. Class attributes, also referred to as properties, are defined inside the class using the attr_accessor
keyword using the syntax outlined here:
attr_accessor : attribute1, :attribute2, ...
attr_accessor
is a keyword that identifies a list of one or more object properties. Each property is preceded by the colon character. Successive properties are separated from each other with a comma. There is no limit to the number or properties that you can define. The following example demonstrates how to assign two properties to the Automobile
class.
class Automobile attr_accessor :model, :color end
The first property specifies the model or type of automobile being defined and the second property will be used to store the color of the automobile.
At this point, the Automobile
class represents a functional class definition that is ready to be used as the basis for instantiating scripts objects, which is accomplished using the syntax outlined here:
variableName = ClassName.new
variableName
is the name of a variable that will be used to store and refer to the object, and ClassName
is the name of the class being used to create the new object. new
is a method that initiates the creation of the new object. Using the above syntax, you can create a new object as shown here:
superCar = Automobile.new
When executed, this statement creates a new object named superCar
using the Automobile
class as its template. Once instantiated, you can assign values to the object’s properties, as demonstrated here.
superCar.model = "Edsel" superCar.color = "Red"
Once defined, you can reference object property values as shown here.
puts "Super car is the car of tomorrow. It is based on the " + "original #{superCar.model} design."
When executed, this statement displays the text string shown here:
Super car is the car of tomorrow. It is based on the original Edsel design.
If you want, you can modify the value assigned to a property by reassigning another value as demonstrated here.
superCar.model = "Mustang"
In order to control your objects, you need to define class methods. You can then use the methods to programmatically interact with any object you instantiate. Ruby methods are defined using the following syntax.
def methodname(arguments) Statements end
methodname
is the name to be assigned to the new method. arguments
is a comma-separated list of parameters passed to the method for processing. Within the method, the parameters are treated as local variables. Statements
is a placeholder representing one or more statements that will be executed whenever the method is called. To get a better feel for how to define a custom method, take a look at the following example.
class Automobile attr_accessor :model, :color def honk puts "Honk!!!" end end
As with properties, you can interact with object methods by specifying the name of the class, followed by a period and then the name of the method.
The following statements demonstrate how to create a new object based on the Automobile
class and how to execute the class’s honk
method.
myCar = Automobile.new myCar.honk
One of the primary benefits of object-oriented programming is its ability to allow programmers to model the creation of classes (and therefore objects) based on real-life concepts like automobiles, tools, people, or anything else you can imagine. In real life, objects often have relationships with one another. For example, you have a father from whom you inherited certain qualities. You may have children to whom you will pass on certain attributes. In addition, you may have brothers or sisters with whom you share common attributes.
By supporting an object-oriented process known as inheritance, Ruby allows you to use one class definition as the basis for creating another class definition. In this case, the new or child class is created as a copy of the original or parent class. You can create as many child instances as you want. If necessary, you can modify the characteristics of the child class to suit your particular needs.
Thanks to inheritance, you can define classes that model real-life concepts. For example, you might define a generic automobile class that defines all of the basic properties associated with a car and which includes all of the methods required to control the car. And then you can use this class as a template for creating a whole series of child subclasses, each of which might represent an individual make and model of a car. For example, the class definition for a simple car is outlined here:
class Automobile attr_accessor :model, :color def honk puts "Honk!!!" end end
Here, an Automobile
class has been defined that contains two property definitions, model
and color
, and a method named honk
. If you want, you can use the Automobile
class as the basis for defining a new class. Once way of doing this would be to copy and paste the Automobile
class and then to rename it as shown here:
class Edsel attr_accessor :model, :color def honk puts "Honk!!!" end end
Creating a new class in this manner is highly inefficient because it results in two nearly identical sets of code that must be maintained. Should you later want to make a change in the class by adding another property, you have to make the modification twice, once for the Automobile
class and again for the Edsel
class. Instead of doing things this way, a much better way of creating another, related class of cars would be to base the new Edsel
class on the Automobile
class, taking advantage of object inheritance as demonstrated here:
class Edsel < Automobile end
Here the superclass
operator was used to create a new class named Edsel
, which is modeled on the Automobile
class. The Edsel class inherits all of the properties and methods in the Automobile
class. Not only does this require less code, but this approach also reduces the chance of making typing errors since there is less to type in. In addition, if you should later decide to make a fundamental change that would affect all the car-related classes, all you have to do is make that change in the Automobile
class and any child classes (or subclasses) will automatically inherit the change as well. There is no limit to the number of child classes that you can create from a parent class. Therefore, if you need to expand your product line to include a second type of car, you could easily do so as demonstrated here:
class Mustang < Automobile end
Here, a new Mustang
class has been created. It automatically inherits all the properties and methods of the Automobile
class. Ruby allows you to modify child classes as necessary to customize them to match up to whatever changes need to be made to differentiate the child class from it parent class. For example, take a look at the following statements.
class Explorer < Automobile attr_accessor :transmission def breaks puts "... screech!" end end
Here, a new Explorer
class has been defined based on the Automobile
class. In addition to inheriting all of the Automobile's
properties and methods, the Explorer
class has been modified to include a new property and a new method that is unique unto itself.
This book has shown you how to use a number of different methods. In most cases, you’ve been told which class the method is associated with. However, for methods like puts, chop!
, and print
, you’ve simply been shown how to use them without any explanation of where they come from. These methods are just a few of the methods stored in the Kernel
module. A module is a container used to group classes, methods, and constants. This module is a component of the Object
class. Methods belonging to the Object
class are made available to every Ruby object.
Normally when you work with a method, you do so by specifying the name of the class where the method resides followed by a period and then the method name, as demonstrated here:
pause = STDIN.gets
When working with methods belonging to the Kernel
module, you can simply specify the method’s name, as demonstrated here:
puts "Well, hello there."
Alternatively, if you want to be specific, you could rewrite the preceding statement as shown here:
Kernel.puts "Well, hello there."
As has already been stated, in Ruby, numbers and strings are really just different types of objects. Ruby supports a number of different types of numeric classes, including Fixnum, Integer, Bignum
, and float
. Ruby automatically handles numeric class assignments. For the most part, you do not need to concern yourself with the class that Ruby has assigned to a given number. However, you may come across situations where you need to convert an object from one type to another. In some situations, Ruby will implicitly handle object conversion for you. Consider the following example:
irb(main):001:0> x = 10
Here, a variable named x
has been assigned a value of 10
. In response, Ruby will create a new object based on the Fixnum
class. To verify this, you can execute the class
method as demonstrated here:
irb(main):002:0> x.class => Fixnum
As you can see, x
is assigned to the Fixnum
class. However, if you reassign a value too large to fit into that class, Ruby will automatically, or implicitly, convert x
to Bignum
, as demonstrated here:
irb(main):007:0> x = 1000000000000000 => 1000000000000000 irb(main):008:0> x.class => Bignum
If you then assign a string as the value of x
, Ruby will again change the object’s class, as demonstrated here:
irb(main):009:0> x = "Hello" => "Hello" irb(main):010:0> x.class => String
In addition to implicitly changing or coercing an object from one class to another, Ruby also provides you with the ability to explicitly coerce objects from one class to another. You might need to do this if you have written a script that collects and processes user input. By default, any input provided by the user will be treated by Ruby as a string, even if the input that was provided was a number. For example, consider the following example.
irb(main):001:0> answer = STDIN.gets 10 => "10 "
The previous example collects user input by executing the STDIN
class’s gets method. This method pauses the console session and waits for the user to provide input, which is then assigned the specified variable.
As you can see, the value entered by the user was the number 10
. Ruby appended the
characters to the end of the user’s input when the Enter key was pressed. Executing the following command, remove the trailing /
n
characters, ensuring that the value assigned to answer
is exactly what the user entered.
irb(main):002:0> answer.chop! => "10"
When the user presses the Enter key to submit her input, Ruby automatically appends an end of line marker to the end of the input. In this example, the presence of the end of line marker has no impact on the example.
The end of line marker can easily be removed from the answer variable using the chop
! method. When executed, this method removes the last character from a specified string. If the string ends with the
characters, the chop
! method will remove both characters.
Using this method, you can easily remove the /
n
characters from any input provided by the user, as demonstrated here:
answer = STDIN.gets answer.chop
If you now try to perform addition using the input, you will run into an error because Ruby is unable to explicitly convert a value of “10” to 10.
irb(main):003:0> x = 5 + answer TypeError: String can't be coerced into Fixnum from (irb):3:in '+' from (irb):3 irb(main):004:0>
To prevent this type of error from occurring and to get the results you expect, you can explicitly force the conversion of an object’s type using different conversion methods. For example, you could use the to_i
method to return the value stored in answer
to an integer.
irb(main):005:0> x = 5 + answer.to_i => 15 irb(main):006:0>
As you can see, once explicitly converted, the error no longer occurs and the expected result is attached. If you prefer, you can use the tp_f
method to convert a string to a floating point number. Going in the opposite direction, you can use the to_s
method to convert any numeric value to a string, as demonstrated here:
irb(main):001:0> x = 5 => 5 irb(main):002:0> y = 4 => 4 irb(main):003:0> z = x + y => 9
In this example, two variables are defined and assigned values of 5
and 4
, which are then added together and assigned to a variable named z
. Using the to_s
method, you can instruct Ruby to treat the values assigned to x
and y
as strings instead of numeric values, as demonstrated here:
irb(main):004:0> z = x.to_s + y.to_s => "54" irb(main):005:0>
This time, instead of adding two numeric values together, Ruby coerces x
and y
into strings and concatenates both strings together to form a new string that is then assigned to a variable named z
. If you use the class
method to check on the z
variable’s class type assignment, you will see that it has been set to String
.
irb(main):006:0> z.class => String irb(main):007:0>
When working with numbers, strings, and other types of objects, it often helps to be able to store their values in order to later be able to reference and modify them. This is accomplished through the use of variables. A variable is a pointer to a location in memory where the objects that are created in your scripts are stored. These objects may include numbers, text, or any custom objects that you have defined. For example, the following statement defines a variable named x
and assigns it an integer value of 10
.
x = 10
Likewise, the following statement assigns a text string to a variable named y
.
y = "Well, hello there."
Variables are an essential part of any Ruby script, which is why you have already seen them used many times in this book. Now it is time to learn more about how to create and work with them.
In Ruby, variable names are case sensitive. This means that to Ruby, totalcount
and TotalCount
are two separate variables. Ruby has a few rules that you need to be familiar with regarding the naming of variables. These rules are listed here:
Variable names must begin with a letter or an underscore character
Variable names can only contain letters, numbers, and underscore characters
Variable names cannot include blank spaces
Following these rules, each of the following variable names would be regarded by Ruby as valid.
Totalscore
totalScore
total_score
Total_Score
x
TotalTimes2
The variable names shown in Table 3.3, on the other hand, are not considered by Ruby to be valid.
As you have already seen many times, variable value assignments in Ruby are made using the equals assignment operator (=)
, as demonstrated here:
x = 10
Here an integer value of 10
has been assigned to a variable named x
. Use the equals assignment operator to also modify a variable’s value by assigning it the results of an expression, as demonstrated here:
x = 1 x = x + 1
Here, a variable named x
is assigned an initial value of 1
. Then the value assigned to x
is modified by assigning the values returned from the expression x + 1
to x
. As a result the value of x
was incremented by 1
.
Incrementing a variable’s value is a common task. To help make it easier to perform, you can use the +=
operator, as demonstrated here:
x += 1
The +=
operator provides a shorthand way of incrementing a variable’s value of a specified amount. In the case of the previous example, the value of x
is incremented by 1
.
In Ruby, variable access depends on the scope that has been set for that variable. Scope is a term that describes the areas within a script where a variable can be seen and accessed. Ruby supports three different scopes, as outlined in Table 3.4.
Table 3.4. Variable Scopes
Type | Opening Character(s) | Description |
---|---|---|
Local | a-z and _ | Scope is limited to each iteration loop, module, class, and method in which it is defined or to the entire script if the variable is defined outside of one of the structures. |
Instance | @ | Scope is limited to the scope associated with the object itself. |
Class | @@ | Scope is limited to objects of class. |
Global | $ | Scope has no limit, allowing the variable to be accessed throughout the script. |
In Ruby, variable scope is indicated by the characters you use at the beginning of the variable name. As Table 3.4 shows, a variable whose name begins with the $ character is a global variable, and a variable that begins with a lowercase letter or the underscore character is a local variable. For this book, you will only need to worry about working with local and global variables.
Local variables are variables that have a limited scope. For example, as Table 3.4 shows, any variable whose name begins with a lowercase letter and that is defined inside a method is accessible only within that method. For example, the following statements show a method named Add_Stuff
. This method accepts two arguments, x
and y
, which are local variables within the method and thus not accessible outside of the method.
def Add_Stuff(x, y) puts x + y end
If you key this example into the irb and then execute it by passing the method arguments of 3 and 4, a result of 7 will be displayed.
irb(main):004:0> Add_Stuff(3, 4) 7
However, if you attempt to access either the x
or the y
variable from outside of the method, as demonstrated next, an error will occur.
irb(main):005:0> puts x NameError: undefined local variable or method 'x' for main:Object from (irb):5
Global variables can be accessed from anywhere within a Ruby script and are created by making the first character of the variable name a $
, as demonstrated here:
$x = 1000
The $x
variable will be accessible from anywhere within the Ruby script that it is defined in. You will see an example of how to use global variables later in this chapter’s game project, the Ruby Virtual Crazy 8 Ball game.
Any time you are creating a Ruby script that will use a value that is known at design time and not subject to change, you should define that value as a constant. A constant is very much like a variable, the differences being that constant names begin with a capital letter and will generate warning messages in the event you change their values during script execution.
If you change a constant’s value, Ruby will complain, displaying a warning message, allowing the scripts to continue running. This makes Ruby different from most other programming languages that generate an error message and halt script and program execution.
The following expression demonstrates how to define a constant and assign it a value.
irb(main):001:0> Pi = 3.14 => 3.14
Once defined, you can reference the value assigned to the constant as needed. If you forget that you are working with a constant and change the value that is assigned to it, Ruby will generate a working message while allowing the script to continue running.
irb(main):002:0> Pi = 3.1415 (irb):2: warning: already initialized constant Pi => 3.1415
Okay, now it is time to turn your attention back to the development of this chapter’s game project, the Ruby Virtual Crazy 8 Ball game. As you follow along with the development of this script file, be sure to focus on the usage of text strings, variables, classes, and objects. In particular, pay close attention to the manner in which the script interacts with and controls objects once they have been instantiated.
The development of the Ruby Virtual Crazy 8 Ball game will be completed in 10 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 game’s virtual 8 ball window.
Instantiate custom script objects.
Display a greeting message.
Get confirmation before continuing game play.
Analyze the player’s reply.
Manage early game termination.
Process and respond to player questions.
Remember to follow along carefully and not to skip any steps or parts of steps as you work your way through this exercise. Specifically, look out for typos and make sure that you do things in the correct order.
The first step in creating the Ruby Virtual Crazy 8 Ball game is to start up your preferred text or code editor and then to create a new Ruby script file. Save this file with a file name of Crazy8Ball.rb and store it in whatever folder you have decided to keep your Ruby scripts.
Once you have created your new script file, the next step is to add the following comment statements to it. These statements provide a high-level description of the game and its purpose.
#-------------------------------------------------------------------------- # # Script Name: Crazy8Ball.rb # Version: 1.0 # Author: Jerry Lee Ford, Jr. # Date: October 2007 # # Description: This Ruby script demonstrates how to work with variables # and to generate random numbers in order to create a fortune # telling game that provides randomly selected answers to # player questions. # #--------------------------------------------------------------------------
To get as much value as possible out of the script’s opening documentation statements, modify them to suit your own needs. For example, you might want to consider including a place for your URL if you have a website. In addition, you might want to add a space for recording changes, game instructions, or anything else that you think would be useful.
Now it is time to define the first of two custom classes used by the script. The first class is named Screen
. It closely resembles the Screen
class used in Chapter 2. However, this version of the Screen
class includes a new method definition.
# 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 first method defined within this class is the cls
method. It contains two statements. The first statement writes 25 blank lines to the console window, clearing the screen. The second statement processes a string containing the a
escape character sequence, which makes an audible beep sound, thus notifying the player each time the terminal screen is cleared.
The script’s second class is named Ball
. It serves as a template that the script will use to instantiate an object that represents a virtual 8 ball. As such, the class defines a number of properties and methods required to operate and interact with the 8 ball.
#Define a class representing the 8 ball class Ball #Define class properties for the 8 ball attr_accessor :randomNo, :greeting, :question, :goodbye #Define a method to be used to generate random answers def get_fortune randomNo = 1 + rand(6) #Assign an answer based on the randomly generated number case randomNo when 1 $prediction = "yes" when 2 $prediction = "no" when 3 $prediction = "maybe" when 4 $prediction = "hard to tell. Try again" when 5 $prediction = "unlikely" when 6 $prediction = "unknown" end end #This method displays the 8 ball greeting message def say_greeting greeting = " Welcome to the Virtual Crazy 8 Ball game!" + " Press Enter to " + "continue. : " print greeting end #This method displays the 8 ball's primary query def get_question question = "Type your question and press the Enter key. : " print question end #This method displays the 8 ball answers def tell_fortune(randomAnswer) print "The answer is " + randomAnswer + ". : " end #This method displays the 8 ball's closing message def say_goodbye goodbye = "Thanks for playing the Virtual Crazy 8 Ball game! " puts goodbye end end
The class
definition begins by specifying four class properties. The randomNo
property will be used to store a random number between 1 and 6. The greeting
property will be used to store a text string containing the game’s welcome message. The question
property will be used to store a text string that will be used to notify the player when it is time to ask a question. The goodbye
property will be used to store a text string that holds the game’s closing message.
In addition to the four class properties, the class also defines five methods. The first method is named get_fortune
and is responsible for randomly selecting one of six possible answers to the player’s questions. It accomplishes this task using the rand
method, which retrieves a random number in the form of an integer.
The rand method returns a random number between 1 and the specified upper limit. Therefore rand(6)
returns a number that is greater than 0 and less than 6. Adding 1 to this number results in a range of number from 1 to 6.
The randomly generated number, stored in randomNo
, is used in a case code block that assigns a text string representing one of six 8 ball answers to the $prediction
global variable.
A case code block is a structure for implementing conditional logic. It compares a single value, in this case randomNo
, to a series of possible matching values, as specified by one or more when
statements. You will learn more about how to work with the case code block in Chapter 4, “Implementing Conditional Logic.”
The next method defined is the say_greeting
method, which assigns a text string to the class’s greeting
property and then uses the print
method to display that string. The get_question
method comes next. It assigns a text string to the class’s question
property and then uses the print
method to display that string. The tell_fortune
method is then defined. It accepts a single argument, which is assigned to a local variable named randomAnswer
. This variable is then used to formulate a text statement that is displayed using the print
method. The last method that is defined is the say_goodbye
method, which assigns a text string to the class’s goodbye
property and then uses the print
method to display that string.
The next step in creating the Ruby Virtual Crazy 8 Ball game is to instantiate both of the custom classes that you just defined by adding the following statements to the end of the script file.
# Main Script Logic ------------------------------------------------------- Console_Screen = Screen.new #Initialize a new Screen object Eight_Ball = Ball.new #Initialize a new Ball object
As you can see, a single object is being instantiated based on each class using the new
method. A variable named Console_Screen
is used to represent the Screen
object and a variable named Eight_Ball
is used to represent the Ball
object.
Now that the scripts’ objects have been instantiated, it is time to begin working with them to interact with and control both the screen and 8 ball. To begin, add the following statements to the end of the script file.
Console_Screen.cls #Clear the display area Eight_Ball.say_greeting #Call method responsible for greeting the player Console_Screen.pause #Pause the game
The first statement executes the Screen
class’s cls
method to clear the display area. The second statement executes the Ball
class’s say_greeting
method to display a greeting message. The last statement uses the Screen
class’s pause
method to pause the game and give the player a chance to review the greeting message.
Once the player dismisses the 8 ball’s greeting message, the game sets up a loop that requires the player to provide confirmation of her intention to play. This will provide the player the chance to cancel game play in the event the game was started by accident. The script statements responsible for performing this task are shown next and should be added to the end of the script file.
answer = "" #Initialize variable that is used to control the game's first #loop #Loop until the player enters y or n and do not accept any other input. until answer == "y" || answer == "n" Console_Screen.cls #Clear the display area #Prompt the player for permission to begin the game print "Would you like to have your fortune predicted? (y/n) : " answer = STDIN.gets #Collect the player's response answer.chop! #Remove any extra characters appended to the string end
The first statement initializes a variable that will be used to control the execution of the loop that follows, in which a series of script statements have been embedded. The loop has been set up to execute until the player enters a value of y or n when prompted for confirmation. By using a loop to control the execution of the embedded statements, you are able to validate the player’s input. If the player responds by keying in anything other than a y or n, the loop will execute again, re-prompting the player to provide valid input.
A loop is a structure that permits a set of statements to be executed repeatedly a preset number of times or until a specified condition occurs. You will learn all about Ruby’s support for loops in Chapter 5, “Working with Loops.”
The next step in the development of the game is to further analyze the input that the player has provided. This is accomplished by adding the following conditional logic statements to the end of the script file.
#Analyze the player's response if answer == "n" #See if the player elected not to play else #The player has elected to play the game end
The rest of the statements in the script will either be embedded within these statements or placed immediately after them. These statements control the high-level conditional logic for the script. The statements that you embed between the first two statements will execute when the player responds with a reply of n when asked for permission to play the game. The statements that you embed between the last two statements will execute when the player responds with a reply of y.
This next script statements are to be executed when the player responds with a value of n when prompted for confirmation to play the game and therefore should be placed between the opening if
statement and the else
statement that you added to the script file in the previous step.
Console_Screen.cls #Clear the display area #Invite the player to return and play again puts "Okay, perhaps another time. "
As you can see, these statements clear the scripts and then display a text message that encourages the player to return and play again later.
The rest of the statements that you will add to the script file need to be placed between the else
and the end
statements that are responsible for outlining the script’s overall controlling logic.
#Initialize variable that is used to control the game's primary loop gameOver = "No" #Loop until the player decides to quit until gameOver == "Yes" Console_Screen.cls #Clear the display area #Call upon the method responsible for prompting the player to ask a #question Eight_Ball.get_question #Call upon the method responsible for generating an answer Eight_Ball.get_fortune Console_Screen.pause #Pause the game Console_Screen.cls #Clear the display area #Call upon the method responsible for telling the player the 8 ball's #answer Eight_Ball.tell_fortune $prediction Console_Screen.pause #Pause the game Console_Screen.cls #Clear the display area #Find out if the player wants to ask another question print "Press Enter to ask another question or type q to quit. : " answer = STDIN.gets #Collect the player's response answer.chop! #Remove any extra characters appended to the string #Analyze the player's response if answer == "q" #See if the player elected not to play gameOver = "Yes" #The player wants to quit end end Console_Screen.cls #Clear the display area #call upon the method responsible for saying goodbye to the player Eight_Ball.say_goodbye
This final set of statements is responsible for managing the overall play of the game. For starters, a variable named gameOver
is defined and assigned an initial value of "No"
. This variable is used to control the execution of the loop that follows. This loop contains the script statements that prompt the player to ask the 8 ball a question and then call upon the various object methods as required to execute Screen
and Ball
methods that control Screen
and Ball
interaction.
Upon each execution of the loop, the Ball
class’s get_question
method is executed. This method displays a message that is used to prompt the player to enter a question. Next, the get_fortune
method is called. This method is responsible for randomly selecting the 8 ball’s answer. The 8 ball’s answer is then displayed by calling on the tell_fortune
method. Finally, a message is displayed that instructs the player to either press the Enter key to ask another question or to type q and press Enter to end the game. The player’s response is then collected and assigned to the answer
variable. The String
class’s chop!
method is executed to remove the end of line character from the end of the variable’s value, after which the value is analyzed to determine whether it is equal to q. If it is, the value of gameOver
is set equal to "Yes"
, resulting in the end of the loop. Otherwise the value of gameOver
remains unchanged and the loop executes again.
Once the loop finishes executing, it is time to bring the game to a close. This is accomplished with the last three statements shown above—which clear the screen and display the 8 ball closing message, thanking the player for taking time to play.
Okay, that’s it. Go ahead and save your Ruby script. Assuming that you have followed along carefully and that you did not make any typing mistakes when keying in the code statements that make up the script file, everything should work as expected. If you run into any errors, read the resulting error messages carefully to ascertain what went wrong. If necessary, go back and review the script and look for mistyped or missing scripts statements.
If you really run into trouble when creating your version of the Ruby Virtual Crazy 8 Ball game, you can go to this book’s companion website at www.courseptr.com/ downloads and download the source code for this game and then compare it to your script file to see where things went wrong.
In this chapter, you learned how to create and work with text strings. This includes learning different ways of formulating strings as well as how to manipulate strings using different string methods. You learned how to define custom classes and then to instantiate new objects based on these classes. You learned how to define object properties and methods and to reference these properties and methods. You also learned how to store and retrieve object data using variables and the rules for formulating variables names.
Now, before you move on to Chapter 4, “Implementing Conditional Logic,” I suggest you set aside a few more minutes to work on the Ruby Virtual Crazy 8 Ball game by implementing the following list of challenges.