Welcome to Learn Ruby the hard way. This is a translation of "Learn Python The Hard Way" to teach total beginners Ruby. It's in the same style, and the content is nearly the same, but it will teach you Ruby. If you have problems email help @learncodethehardway .org.
This exercise has no code.  You should follow these instructions as exactly as possible.
This tutorial assumes that you are using Ruby version 1.9.2.
Your system might already have Ruby installed. Open up a console and try running:
$ ruby -v
ruby 1.9.2
If you don't already have Ruby installed on your system, I highly recommend using Ruby Version Manager (RVM) to install it, regardless of which OS you are running.
To complete this exercise, complete the following tasks:
Note
If you have problems with gedit, which is possible with non-English keyboard layouts, then I suggest you try Textwrangler found at http://www.barebones.com/products/textwrangler/ instead.
Here's me doing the above on my computer in Terminal. Your computer would be different, so see if you can figure out all the differences between what I did and what you should do.
Last login: Sat Apr 24 00:56:54 on ttys001
~ $ irb
ruby-1.9.2-p180 :001 >
ruby-1.9.2-p180 :002 > ^D
~ $ mkdir mystuff
~ $ cd mystuff
mystuff $ ls
# ... Use Gedit here to edit test.txt....
mystuff $ ls
test.txt
mystuff $
Note
Contributed by zhmark.
Warning
Windows is a big problem for Ruby. Sometimes you install Ruby and one computer will have no problems, and another computer will be missing important features. If you have problems, please visit: http://rubyinstaller.org/
C:\Documents and Settings\you>irb
ruby-1.9.2-p180 :001 >
ruby-1.9.2-p180 :001 > ^Z
C:\Documents and Settings\you>mkdir mystuff
C:\Documents and Settings\you>cd mystuff
... Here you would use gedit to make test.txt in mystuff ...
C:\Documents and Settings\you\mystuff>
<bunch of unimportant errors if you istalled it as non-admin - ignore them - hit Enter>
C:\Documents and Settings\you\mystuff>dir
Volume in drive C is
Volume Serial Number is 085C-7E02
Directory of C:\Documents and Settings\you\mystuff
04.05.2010 23:32 <DIR> .
04.05.2010 23:32 <DIR> ..
04.05.2010 23:32 6 test.txt
1 File(s) 6 bytes
2 Dir(s) 14 804 623 360 bytes free
C:\Documents and Settings\you\mystuff>
You will probably see a very different prompt, Ruby information, and other stuff but this is the general idea. If your system is different let us know and we'll fix it.
Linux is a varied operating system with a bunch of different ways to install software. I'm assuming if you are running Linux then you know how to install packages so here are your instructions:
$ irb
ruby-1.9.2-p180 :001 >
ruby-1.9.2-p180 :002 > ^D
$ mkdir mystuff
$
cd mystuff
# ... Use gedit here to edit test.txt ...
$ ls
test.txt
$
You will probably see a very different prompt, Ruby information, and other stuff but this is the general idea.
You are done with this exercise. This exercise might be hard for you depending on your familiarity with your computer. If it is difficult, take the time to read and study and get through it, because until you can do these very basic things you will find it difficult to get much programming done.
If a programmer tells you to use vim or emacs, tell them, "No." These editors are for when you are a better programmer. All you need right now is an editor that lets you put text into a file. We will use gedit because it is simple and the same on all computers. Professional programmers use gedit so it's good enough for you starting out.
A programmer will eventually tell you to use Mac OSX or Linux. If the programmer likes fonts and typography, they'll tell you to get a Mac OSX computer. If they like control and have a huge beard, they'll tell you to install Linux. Again, use whatever computer you have right now that works. All you need is gedit, a Terminal, and Ruby.
Finally the purpose of this setup is so you can do three things very reliably while you work on the exercises:
Anything else will only confuse you, so stick to the plan.
Remember, you should have spent a good amount of time in Exercise 0 learning how to install a text editor, run the text editor, run the Terminal, and work with both of them. If you haven't done that then do not go on. You will not have a good time. This is the only time I'll start an exercise with a warning that you should not skip or get ahead of yourself.
1 2 3 4 5 6 7 |
puts
"Hello World!"
puts
"Hello Again"
puts
"I like typing this."
puts
"This is fun."
puts
'Yay! Printing.'
puts
"I'd much rather you 'not'."
puts
'I "said" do not touch this.'
|
Type the above into a single file named ex1.rb. This is important as Ruby works best with files ending in .rb.
Then in Terminal run the file by typing:
ruby ex1.rb
If you did it right then you should see the same output I have below. If not, you have done something wrong. No, the computer is not wrong.
$ ruby ex1.rb
Hello World!
Hello Again
I like typing this.
This is fun.
Yay! Printing.
I'd much rather you 'not'.
I "said" do not touch this.
$
You may see the name of your directory before the $ which is fine, but if your output is not exactly the same, find out why and fix it.
If you have an error it will look like this:
ruby ex1.rb
ex1.rb:4: syntax error, unexpected tCONSTANT, expecting $end
puts "This is fun."
^
It's important that you can read these since you will be making many of these mistakes. Even I make many of these mistakes. Let's look at this line-by-line.
You will also have Extra Credit. The Extra Credit contains things you should try to do. If you can't, skip it and come back later.
For this exercise, try these things:
Note
An 'octothorpe' is also called a 'pound', 'hash', 'mesh', or any number of names. Pick the one that makes you chill out.
Comments are very important in your programs. They are used to tell you what something does in English, and they also are used to disable parts of your program if you need to remove them temporarily. Here's how you use comments in Ruby:
1 2 3 4 5 6 7 8 9 |
# A comment, this is so you can read your program later.
# Anything after the # is ignored by Ruby.
puts
"I could have code like this."
# and the comment after is ignored
# You can also use a comment to "disable" or comment out a piece of code:
# print "This won't run."
puts
"This will run."
|
$ ruby ex2.rb
I could have code like this.
This will run.
$
Every programming language has some kind of way of doing numbers and math. Do not worry, programmers lie frequently about being math geniuses when they really aren't. If they were math geniuses, they would be doing math, not writing ads and social network games to steal people's money.
This exercise has lots of math symbols. Let's name them right away so you know what they are called. As you type this one in, say the names. When saying them feels boring you can stop saying them. Here are the names:
+ plus
- minus
/ slash
* asterisk
% percent
< less-than
> greater-than
<= less-than-equal
>= greater-than-equal
Notice how the operations are missing? After you type in the code for this exercise, go back and figure out what each of these does and complete the table. For example, + does addition.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
puts
"I will now count my chickens:"
puts
"Hens"
,
25
+
30
/
6
puts
"Roosters"
,
100
-
25
*
3
%
4
puts
"Now I will count the eggs:"
puts
3
+
2
+
1
-
5
+
4
%
2
-
1
/
4
+
6
puts
"Is it true that 3 + 2 < 5 - 7?"
puts
3
+
2
<
5
-
7
puts
"What is 3 + 2?"
,
3
+
2
puts
"What is 5 - 7?"
,
5
-
7
puts
"Oh, that's why it's false."
puts
"How about some more."
puts
"Is it greater?"
,
5
>
-
2
puts
"Is it greater or equal?"
,
5
>=
-
2
puts
"Is it less or equal?"
,
5
<=
-
2
|
$ ruby ex3.rb
I will now count my chickens:
Hens
30
Roosters
97
Now I will count the eggs:
7
Is it true that 3 + 2 < 5 - 7?
false
What is 3 + 2?
5
What is 5 - 7?
-2
Oh, that's why it's false.
How about some more.
Is it greater?
true
Is it greater or equal?
true
Is it less or equal?
false
$
Now you can print things with puts and you can do math. The next step is to learn about variables. In programming a variable is nothing more than a name for something so you can use the name rather than the something as you code. Programmers use these variable names to make their code read more like English, and because they have lousy memories. If they didn't use good names for things in their software, they'd get lost when they tried to read their code again.
If you get stuck with this exercise, remember the tricks you have been taught so far of finding differences and focusing on details:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
cars
=
100
space_in_a_car
=
4
.
0
drivers
=
30
passengers
=
90
cars_not_driven
=
cars
-
drivers
cars_driven
=
drivers
carpool_capacity
=
cars_driven
*
space_in_a_car
average_passengers_per_car
=
passengers
/
cars_driven
puts
"There are
#{
cars
}
cars available."
puts
"There are only
#{
drivers
}
drivers available."
puts
"There will be
#{
cars_not_driven
}
empty cars today."
puts
"We can transport
#{
carpool_capacity
}
people today."
puts
"We have
#{
passengers
}
passengers to carpool today."
puts
"We need to put about
#{
average_passengers_per_car
}
in each car."
|
Note
The _ in space_in_a_car is called an underscore character. Find out how to type it if you do not already know. We use this character a lot to put an imaginary space between words in variable names.
$ ruby ex4.rb
There are 100 cars available.
There are only 30 drivers available.
There will be 70 empty cars today.
We can transport 120.0 people today.
We have 90 passengers to carpool today.
We need to put about 3 in each car.
$
When I wrote this program the first time I had a mistake, and Ruby told me about it like this:
ex4.rb:8:in `<main>': undefined local variable or method `car_pool_capacity' for main:Object (NameError)
Explain this error in your own words. Make sure you use line numbers and explain why.
Now we'll do even more typing of variables and printing them out. This time we'll use something called a "format string". Every time you put " (double-quotes) around a piece of text you have been making a string. A string is how you make something that your program might give to a human. You print them, save them to files, send them to web servers, all sorts of things.
Strings are really handy, so in this exercise you will learn how to make strings that have variables embedded in them. You embed variables inside a string by using specialized format sequences and then putting the variables at the end with a special syntax that tells Ruby, "Hey, this is a format string, put these variables in there."
As usual, just type this in even if you do not understand it and make it exactly the same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
my_name
=
'Zed A. Shaw'
my_age
=
35
# not a lie
my_height
=
74
# inches
my_weight
=
180
# lbs
my_eyes
=
'Blue'
my_teeth
=
'White'
my_hair
=
'Brown'
puts
"Let's talk about %s."
%
my_name
puts
"He's %d inches tall."
%
my_height
puts
"He's %d pounds heavy."
%
my_weight
puts
"Actually that's not too heavy."
puts
"He's got %s eyes and %s hair."
%
[
my_eyes
,
my_hair
]
puts
"His teeth are usually %s depending on the coffee."
%
my_teeth
# this line is tricky, try to get it exactly right
puts
"If I add %d, %d, and %d I get %d."
%
[
my_age
,
my_height
,
my_weight
,
my_age
+
my_height
+
my_weight
]
|
$ ruby ex5.rb
Let's talk about Zed A. Shaw.
He's 74 inches tall.
He's 180 pounds heavy.
Actually that's not too heavy.
He's got Blue eyes and Brown hair.
His teeth are usually White depending on the coffee.
If I add 35, 74, and 180 I get 289.
$
While you have already been writing strings, you still do not know what they do. In this exercise we create a bunch of variables with complex strings so you can see what they are for. First an explanation of strings.
A string is usually a bit of text you want to display to someone, or "export" out of the program you are writing. Ruby knows you want something to be a string when you put either " (double-quotes) or ' (single-quotes) around the text. You saw this many times with your use of puts when you put the text you want to go to the string inside " or ' after the puts. Then Ruby displays it.
Strings may contain the format characters you have discovered so far. You simply put the formatted variables in the string, and then a % (percent) character, followed by the variable. The only catch is that if you want multiple formats in your string to print multiple variables, you need to put them inside [ ] (brackets) separated by , (commas). It's as if you were telling me to buy you a list of items from the store and you said, "I want milk, eggs, bread, and soup." Only as a programmer we say, "[milk, eggs, bread, soup]".
Another way of injecting variables into your strings is to use something called "string interpolation", which uses the #{ } (pound and curly brace) characters. So, instead of using format strings:
name1
=
"Joe"
name2
=
"Mary"
puts
"Hello %s, where is %s?"
%
[
name1
,
name2
]
We can type:
name1
=
"Joe"
name2
=
"Mary"
puts
"Hello
#{
name1
}
, where is
#{
name2
}
?"
We will now type in a whole bunch of strings, variables, formats, and print them. You will also practice using short abbreviated variable names. Programmers love saving themselves time at your expense by using annoying cryptic variable names, so let's get you started being able to read and write them early on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
x
=
"There are
#{
10
}
types of people."
binary
=
"binary"
do_not
=
"don't"
y
=
"Those who know
#{
binary
}
and those who
#{
do_not
}
."
puts
x
puts
y
puts
"I said:
#{
x
}
."
puts
"I also said: '
#{
y
}
'."
hilarious
=
false
joke_evaluation
=
"Isn't that joke so funny?!
#{
hilarious
}
"
puts
joke_evaluation
w
=
"This is the left side of..."
e
=
"a string with a right side."
puts
w
+
e
|
There are 10 types of people.
Those who know binary and those who don't.
I said: There are 10 types of people..
I also said: 'Those who know binary and those who don't.'.
Isn't that joke so funny?! false
This is the left side of...a string with a right side.
Now we are going to do a bunch of exercises where you just type code in and make it run. I won't be explaining much since it is just more of the same. The purpose is to build up your chops. See you in a few exercises, and do not skip! Do not paste!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
puts
"Mary had a little lamb."
puts
"Its fleece was white as %s."
%
'snow'
puts
"And everywhere that Mary went."
puts
"."
*
10
# what'd that do?
end1
=
"C"
end2
=
"h"
end3
=
"e"
end4
=
"e"
end5
=
"s"
end6
=
"e"
end7
=
"B"
end8
=
"u"
end9
=
"r"
end10
=
"g"
end11
=
"e"
end12
=
"r"
# notice how we are using print instead of puts here. change it to puts
# and see what happens.
print
end1
+
end2
+
end3
+
end4
+
end5
+
end6
print
end7
+
end8
+
end9
+
end10
+
end11
+
end12
# this just is polite use of the terminal, try removing it
puts
|
$ ruby ex7.rb
Mary had a little lamb.
Its fleece was white as snow.
And everywhere that Mary went.
..........
CheeseBurger
$
For these next few exercises, you will have the exact same extra credit.
1 2 3 4 5 6 7 8 9 10 11 12 |
formatter
=
"%s %s %s %s"
puts
formatter
%
[
1
,
2
,
3
,
4
]
puts
formatter
%
[
"one"
,
"two"
,
"three"
,
"four"
]
puts
formatter
%
[
true
,
false
,
false
,
true
]
puts
formatter
%
[
formatter
,
formatter
,
formatter
,
formatter
]
puts
formatter
%
[
"I had this thing."
,
"That you could type up right."
,
"But it didn't sing."
,
"So I said goodnight."
]
|
$ ruby ex8.rb
1 2 3 4
one two three four
true false false true
%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s
I had this thing. That you could type up right. But it didn't sing. So I said goodnight.
$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Here's some new strange stuff, remember type it exactly.
days
=
"Mon Tue Wed Thu Fri Sat Sun"
months
=
"Jan
\n
Feb
\n
Mar
\n
Apr
\n
May
\n
Jun
\n
Jul
\n
Aug"
puts
"Here are the days: "
,
days
puts
"Here are the months: "
,
months
puts
<<
PARAGRAPH
There's something going on here.
With the PARAGRAPH thing
We'll be able to type as much as we like.
Even 4 lines if we want, or 5, or 6.
PARAGRAPH
|
$ ruby ex9.rb
Here are the days:
Mon Tue Wed Thu Fri Sat Sun
Here are the months:
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
There's something going on here.
With the PARAGRAPH thing
We'll be able to type as much as we like.
Even 4 lines if we want, or 5, or 6.
Do your checks of your work, write down your mistakes, try not to make them on the next exercise.
 I showed you two ways to make a string that goes across multiple lines. In the first way, I put the characters \n (back-slash n) between the names of the months. What these two characters do is put a new line character into the string at that point.
This use of the \ (back-slash) character is a way we can put difficult-to-type characters into a string. There are plenty of these "escape sequences" available for different characters you might want to put in, but there's a special one, the double back-slash which is just two of them \\. These two characters will print just one back-slash. We'll try a few of these sequences so you can see what I mean.
Another important escape sequence is to escape a single-quote ' or double-quote ". Imagine you have a string that uses double-quotes and you want to put a double-quote in for the output. If you do this "I "understand" joe." then Ruby will get confused since it will think the " around "understand" actually ends the string. You need a way to tell Ruby that the " inside the string isn't a real double-quote.
To solve this problem you escape double-quotes and single-quotes so Ruby knows to include in the string. Here's an example:
"I am 6'2
\"
tall."
# escape double-quote inside string
'I am 6\'2" tall.'
# escape single-quote inside string
The second way is by using here document syntax, which uses <<NAME and works like a string, but you also can put as many lines of text you as want until you type NAME again. We'll also play with these.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
tabby_cat
=
"
\t
I'm tabbed in."
persian_cat
=
"I'm split
\n
on a line."
backslash_cat
=
"I'm
\\
a
\\
cat."
fat_cat
=
<<
MY_HEREDOC
I'll do a list:
\t* Cat food
\t* Fishies
\t* Catnip\n\t* Grass
MY_HEREDOC
puts
tabby_cat
puts
persian_cat
puts
backslash_cat
puts
fat_cat
|
Look for the tab characters that you made. In this exercise the spacing is important to get right.
$ ruby ex10.rb
I'm tabbed in.
I'm split
on a line.
I'm \ a \ cat.
I'll do a list:
* Cat food
* Fishies
* Catnip
* Grass
$
Now it is time to pick up the pace. I have got you doing a lot of printing so that you get used to typing simple things, but those simple things are fairly boring. What we want to do now is get data into your programs. This is a little tricky because you have learn to do two things that may not make sense right away, but trust me and do it anyway. It will make sense in a few exercises.
Most of what software does is the following:
So far you have only been printing, but you haven't been able to get any input from a person, or change it. You may not even know what "input" means, so rather than talk about it, let's have you do some and see if you get it. Next exercise we'll do more to explain it.
1 2 3 4 5 6 7 8 |
print
"How old are you? "
age
=
gets
.
chomp
()
print
"How tall are you? "
height
=
gets
.
chomp
()
print
"How much do you weigh? "
weight
=
gets
.
chomp
()
puts
"So, you're
#{
age
}
old,
#{
height
}
tall and
#{
weight
}
heavy."
|
Note
Notice that we are using print instead of puts to do the prompting. print doesn't add a new line automatically, so your answer can go on the same line as the question. puts on the other hand, adds a newline automatically.
$ ruby ex11.rb
How old are you? 35
How tall are you? 6'2"
How much do you weigh? 180lbs
So, you're '35' old, '6'2"' tall and '180lbs' heavy.
$
Take a look at this code:
1 2 3 4 5 6 7 8 9 10 |
require
'open-uri'
open
(
"http://www.ruby-lang.org/en"
)
do
|
f
|
f
.
each_line
{
|
line
|
p
line
}
puts
f
.
base_uri
# <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
puts
f
.
content_type
# "text/html"
puts
f
.
charset
# "iso-8859-1"
puts
f
.
content_encoding
# []
puts
f
.
last_modified
# Thu Dec 05 02:45:02 UTC 2002
end
|
On line 1 we have what's called a "require". This is how you add features to your script from the Ruby feature set or other sources (e.g., Ruby Gems, stuff you wrote yourself). Rather than give you all the features at once, Ruby asks you to say what you plan to use. This keeps your programs small, but it also acts as documentation for other programmers who read your code later.
 I just used that name because I needed to trick you into learning what they are without jargon. Before you can continue, you need to learn their real name: libraries.
From now on we will be calling these "features" that we require libraries. I'll say things like, "You want to require the open-uri library." They are also called "modules" by other programmers, but let's just stick with libraries.
In this exercise we will cover one more input method you can use to pass variables to a script (script being another name for your .rb files). You know how you type ruby ex13.rb to run the ex13.rb file? Well the ex13.rb part of the command is called an "argument". What we'll do now is write a script that also accepts arguments.
Type this program and I'll explain it in detail:
1 2 3 4 5 6 |
first
,
second
,
third
=
ARGV
puts
"The script is called:
#{
$0
}
"
puts
"Your first variable is:
#{
first
}
"
puts
"Your second variable is:
#{
second
}
"
puts
"Your third variable is:
#{
third
}
"
|
The ARGV is the "argument variable", a very standard name in programming, that you will find used in many other languages. It's in all caps because it's a constant, meaning you shouldn't change the value once it's been assigned. This variable holds the arguments you pass to your Ruby script when you run it. In the exercises you will get to play with this more and see what happens.
Line 1 "unpacks" ARGV so that, rather than holding all the arguments, it gets assigned to three variables you can work with: first, second, and third. The name of the script itself is stored in a special variable $0, which we don't need to unpack. This may look strange, but "unpack" is probably the best word to describe what it does. It just says, "Take whatever is in ARGV, unpack it, and assign it to all of these variables on the left in order."
After that we just print them out like normal.
Run the program like this:
ruby ex13.rb first 2nd 3rd
This is what you should see when you do a few different runs with different arguments:
$ ruby ex13.rb first 2nd 3rd
The script is called: ex13.rb
Your first variable is: first
Your second variable is: 2nd
Your third variable is: 3rd
$ ruby ex13.rb cheese apples bread
The script is called: ex13.rb
Your first variable is: cheese
Your second variable is: apples
Your third variable is: bread
$ ruby ex13.rb Zed A. Shaw
The script is called: ex13.rb
Your first variable is: Zed
Your second variable is: A.
Your third variable is: Shaw
You can actually replace "first", "2nd", and "3rd" with any three things. You do not have to give these parameters either, you can give any 3 strings you want:
ruby ex13.rb stuff I like
ruby ex13.rb anything 6 7
Let's do one exercise that uses ARGV and gets.chomp() together to ask the user something specific. You will need this for the next exercise where we learn to read and write files. In this exercise we'll print a simple > prompt. This is similar to a game like Zork or Adventure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
user
=
ARGV
.
first
prompt
=
'> '
puts
"Hi
#{
user
}
, I'm the
#{
$0
}
script."
puts
"I'd like to ask you a few questions."
puts
"Do you like me
#{
user
}
?"
print
prompt
likes
=
STDIN
.
gets
.
chomp
()
puts
"Where do you live
#{
user
}
?"
print
prompt
lives
=
STDIN
.
gets
.
chomp
()
puts
"What kind of computer do you have?"
print
prompt
computer
=
STDIN
.
gets
.
chomp
()
puts
<<
MESSAGE
Alright, so you said #{likes} about liking me.
You live in #{lives}. Not sure where that is.
And you have a #{computer} computer. Nice.
MESSAGE
|
Important: Also notice that we're using STDIN.gets instead of plain 'ol gets. That is because if there is stuff in ARGV, the default gets method tries to treat the first one as a file and read from that. To read from the user's input (i.e., stdin) in such a situation, you have to use it STDIN.gets explicitly.
When you run this, remember that you have to give the script your name for the ARGV arguments.
$ ruby ex14.rb Zed
Hi Zed, I'm the ex14.rb script.
I'd like to ask you a few questions.
Do you like me Zed?
> yes
Where do you live Zed?
> America
What kind of computer do you have?
> Tandy
Alright, so you said 'yes' about liking me.
You live in 'America'. Not sure where that is.
And you have a 'Tandy' computer. Nice.
Everything you've learned about STDIN.gets and ARGV is so you can start reading files. You may have to play with this exercise the most to understand what's going on, so do it carefully and remember your checks. Working with files is an easy way to erase your work if you are not careful.
This exercise involves writing two files. One is your usual ex15.rb file that you will run, but the other is named ex15_sample.txt. This second file isn't a script but a plain text file we'll be reading in our script. Here are the contents of that file:
This is stuff I typed into a file.
It is really cool stuff.
Lots and lots of fun to have in here.
What we want to do is "open" that file in our script and print it out. However, we do not want to just "hard code" the name ex15_sample.txt into our script. "Hard coding" means putting some bit of information that should come from the user as a string right in our program. That's bad because we want it to load other files later. The solution is to use ARGV and STDIN.gets to ask the user what file they want instead of "hard coding" the file's name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
filename
=
ARGV
.
first
prompt
=
"> "
txt
=
File
.
open
(
filename
)
puts
"Here's your file:
#{
filename
}
"
puts
txt
.
read
()
puts
"Type the filename again:"
print
prompt
file_again
=
STDIN
.
gets
.
chomp
()
txt_again
=
File
.
open
(
file_again
)
puts
txt_again
.
read
()
|
A few fancy things are going on in this file, so let's break it down real quick:
Line 1-3 should be a familiar use of ARGV to get a filename and setting up the prompt. Next we have line 4 where we use a new command File.open. Right now, run ri File.open from the command line and read the instructions. Notice how like your own scripts, it takes a parameter and returns a value you can set to your own variable. You just opened a file.
Line 6 we print a little line, but on line 7 we have something very new and exciting. We call a function on txt. What you got back from open is a file, and it's also got commands you can give it. You give a file a command by using the . (dot or period), the name of the command, and parameters. Just like with File.open. The difference is that when you say txt.read() you are saying, "Hey txt! Do your read command with no parameters!"
The remainder of the file is more of the same, but we'll leave the analysis to you in the extra credit.
I made a file called "ex15_sample.txt" and ran my script.
$ ruby ex15.rb ex15_sample.txt
Here's your file 'ex15_sample.txt':
This is stuff I typed into a file.
It is really cool stuff.
Lots and lots of fun to have in here.
I'll also ask you to type it again:
> ex15_sample.txt
This is stuff I typed into a file.
It is really cool stuff.
Lots and lots of fun to have in here.
$
This is a big jump so be sure you do this extra credit as best you can before moving on.
If you did the extra credit from the last exercise you should have seen all sorts of commands (methods/functions) you can give to files. Here's the list of commands I want you to remember:
For now these are the important commands you need to know. Some of them take parameters, but we do not really care about that. You only need to remember that write takes a parameter of a string you want to write to the file.
Let's use some of this to make a simple little text editor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
filename
=
ARGV
.
first
script
=
$0
puts
"We're going to erase
#{
filename
}
."
puts
"If you don't want that, hit CTRL-C (^C)."
puts
"If you do want that, hit RETURN."
print
"? "
STDIN
.
gets
puts
"Opening the file..."
target
=
File
.
open
(
filename
,
'w'
)
puts
"Truncating the file. Goodbye!"
target
.
truncate
(
target
.
size
)
puts
"Now I'm going to ask you for three lines."
print
"line 1: "
;
line1
=
STDIN
.
gets
.
chomp
()
print
"line 2: "
;
line2
=
STDIN
.
gets
.
chomp
()
print
"line 3: "
;
line3
=
STDIN
.
gets
.
chomp
()
puts
"I'm going to write these to the file."
target
.
write
(
line1
)
target
.
write
(
"
\n
"
)
target
.
write
(
line2
)
target
.
write
(
"
\n
"
)
target
.
write
(
line3
)
target
.
write
(
"
\n
"
)
puts
"And finally, we close it."
target
.
close
()
|
That's a large file, probably the largest you have typed in. So go slow, do your checks, and make it run. One trick is to get bits of it running at a time. Get lines 1-8 running, then 5 more, then a few more, etc., until it's all done and running.
There are actually two things you will see, first the output of your new script:
$ ruby ex16.rb test.txt
We're going to erase 'test.txt'.
If you don't want that, hit CTRL-C (^C).
If you do want that, hit RETURN.
?
Opening the file...
Truncating the file. Goodbye!
Now I'm going to ask you for three lines.
line 1: To all the people out there.
line 2: I say I don't like my hair.
line 3: I need to shave it off.
I'm going to write these to the file.
And finally, we close it.
$
Now, open up the file you made (in my case test.txt) in your editor and check it out. Neat right?
Now let's do a few more things with files. We're going to actually write a Ruby script to copy one file to another. It'll be very short but will give you some ideas about other things you can do with files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from_file
,
to_file
=
ARGV
script
=
$0
puts
"Copying from
#{
from_file
}
to
#{
to_file
}
"
# we could do these two on one line too, how?
input
=
File
.
open
(
from_file
)
indata
=
input
.
read
()
puts
"The input file is
#{
indata
.
length
}
bytes long"
puts
"Does the output file exist?
#{
File
.
exists?
to_file
}
"
puts
"Ready, hit RETURN to continue, CTRL-C to abort."
STDIN
.
gets
output
=
File
.
open
(
to_file
,
'w'
)
output
.
write
(
indata
)
puts
"Alright, all done."
output
.
close
()
input
.
close
()
|
Here we used a new method called File.exists?. This returns true if a file exists, based on its name in a string as an argument. It returns false if not. We'll be using this function in the second half of this book to do lots of things.
Just like your other scripts, run this one with two arguments, the file to copy from and the file to copy it to. If we use your test.txt file from before we get this:
$ ruby ex17.rb test.txt copied.txt
Copying from test.txt to copied.txt
The input file is 81 bytes long
Does the output file exist? False
Ready, hit RETURN to continue, CTRL-C to abort.
Alright, all done.
$ cat copied.txt
To all the people out there.
I say I don't like my hair.
I need to shave it off.
$
It should work with any file. Try a bunch more and see what happens. Just be careful you do not blast an important file.
Warning
Did you see that trick I did with cat? It only works on Linux or OSX, on Windows use type to do the same thing.
Big title right? I am about to introduce you to the function! Dum dum dah! Every programmer will go on and on about functions and all the different ideas about how they work and what they do, but I will give you the simplest explanation you can use right now.
Functions do three things:
You can create a function (also called "methods") by using the word def in Ruby. I'm going to have you make four different functions that work like your scripts, and then show you how each one is related.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# this one is like your scripts with argv
def
puts_two
(
*
args
)
arg1
,
arg2
=
args
puts
"arg1:
#{
arg1
}
, arg2:
#{
arg2
}
"
end
# ok, that *args is actually pointless, we can just do this
def
puts_two_again
(
arg1
,
arg2
)
puts
"arg1:
#{
arg1
}
, arg2:
#{
arg2
}
"
end
# this just takes one argument
def
puts_one
(
arg1
)
puts
"arg1:
#{
arg1
}
"
end
# this one takes no arguments
def
puts_none
()
puts
"I got nothin'."
end
puts_two
(
"Zed"
,
"Shaw"
)
puts_two_again
(
"Zed"
,
"Shaw"
)
puts_one
(
"First!"
)
puts_none
()
|
Let's break down the first function, puts_two which is the most similar to what you already know from making scripts:
After that you have an example of how you make a function that takes one argument in puts_one.
Finally you have a function that has no arguments in puts_none.
Warning
This is very important. Do not get discouraged right now if this doesn't quite make sense. We're going to do a few exercises linking functions to your scripts and show you how to make more. For now just keep thinking "mini script" when I say "function" and keep playing with them.
If you run the above script you should see:
$ ruby ex18.rb
arg1: 'Zed', arg2: 'Shaw'
arg1: 'Zed', arg2: 'Shaw'
arg1: 'First!'
I got nothin'.
$
Right away you can see how a function works. Notice that you used your functions the way you use things like File.exists?, File.open, and other "commands". In fact, I've been tricking you because in Ruby those "commands" are just functions. This means you can make your own commands and use them in your scripts too.
Write out a function checklist for later exercises. Write these on an index card and keep it by you while you complete the rest of these exercises or until you feel you do not need it:
And when you run (aka "use" or "call") a function, check these things:
Use these two checklists on the remaining lessons until you do not need them anymore.
Finally, repeat this a few times:
"To 'run', 'call', or 'use' a function all mean the same thing."
Functions may have been a mind-blowing amount of information, but do not worry. Just keep doing these exercises and going through your checklist from the last exercise and you will eventually get it.
There is one tiny point though that you might not have realized which we'll reinforce right now: The variables in your function are not connected to the variables in your script. Here's an exercise to get you thinking about this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def
cheese_and_crackers
(
cheese_count
,
boxes_of_crackers
)
puts
"You have
#{
cheese_count
}
cheeses!"
puts
"You have
#{
boxes_of_crackers
}
boxes of crackers!"
puts
"Man that's enough for a party!"
puts
"Get a blanket."
puts
# a blank line
end
puts
"We can just give the function numbers directly:"
cheese_and_crackers
(
20
,
30
)
puts
"OR, we can use variables from our script:"
amount_of_cheese
=
10
amount_of_crackers
=
50
cheese_and_crackers
(
amount_of_cheese
,
amount_of_crackers
)
puts
"We can even do math inside too:"
cheese_and_crackers
(
10
+
20
,
5
+
6
)
puts
"And we can combine the two, variables and math:"
cheese_and_crackers
(
amount_of_cheese
+
100
,
amount_of_crackers
+
1000
)
|
This shows all different ways we're able to give our function cheese_and_crackers the values it needs to print them. We can give it straight numbers. We can give it variables. We can give it math. We can even combine math and variables.
In a way, the arguments to a function are kind of like our = character when we make a variable. In fact, if you can use = to name something, you can usually pass it to a function as an argument.
You should study the output of this script and compare it with what you think you should get for each of the examples in the script.
$ ruby ex19.rb
We can just give the function numbers directly:
You have 20 cheeses!
You have 30 boxes of crackers!
Man that's enough for a party!
Get a blanket.
OR, we can use variables from our script:
You have 10 cheeses!
You have 50 boxes of crackers!
Man that's enough for a party!
Get a blanket.
We can even do math inside too:
You have 30 cheeses!
You have 11 boxes of crackers!
Man that's enough for a party!
Get a blanket.
And we can combine the two, variables and math:
You have 110 cheeses!
You have 1050 boxes of crackers!
Man that's enough for a party!
Get a blanket.
$
Remember your checklist for functions, then do this exercise paying close attention to how functions and files can work together to make useful stuff.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
input_file
=
ARGV
[
0
]
def
print_all
(
f
)
puts
f
.
read
()
end
def
rewind
(
f
)
f
.
seek
(
0
,
IO
::
SEEK_SET
)
end
def
print_a_line
(
line_count
,
f
)
puts
"
#{
line_count
}
#{
f
.
readline
()
}
"
end
current_file
=
File
.
open
(
input_file
)
puts
"First let's print the whole file:"
puts
# a blank line
print_all
(
current_file
)
puts
"Now let's rewind, kind of like a tape."
rewind
(
current_file
)
puts
"Let's print three lines:"
current_line
=
1
print_a_line
(
current_line
,
current_file
)
current_line
=
current_line
+
1
print_a_line
(
current_line
,
current_file
)
current_line
=
current_line
+
1
print_a_line
(
current_line
,
current_file
)
|
Pay close attention to how we pass in the current line number each time we run print_a_line.
$ ruby ex20.rb test.txt
First let's print the whole file:
To all the people out there.
I say I don't like my hair.
I need to shave it off.
Now let's rewind, kind of like a tape.
Let's print three lines:
1 To all the people out there.
2 I say I don't like my hair.
3 I need to shave it off.
$
You have been using the = character to name variables and set them to numbers or strings. We're now going to blow your mind again by showing you how to use = to set variables to be a value from a function. There will be one thing to pay close attention to, but first type this in:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
def
add
(
a
,
b
)
puts
"ADDING
#{
a
}
+
#{
b
}
"
a
+
b
end
def
subtract
(
a
,
b
)
puts
"SUBTRACTING
#{
a
}
-
#{
b
}
"
a
-
b
end
def
multiply
(
a
,
b
)
puts
"MULTIPLYING
#{
a
}
*
#{
b
}
"
a
*
b
end
def
divide
(
a
,
b
)
puts
"DIVIDING
#{
a
}
/
#{
b
}
"
a
/
b
end
puts
"Let's do some math with just functions!"
age
=
add
(
30
,
5
)
height
=
subtract
(
78
,
4
)
weight
=
multiply
(
90
,
2
)
iq
=
divide
(
100
,
2
)
puts
"Age:
#{
age
}
, Height:
#{
height
}
, Weight:
#{
weight
}
, IQ:
#{
iq
}
"
# A puzzle for the extra credit, type it in anyway.
puts
"Here is a puzzle."
what
=
add
(
age
,
subtract
(
height
,
multiply
(
weight
,
divide
(
iq
,
2
))))
puts
"That becomes:
#{
what
}
Can you do it by hand?"
|
We are now doing our own math functions for add, subtract, multiply, and divide. The important thing to notice is the last line where we say a + b (in add). What this does is the following:
As with many other things in this book, you should take this real slow, break it down and try to trace what's going on. To help there's extra credit to get you to solve a puzzle and learn something cool.
$ ruby ex21.rb
Let's do some math with just functions!
ADDING 30 + 5
SUBTRACTING 78 - 4
MULTIPLYING 90 * 2
DIVIDING 100 / 2
Age: 35, Height: 74, Weight: 180, IQ: 50
Here is a puzzle.
 Figuring out puzzles like this is what makes programming fun, so I'll be giving you more little problems like this as we go.
There won't be any code in this exercise or the next one, so there's no WYSS or Extra Credit either. In fact, this exercise is like one giant Extra Credit. I'm going to have you do a form of review what you have learned so far.
First, go back through every exercise you have done so far and write down every word and symbol (another name for 'character') that you have used. Make sure your list of symbols is complete.
Next to each word or symbol, write its name and what it does. If you can't find a name for a symbol in this book, then look for it online. If you do not know what a word or symbol does, then go read about it again and try using it in some code.
You may run into a few things you just can't find out or know, so just keep those on the list and be ready to look them up when you find them.
Once you have your list, spend a few days rewriting the list and double checking that it's correct. This may get boring but push through and really nail it down.
Once you have memorized the list and what they do, then you should step it up by writing out tables of symbols, their names, and what they do from memory. When you hit some you can't recall from memory , go back and memorize them again.
Warning
The most important thing when doing this exercise is: "There is no failure, only trying."
It's important when you are doing a boring mindless memorization exercise like this to know why. It helps you focus on a goal and know the purpose of all your efforts.
In this exercise you are learning the names of symbols so that you can read source code more easily. It's similar to learning the alphabet and basic words of English, except that Ruby's alphabet has extra symbols you might not know.
Just take it slow and do not hurt your brain. Hopefully by now these symbols are natural for you so this isn't a big effort. It's best to take 15 minutes at a time with your list and then take a break. Giving your brain a rest will help you learn faster with less frustration.
You should have spent last week getting your list of symbols straight and locked in your mind. Now you get to apply this to another week reading code on the internet. This exercise will be daunting at first. I'm going to throw you in the deep end for a few days and have you just try your best to read and understand some source code from real projects. The goal isn't to get you to understand code, but to teach you the following three skills:
When you do this exercise, think of yourself as an anthropologist, trucking through a new land with just barely enough of the local language to get around and survive. Except, of course, that you will actually get out alive because the internet isn't a jungle. Anyway.
Here's what you do:
That's it. Your job is to use what you know so far and see if you can read the code and get a grasp of what it does. Try skimming the code first, and then read it in detail. Maybe also try taking very difficult parts and reading each symbol you know outloud.
Now try several other sites:
On each of these sites you may find weird files ending in .c so stick to .rb files like the ones you have written in this book.
A final fun thing to do is use the above four sources of Ruby code and type in topics you are interested in instead of "ruby". Search for "journalism", "cooking", "physics", or anything you are curious about. Chances are there's some code out there you could use right away.
You are getting to the end of this section. You should have enough Ruby "under your fingers" to move onto learning about how programming really works, but you should do some more practice. This exercise is longer and all about building up stamina. The next exercise will be similar. Do them, get them exactly right, and do your checks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
puts
"Let's practice everything."
puts
"You
\'
d need to know
\'
bout escapes with
\\
that do
\n
newlines and
\t
tabs."
poem
=
<<
MULTI_LINE_STRING
\tThe lovely world
with logic so firmly planted
cannot discern \n the needs of love
nor comprehend passion from intuition
and requires an explanation
\n\t\twhere there is none.
MULTI_LINE_STRING
puts
"--------------"
puts
poem
puts
"--------------"
five
=
10
-
2
+
3
-
6
puts
"This should be five:
#{
five
}
"
def
secret_formula
(
started
)
jelly_beans
=
started
*
500
jars
=
jelly_beans
/
1000
crates
=
jars
/
100
return
jelly_beans
,
jars
,
crates
end
start_point
=
10000
beans
,
jars
,
crates
=
secret_formula
(
start_point
)
puts
"With a starting point of:
#{
start_point
}
"
puts
"We'd have
#{
beans
}
beans,
#{
jars
}
jars, and
#{
crates
}
crates."
start_point
=
start_point
/
10
puts
"We can also do that this way:"
puts
"We'd have %s beans, %s jars, and %s crates."
%
secret_formula
(
start_point
)
|
$ ruby ex24.rb
Let's practice everything.
You'd need to know 'bout escapes with \ that do
newlines and tabs.
--------------
The lovely world
with logic so firmly planted
cannot discern
the needs of love
nor comprehend passion from intuition
and requires an explanation
where there is none.
--------------
This should be five: 5
With a starting point of: 10000
We'd have 5000000 beans, 5000 jars, and 50 crates.
We can also do that this way:
We'd have 500000 beans, 500 jars, and 5 crates.
$
We're going to do some more practice involving functions and variables to make sure you know them well. This exercise should be straight forward for you to type in, break down, and understand.
However, this exercise is a little different. You won't be running it. Instead you will import it into your Ruby interpreter and run the functions yourself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
module
Ex25
def
self
.
break_words
(
stuff
)
# This function will break up words for us.
words
=
stuff
.
split
(
' '
)
words
end
def
self
.
sort_words
(
words
)
# Sorts the words.
words
.
sort
()
end
def
self
.
print_first_word
(
words
)
# Prints the first word and shifts the others down by one.
word
=
words
.
shift
()
puts
word
end
def
self
.
print_last_word
(
words
)
# Prints the last word after popping it off the end.
word
=
words
.
pop
()
puts
word
end
def
self
.
sort_sentence
(
sentence
)
# Takes in a full sentence and returns the sorted words.
words
=
break_words
(
sentence
)
sort_words
(
words
)
end
def
self
.
print_first_and_last
(
sentence
)
# Prints the first and last words of the sentence.
words
=
break_words
(
sentence
)
print_first_word
(
words
)
print_last_word
(
words
)
end
def
self
.
print_first_and_last_sorted
(
sentence
)
# Sorts the words then prints the first and last one.
words
=
sort_sentence
(
sentence
)
print_first_word
(
words
)
print_last_word
(
words
)
end
end
|
First, run this like normal with ruby ex25.rb to find any errors you have made. Once you have found all of the errors you can and fixed them, you will then want to follow the WYSS section to complete the exercise.
In this exercise we're going to interact with your .rb file inside the Ruby interpreter (IRB) you used periodically to do calculations.
Here's what it looks like when I do it:
$ irb
irb(main):001:0> require './ex25'
=> true
irb(main):002:0> sentence = "All good things come to those who wait."
=> "All good things come to those who wait."
irb(main):003:0> words = Ex25.break_words(sentence)
=> ["All", "good", "things", "come", "to", "those", "who", "wait."]
irb(main):004:0> sorted_words = Ex25.sort_words(words)
=> ["All", "come", "good", "things", "those", "to", "wait.", "who"]
irb(main):005:0> Ex25.print_first_word(words)
All
=> nil
irb(main):006:0> Ex25.print_last_word(words)
wait.
=> nil
irb(main):007:0> Ex25.wrods
NoMethodError: undefined method `wrods' for Ex25:Module
from (irb):6
irb(main):008:0> words
=> ["good", "things", "come", "to", "those", "who"]
irb(main):009:0> Ex25.print_first_word(sorted_words)
All
=> nil
irb(main):010:0> Ex25.print_last_word(sorted_words)
who
=> nil
irb(main):011:0> sorted_words
=> ["come", "good", "things", "those", "to", "wait."]
irb(main):012:0> Ex25.sort_sentence(sentence)
=> ["All", "come", "good", "things", "those", "to", "wait.", "who"]
irb(main):013:0> Ex25.print_first_and_last(sentence)
All
wait.
=> nil
irb(main):014:0> Ex25.print_first_and_last_sorted(sentence)
All
who
=> nil
irb(main):015:0> ^D
$
Let's break this down line by line to make sure you know what's going on:
You are almost done with the first half of the book. The second half is where things get interesting. You will learn logic and be able to do useful things like make decisions.
Before you continue, I have a quiz for you. This quiz will be very hard because it requires you to fix someone else's code. When you are a programmer you often have to deal with another programmer's code, and also with their arrogance. They will very frequently claim that their code is perfect.
 A good programmer assumes, like a good scientist, that there's always some probability their code is wrong. Good programmers start from the premise that their software is broken and then work to rule out all possible ways it could be wrong before finally admitting that maybe it really is the other guy's code.
In this exercise, you will practice dealing with a bad programmer by fixing a bad programmer's code. I have poorly copied exercises 24 and 25 into a file and removed random characters and added flaws. Most of the errors are things Ruby will tell you, while some of them are math errors you should find. Others are formatting errors or spelling mistakes in the strings.
All of these errors are very common mistakes all programmers make. Even experienced ones.
Your job in this exercise is to correct this file. Use all of your skills to make this file better. Analyze it first, maybe printing it out to edit it like you would a school term paper. Fix each flaw and keep running it and fixing it until the script runs perfectly. Try not to get help, and instead if you get stuck take a break and come back to it later.
Even if this takes days to do, bust through it and make it right.
Finally, the point of this exercise isn't to type it in, but to fix an existing file. To do that, you must go to:
http://ruby.learncodethehardway.org/book/exercise26.txt
Copy-paste the code into a file named ex26.rb. This is the only time you are allowed to copy-paste.
Today is the day you start learning about logic. Up to this point you have done everything you possibly can reading and writing files, to the terminal, and have learned quite a lot of the math capabilities of Ruby.
From now on, you will be learning logic. You won't learn complex theories that academics love to study, but just the simple basic logic that makes real programs work and that real programmers need every day.
Learning logic has to come after you do some memorization. I want you to do this exercise for an entire week. Do not falter. Even if you are bored out of your mind, keep doing it. This exercise has a set of logic tables you must memorize to make it easier for you to do the later exercises.
I'm warning you this won't be fun at first. It will be downright boring and tedious but this is to teach you a very important skill you will need as a programmer.  Most of these concepts will be exciting once you get them. You will struggle with them, like wrestling a squid, then one day snap you will understand it. All that work memorizing the basics pays off big later.
Here's a tip on how to memorize something without going insane: Do a tiny bit at a time throughout the day and mark down what you need to work on most. Do not try to sit down for two hours straight and memorize these tables. This won't work. Your brain will really only retain whatever you studied in the first 15 or 30 minutes anyway.
Instead, what you should do is create a bunch of index cards with each column on the left on one side (True or False) and the column on the right on the back. You should then pull them out, see the "True or False" and be able to immediately say "True!" Keep practicing until you can do this.
Once you can do that, start writing out your own truth tables each night into a notebook. Do not just copy them. Try to do them from memory, and when you get stuck glance quickly at the ones I have here to refresh your memory. Doing this will train your brain to remember the whole table.
Do not spend more than one week on this, because you will be applying it as you go.
In Ruby we have the following terms (characters and phrases) for determining if something is "true" or "false". Logic on a computer is all about seeing if some combination of these characters and some variables is True at that point in the program.
You actually have run into these characters before, but maybe not the phrases. The phrases (and, or, not) actually work the way you expect them to, just like in English.
We will now use these characters to make the truth tables you need to memorize.
|
NOT |
True? |
|---|---|
|
not False |
True |
|
not True |
False |
|
OR |
True? |
|---|---|
|
True or False |
True |
|
True or True |
True |
|
False or True |
True |
|
False or False |
False |
|
AND |
True? |
|---|---|
|
True and False |
False |
|
True and True |
True |
|
False and True |
False |
|
False and False |
False |
|
NOT OR |
True? |
|---|---|
|
not (True or False) |
False |
|
not (True or True) |
False |
|
not (False or True) |
False |
|
not (False or False) |
True |
|
NOT AND |
True? |
|---|---|
|
not (True and False) |
True |
|
not (True and True) |
False |
|
not (False and True) |
True |
|
not (False and False) |
True |
|
!= |
True? |
|---|---|
|
1 != 0 |
True |
|
1 != 1 |
False |
|
0 != 1 |
True |
|
0 != 0 |
False |
|
== |
True? |
|---|---|
|
1 == 0 |
False |
|
1 == 1 |
True |
|
0 == 1 |
False |
|
0 == 0 |
True |
Now use these tables to write up your own cards and spend the week memorizing them. Remember though, there is no failing in this book, just trying as hard as you can each day, and then a little bit more.
The logic combinations you learned from the last exercise are called "boolean" logic expressions. Boolean logic is used everywhere in programming. They are essential fundamental parts of computation and knowing them very well is akin to knowing your scales in music.
In this exercise you will be taking the logic exercises you memorized and start trying them out in IRB. Take each of these logic problems, and write out what you think the answer will be. In each case it will be either true or false. Once you have the answers written down, you will start IRB in your terminal and type them in to confirm your answers.
1. true and true
2. false and true
3. 1 == 1 and 2 == 1
4. "test" == "test"
5. 1 == 1 or 2 != 1
6. true and 1 == 1
7. false and 0 != 0
8. true or 1 == 1
9. "test" == "testing"
10. 1 != 0 and 2 == 1
11. "test" != "testing"
12. "test" == 1
13. not (true and false)
14. not (1 == 1 and 0 != 1)
15. not (10 == 1 or 1000 == 1000)
16. not (1 != 10 or 3 == 4)
17. not ("testing" == "testing" and "Zed" == "Cool Guy")
18. 1 == 1 and not ("testing" == 1 or 1 == 0)
19. "chunky" == "bacon" and not (3 == 4 or 3 == 3)
20. 3 == 3 and not ("testing" == "testing" or "Ruby" == "Fun")
I will also give you a trick to help you figure out the more complicated ones toward the end.
Whenever you see these boolean logic statements, you can solve them easily by this simple process:
I will demonstrate with a variation on #20:
3 != 4 and not ("testing" != "test" or "Ruby" == "Ruby")
Here's me going through each of the steps and showing you the translation until I've boiled it down to a single result:
With that we're done and know the result is false.
Warning
The more complicated ones may seem very hard at first. You should be able to give a good first stab at solving them, but do not get discouraged. I'm just getting you primed for more of these "logic gymnastics" so that later cool stuff is much easier. Just stick with it, and keep track of what you get wrong, but do not worry that it's not getting in your head quite yet. It'll come.
After you have tried to guess at these, this is what your session with IRB might look like:
$ irb
ruby-1.9.2-p180 :001 > true and true
=> true
ruby-1.9.2-p180 :002 > 1 == 1 and 2 == 2
=> true
Here is the next script of Ruby you will enter, which introduces you to the if-statement. Type this in, make it run exactly right, and then we'll try see if your practice has paid off.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
people
=
20
cats
=
30
dogs
=
15
if
people
<
cats
puts
"Too many cats! The world is doomed!"
end
if
people
>
cats
puts
"Not many cats! The world is saved!"
end
if
people
<
dogs
puts
"The world is drooled on!"
end
if
people
>
dogs
puts
"The world is dry!"
end
dogs
+=
5
if
people
>=
dogs
puts
"People are greater than or equal to dogs."
end
if
people
<=
dogs
puts
"People are less than or equal to dogs."
end
if
people
==
dogs
puts
"People are dogs."
end
|
$ ruby ex29.rb
Too many cats! The world is doomed!
The world is dry!
People are greater than or equal to dogs.
People are less than or equal to dogs.
People are dogs.
$
In this extra credit, try to guess what you think the if-statement is and what it does. Try to answer these questions in your own words before moving onto the next exercise:
In the last exercise you worked out some if-statements, and then tried to guess what they are and how they work. Before you learn more I'll explain what everything is by answering the questions you had from extra credit. You did the extra credit right?
Compare my answers to your answers, and make sure you really understand the concept of a "block" of code. This is important for when you do the next exercise where you write all the parts of if-statements that you can use.
Type this one in and make it work too.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
people
=
30
cars
=
40
buses
=
15
if
cars
>
people
puts
"We should take the cars."
elsif
cars
<
people
puts
"We should not take the cars."
else
puts
"We can't decide."
end
if
buses
>
cars
puts
"That's too many buses."
elsif
buses
<
cars
puts
"Maybe we could take the buses."
else
puts
"We still can't decide."
end
if
people
>
buses
puts
"Alright, let's just take the buses."
else
puts
"Fine, let's stay home then."
end
|
$ ruby ex30.rb
We should take the cars.
Maybe we could take the buses.
Alright, let's just take the buses.
$
In the first half of this book you mostly just printed out things and called functions, but everything was basically in a straight line. Your scripts ran starting at the top, and went to the bottom where they ended. If you made a function you could run that function later, but it still didn't have the kind of branching you need to really make decisions. Now that you have if, else, and elsif you can start to make scripts that decide things.
In the last script you wrote out a simple set of tests asking some questions. In this script you will ask the user questions and make decisions based on their answers. Write this script, and then play with it quite a lot to figure it out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
def
prompt
print
"> "
end
puts
"You enter a dark room with two doors. Do you go through door #1 or door #2?"
prompt
;
door
=
gets
.
chomp
if
door
==
"1"
puts
"There's a giant bear here eating a cheese cake. What do you do?"
puts
"1. Take the cake."
puts
"2. Scream at the bear."
 Good job!"
 Good job!"
else
puts
"Well, doing
#{
bear
}
is probably better. Bear runs away."
end
elsif
door
==
"2"
puts
"You stare into the endless abyss at Cthuhlu's retina."
puts
"1. Blueberries."
puts
"2. Yellow jacket clothespins."
puts
"3. Understanding revolvers yelling melodies."
prompt
;
insanity
=
gets
.
chomp
if
insanity
==
"1"
or
insanity
==
"2"
puts
"Your body survives powered by a mind of jello. Good job!"
else
puts
"The insanity rots your eyes into a pool of muck. Good job!"
 Good job!"
end
|
A key point here is that you are now putting the if-statements inside if-statements as code that can run. This is very powerful and can be used to create "nested" decisions, where one branch leads to another and another.
Make sure you understand this concept of if-statements inside if-statements. In fact, do the extra credit to really nail it.
Here is me playing this little adventure game. I do not do so well.
$ ruby ex31.rb
You enter a dark room with two doors.  What do you do?
1. Take the cake.
2. Scream at the bear.
> 2
The bear eats your legs off. Good job!
$ ruby ex31.rb
You enter a dark room with two doors.  What do you do?
1. Take the cake.
2.  Good job!
$ ruby ex31.rb
You enter a dark room with two doors. Do you go through door #1 or door #2?
> 2
You stare into the endless abyss at Cthuhlu's retina.
1. Blueberries.
2. Yellow jacket clothespins.
3. Understanding revolvers yelling melodies.
> 1
Your body survives powered by a mind of jello. Good job!
$ ruby ex31.rb
You enter a dark room with two doors. Do you go through door #1 or door #2?
> 2
You stare into the endless abyss at Cthuhlu's retina.
1. Blueberries.
2. Yellow jacket clothespins.
3. Understanding revolvers yelling melodies.
> 3
The insanity rots your eyes into a pool of muck. Good job!
$ ruby ex31.rb
You enter a dark room with two doors.  Good job!
$ ruby ex31.rb
You enter a dark room with two doors.  What do you do?
1. Take the cake.
2. Scream at the bear.
> apples
Well, doing apples is probably better. Bear runs away.
Make new parts of the game and change what decisions people can make. Expand the game out as much as you can before it gets ridiculous.
You should now be able to do some programs that are much more interesting. If you have been keeping up, you should realize that now you can combine all the other things you have learned with if-statements and boolean expressions to make your programs do smart things.
However, programs also need to do repetitive things very quickly. We are going to use a for-loop in this exercise to build and print various arrays. When you do the exercise, you will start to figure out what they are. I won't tell you right now. You have to figure it out.
Before you can use a for-loop, you need a way to store the results of loops somewhere. The best way to do this is with an array. An array is a container of things that are organized in order. It's not complicated; you just have to learn a new syntax. First, there's how you make an array:
hairs
=
[
'brown'
,
'blond'
,
'red'
]
eyes
=
[
'brown'
,
'blue'
,
'green'
]
weights
=
[
1
,
2
,
3
,
4
]
What you do is start the array with the [ (left-bracket) which "opens" the array. Then you put each item you want in the array separated by commas, just like when you did function arguments. Lastly you end the array with a ] (right-bracket) to indicate that it's over. Ruby then takes this array and all its contents, and assigns them to the variable.
Warning
This is where things get tricky for people who can't program.  Remember in the last exercise where you put if-statements inside if-statements?  In programming this is all over the place. You will find functions that call other functions that have if-statements that have arrays with arrays inside arrays. If you see a structure like this that you can't figure out, take out pencil and paper and break it down manually bit by bit until you understand it.
We now will build some arrays using some loops and print them out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
the_count
=
[
1
,
2
,
3
,
4
,
5
]
fruits
=
[
'apples'
,
'oranges'
,
'pears'
,
'apricots'
]
change
=
[
1
,
'pennies'
,
2
,
'dimes'
,
3
,
'quarters'
]
# this first kind of for-loop goes through an array
for
number
in
the_count
puts
"This is count
#{
number
}
"
end
# same as above, but using a block instead
fruits
.
each
do
|
fruit
|
puts
"A fruit of type:
#{
fruit
}
"
end
# also we can go through mixed arrays too
for
i
in
change
puts
"I got
#{
i
}
"
end
# we can also build arrays, first start with an empty one
elements
=
[]
# then use a range object to do 0 to 5 counts
for
i
in
(
0
.
.
5
)
puts
"Adding
#{
i
}
to the list."
# push is a function that arrays understand
elements
.
push
(
i
)
end
# now we can puts them out too
for
i
in
elements
puts
"Element was:
#{
i
}
"
end
|
$ ruby ex32.rb
This is count 1
This is count 2
This is count 3
This is count 4
This is count 5
A fruit of type: apples
A fruit of type: oranges
A fruit of type: pears
A fruit of type: apricots
I got 1
I got 'pennies'
I got 2
I got 'dimes'
I got 3
I got 'quarters'
Adding 0 to the list.
Adding 1 to the list.
Adding 2 to the list.
Adding 3 to the list.
Adding 4 to the list.
Adding 5 to the list.
 A while-loop will keep executing the code block under it as long as a boolean expression is True.
Wait, you have been keeping up with the terminology right? That if we write a statement such as if items > 5 or for fruit in fruits we are starting a code block. Then we indent the lines that follow, which are said to be within the block, until we reach an end statement, which closes the block. This is all about structuring your programs so that Ruby knows what you mean. If you do not get that idea then go back and do some more work with if-statements, functions, and the for-loop until you get it.

Back to while-loops. What they do is simply do a test like an if-statement, but instead of running the code block once, they jump back to the "top" where the while is, and repeat. It keeps doing this until the expression is False.
Here's the problem with while-loops: sometimes they do not stop. This is great if your intention is to just keep looping until the end of the universe.
To avoid these problems, there's some rules to follow:
In this exercise, you will learn the while-loop by doing the above three things:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
i
=
0
numbers
=
[]
while
i
<
6
puts
"At the top i is
#{
i
}
"
numbers
.
push
(
i
)
i
=
i
+
1
puts
"Numbers now:
#{
numbers
}
"
puts
"At the bottom i is
#{
i
}
"
end
puts
"The numbers: "
for
num
in
numbers
puts
num
end
|
$ ruby ex33.rb
At the top i is 0
Numbers now: [0]
At the bottom i is 1
At the top i is 1
Numbers now: [0, 1]
At the bottom i is 2
At the top i is 2
Numbers now: [0, 1, 2]
At the bottom i is 3
At the top i is 3
Numbers now: [0, 1, 2, 3]
At the bottom i is 4
At the top i is 4
Numbers now: [0, 1, 2, 3, 4]
At the bottom i is 5
At the top i is 5
Numbers now: [0, 1, 2, 3, 4, 5]
At the bottom i is 6
The numbers:
0
1
2
3
4
5
If at any time that you are doing this it goes crazy (it probably will), just hold down CTRL and hit c (CTRL-c) and the program will abort.
Arrays are pretty useful, but unless you can get at the things in them they aren't all that great. You can already go through the elements of a list in order, but what if you want say, the 5th element? You need to know how to access the elements of an array. Here's how you would access the first element of an array:
animals
=
[
'bear'
,
'tiger'
,
'penguin'
,
'zebra'
]
bear
=
animals
[
0
]
You take a list of animals, and then you get the first one using 0?! How does that work? Because of the way math works, Ruby start its lists at 0 rather than 1. It seems weird, but there's many advantages to this, even though it is mostly arbitrary.
The best way to explain why is by showing you the difference between how you use numbers and how programmers use numbers.
Imagine you are watching the four animals in our array above ( ['bear', 'tiger', 'penguin', 'zebra']) run in a race. They win in the order we have them in this array. The race was really exciting because the animals didn't eat each other and somehow managed to run a race. Your friend, however, shows up late and wants to know who won. Does your friend say, "Hey, who came in zeroth?" No, he says, "Hey, who came in first?"
This is because the order of the animals is important. You can't have the second animal without the first animal, and can't have the third without the second. It's also impossible to have a "zeroth" animal since zero means nothing. How can you have a nothing win a race? It just doesn't make sense. We call these kinds of numbers "ordinal" numbers, because they indicate an ordering of things.
Programmers, however, can't think this way because they can pick any element out of a list at any point. To a programmer, the above list is more like a deck of cards. If they want the tiger, they grab it. If they want the zebra, they can take it too. This need to pull elements out of lists at random means that they need a way to indicate elements consistently by an address, or an "index", and the best way to do that is to start the indices at 0. Trust me on this, the math is way easier for these kinds of accesses. This kind of number is a "cardinal" number and means you can pick at random, so there needs to be a 0 element.
So, how does this help you work with arrays? Simple, every time you say to yourself, "I want the 3rd animal," you translate this "ordinal" number to a "cardinal" number by subtracting 1. The "3rd" animal is at index 2 and is the penguin. You have to do this because you have spent your whole life using ordinal numbers, and now you have to think in cardinal. Just subtract 1 and you will be good.
Remember: ordinal == ordered, 1st; cardinal == cards at random, 0.
Let's practice this. Take this list of animals, and follow the exercises where I tell you to write down what animal you get for that ordinal or cardinal number. Remember if I say "first", "second", etc. then I'm using ordinal, so subtract 1. If I give you cardinal (0, 1, 2) then use it directly.
animals
=
[
'bear'
,
'python'
,
'peacock'
,
'kangaroo'
,
'whale'
,
'platypus'
]
The animal at 1. The 3rd animal. The 1st animal. The animal at 3. The 5th animal. The animal at 2. The 6th animal. The animal at 4.
For each of these, write out a full sentence of the form: "The 1st animal is at 0 and is a bear." Then say it backwards, "The animal at 0 is the 1st animal and is a bear."
Use your Ruby to check your answers.
Hint: Ruby has also a few convenience methods for accessing particular elements in an array: animals.first and animals.last
Warning
Programmers will tell you to read this guy named "Dijkstra" on this subject. I recommend you avoid his writings on this unless you enjoy being yelled at by someone who stopped programming at the same time programming started.
You have learned to do if-statements, functions, and arrays. Now it's time to bend your mind. Type this in, and see if you can figure out what it's doing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
def
prompt
()
print
"> "
end
def
gold_room
()
puts
"This room is full of gold. How much do you take?"
prompt
;
next_move
=
gets
.
chomp
if
next_move
.
include?
"0"
or
next_move
.
include?
"1"
how_much
=
next_move
.
to_i
()
else
dead
(
"Man, learn to type a number."
)
end
if
how_much
<
50
puts
"Nice, you're not greedy, you win!"
Process
.
exit
(
0
)
else
dead
(
"You greedy bastard!"
)
end
end
def
bear_room
()
puts
"There is a bear here."

puts
"How are you going to move the bear?"
bear_moved
=
false
while
true
prompt
;
next_move
=
gets
.
chomp
if
next_move
==
"take honey"
dead
(
"The bear looks at you then slaps your face off."
)
elsif
next_move
==
"taunt bear"
and
not
bear_moved
puts
"The bear has moved from the door. You can go through it now."

)
elsif
next_move
==
"open door"
and
bear_moved
gold_room
()
else
puts
"I got no idea what that means."
end
end
end
def
cthulu_room
()
puts
"Here you see the great evil Cthulu."

prompt
;
next_move
=
gets
.
chomp
if
next_move
.
include?
"flee"
start
()
elsif
next_move
.
include?
"head"
dead
(
"Well that was tasty!"
)
else
cthulu_room
()
end
end
def
dead
(
why
)
puts
"
#{
why
}
Good job!"
Process
.
exit
(
0
)
end
def
start
()
puts
"You are in a dark room."
puts
"There is a door to your right and left."
puts
"Which one do you take?"
prompt
;
next_move
=
gets
.
chomp
if
next_move
==
"left"
bear_room
()
elsif
next_move
==
"right"
cthulu_room
()
else
dead
(
"You stumble around the room until you starve."
)
end
end
start
()
|
Here's me playing the game:
$ ruby ex35.rb
You are in a dark room.
There is a door to your right and left.
Which one do you take?
> left
There is a bear here.
The bear has a bunch of honey.

How are you going to move the bear?
> taunt bear
The bear has moved from the door. You can go through it now.
> open door
This room is full of gold. How much do you take?
> asf
Man, learn to type a number. Good job!
$
Now that you know if-statements, I'm going to give you some rules for for-loops and while-loops that will keep you out of trouble. I'm also going to give you some tips on debugging so that you can figure out problems with your program.
If you follow these simple rules, you will start writing better code than most programmers. Go back to the last exercise and see if I followed all of these rules. If not, fix it.

Now write a similar game to the one that I created in the last exercise. It can be any kind of game you want in the same flavor. Spend a week on it making it as interesting as possible. For extra credit, use arrays, functions, and modules (remember those from Ex. 13?) as much as possible, and find as many new pieces of Ruby as you can to make the game work.
There is one catch though, write up your idea for the game first. Before you start coding you must write up a map for your game. Create the rooms, monsters, and traps that the player must go through on paper before you code.
Once you have your map, try to code it up. If you find problems with the map then adjust it and make the code match.
One final word of advice: Every programmer becomes paralyzed by irrational fear starting a new large project. They then use procrastination to avoid confronting this fear and end up not getting their program working or even started. I do this. Everyone does this. The best way to avoid this is to make a list of things you should do, and then do them one at a time.
Just start doing it, do a small version, make it bigger, keep a list of things to do, and do them.
It's time to review the symbols and Ruby words you know, and to try to pick up a few more for the next few lessons. What I've done here is written out all the Ruby symbols and keywords that are important to know.
In this lesson take each keyword, and first try to write out what it does from memory. Next, search online for it and see what it really does. It may be hard because some of these are going to be impossible to search for, but keep trying.
If you get one of these wrong from memory, write up an index card with the correct definition and try to "correct" your memory. If you just didn't know about it, write it down, and save it for later.
Finally, use each of these in a small Ruby program, or as many as you can get done. The key here is to find out what the symbol does, make sure you got it right, correct it if you do not, then use it to lock it in.
For data types, write out what makes up each one. For example, with strings write out how you create a string. For numbers write out a few numbers.
String Escapes Sequences For string escape sequences, use them in strings to make sure they do what you think they do.
Operators Some of these may be unfamiliar to you, but look them up anyway. Find out what they do, and if you still can't figure it out, save it for later.
Spend about a week on this, but if you finish faster that's great. The point is to try to get coverage on all these symbols and make sure they are locked in your head. What's also important is to find out what you do not know so you can fix it later.
Now go find some Ruby code to read. You should be reading any Ruby code you can and trying to steal ideas that you find. You actually should have enough knowledge to be able to read, but maybe not understand what the code does. What I'm going to teach you in this lesson is how to apply things you have learned to understand other people's code.
First, print out the code you want to understand. Yes, print it out, because your eyes and brain are more used to reading paper than computer screens. Make sure you only print a few pages at a time.
Second, go through your printout and take notes of the following:
Third, once you have all of this marked up, try to explain it to yourself by writing comments as you go. Explain the functions, how they are used, what variables are involved, anything you can to figure this code out.
Lastly, on all of the difficult parts, trace the values of each variable line by line, function by function. In fact, do another printout and write in the margin the value of each variable that you need to "trace".
Once you have a good idea of what the code does, go back to the computer and read it again to see if you find new things. Keep finding more code and doing this until you do not need the printouts anymore.
You have learned about arrays. When you learned about while-loops you "pushed" numbers onto the end of an array and printed them out. There was also extra credit where you were supposed to find all the other things you can do to arrays in the Ruby documentation. That was a while back, so go find in the book where you did that and review if you do not know what I'm talking about.
Found it? Remember it? Good. When you did this you had a list, and you "called" the function push on it. However, you may not really understand what's going on so let's see what we can do to lists, and how doing things with "on" them works.
When you type Ruby code that reads mystuff.push('hello') you are actually setting off a chain of events inside Ruby to cause something to happen to the mystuff array. Here's how it works:
That might be a lot to take in, but we're going to spend a few exercises getting this concept firm in your brain. To kick things off, here's an exercise that mixes strings and lists for all kinds of fun.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
ten_things
=
"Apples Oranges Crows Telephone Light Sugar"
puts
"Wait there's not 10 things in that list, let's fix that."
stuff
=
ten_things
.
split
(
' '
)
more_stuff
=
%w(Day Night Song Frisbee Corn Banana Girl Boy)
while
stuff
.
length
!=
10
next_one
=
more_stuff
.
pop
()
puts
"Adding:
#{
next_one
}
"
stuff
.
push
(
next_one
)
puts
"There's
#{
stuff
.
length
}
items now."
end
puts
"There we go:
#{
stuff
}
"
puts
"Let's do some things with stuff."
puts
stuff
[
1
]
puts
stuff
[-
1
]
# whoa! fancy
puts
stuff
.
pop
()
puts
stuff
.
join
(
' '
)
# what? cool!
puts
stuff
.
values_at
(
3
,
5
)
.
join
(
'#'
)
# super stellar!
|
$ ruby ex39.rb
Wait there's not 10 things in that list, let's fix that.
Adding: Boy
There's 7 items now.
Adding: Girl
There's 8 items now.
Adding: Banana
There's 9 items now.
Adding: Corn
There's 10 items now.
There we go: ["Apples", "Oranges", "Crows", "Telephone", "Light", "Sugar", "Boy", "Girl", "Banana", "Corn"]
Let's do some things with stuff.
Oranges
Corn
Corn
Apples Oranges Crows Telephone Light Sugar Boy Girl Banana
Telephone#Sugar
$
Now I have to hurt you with another container you can use, because once you learn this container a massive world of ultra-cool will be yours. It is the most useful container ever: the hash.
Ruby calls them "hashes", other languages call them, "dictionaries". I tend to use both names, but it doesn't matter. What does matter is what they do when compared to arrays. You see, an array lets you do this:
ruby
-
1
.
9
.
2
-
p180
:
015
>
things
=
[
'a'
,
'b'
,
'c'
,
'd'
]
=>
[
"a"
,
"b"
,
"c"
,
"d"
]
ruby
-
1
.
9
.
2
-
p180
:
016
>
print
things
[
1
]
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
017
>
things
[
1
]
=
'z'
=>
"z"
ruby
-
1
.
9
.
2
-
p180
:
01
8
>
print
things
[
1
]
z
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
01
9
>
print
things
[
"a"
,
"z"
,
"c"
,
"d"
]
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
020
>
You can use numbers to "index" into an array, meaning you can use numbers to find out what's in arrays. You should know this by now, but what a hash does is let you use anything, not just numbers. Yes, a hash associates one thing to another, no matter what it is. Take a look:
ruby
-
1
.
9
.
2
-
p180
:
001
>
stuff
=
{
:name
=>
"Rob"
,
:age
=>
30
,
:height
=>
5
\
*
12
+
10
}
=>
{
:name
=>
"Rob"
,
:age
=>
30
,
:height
=>
70
}
ruby
-
1
.
9
.
2
-
p180
:
002
>
puts
stuff
[
:name
]
Rob
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
003
>
puts
stuff
[
:age
]
30
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
004
>
puts
stuff
[
:height
]
70
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
005
>
stuff
[
:city
]
=
"New York"
=>
"New York"
ruby
-
1
.
9
.
2
-
p180
:
006
>
puts
stuff
[
:city
]
New
York
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
007
>
You will see that instead of just numbers we're using symbols, which are just lightweight strings (think name tags) in Ruby, to say what we want from the stuff hash. We can also put new things into the hash with symbols. It doesn't have to be symbols though, we can also do this:
ruby
-
1
.
9
.
2
-
p180
:
004
>
stuff
[
1
]
=
"Wow"
=>
"Wow"
ruby
-
1
.
9
.
2
-
p180
:
005
>
stuff
[
2
]
=
"Neato"
=>
"Neato"
ruby
-
1
.
9
.
2
-
p180
:
006
>
puts
stuff
[
1
]
Wow
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
007
>
puts
stuff
[
2
]
Neato
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
00
8
>
puts
stuff
{
:name
=>
"Rob"
,
:age
=>
30
,
:height
=>
70
,
:city
=>
"New York"
,
1
=>
"Wow"
,
2
=>
"Neato"
}
=>
nil
ruby
-
1
.
9
.
2
-
p180
:
00
9
>
In this one I just used numbers. I could use anything. Well almost but just pretend you can use anything for now.
Of course, a hash that you can only put things in is pretty stupid, so here's how you delete things, with the delete function:
ruby
-
1
.
9
.
2
-
p180
:
00
9
>
stuff
.
delete
(
:city
)
=>
"New York"
ruby
-
1
.
9
.
2
-
p180
:
010
>
stuff
.
delete
(
1
)
=>
"Wow"
ruby
-
1
.
9
.
2
-
p180
:
011
>
stuff
.
delete
(
2
)
=>
"Neato"
ruby
-
1
.
9
.
2
-
p180
:
012
>
stuff
=>
{
:name
=>
"Rob"
,
:age
=>
30
,
:height
=>
70
}
ruby
-
1
.
9
.
2
-
p180
:
013
>
We'll now do an exercise that you must study very carefully. I want you to type this exercise in and try to understand what's going on. It is a very interesting exercise that will hopefully make a big light turn on in your head very soon.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
cities
=
{
'CA'
=>
'San Francisco'
,
'MI'
=>
'Detroit'
,
'FL'
=>
'Jacksonville'
}
cities
[
'NY'
]
=
'New York'
cities
[
'OR'
]
=
'Portland'
def
find_city
(
map
,
state
)
if
map
.
include?
state
return
map
[
state
]
else
return
"Not found."
end
end
# ok pay attention!
cities
[
:find
]
=
method
(
:find_city
)
while
true
print
"State? (ENTER to quit) "
state
=
gets
.
chomp
break
if
state
.
empty?
# this line is the most important ever! study!
puts
cities
[
:find
].
call
(
cities
,
state
)
end
|
$ ruby ex40.rb
State? (ENTER to quit) > CA
San Francisco
State? (ENTER to quit) > FL
Jacksonville
State? (ENTER to quit) > O
Not found.
State? (ENTER to quit) > OR
Portland
State? (ENTER to quit) > VT
Not found.
State? (ENTER to quit) >
Did you figure out the secret of the method in the hash from the last exercise? Can you explain it to yourself? I'll explain it and you can compare your explanation with mine. Here are the lines of code we are talking about:
cities
[
:find
]
=
method
(
:find
\
_city
)
puts
cities
[
:find
].
call
(
cities
,
state
)
Remember that code can be stored in variables too. In order to store a block of code in a variable, we create something called a proc, which is short for procedure. In this code, first we are calling Ruby's built-in method called method, which returns a proc version of the find_city method, which we then store in the hash cities with the key :find. This is the same as all the others where we set states to some cities, but in this case it's actually the proc.
Alright, so once we know that find_city is in the hash at :find, that means we can do work with it. The 2nd line of code (used later in the previous exercise) can be broken down like this:
Here's a trick. Sometimes these things read better in English if you read the code backwards. This is how I would do it for that same line (remember backwards):
Here's another way to read it, this time "inside-out".
After decades of programming I don't even think about these three ways to read code. I just glance at it and know what it means. I can even glance at a whole screen of code, and all the bugs and errors jump out at me. That took an incredibly long time and quite a bit more study than is sane. To get that way, I learned these three ways of reading most any programming language:
Try them out when you have a difficult statement to figure out.
Now type in your next exercise, then go over it. This one is gonna be fun.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
def
prompt
()
print
"> "
end
def
death
()
quips
=
[
"You died.
]
puts
quips
[
rand
(
quips
.
length
())
]
Process
.
exit
(
1
)
end
def
central_corridor
()
puts
"The Gothons of Planet Percal #25 have invaded your ship and destroyed"
puts
"your entire crew. You are the last surviving member and your last"
puts
"mission is to get the neutron destruct bomb from the Weapons Armory,"
puts
"put it in the bridge, and blow the ship up after getting into an "
puts
"escape pod."
 He's blocking the door to the"
puts
"Armory and about to pull a weapon to blast you."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"shoot!"

puts
"His clown costume is flowing and moving around his body, which throws"
puts
"off your aim. Your laser hits his costume but misses him entirely.  Then he eats you."
return
:death
elsif
action
==
"dodge!"
puts
"Like a world class boxer you dodge, weave, slip and slide right"
puts
"as the Gothon's blaster cranks a laser past your head."

puts
"The Gothon stops, tries not to laugh, then busts out laughing and can't move."

return
:laser_weapon_armory
else
puts
"DOES NOT COMPUTE!"
return
:central_corridor
end
end
def
laser_weapon_armory
()
puts
"You do a dive roll into the Weapon Armory, crouch and scan the room"
puts
"for more Gothons that might be hiding. It's dead quiet, too quiet."
puts
"You stand up and run to the far side of the room and find the"
puts
"neutron bomb in its container. There's a keypad lock on the box"
puts
"and you need the code to get the bomb out. If you get the code"
puts
"wrong 10 times then the lock closes forever and you can't"
puts
"get the bomb. The code is 3 digits."
code
=
"%s%s%s"
%
[
rand
(
9
)
+
1
,
rand
(
9
)
+
1
,
rand
(
9
)
+
1
]
print
"[keypad]> "
guess
=
gets
.
chomp
()
guesses
=
0
while
guess
!=
code
and
guesses
<
10
puts
"BZZZZEDDD!"
guesses
+=
1
print
"[keypad]> "
guess
=
gets
.
chomp
()
end
if
guess
==
code
puts
"The container clicks open and the seal breaks, letting gas out."
puts
"You grab the neutron bomb and run as fast as you can to the"
puts
"bridge where you must place it in the right spot."
return
:the_bridge
else
puts
"The lock buzzes one last time and then you hear a sickening"
puts
"melting sound as the mechanism is fused together."

return
:death
end
end
def
the_bridge
()
puts
"You burst onto the Bridge with the netron destruct bomb"
puts
"under your arm and surprise 5 Gothons who are trying to"
puts
"take control of the ship. Each of them has an even uglier"
puts
"clown costume than the last. They haven't pulled their"
puts
"weapons out yet, as they see the active bomb under your"
puts
"arm and don't want to set it off."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"throw the bomb"
puts
"In a panic you throw the bomb at the group of Gothons"
puts
"and make a leap for the door.

return
:death
elsif
action
==
"slowly place the bomb"
puts
"You point your blaster at the bomb under your arm"
puts
"and the Gothons put their hands up and start to sweat."
puts
"You inch backward to the door, open it, and then carefully"
puts
"place the bomb on the floor, pointing your blaster at it."
puts
"You then jump back through the door, punch the close button"
puts
"and blast the lock so the Gothons can't get out."
puts
"Now that the bomb is placed you run to the escape pod to"
puts
"get off this tin can."
return
:escape_pod
else
puts
"DOES NOT COMPUTE!"
return
:the_bridge
end
end
def
escape_pod
()
puts
"You rush through the ship desperately trying to make it to"
puts
"the escape pod before the whole ship explodes. It seems like"
puts
"hardly any Gothons are on the ship, so your run is clear of"
puts
"interference. You get to the chamber with the escape pods, and"
puts
"now need to pick one to take. Some of them could be damaged"
puts
"but you don't have time to look. There's 5 pods, which one"
puts
"do you take?"
good_pod
=
rand
(
5
)
+
1
print
"[pod #]>"
guess
=
gets
.
chomp
()
if
guess
.
to_i
!=
good_pod
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod escapes out into the void of space, then"
puts
"implodes as the hull ruptures, crushing your body"
puts
"into jam jelly."
return
:death
else
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod easily slides out into space heading to"
puts
"the planet below. As it flies to the planet, you look"
puts
"back and see your ship implode then explode like a"
puts
"bright star, taking out the Gothon ship at the same"
puts
"time. You won!"
Process
.
exit
(
0
)
end
end
ROOMS
=
{
:death
=>
method
(
:death
),
:central_corridor
=>
method
(
:central_corridor
),
:laser_weapon_armory
=>
method
(
:laser_weapon_armory
),
:the_bridge
=>
method
(
:the_bridge
),
:escape_pod
=>
method
(
:escape_pod
)
}
def
runner
(
map
,
start
)
next_one
=
start
while
true
room
=
map
[
next_one
]
puts
"
\n
--------"
next_one
=
room
.
call
()
end
end
runner
(
ROOMS
,
:central_corridor
)
|
$ ruby ex41.rb
--------
The Gothons of Planet Percal #25 have invaded your ship and destroyed
your entire crew. You are the last surviving member and your last
mission is to get the neutron destruct bomb from the Weapons Armory,
put it in the bridge, and blow the ship up after getting into an
escape pod.
 He's blocking the door to the
Armory and about to pull a weapon to blast you.
> dodge!
Like a world class boxer you dodge, weave, slip and slide right
as the Gothon's blaster cranks a laser past your head.

 You are the last surviving member and your last
mission is to get the neutron destruct bomb from the Weapons Armory,
put it in the bridge, and blow the ship up after getting into an
escape pod.
 He's blocking the door to the
Armory and about to pull a weapon to blast you.
> tell a joke
Lucky for you they made you learn Gothon insults in the academy.

The Gothon stops, tries not to laugh, then busts out laughing and can't move.

--------
You do a dive roll into the Weapon Armory, crouch and scan the room
for more Gothons that might be hiding. It's dead quiet, too quiet.
You stand up and run to the far side of the room and find the
neutron bomb in its container. There's a keypad lock on the box
and you need the code to get the bomb out. If you get the code
wrong 10 times then the lock closes forever and you can't
get the bomb. The code is 3 digits.
[keypad]> 123
BZZZZEDDD!
[keypad]> 234
BZZZZEDDD!
[keypad]> 345
BZZZZEDDD!
[keypad]> 456
BZZZZEDDD!
[keypad]> 567
BZZZZEDDD!
[keypad]> 678
BZZZZEDDD!
[keypad]> 789
BZZZZEDDD!
[keypad]> 384
BZZZZEDDD!
[keypad]> 764
BZZZZEDDD!
[keypad]> 354
BZZZZEDDD!
[keypad]> 263
The lock buzzes one last time and then you hear a sickening
melting sound as the mechanism is fused together.

--------
You died.
While it's fun to put functions inside of hashes, you'd think there'd be something in Ruby that does this for you. There is: the class keyword. Using class is how you create an even more awesome "hash with functions" than the one you made in the last exercise. Classes have all sorts of powerful features and uses that I could never go into in this book. Instead, you'll just use them like they're fancy hashes with functions.
A programming language that uses classes is called "Object Oriented Programming". This is an old style of programming where you make "things" and you "tell" those things to do work. You've been doing a lot of this. A whole lot. You just didn't know it. Remember when you were doing this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
class
Game
def
initialize
(
start
)
@quips
=
[
"You died.
]
@start
=
start
puts
"in init @start = "
+
@start
.
inspect
end
def
prompt
()
print
"> "
end
def
play
()
puts
"@start => "
+
@start
.
inspect
next_room
=
@start
while
true
puts
"
\n
--------"
room
=
method
(
next_room
)
next_room
=
room
.
call
()
end
end
def
death
()
puts
@quips
[
rand
(
@quips
.
length
())
]
Process
.
exit
(
1
)
end
def
central_corridor
()
puts
"The Gothons of Planet Percal #25 have invaded your ship and destroyed"
puts
"your entire crew. You are the last surviving member and your last"
puts
"mission is to get the neutron destruct bomb from the Weapons Armory,"
puts
"put it in the bridge, and blow the ship up after getting into an "
puts
"escape pod."
 He's blocking the door to the"
puts
"Armory and about to pull a weapon to blast you."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"shoot!"

puts
"His clown costume is flowing and moving around his body, which throws"
puts
"off your aim. Your laser hits his costume but misses him entirely.  Then he eats you."
return
:death
elsif
action
==
"dodge!"
puts
"Like a world class boxer you dodge, weave, slip and slide right"
puts
"as the Gothon's blaster cranks a laser past your head."

puts
"The Gothon stops, tries not to laugh, then busts out laughing and can't move."

return
:laser_weapon_armory
else
puts
"DOES NOT COMPUTE!"
return
:central_corridor
end
end
def
laser_weapon_armory
()
puts
"You do a dive roll into the Weapon Armory, crouch and scan the room"
puts
"for more Gothons that might be hiding. It's dead quiet, too quiet."
puts
"You stand up and run to the far side of the room and find the"
puts
"neutron bomb in its container. There's a keypad lock on the box"
puts
"and you need the code to get the bomb out. If you get the code"
puts
"wrong 10 times then the lock closes forever and you can't"
puts
"get the bomb. The code is 3 digits."
code
=
"%s%s%s"
%
[
rand
(
9
)
+
1
,
rand
(
9
)
+
1
,
rand
(
9
)
+
1
]
print
"[keypad]> "
guess
=
gets
.
chomp
()
guesses
=
0
while
guess
!=
code
and
guesses
<
10
puts
"BZZZZEDDD!"
guesses
+=
1
print
"[keypad]> "
guess
=
gets
.
chomp
()
end
if
guess
==
code
puts
"The container clicks open and the seal breaks, letting gas out."
puts
"You grab the neutron bomb and run as fast as you can to the"
puts
"bridge where you must place it in the right spot."
return
:the_bridge
else
puts
"The lock buzzes one last time and then you hear a sickening"
puts
"melting sound as the mechanism is fused together."

return
:death
end
end
def
the_bridge
()
puts
"You burst onto the Bridge with the netron destruct bomb"
puts
"under your arm and surprise 5 Gothons who are trying to"
puts
"take control of the ship. Each of them has an even uglier"
puts
"clown costume than the last. They haven't pulled their"
puts
"weapons out yet, as they see the active bomb under your"
puts
"arm and don't want to set it off."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"throw the bomb"
puts
"In a panic you throw the bomb at the group of Gothons"
puts
"and make a leap for the door.

return
:death
elsif
action
==
"slowly place the bomb"
puts
"You point your blaster at the bomb under your arm"
puts
"and the Gothons put their hands up and start to sweat."
puts
"You inch backward to the door, open it, and then carefully"
puts
"place the bomb on the floor, pointing your blaster at it."
puts
"You then jump back through the door, punch the close button"
puts
"and blast the lock so the Gothons can't get out."
puts
"Now that the bomb is placed you run to the escape pod to"
puts
"get off this tin can."
return
:escape_pod
else
puts
"DOES NOT COMPUTE!"
return
:the_bridge
end
end
def
escape_pod
()
puts
"You rush through the ship desperately trying to make it to"
puts
"the escape pod before the whole ship explodes. It seems like"
puts
"hardly any Gothons are on the ship, so your run is clear of"
puts
"interference. You get to the chamber with the escape pods, and"
puts
"now need to pick one to take. Some of them could be damaged"
puts
"but you don't have time to look. There's 5 pods, which one"
puts
"do you take?"
good_pod
=
rand
(
5
)
+
1
print
"[pod #]>"
guess
=
gets
.
chomp
()
if
guess
.
to_i
!=
good_pod
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod escapes out into the void of space, then"
puts
"implodes as the hull ruptures, crushing your body"
puts
"into jam jelly."
return
:death
else
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod easily slides out into space heading to"
puts
"the planet below. As it flies to the planet, you look"
puts
"back and see your ship implode then explode like a"
puts
"bright star, taking out the Gothon ship at the same"
puts
"time. You won!"
Process
.
exit
(
0
)
end
end
end
a_game
=
Game
.
new
(
:central_corridor
)
a_game
.
play
()
|
You were actually using classes. The variable stuff is actually an Array class. The stuff.join(' ') is calling the join function of the Array and passing ' ' (just an empty space), which is also a class, a String class. It's all classes!
Well, and objects, but let's just skip that word for now. You'll learn what those are after you make some classes. How do you make classes? Very similar to how you made the ROOMS hash, but easier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
class
Game
def
initialize
(
start
)
@quips
=
[
"You died.
]
@start
=
start
puts
"in init @start = "
+
@start
.
inspect
end
def
prompt
()
print
"> "
end
def
play
()
puts
"@start => "
+
@start
.
inspect
next_room
=
@start
while
true
puts
"
\n
--------"
room
=
method
(
next_room
)
next_room
=
room
.
call
()
end
end
def
death
()
puts
@quips
[
rand
(
@quips
.
length
())
]
Process
.
exit
(
1
)
end
def
central_corridor
()
puts
"The Gothons of Planet Percal #25 have invaded your ship and destroyed"
puts
"your entire crew. You are the last surviving member and your last"
puts
"mission is to get the neutron destruct bomb from the Weapons Armory,"
puts
"put it in the bridge, and blow the ship up after getting into an "
puts
"escape pod."
 He's blocking the door to the"
puts
"Armory and about to pull a weapon to blast you."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"shoot!"

puts
"His clown costume is flowing and moving around his body, which throws"
puts
"off your aim. Your laser hits his costume but misses him entirely.  Then he eats you."
return
:death
elsif
action
==
"dodge!"
puts
"Like a world class boxer you dodge, weave, slip and slide right"
puts
"as the Gothon's blaster cranks a laser past your head."

puts
"The Gothon stops, tries not to laugh, then busts out laughing and can't move."

return
:laser_weapon_armory
else
puts
"DOES NOT COMPUTE!"
return
:central_corridor
end
end
def
laser_weapon_armory
()
puts
"You do a dive roll into the Weapon Armory, crouch and scan the room"
puts
"for more Gothons that might be hiding. It's dead quiet, too quiet."
puts
"You stand up and run to the far side of the room and find the"
puts
"neutron bomb in its container. There's a keypad lock on the box"
puts
"and you need the code to get the bomb out. If you get the code"
puts
"wrong 10 times then the lock closes forever and you can't"
puts
"get the bomb. The code is 3 digits."
code
=
"%s%s%s"
%
[
rand
(
9
)
+
1
,
rand
(
9
)
+
1
,
rand
(
9
)
+
1
]
print
"[keypad]> "
guess
=
gets
.
chomp
()
guesses
=
0
while
guess
!=
code
and
guesses
<
10
puts
"BZZZZEDDD!"
guesses
+=
1
print
"[keypad]> "
guess
=
gets
.
chomp
()
end
if
guess
==
code
puts
"The container clicks open and the seal breaks, letting gas out."
puts
"You grab the neutron bomb and run as fast as you can to the"
puts
"bridge where you must place it in the right spot."
return
:the_bridge
else
puts
"The lock buzzes one last time and then you hear a sickening"
puts
"melting sound as the mechanism is fused together."

return
:death
end
end
def
the_bridge
()
puts
"You burst onto the Bridge with the netron destruct bomb"
puts
"under your arm and surprise 5 Gothons who are trying to"
puts
"take control of the ship. Each of them has an even uglier"
puts
"clown costume than the last. They haven't pulled their"
puts
"weapons out yet, as they see the active bomb under your"
puts
"arm and don't want to set it off."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"throw the bomb"
puts
"In a panic you throw the bomb at the group of Gothons"
puts
"and make a leap for the door.

return
:death
elsif
action
==
"slowly place the bomb"
puts
"You point your blaster at the bomb under your arm"
puts
"and the Gothons put their hands up and start to sweat."
puts
"You inch backward to the door, open it, and then carefully"
puts
"place the bomb on the floor, pointing your blaster at it."
puts
"You then jump back through the door, punch the close button"
puts
"and blast the lock so the Gothons can't get out."
puts
"Now that the bomb is placed you run to the escape pod to"
puts
"get off this tin can."
return
:escape_pod
else
puts
"DOES NOT COMPUTE!"
return
:the_bridge
end
end
def
escape_pod
()
puts
"You rush through the ship desperately trying to make it to"
puts
"the escape pod before the whole ship explodes. It seems like"
puts
"hardly any Gothons are on the ship, so your run is clear of"
puts
"interference. You get to the chamber with the escape pods, and"
puts
"now need to pick one to take. Some of them could be damaged"
puts
"but you don't have time to look. There's 5 pods, which one"
puts
"do you take?"
good_pod
=
rand
(
5
)
+
1
print
"[pod #]>"
guess
=
gets
.
chomp
()
if
guess
.
to_i
!=
good_pod
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod escapes out into the void of space, then"
puts
"implodes as the hull ruptures, crushing your body"
puts
"into jam jelly."
return
:death
else
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod easily slides out into space heading to"
puts
"the planet below. As it flies to the planet, you look"
puts
"back and see your ship implode then explode like a"
puts
"bright star, taking out the Gothon ship at the same"
puts
"time. You won!"
Process
.
exit
(
0
)
end
end
end
a_game
=
Game
.
new
(
:central_corridor
)
a_game
.
play
()
|
See the @ symbol before the @name variable? That makes it an instance variable. Every instance of TheThing that you create will have its own value for @name. Instance variables are hidden away inside the object. We can't get at the name simply by typing a.number unless we explicitly make that data readable to the outside world.
By including the attr_reader :name line. To make @name write-only, we could do attr_writer :name. And to make it read/write we could do attr_accessor :name. Ruby uses the good object-oriented principle of encapsulating data.
Next, see the initialize method? That is how you set up a Ruby class with internal variables. You can set them with the @ symbol just like I showed you here. See also how we then use this in add_me_up() later which lets you add to the @number you created. Later you can see how we use this to add to our number and print it.
Classes are very powerful, so you should read everything you can about them and play with them. You actually know how to use them, you just have to try it. In fact, I want to play some guitar right now so I'm not going to give you an exercise to type. You're going to write an exercise using classes.
Here's how we'd do exercise 41 using classes instead of the thing we created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
class
Game
def
initialize
(
start
)
@quips
=
[
"You died.
]
@start
=
start
puts
"in init @start = "
+
@start
.
inspect
end
def
prompt
()
print
"> "
end
def
play
()
puts
"@start => "
+
@start
.
inspect
next_room
=
@start
while
true
puts
"
\n
--------"
room
=
method
(
next_room
)
next_room
=
room
.
call
()
end
end
def
death
()
puts
@quips
[
rand
(
@quips
.
length
())
]
Process
.
exit
(
1
)
end
def
central_corridor
()
puts
"The Gothons of Planet Percal #25 have invaded your ship and destroyed"
puts
"your entire crew. You are the last surviving member and your last"
puts
"mission is to get the neutron destruct bomb from the Weapons Armory,"
puts
"put it in the bridge, and blow the ship up after getting into an "
puts
"escape pod."
 He's blocking the door to the"
puts
"Armory and about to pull a weapon to blast you."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"shoot!"

puts
"His clown costume is flowing and moving around his body, which throws"
puts
"off your aim. Your laser hits his costume but misses him entirely.  Then he eats you."
return
:death
elsif
action
==
"dodge!"
puts
"Like a world class boxer you dodge, weave, slip and slide right"
puts
"as the Gothon's blaster cranks a laser past your head."

puts
"The Gothon stops, tries not to laugh, then busts out laughing and can't move."

return
:laser_weapon_armory
else
puts
"DOES NOT COMPUTE!"
return
:central_corridor
end
end
def
laser_weapon_armory
()
puts
"You do a dive roll into the Weapon Armory, crouch and scan the room"
puts
"for more Gothons that might be hiding. It's dead quiet, too quiet."
puts
"You stand up and run to the far side of the room and find the"
puts
"neutron bomb in its container. There's a keypad lock on the box"
puts
"and you need the code to get the bomb out. If you get the code"
puts
"wrong 10 times then the lock closes forever and you can't"
puts
"get the bomb. The code is 3 digits."
code
=
"%s%s%s"
%
[
rand
(
9
)
+
1
,
rand
(
9
)
+
1
,
rand
(
9
)
+
1
]
print
"[keypad]> "
guess
=
gets
.
chomp
()
guesses
=
0
while
guess
!=
code
and
guesses
<
10
puts
"BZZZZEDDD!"
guesses
+=
1
print
"[keypad]> "
guess
=
gets
.
chomp
()
end
if
guess
==
code
puts
"The container clicks open and the seal breaks, letting gas out."
puts
"You grab the neutron bomb and run as fast as you can to the"
puts
"bridge where you must place it in the right spot."
return
:the_bridge
else
puts
"The lock buzzes one last time and then you hear a sickening"
puts
"melting sound as the mechanism is fused together."

return
:death
end
end
def
the_bridge
()
puts
"You burst onto the Bridge with the netron destruct bomb"
puts
"under your arm and surprise 5 Gothons who are trying to"
puts
"take control of the ship. Each of them has an even uglier"
puts
"clown costume than the last. They haven't pulled their"
puts
"weapons out yet, as they see the active bomb under your"
puts
"arm and don't want to set it off."
prompt
()
action
=
gets
.
chomp
()
if
action
==
"throw the bomb"
puts
"In a panic you throw the bomb at the group of Gothons"
puts
"and make a leap for the door.

return
:death
elsif
action
==
"slowly place the bomb"
puts
"You point your blaster at the bomb under your arm"
puts
"and the Gothons put their hands up and start to sweat."
puts
"You inch backward to the door, open it, and then carefully"
puts
"place the bomb on the floor, pointing your blaster at it."
puts
"You then jump back through the door, punch the close button"
puts
"and blast the lock so the Gothons can't get out."
puts
"Now that the bomb is placed you run to the escape pod to"
puts
"get off this tin can."
return
:escape_pod
else
puts
"DOES NOT COMPUTE!"
return
:the_bridge
end
end
def
escape_pod
()
puts
"You rush through the ship desperately trying to make it to"
puts
"the escape pod before the whole ship explodes. It seems like"
puts
"hardly any Gothons are on the ship, so your run is clear of"
puts
"interference. You get to the chamber with the escape pods, and"
puts
"now need to pick one to take. Some of them could be damaged"
puts
"but you don't have time to look. There's 5 pods, which one"
puts
"do you take?"
good_pod
=
rand
(
5
)
+
1
print
"[pod #]>"
guess
=
gets
.
chomp
()
if
guess
.
to_i
!=
good_pod
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod escapes out into the void of space, then"
puts
"implodes as the hull ruptures, crushing your body"
puts
"into jam jelly."
return
:death
else
puts
"You jump into pod %s and hit the eject button."
%
guess
puts
"The pod easily slides out into space heading to"
puts
"the planet below. As it flies to the planet, you look"
puts
"back and see your ship implode then explode like a"
puts
"bright star, taking out the Gothon ship at the same"
puts
"time. You won!"
Process
.
exit
(
0
)
end
end
end
a_game
=
Game
.
new
(
:central_corridor
)
a_game
.
play
()
|
The output from this version of the game should be exactly the same as the previous version. In fact you'll notice that some of the code is nearly the same. Compare this new version of the game with the last one so you understand the changes that were made. Key things to really get are:
You need to start learning to feed yourself. Hopefully as you have worked through this book, you have learned that all the information you need is on the internet, you just have to go search for it. The only thing you have been missing are the right words and what to look for when you search. Now you should have a sense of it, so it's about time you struggled through a big project and tried to get it working.
Here are your requirements:
Other than that I leave it to you. Spend a whole week on this and make it the best game you can. Use classes, functions, dicts, lists anything you can to make it nice. The purpose of this lesson is to teach you how to structure classes that need other classes inside other files.
Remember, I'm not telling you exactly how to do this because you have to do this yourself. Go figure it out. Programming is problem solving, and that means trying things, experimenting, failing, scrapping your work, and trying again. When you get stuck, ask for help and show people your code. If they are mean to you, ignore them, focus on the people who are not mean and offer to help. Keep working it and cleaning it until it's good, then show it some more.
Good luck, and see you in a week with your game.
In this exercise you will evaluate the game you just made. Maybe you got part-way through it and you got stuck. Maybe you got it working but just barely. Either way, we're going to go through a bunch of things you should know now and make sure you covered them in your game. We're going to study how to properly format a class, common conventions in using classes, and a lot of "textbook" knowledge.
Why would I have you try to do it yourself and then show you how to do it right? From now on in the book I'm going to try to make you self-sufficient. I've been holding your hand mostly this whole time, and I can't do that for much longer. I'm now instead going to give you things to do, have you do them on your own, and then give you ways to improve what you did.
You will struggle at first and probably be very frustrated but stick with it and eventually you will build a mind for solving problems. You will start to find creative solutions to problems rather than just copy solutions out of textbooks.
All the other rules I've taught you about how to make a function nice apply here, but add these things:
I want you now to pretend you are me. Adopt a very stern look, print out your code, and take a red pen and mark every mistake you find. Anything from this exercise and from other things you have known.  Then repeat this a couple of times, looking for anything that could be better. Use all the tricks I've given you to break your code down into the smallest tiniest little analysis you can.
The purpose of this exercise is to train your attention to detail on classes. Once you are done with this bit of code, find someone else's code and do the same thing. Go through a printed copy of some part of it and point out all the mistakes and style errors you find. Then fix it and see if your fixes can be done without breaking their program.
I want you to do nothing but evaluate and fix code for the week. Your own code and other people's.
An important concept that you have to understand is the difference between a Class and an Object. The problem is, there is no real "difference" between a class and an object. They are actually the same thing at different points in time. I will demonstrate by a Zen koan:
What is the difference between a Fish and a Salmon?
Did that question sort of confuse you? Really sit down and think about it for a minute. I mean, a Fish and a Salmon are different but, wait, they are the same thing right? A Salmon is a kind of Fish, so I mean it's not different. But at the same time, becase a Salmon is a particular type of Fish and so it's actually different from all other Fish. That's what makes it a Salmon and not a Halibut. So a Salmon and a Fish are the same but different. Weird.
This question is confusing because most people do not think about real things this way, but they intuitively understand them. You do not need to think about the difference between a Fish and a Salmon because you know how they are related. You know a Salmon is a kind of Fish and that there are other kinds of Fish without having to understand that.
 Now, think about this question:
What is the difference between Mary and a Salmon?
Again this is a weird question, but it's a bit easier than the Fish vs. Salmon question. You know that Mary is a Salmon, and so she's not really different. She's just a specific "instance" of a Salmon. Joe and Frank are also instances of Salmon. But, what do I mean when I say instance? I mean they were created from some other Salmon and now represent a real thing that has Salmon-like attributes.
Now for the mind bending idea: Fish is a Class, and Salmon is a Class, and Mary is an Object. Think about that for a second. Alright let's break it down real slow and see if you get it.
A Fish is a Class, meaning it's not a real thing, but rather a word we attach to instances of things with similar attributes. Got fins? Got gills? Lives in water? Alright it's probably a Fish.
Someone with a Ph.D. then comes along and says, "No my young friend, this Fish is actually Salmo salar, affectionately known as a Salmon." This professor has just clarified the Fish further and made a new Class called "Salmon" that has more specific attributes. Longer nose, reddish flesh, big, lives in the ocean or fresh water, tasty? Ok, probably a Salmon.
Finally, a cook comes along and tells the Ph.D.,  Now you have this instance of a Salmon (which also is an instance of a Fish) named Mary turned into something real that is filling your belly. It has become an Object.
There you have it: Mary is a kind of Salmon that is a kind of Fish. Object is a Class is a Class.
How This Looks In Code This is a weird concept, but to be very honest you only have to worry about it when you make new classes, and when you use a class. I will show you two tricks to help you figure out whether something is a Class or Object.
First, you need to learn two catch phrases "is-a" and "has-a". You use the phrase is-a when you talk about objects and classes being related to each other by a class relationship. You use has-a when you talk about objects and classes that are related only because they reference each other.
Now, go through this piece of code and replace each ##?? comment with a replacement comment that says whether the next line represents an is-a or a has-a relationship, and what that relationship is. In the beginning of the code, I've laid out a few examples, so you just have to write the remaining ones.
Remember, is-a is the relationship between Fish and Salmon, while has-a is the relationship between Salmon and Gills.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
## Animal is-a object (yes, sort of confusing) look at the extra credit
class
Animal
end
## ??
class
Dog
<
Animal
def
initialize
(
name
)
## ??
@name
=
name
end
end
## ??
class
Cat
<
Animal
def
initialize
(
name
)
## ??
@name
=
name
end
end
## ??
class
Person
attr_accessor
:pet
def
initialize
(
name
)
## ??
@name
=
name
## Person has-a pet of some kind
@pet
=
nil
end
end
## ??
class
Employee
<
Person
def
initialize
(
name
,
salary
)
## ?? hmm what is this strange magic?
super
(
name
)
## ??
@salary
=
salary
end
end
## ??
class
Fish
end
## ??
class
Salmon
<
Fish
end
## ??
class
Halibut
<
Fish
end
## rover is-a Dog
rover
=
Dog
.
new
(
"Rover"
)
## ??
satan
=
Cat
.
new
(
"Satan"
)
## ??
mary
=
Person
.
new
(
"Mary"
)
## ??
mary
.
pet
=
satan
## ??
frank
=
Employee
.
new
(
"Frank"
,
120000
)
## ??
frank
.
pet
=
rover
## ??
flipper
=
Fish
.
new
## ??
crouse
=
Salmon
.
new
## ??
harry
=
Halibut
.
new
|
This will be where you start learning how to setup a good project "skeleton" directory. This skeleton directory will have all the basics you need to get a new project up and running. It will have your project layout, automated tests, modules, and install scripts.
First, create the structure of your skeleton directory with these commands:
$ mkdir -p projects
$
cd projects/
$ mkdir skeleton
$
cd skeleton
$ mkdir bin lib lib/NAME
test
I use a directory named projects to store all the various things I'm working on. Inside that directory I have my skeleton directory that I put the basis of my projects into. The directory NAME will be renamed to whatever you are calling your project's main module when you use the skeleton.
Next we need to setup some initial files:
$ touch lib/NAME.rb
$ touch lib/NAME/version.rb
Then we can create a NAME.gemspec file in our project's root directory which we can use to install our project later if we want:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# -*- encoding: utf-8 -*-
$:
.
push
File
.
expand_path
(
"../lib"
,
__FILE__
)
require
"NAME/version"
Gem
::
Specification
.
new
do
|
s
|
s
.
name
=
"NAME"
s
.
version
=
NAME
::
VERSION
s
.
authors
=
[
"Rob Sobers"
]
s
.
email
=
[
<EMAIL_ADDRESS>]
s
.
homepage
=
""
s
.
summary
=
%q{TODO: Write a gem summary}
s
.
description
=
%q{TODO: Write a gem description}
s
.
rubyforge_project
=
"NAME"
s
.
files
=
`git ls-files`
.
split
(
"
\n
"
)
s
.
test_files
=
`git ls-files -- {test,spec,features}/*`
.
split
(
"
\n
"
)
s
.
executables
=
`git ls-files -- bin/*`
.
split
(
"
\n
"
)
.
map
{
|
f
|
File
.
basename
(
f
)
}
s
.
require_paths
=
[
"lib"
]
end
|
Edit this file so that it has your contact information and is ready to go for when you copy it.
Finally you will want a simple skeleton file for unit tests (which will talk about more in the next lesson) named test/test_NAME.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
require
'test/unit'
class
MyUnitTests
<
Test
::
Unit
::
TestCase
def
setup
puts
"setup!"
end
def
teardown
puts
"teardown!"
end
def
test_basic
puts
"I RAN!"
end
end
|
Gems are packages of Ruby code that help you get things done, so you will need to know how to install them and use them. Here's the problem though. You are at a point where it's difficult for me to help you do that and keep this book sane and clean.
Rather than tell you how to do it exactly, I'm going to tell you what you should install, and then tell you to figure it out and get it working. This will be really good for you since it will open a whole world of software you can use that other people have released to the world.
Next, install the following software packages:
Do not just download these packages and install them by hand. Instead see how other people recommend you install these packages and use them for your particular system. The process will be different for most versions of Linux, OSX, and definitely different for Windows.
I am warning you, this will be frustrating. In the business we call this "yak shaving". Yak shaving is any activity that is mind numblingly irritatingly boring and tedious that you have to do before you can do something else that's more fun. You want to create cool Ruby projects, but you can't do that until you setup a skeleton directory, but you can't setup a skeleton directory until you install some packages, but you can't install packages until you install package installers, and you can't install package installers until you figure out how your system installs software in general, and so on.
Struggle through this anyway. Consider it your trial-by-annoyance to get into the programmer club. Every programmer has to do these annoying tedious tasks before they can do something cool.
You are now done with most of your yak shaving. Whenever you want to start a new project, just do this:
This exercise doesn't have extra credit but a quiz you should complete:
Having to type commands into your game over and over to make sure it's working is annoying. Wouldn't it be better to write little pieces of code that test your code? Then when you make a change, or add a new thing to your program, you just "run your tests" and the tests make sure things are still working.
Every exercise after this one will not have a What You Should See section, but instead it will have a What You Should Test section. You will be writing automated tests for all of your code starting now, and this will hopefully make you an even better programmer.
I won't try to explain why you should write automated tests. I will only say that, you are trying to be a programmer, and programmers automate boring and tedious tasks. Testing a piece of software is definitely boring and tedious, so you might as well write a little bit of code to do it for you.
 You have gone through this book writing code to do things. Now you are going to take the next leap and write code that knows about other code you have written. This process of writing a test that runs some code you have written forces you to understand clearly what you have just written. It solidifies in your brain exactly what it does and why it works and gives you a new level of attention to detail.
We're going to take a very simple piece of code and write one simple test. We're going to base this little test on a new project from your project skeleton.
First, make a ex47 project from your project skeleton. Make sure you do it right and rename the library and get that first test/test_ex47.rb test file going right.
Next, create a simple file ex47/lib/game.rb where you can put the code to test. This will be a very silly little class that we want to test with this code in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class
Room
attr_accessor
:name
,
:description
,
:paths
def
initialize
(
name
,
description
)
@name
=
name
@description
=
description
@paths
=
{}
end
def
go
(
direction
)
@paths
[
direction
]
end
def
add_paths
(
paths
)
@paths
.
update
(
paths
)
end
end
|
Once you have that file, change unit test skeleton to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
require
'test/unit'
require_relative
'../lib/ex47'
class
MyUnitTests
<
Test
::
Unit
::
TestCase
def
test_room
()
gold
=
Room
.
new
(
"GoldRoom"
,
"""This room has gold in it you can grab. There's a
door to the north."""
)
assert_equal
(
gold
.
name
,
"GoldRoom"
)
assert_equal
(
gold
.
paths
,
{})
end
def
test_room_paths
()
center
=
Room
.
new
(
"Center"
,
"Test room in the center."
)
north
=
Room
.
new
(
"North"
,
"Test room in the north."
)
south
=
Room
.
new
(
"South"
,
"Test room in the south."
)
center
.
add_paths
({
:north
=>
north
,
:south
=>
south
})
assert_equal
(
center
.
go
(
:north
),
north
)
assert_equal
(
center
.
go
(
:south
),
south
)
end
def
test_map
()
start
=
Room
.
new
(
"Start"
,
"You can go west and down a hole."
)
west
=
Room
.
new
(
"Trees"
,
"There are trees here, you can go east."
)
down
=
Room
.
new
(
"Dungeon"
,
"It's dark down here, you can go up."
)
start
.
add_paths
({
:west
=>
west
,
:down
=>
down
})
west
.
add_paths
({
:east
=>
start
})
down
.
add_paths
({
:up
=>
start
})
assert_equal
(
start
.
go
(
:west
),
west
)
assert_equal
(
start
.
go
(
:west
)
.
go
(
:east
),
start
)
assert_equal
(
start
.
go
(
:down
)
.
go
(
:up
),
start
)
end
end
|
This file requires the Room class you made in the lib/ex47.rb file so that you can do tests on it. There are then a set of tests that are functions starting with test_. Inside each test case there's a bit of code that makes a Room or a set of Rooms, and then makes sure the rooms work the way you expect them to work. It tests out the basic room features, then the paths, then tries out a whole map.
The important functions here are assert_equal which makes sure that variables you have set or paths you have built in a Room are actually what you think they are. If you get the wrong result, then Ruby's Test::Unit module will print out an error message so you can go figure it out.
Follow these general loose set of guidelines when making your tests:
$ ruby test_ex47.rb
Loaded suite test_ex47
Started
...
Finished in 0.000353 seconds.
3 tests, 7 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 63537
That's what you should see if everything is working right. Try causing an error to see what that looks like and then fix it.
Your game probably was coming along great, but I bet how you handled what the user typed was becoming tedious. Each room needed its own very exact set of phrases that only worked if your player typed them perfectly. What you'd rather have is a device that lets users type phrases in various ways. For example, we'd like to have all of these phrases work the same:
It should be alright for a user to write something a lot like English for your game, and have your game figure out what it means. To do this, we're going to write a library that does just that. This module will have a few classes that work together to handle use input and convert it into something your game can work with reliably.
In a simple version of English the following elements: