Pipe Dreams - Ruby Wizardry: An Introduction to Programming for Kids (2014)

Ruby Wizardry: An Introduction to Programming for Kids (2014)

Chapter 3. Pipe Dreams

The Apprentice Plumber’s Dilemma

The King, Scarlet, and Ruben made their way back from the Royal Bathroom, the King gleefully batting his string about like a big, beardy cat.

“All those waterworks for a string in a shower!” Scarlet said to the King. “I hope you’re feeling better now.”

“Much,” said the King, spinning the beads and trinkets on his string every which way.

“Speaking of waterworks,” said Ruben, “do you hear that?” And as they rounded the corner and reentered the King’s study, they found themselves ankle-deep in a miniature lake. There was water, water everywhere!

“The Mysterious Pipe!” cried the King. “Look!” And he pointed to the Mysterious Pipe, which was shaking violently and gushing a surprising amount of water from its narrow top.

image with no caption

“Check out the Flowmatic Something-or-Other!” said the King.

“That’s not terribly descriptive,” Ruben said.

“No, that’s what it’s called,” said the King. “The Flowmatic Something-or-Other™.”

“Found it!” said Scarlet, grabbing a square metal box labeled HIS MAJESTY’S FLOWMATIC SOMETHING-OR-OTHER™ on the back of the Pipe. She pried open the cover of the Flowmatic Something-or-Other to find a miniature Computing Contraption with its glowing >> IRB prompt.

“What do I do?” Scarlet asked the King.

“I seem to recall this program uses a flowmatic_on variable,” the King said. “Try turning it off.” He paused a moment. “Hey! I remembered the stuff we learned about variables!”

Scarlet flashed the King a thumbs-up, typed at the prompt, and pressed ENTER:

>> flowmatic_on = false

=> false

The Mysterious Pipe shuddered once and sputtered, and the water stopped flowing.

“Whew!” said Ruben. “Nice work!” He peered over Scarlet’s shoulder at the screen. “How’d you do that? What’s false? It can’t be a string; there are no quotes around it. Is it also a variable?”

“Nope!” said Scarlet. “But it’s built into Ruby just like numbers, strings, and variables are. It’s called a Boolean, and there are actually two of them: true and false. It looks like the Mysterious Pipe works when flowmatic_on is true and shuts off when it’s false.”

“Then how was flowmatic_on true before?” Ruben asked.

“I don’t know!” said Scarlet. “Someone or something must have created that variable.”

“Well, it’s stopped leaking,” said the King, “but it’s not really fixed. It should work correctly even when flowmatic_on is true! After all, the Flowmatic supplies all the water to the castle; without it, there can be no Royal Baths, Royal Toothbrushings, or Royal Water Balloon Fights! We need the Mysterious Pipe and its Flowmatic to be on without leaking all over the place.”

“What about this?” Ruben said, pointing to a line on the Computing Contraption just below the Flowmatic’s on/off control:

Warning! flow_rate is above 50!

“The water must be coming into the Mysterious Pipe too fast,” said Scarlet.

“Gadzooks!” said the King. “The flow rate must be above 50!”

“What should we do?” asked Ruben.

The King thought for a minute. “I think it’s best that we do what should always be done in these situations,” he said. “We should call a professional. In this case, the Royal Plumber!”

Writing and Running Ruby Scripts

While the King calls the Royal Plumber, I’ll take a second to explain some more Ruby magic to you. Don’t worry, it won’t take but a minute.

image with no caption

You see, you don’t always have to type commands into IRB one at a time. As mentioned in Chapter 1, you can write a big block of Ruby code and save it as a Ruby script. Then, you can run your Ruby script in IRB! (This is a lot like running your code in the terminal with the rubycommand, as we did in Chapter 1, but IRB will stay open the whole time.) Just start IRB while you’re in the folder that contains your Ruby script, then type load 'filename.rb'. That’s exactly the same as typing everything in the file into IRB—but this way it’s easy to make changes and try again!

Let’s try this little guy on for size. Type the following code in your favorite text editor and save it as a file called flow.rb. (Look back at Chapter 1 if you need a reminder of how to do this, and don’t worry—we’ll cover the new #{} syntax in two shakes of a fox’s tail.)

flow.rb

flow_rate = 100

puts "The flow rate is currently #{flow_rate}."

flow_rate = 100 / 2

puts "Now the flow rate is #{flow_rate}!"

If you open IRB, type load 'flow.rb', and press ENTER, you should see:

>> load 'flow.rb'

The flow rate is currently 100.

Now the flow rate is 50!

=> true

Let’s walk through this line by line.

First, load 'flow.rb' (it doesn’t matter if you use single or double quotes here) tells Ruby to look for a file called flow.rb in the current directory (a directory is just a fancy name for a folder on your computer). If Ruby finds flow.rb and there are no problems with the code in the file, Ruby will run that code just as if you’d typed it bit by bit into IRB. Next, you know what flow_rate = 100 and puts do: the first one sets the flow_rate variable to the value 100, and puts prints out the string you give it. (You also get a bonus => true from Ruby, which lets you know that loading the file worked.) But you probably want to know: what’s this crazy-looking #{flow_rate} business?

Well, strings and variables are different things, but sometimes you might want to combine them—say, to print out a message displaying different values for the flow_rate variable. Rather than making us look up the value of that variable and type it into the string by hand every time we want to use it, Ruby lets us use #{} to say, “Hey! Just insert the value of this variable right into the string.” So when you have:

flow_rate = 100

puts "The flow rate is currently #{flow_rate}."

you get:

The flow rate is currently 100.

One last thing: remember in Chapter 2 when Ruben said that strings with double quotes (") were very slightly different from strings with single quotes (')? Well, the #{} magic (called string interpolation if you want to be super fancy) is possible only with double-quoted strings; it can’t be done with single-quoted ones. (This is precisely what the King meant in Chapter 2 when he said you could put more complicated bits and trinkets on a double-quoted string than on a single-quoted string.)

That’s really all I wanted to show you. And speaking of the King . . .

His Majesty’s Flow Control

“Hello?” said the King. (He had been on hold for a while.) “Is this the Royal Plumber?”

“Chuff! Chuff! Chuff!” said the Royal Plumber.

image with no caption

“Oh dear,” said the King. “It sounds like the Royal Plumber has come down with a bad case of the Chuffs.”

“Chuffs?” said Scarlet.

“Chuff!” said the Royal Plumber.

“It’s a bit like a cold, but coughier and huffier,” said the King. “Royal Plumber, could you send down your Apprentice to help us with the Mysterious Pipe? It’s been overflowing terribly.”

“Chuff!” she said, and hung up.

“I think that was a yes,” said the King.

“I think so, too,” said Ruben. “It looks like the Apprentice is already here!”

The Apprentice to the Royal Plumber strolled into the King’s study carrying a large red toolbox. Ruben and Scarlet found his expression hard to read behind his dark rectangular sunglasses and heavy black beard. The name Haldo was stitched in red on the front of his coveralls.

“Haldo!” said the King.

“That’s me,” said Haldo. “I hear the Mysterious Pipe is on the fritz.”

“Definitely,” said Scarlet. “Can you help us fix it?”

image with no caption

“I think so,” said Haldo, “but I’m just the Apprentice, so it may take me a little while. Let’s see what’s what.” He walked over to the Flowmatic Something-or-Other and looked at the screen for a moment. “I seem to remember there’s an instructions.rb file in here somewhere.” He typed load 'instructions.rb', and this is what came up:

|~~ |~~

| |

:$: HIS MAJESTY'S FLOWMATIC SOMETHING-OR-OTHER :$:

`'''''''''''''''''''''''''''''''''''''''''''''`

~= Instructions =~

1. Water should flow if flowmatic_on is true and

water_available is true.

2. If flowmatic_on is false, the message

"Flowmatic is off!" should appear.

3. If water_available is false, the message

"No water!" should appear.

4. If the flow_rate is above 50, the warning

"Warning! flow_rate is above 50!" should

appear, along with the current flow rate.

5. If the flow_rate is below 50, the warning

"Warning! flow_rate is below 50!" should

appear, along with the current flow rate.

=> true

“Huh!” said Ruben. “So the problem is that if the flow rate is too high or too low, we only get a message. Ruby doesn’t automatically correct the flow rate, so we can end up with a flood.”

“We can fix that!” said Scarlet. “We’ll write a Ruby program to check the flow rate. If the flow rate is too high, we’ll lower it, and if it’s too low, we’ll increase it!”

Haldo scratched his head. “Well, here’s the thing,” he said. “I think I know what we need to do, but I haven’t learned enough Ruby to enter the right commands. If you kids can give me a hand, though, I think we’ll be in business.”

“No problem,” said Ruben. “Making a Ruby program do different actions based on different conditions is something Scarlet and I know backward and forward.”

“It’s called control flow,” said Scarlet, “and it’s not hard at all. Take a look!” She opened a new file in her text editor on the Computing Contraption, saved it as flowmatic_rules.rb, and typed:

flowmatic_on = true

water_available = true

if flowmatic_on && water_available

flow_rate = 50

end

“You’ve lost me,” said the King.

“We’ll take it slow,” said Scarlet. “First, we assign the variables flowmatic_on and water_available to true. Then, we have the if, which is a conditional, on the second line. It means that if the code that follows on the same line is true, then everything before end gets run.”

“And && is just Ruby’s way of saying and,” said Ruben. “We already know that the fourth line sets the flow rate to 50, so together, the whole thing says, ‘If flowmatic_on is true and water_available is also true, this program will set the flow_rate variable to 50. end just tells Ruby that if we’re not setting the flow rate to 50, we shouldn’t do anything—at least, not yet.”

“I see,” said Haldo. “And that’s just the very first of the instructions! Great work. But what happens if the Flowmatic isn’t on or there isn’t water available?”

“Well, at the moment, nothing,” said Ruben. “But we can fix that.” He reached over and added to the flowmatic_rules.rb code in his text editor:

flowmatic_on = true

water_available = true

➊ if flowmatic_on && water_available

flow_rate = 50

➋ elsif !flowmatic_on

puts "Flowmatic is off!"

➌ else

puts "No water!"

end

“I think I’m starting to get this,” said the King. “➊ is just what we had before. Then at ➋, we’re trying something new: elsif! Does elsif mean ‘if the first bit didn’t get run, try this next step’?”

“That’s exactly it,” said Scarlet. “Don’t worry about the weird spelling, either! It’s just a shorter way of writing ‘else, if.’ And the ! is just Ruby’s way of saying not. So if flowmatic_on happens to be false, !flowmatic_on will be true, and vice versa.”

“And since there’s only one condition left—if the Flowmatic is on but there’s just no water—the program puts the ‘No water!’ message at ➌ using an else, which means: ‘If none of the other code was run, then run the code that follows,’” Ruben said.

image with no caption

“And all of that’s followed by an end, like before,” said Scarlet.

“Do you need to add the two spaces before the lines following if, elsif, and else?” asked the King.

“The indentation?” said Scarlet. “No, but it sure does look nice.”

“That takes care of the first three instructions!” said Haldo. “And I think I’m getting the hang of this. Let’s see if I can rewrite the last two instructions in Ruby.” He added these lines to his flowmatic_rules.rb script:

➍ if flow_rate > 50

puts "Warning! flow_rate is above 50! It's #{flow_rate}."

flow_rate = 50

puts "The flow_rate's been reset to #{flow_rate}."

➎ elsif flow_rate < 50

puts "Warning! flow_rate is below 50! It's #{flow_rate}."

flow_rate = 50

puts "The flow_rate's been reset to #{flow_rate}."

➏ else

puts "The flow_rate is #{flow_rate} (thank goodness)."

end

“Okay, this I understand,” said the King. “The > means greater than and the < means less than, so that first bit at ➍ says: if the flow rate is above 50, we show a ‘too high’ warning and then assign the variable flow_rate to 50. The program then puts a new flow_rate value using string interpolation, like we saw before.”

“But at ➎, the program checks if flow_rate is below 50. If it is, we show a ‘too low’ warning and reset it to 50.

“At ➏, we have the else. If flow_rate isn’t greater than 50 or less than 50, that means it’s exactly 50. So, we just show the flow rate without changing the variable and puts it (thank goodness).” The King smiled, clearly pleased with himself.

“Perfect!” said Ruben. “You can also use <= for less than or equal to and >= for greater than or equal to, but we don’t need those quite yet, I don’t think.”

Improving flow_rate.rb with Fancier Logical Operators

Ruben studied the screen for a moment. “You know,” he said, “I think you could replace the section from ➍ to ➏ with even less code. Check this out!”

if flow_rate < 50 || flow_rate > 50

puts "Warning! flow_rate is not 50! It's #{flow_rate}."

flow_rate = 50

puts "The flow_rate's been reset to #{flow_rate}."

else

puts "The flow_rate is #{flow_rate} (thank goodness)."

end

“What do those two vertical lines mean?” asked Haldo. “I haven’t seen those before.”

“Just like && means and and ! means not, || means or,” said Scarlet. “So we’re saying, ‘If the flow rate is less than 50 or it’s greater than 50, show a warning and reset it to 50; otherwise, just let us know it’s 50 (thank goodness).’

“That works pretty well,” she continued, “but we can make it even simpler.”

if flow_rate != 50

puts "Warning! flow_rate is not 50! It's #{flow_rate}."

flow_rate = 50

puts "The flow_rate's been reset to #{flow_rate}."

else

puts "The flow_rate is #{flow_rate} (thank goodness)."

end

“I know that ! means not,” said the King, “so is it fair to guess that != means is not equal to?”

“It’s not only fair, it’s right!” said Ruben. “You can use != to mean is not equal to and == to mean is equal to. But be really careful not to mix up = and ==. The first one is used to assign values to variables, and the second is used to check if two things are equal.”

“This is amazing,” said Haldo. “I think I’m really getting the hang of Ruby control flow. Is there anything else I should know?”

“One more quick thing,” Scarlet said. “Because if followed by a negative condition appears all the time in programs, Ruby came up with another way to write it. Instead of always typing something like:

if flow_rate != 50

puts "Warning! flow_rate is not 50! It's #{flow_rate}."

end

you can instead type unless:

unless flow_rate == 50

puts "Warning! flow_rate is not 50! It's #{flow_rate}."

end

“And those two examples are exactly the same,” finished Scarlet. “But if you have elsifs and elses, it’s sometimes nicer-looking to just use ifs.”

While Scarlet was talking, Haldo saved their finished flowmatic_rules.rb file and typed load 'flowmatic_rules.rb' at the IRB prompt. When he pressed ENTER, the Mysterious Pipe shuddered once, then began to gently vibrate. Ruben and Scarlet could hear water flowing through the castle walls, and not a drop was spilled anywhere.

“Huzzah!” said the King. “I can’t thank you all enough! But I do wonder,” he continued, “how did the flow rate get set to 100 in the first place?”

“That, I’m not sure about,” said Haldo. “There must be another Ruby program in the castle that has access to the flow_rate variable and changed it.” He rummaged through his red toolbox and pulled out a flashlight. “I’ll look into it right away,” he said.

“Aren’t you going to take off your sunglasses?” asked Scarlet.

“No need,” said Haldo, and with that, he opened a small door on the same side of the room as the Mysterious Pipe and disappeared into the bowels of the castle, whistling as he went.

A Biggerish Project for You

You’ve learned a lot in the last handful of pages, and now it’s time to put your newfound knowledge to the test! (Don’t worry: I have complete and utter faith in you.) Haldo—now the Senior Apprentice to the Royal Plumber, thanks to Ruben and Scarlet—needs your help. While he hasn’t tracked down the precise cause of the Mysterious Pipe’s overflow, he did briefly find himself in a small but tricky maze. He’s asked you to record his adventures in the maze, so let’s start by making a new file called maze.rb. (Peek back at Chapter 1 if you don’t remember how to do this, or ask your local adult for help.) Type the following into your file.

maze.rb

puts "Holy giraffes! You fell into a maze!"

print "Where to? (N, E, S, W): "

direction = gets.chomp

puts "#{direction}, you say? A fine choice!"

if direction == "N"

puts "You are in a maze of twisty little passages, all alike."

elsif direction == "E"

puts "An elf! And his pet ham!"

elsif direction == "S"

puts "A minotaur! Wait, no, that's just your reflection."

elsif direction == "W"

puts "You're here, wherever here is."

else

puts "Wait, is that even a direction?"

end

Run the program by typing ruby maze.rb in the terminal and pressing ENTER. You should see something like this (though your output will change depending on which direction you pick):

Holy giraffes! You fell into a maze!

Where to? (N, E, S, W): E

E, you say? A fine choice!

An elf! And his pet ham!

The print command is new, but never fear: it’s almost exactly like puts, except it doesn’t add a new blank line after it prints out its text.

This bit is also new:

direction = gets.chomp

What we’re doing here is setting a variable, direction, equal to calling the gets method and then the chomp method right after it. This is a fancy way of saying we’re chomping gets. gets is a built-in method (you can think of it as a Ruby command) that gets the most recent input the user typed; chomp removes anything extra from the end, like spaces or a blank line. This means that we’ve now taken whatever the user typed (from gets.chomp) and stored it in our direction variable.

After that, it’s all smooth sailing! You’ve seen string interpolation with #{} already, and everything after that is just checking to see what letter the user entered with == (is equal to) and using if, elsif, and else to control what message the user sees.

You can test out your maze program by typing ruby maze.rb from the command line or, after starting up IRB, load 'maze.rb'. You can keep rerunning it with different input to see what happens each time!

You can go a bit further, though. (Don’t worry, it’s seriously a really small maze.) Here are a few ideas:

§ How might you add more directions, like NW, SW, NE, SE, up, or down?

§ How could you handle accepting lowercase letters for directions?

§ A circle has 360 degrees, and turning right is the same as turning 90 degrees. What if you wanted to let your users enter a number so they could turn that many degrees? How could you use <, <=, >, >=, ==, or != to make this work? (This is a bit beyond where we already went, but you can do it! You wouldn’t be wandering around in a maze under a castle if you weren’t the adventurous type.)

You Know This!

Control flow is tricky stuff, but doing that biggerish project proves you’ve gotten the hang of it. Let’s review some of the things we learned along the way.

We talked about Booleans, which can be true or false. They’re part of Ruby just like strings, numbers, and variables are, but they’re definitely not strings! Don’t put quotes around them, or they won’t work right.

We covered scripts and how you can run them in IRB using load 'script_name.rb'. (You can also run your Ruby programs outside of IRB entirely by typing ruby script_name.rb on the command line.) Remember: you need quotes if you’re loading a file in IRB, but you don’t need quotes if you’re typing on the command line! (Computers are very dumb and very picky.)

We explained string interpolation using #{} and how you can use it to put the values of variables directly into your strings. This comes in handy a lot, and remember: you can only do string interpolation with double-quoted (") strings. It doesn’t work with single quotes (')!

Finally, we learned about control flow using if, elsif, else, and unless, and how to combine these with logical operators && (and), ! (not), and || (or), and comparison operators < (less than), > (greater than), <= (less than or equal to), >= (greater than or equal to), == (is equal to), and!= (is not equal to). Using all these together, we can see (for example) if one thing and another thing are true, determine if one thing is less than another thing, or say we should do something unless something is not equal to something else. (Whew!)

It’s hard to believe, but this is pretty much everything computer programs do: compare values and test to see what is or isn’t true. Let’s take a minute to kick back, relax, and bask in the glow of all this Ruby know-how. (The next chapter’s gonna throw you for a bit of a loop.)