Project 7
Sometimes when you have a group of people, you need to randomly select one of them to do something. Maybe that’s to go first in a game, to do a chore, or just for fun you want to see who is the last person standing (think musical chairs). One old game that’s sometimes used to pick somebody is to take a bundle of straws (or sticks or pencils), make them all the same size except for one of them, and then randomly pick from the bundle. Whoever gets the short straw is “out” (or wins, depending on how you look at it!).
This project is going to dive a little more deeply into the Array object you’ve been using in some of the prior projects. Up until now, I’ve just used it with some basic functionality, but here, you’ll see that it’s a pretty powerful tool. I’ll show you a few different ways to use arrays, and as a bonus, I’ll show you a few shortcuts you can use when working with arrays and classes.
You’ll use Atom to create and edit your program. You’ll store this project in a single Ruby file. And you’ll use the terminal program to run, test, and play around with the short straw code.
$ cd development
$ mkdir project07
$ cd project07
You’re now ready to dive into arrays some more and build an elimination-round, tournament-style straw drawing game.
In theory, this project is much simpler than prior projects, at least with respect to the idea of a bunch of people taking a random straw, and then seeing who picked the “short” one. Don’t let this simplicity fool you, though! You still want to plan out the overall approach to the code, and as you write out the Ruby, I’ll take a little time to show you that there is more than one way to do many of the same logical steps. You’ll see that Ruby provides a number of tools that make it easier to write less code.
There will be a game object that manages a group of players (also objects) and runs rounds of the game. Each round, the players will be given new straws (yet additional objects) to compare against one another. The game object is also where the rules of the game are coded, even for such a simple game.
The game should welcome the players and provide a short introduction of what’s going to happen.
Unlike prior projects, you won’t have to enter a bunch of players at the start of the game. Instead, you’ll provide an array of names, and the game object will use that to generate a set of associated player objects.
Each round, the game will create a new “bundle” (really just another array) of straw objects. It will count how many players remain in the game, and generate one straw for each of those players. The game will make one of the straws “shorter” than the others.
The game will assign one straw to each player. It’ll print out a message that shows what round is being played. It’ll also print out all remaining players so you can see which player has which kind of straw. The game will remove the player with the short straw from the remaining players (how sad!). If the game determines that there is more than one player remaining, it will run another round. When only one player remains, the game ends and displays the winner.
This is a simple game, but it gives you a chance to try a few different techniques for working with arrays. Ruby arrays are one of the core data structures you’ll use all the time in your own programs, so it’s worth getting familiar with all the tools Ruby gives you.
You’ll continue to use what you’ve learned about object-oriented programming and sketch out this project in the form of several classes and a main program to use them. You’ll need to create a Player, Straw, and Game set of classes and a little bit of Ruby to get everything started.
#
# Ruby For Kids Project 7: Straws
# Programmed By: Chris Haupt
# Elimination-round, tournament-style, avoid the shortest straw, or else!
#
puts "Welcome to the Last Straw Game"
puts "In each round, players will draw straws of two different lengths."
puts "The players who pick the short straw will be eliminated and"
puts "a new round will begin."
PLAYERS = %w(anne bert chris donna ernie franz garfield holden ivy jose)
Wait a second! That is an odd-looking array. Remember how I told you I was going to show a few Ruby shortcuts? Here is the first one. The little %w symbol means to create an array where the strings are separated by white space. You can absolutely write this array the long way like this:
PLAYERS = ["anne", "bert", "chris", "donna", "ernie", "franz", "garfield", "holden", "ivy", "jose"]
Doing so requires three extra characters to be typed for each name (two quotes and one comma, except for the last name). Why do all that extra typing if there is a shortcut? Sometimes you have to use the longer form — for instance, if your name is Chris von Programmer, the white space in the name would confuse Ruby and it would break up the name incorrectly.
game = Game.new(PLAYERS)
while !game.done? do
game.show_round_number
game.play_round
game.show_results
game.finish_round
end
The sequence of events here pretty much follows what you planned out earlier. By naming methods appropriately, you’ll find that reading the code is super easy.
I mentioned this before, but naming is really important! Pick variable, method, and class names that are meaningful to you and to future readers of your code. You may be that future reader. When you come back and look at your program in six months, you’ll be a lot happier that you sent your future self some easy-to-read code.
game.show_winner
$ ruby straws.rb
As expected, Ruby lets you know that the program isn’t finished yet (see Figure 7-1).
As you learned in the previous project, you can use Ruby to guide you while writing your code. If you add some small bits of Ruby, save, and then run, the various warning and error messages Ruby produces give you a pretty good idea whether you’re on track. I’ll use that technique again here to help you get comfortable with it.
I’m going to show you the details of this program from the bottom up, but let’s get some placeholders in the file first:
class Game
def initialize(player_names)
end
# the rest of the game class code will go here
end
Because you already wrote the main program’s implementation that uses this class, stub in the methods that it calls next.
The term stub is used by programmers to indicate a placeholder, temporary, or test implementation of some code. Here you’re writing the methods that make your programming interface for the Game class but not (yet) filling in the code that actually does anything.
Write the method that indicates if the game is done. Put the code inside the Game class:
def done?
end
def show_round_number
end
def play_round
end
def show_results
end
def finish_round
end
def show_winner
end
Next up is the Player class, which will be used to store a player’s name and current straw.
class Player
def initialize(name)
@name = name
end
# the rest of the player code will go here
end
You’re going to represent the straws that players get with an object as well. The Straw class will keep track of the size of the straw (as a number) and be able to tell if it’s the “short straw.” Eventually, it will be able to draw itself, too.
class Straw
def initialize(size)
@straw_size = size
end
# the rest of the straw code will go here
end
SHORT_STRAW = 5
LONG_STRAW = 20
Put this code right above the initialize method. In most cases the position doesn’t matter, but I like putting things like constants at the top of the class to make it easier to find and see them.
I’m going to explain the short straw code in a bottom-up fashion for the rest of this project. As you fill out the classes, you’ll begin to see that there are different ways to do things in Ruby, and I’ll point out a few.
The purpose of the Straw class is to represent an object that knows how big it is and is able to display itself, and that’s about it. Pretty simple. Simple objects are important, because it can make code easier to understand, easier to reuse, and easier to debug.
It’s also a good idea to put code that is logically related together when possible. On the other hand, it’s also a good idea to separate out pieces of code that have different jobs or responsibilities. We could have just built in the Straw capability directly into the Player class, but we’re breaking it out because it’s a different thing.
There are a couple of different pieces of information that need to be shared by a straw: whether it’s short and what it looks like.
def short?
@straw_size == SHORT_STRAW
end
This method will return a Boolean (a true or false) value. If the size of the straw is equal to your constant, it will return true. Otherwise, the straw will not be considered short and the method will return false.
def appearance
'=' * @straw_size
end
Pretty simple so far.
For this program, the main game object will run multiple rounds, each time reducing the number of players by one. On each of those rounds, the game also needs to construct another collection of straws for the players to use. To make this easier, you’ll create a factory method that builds all the straw objects you need in one shot.
def self.create_bundle(short, long)
This almost looks like a regular method. The name is fine — create_bundle tells us what the factory is going to build. The arguments are the number of short and long straws we want built. The self. part tells Ruby that this is a class method.
What is a class method? All the methods you have created so far didn’t have that self. part in front of the method name. Remember that a method is a message that you send to a related object. But that assumes that the object exists already. How do you send a message when the object doesn’t exist? One way is to send a message to the class itself. For the purposes of a factory method, you want to create new objects of a particular type, so you attach the method to the desired class using the self. syntax. You can then use the code to create any number of objects. You’ll see this pattern often in Ruby for methods that are used to create or manipulate groups of objects.
bundle = []
1.upto(short) do
bundle << Straw.new(SHORT_STRAW)
end
You’ve created a loop that will go as many times as the count inside the short variable. The << syntax is one way to add an object to an array. Here you’re creating a new Straw object, setting its size to the value in the SHORT_STRAW constant, and then adding that object to the array.
1.upto(long) do
bundle << Straw.new(LONG_STRAW)
end
You’re adding the long straws to the end of the existing array.
bundle
end
Arrays are one of the most basic and most useful container data structures you’ll use when programming. Arrays are like boxes with many compartments or slots in which you can put things. An array of straws might look something like Figure 7-3.
Each slot holds one object. The slots are numbered starting at zero. In Figure 7-3, the first slot holds a short straw, and its number is zero. Programmers call the position number of an item in an array its index number.
In the previous section, the create_bundle factory method added new straws to the end of the array with the << method. In Figure 7-3, if you were going to add another object at the end, it would go at index four.
Arrays in Ruby aren’t a fixed size. The illustration here shows a box of a certain size, but in reality, the only limitation is the amount of memory available for Ruby to use. You can create some massive arrays if you need to.
Objects in an array are randomly accessible by referring to the index number of the object you want. For instance, in Figure 7-3, if you wanted to access the short straw, and the array was in the variable named bundle, you would use the syntax bundle[0]. If you wanted to get to the second item, that would be bundle[1], and so on.
As you’ll see throughout this project and others, there are a large number of helpful methods available to you to work with arrays.
The player object in this project is pretty simplistic and is composed of some getters and setters and a couple of helper methods.
The only data that the Player class is concerned with in this project is a name and its current Straw object.
def name
@name
end
This is a super simple method that just returns the value of the @name instance variable. Writing methods like this is extremely common, so Ruby gives you a shortcut that can be written in one line:
attr_reader :name
Behind the scenes, Ruby basically writes the same code as the first version. You get the same behavior but save some possible keystrokes.
What does the attr stand for? Programmers have a special name for instance variables of an object, particularly if they’re exposed to the outside world. In Ruby, these are called attributes. Some programmers also call these properties. The call above is an attribute reader, which is another way to say a getter.
attr_reader :straw
attr_writer :straw
Huh, that seems like too much typing, right? Well, Ruby has shorter shorthand for this common case, so use that instead:
attr_accessor :straw
The game requires a couple of helpers when working with Player objects, so write those now:
def appearance
"#{straw.appearance} #{name}"
end
def short_straw?
straw.short?
end
This is a simple little method that lets the game engine deal with just testing the player object to see if it’s holding a short straw. The game engine could reach in and check the straw by getting it from the player first, but that isn’t a good programming approach. Using a method on the player instead hides the details of figuring out what the player is holding and whether it qualifies as “short.”
It’s time to finish the project and make use of the other objects you’ve just written. For each of these tasks, create or update the associated method inside the Game class.
Move over to the Game class to start updating the stubbed methods there.
def initialize(player_names)
@players = []
player_names.each do |name|
@players.push(Player.new(name))
end
@rounds = 1
end
You’re also starting the round count at one, and you’ll use that for the user interface.
This setup code looks pretty familiar to what you did in the create_bundle factory method in Straw, doesn’t it? You’re loading an array with Player objects in this case, but I’m using a different method called push, rather than <<. The push method appends the object at the end of the array and is nearly identical to <<. I want to illustrate that there is often more than one way to do something in Ruby. If you carefully look at the Ruby documentation, you may spot some differences, but they aren’t important here. You should use the method that is easier for you to understand.
def done?
@players.length <= 1
end
The test you’re using here is to see if there is more than one player object in the array in the @players instance variable. Every round will be removing at least one object, so eventually this condition should be true.
The user interface for the project will display the current round number, the results of the round, and the final winner’s name.
def show_round_number
puts ""
puts "----> Round #{@rounds}"
puts ""
end
def show_results
@players.each do |player|
puts player.appearance
end
end
The each method loops through the array in the @players instance variable. In each cycle, it puts the next player object in the player local variable and then does whatever is in the block of code between the do and the associated end keyword. This is probably the most common way to loop through an array.
def show_winner
last_player = @players.first
puts ""
puts "The winner is #{last_player.name}"
puts ""
end
Ruby’s array class gives you a nice method called first that returns the first element in the array. In your code, there should only ever be one remaining player at the end of the game. Remember from the array discussion earlier in this chapter that you can refer to objects in an array by their index numbers. You could have wrote the last_player assignment line like this:
last_player = @players[0]
I think using the first method is a little easier to read, but that’s a matter of personal preference.
We’re in the home stretch, but we still need to implement the basic game logic methods, so let’s do that now.
def play_round
bundle_of_straws = setup_new_bundle
0.upto(@players.length - 1) do |index|
player = @players[index]
player.straw = bundle_of_straws.pop
end
end
Notice that I’m showing you yet another way to loop through an array. You may think this is a little more complicated looking than using the each method, and you’re right. However, let’s look at what’s going on. I’m using the trusty upto method to count from zero to the length of the player array minus one. Why is that? I’m trying to generate the index numbers for the array. Remember, these start at the number zero for the first item. If I didn’t take away one at the end, I’d be trying to get one too many items from the array. Ruby doesn’t like that! Inside of the loop, I get the current number in the index variable and use the array index access method (the square brackets: @players[index]) to get the next player.
One new array method being used here is the pop method. The pop method removes the last item on the array and returns that. The bundle_of_straws local variable contains a randomly sorted array of Straw objects. The code grabs the last one off the array and assigns it to the player using the straw setter (accessor) of the player object. Phew! That’s a lot of words for a few short lines of code.
def setup_new_bundle
number_of_players = @players.length
bundle = Straw.create_bundle(1, number_of_players - 1)
bundle.shuffle
end
This method first determines how many players there are. The array object provides the length method to return the total number of items in the array. Next, you use the handy factory method from the Straw class to create a new array of Straw objects. In this game, you’re going to create one short straw, and the rest will be long straws. Finally, the Ruby array class provides a nice utility method for randomly mixing up the items in the array, much as you would shuffle a deck of cards. You use shuffle, and the mixed-up array is returned as the result of the method.
def finish_round
@players.delete_if do |player|
player.short_straw?
end
@rounds += 1
end
Here is another method provided to you by Ruby’s array class: delete_if. This is a special kind of loop. What delete_if does is loop through the contents of the array, passing each item to the block of code using the player local variable. Inside of the block of code, you call the player’s short_straw? method to check to see if that player has the short straw. If the value is true, then that tells the delete_if method to remove that object from the array. How handy!
Although this project is a rather simple idea, it shows you the power of using Ruby’s array class, one of the most common data structures you’re likely to use when programming.
You could do more things with this project. Why not try a few?
3.133.141.6