Project 7

Short Straw

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.

image

Organizing a New Project

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.

remember If you haven’t created a development folder already, refer to Project 2 for information on how to do that.

  1. Start your terminal program and enter the development folder:

    $ cd development

  2. Create a new directory for this project:

    $ mkdir project07

  3. Move into the new directory:

    $ cd project07

  4. Start Atom by double-clicking its icon.
  5. Create a new source code file by choosing File ⇒ New File.
  6. Save it by choosing File ⇒ Save and store it in your project07 directory. Call the file straws.rb.

tip If some of these steps are confusing to you, refer to the “Organizing a New Project” section in Project 4. It provides more details for each step.

You’re now ready to dive into arrays some more and build an elimination-round, tournament-style straw drawing game.

Planning the Project

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.

tip I have this saying that programmer’s are lazy. Do I mean that they don’t like to do their chores? Well, no, not usually. I mean that the best code ever written is the code you never write! What?!? The more lines of code you write, the more opportunities there are to create bugs and other problems. To become an expert programmer, you’ll look for ways to use other people’s code (such as the built-in methods, objects, and classes that Ruby provides), and you’ll look for techniques that allow you to write fewer lines of code yet get the same amount of work done. I’ll show you a few in this project.

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.

technicalstuff Data structure is a fancy term that refers to how you organize and work with your information. It might be something as simple as a plain number or string (often called a primitive or primitive data structure), or it might be something slightly more complicated like a container such as an array or a hash (which we haven’t used yet). You may not know it, but you’ve been creating your own data structures as you’ve implemented classes for objects like Player or Game in these projects. A data structure can be thought of as an abstract type of information (so I talk about Player objects, and know that a player is made up of a name, and a straw in this project).

Looking at the Program Skeleton

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.

  1. Switch over to Atom and your straws.rb file. Enter a comment at the top that provides a little information about the project:

    #

    # Ruby For Kids Project 7: Straws

    # Programmed By: Chris Haupt

    # Elimination-round, tournament-style, avoid the shortest straw, or else!

    #

  2. Be kind to your users and provide a short introduction to let them know what’s about to happen:

    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."

  3. Unlike earlier projects, you’ll skip the data entry required when requesting player names, and instead just build an array of names to use. Feel free to make these names anything you want and assign them to the constant called PLAYERS:

    PLAYERS = %w(anne bert chris donna ernie franz garfield holden ivy jose)

    technicalstuff 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.

  4. Create a new Game object by sending the Game class the new message and passing the PLAYERS array as its argument:

    game = Game.new(PLAYERS)

  5. You’ll create a number of methods in the Game class to run the game. For now, write out the main game loop as if those methods already exist:

    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.

    remember 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.

  6. Finish the main part of the program by sending the Game object a message to display the winner of the short straw tournament:

    game.show_winner

  7. Save your code now. Switch on over to the terminal and run it.

    $ ruby straws.rb

    As expected, Ruby lets you know that the program isn’t finished yet (see Figure 7-1).

image

Figure 7-1: Yup, time to start writing some classes.

Creating Placeholder Classes

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.

Creating an empty Game class

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:

  1. Type the empty Game class to start. Put it at the top of the straws.rb file, just below your comment:

    class Game

    def initialize(player_names)

    end

    # the rest of the game class code will go here

    end

  2. Because you already wrote the main program’s implementation that uses this class, stub in the methods that it calls next.

    technicalstuff 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

  3. Write the method that will display the round’s number:

    def show_round_number

    end

  4. Write the method to play a round:

    def play_round

    end

  5. Write the method to show the round’s results:

    def show_results

    end

  6. Write the method to complete the round and do any final bookkeeping:

    def finish_round

    end

  7. Write the method to display who won the match:

    def show_winner

    end

  8. Save your code and run it. You should see the introduction message, and then the program just sits there. If you recall, you have a while loop that is waiting for the Game object to indicate that it’s done. That never happens, so you’re in an infinite loop. Press Ctrl+C to get out (see Figure 7-2).
image

Figure 7-2: There are no immediate errors, but you’re stuck in a loop.

Creating an empty Player class

Next up is the Player class, which will be used to store a player’s name and current straw.

  1. Add the empty Player class first and an initialization method to accept a name. Put this code above the Game class and below the top comment in the straws.rb file:

    class Player

    def initialize(name)

    @name = name

    end

    # the rest of the player code will go here

    end

  2. Save the code again. You can run it, too, to see if Ruby detects any other errors, but otherwise, you’re still going to be stuck in that loop.

Creating an empty Straw class

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.

  1. Create a new class and add an initializer that accepts the size of the straw. Put this code above the Player class and below the top comment in the straws.rb file:

    class Straw

    def initialize(size)

    @straw_size = size

    end

    # the rest of the straw code will go here

    end

  2. Add some constants that you’ll use to represent two different sizes of straws. The numbers don’t matter much:

    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.

  3. Save again. Go ahead and run Ruby to look for any typos. Press Ctrl+C to escape the loop.

Coding the Straw Methods

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.

Creating straw getter methods

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.

  1. Create a method that can be used to test whether the straw is short:

    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.

  2. Return some kind of string that represents what the straw looks like. You’re still building a program that runs in a terminal window, but why not give the user something to look at other than just a number?

    def appearance

    '=' * @straw_size

    end

Pretty simple so far.

Creating the straw factory method

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.

technicalstuff Programmers usually use the term factory to mean a method that builds other objects for you. Just like a real-world factory, you’ll give the factory method an order (in this case, the number of straws you want), and it’ll build the object(s) for you. In this program, the factory will create an array of Straw objects.

  1. The straw factory will create an array of Straw objects. Let’s call that array of straws a “bundle” just for fun and start writing the method like this:

    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.

    technicalstuff 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.

  2. Define an empty array for the bundle:

    bundle = []

  3. Now fill the array with new Straw objects:

    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.

  4. Write another loop for the long straws using the exact same technique:

    1.upto(long) do

    bundle << Straw.new(LONG_STRAW)

    end

    You’re adding the long straws to the end of the existing array.

  5. Return the value that is held by the bundle variable and end the method. What do you think is the total length of the bundle?

    bundle

    end

  6. Save your code and run a quick test. You shouldn’t see any errors, but nothing else will happen yet either.

An array primer

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.

image

Figure 7-3: An array with four Straw objects in it.

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.

Coding the Player Methods

The player object in this project is pretty simplistic and is composed of some getters and setters and a couple of helper methods.

Creating player getters and setters

The only data that the Player class is concerned with in this project is a name and its current Straw object.

  1. Create a name getter. Up until now, you would write this like so:

    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.

    technicalstuff 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.

  2. You need both a getter and setter to read and write the player’s current Straw object. Use the short attribute access methods that Ruby provides:

    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

technicalstuff Accessor is just a fancy way of saying “getter and setter rolled into one.” Perhaps that isn’t fancy, but it is less of a mouthful! Note that there is an interesting implication with using accessors. Access to attributes is available both to code outside the associated object and inside the object. You’ll notice in future code that I’m not always using the instance variable directly (the @ is missing). When you see that, it means I’m using the attribute reader (or writer) instead.

warning It is very important that the name of the accessor (the part after the colon [:]) is exactly the same as the instance variable name (without the @). In the code you’re writing, when I show an accessor, I’m also sometimes using the instance variable. This is almost always with the initialize method to set up the variable. If the names were spelled differently, then they wouldn’t be referring to the same value and you would have a hard to find bug to track down! For instance, @name and :name are the same other than the leading symbol, so they’re okay.

Creating player helper methods

The game requires a couple of helpers when working with Player objects, so write those now:

  1. For the user interface of this project, you’ll just want to display the straw appearance alongside the player’s name. Use string interpolation to build this up:

    def appearance

    "#{straw.appearance} #{name}"

    end

  2. The game loop is going to need to see if a player is holding the short straw, so provide a method to test this:

    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.”

  3. Save the code before moving on to the game object work.

Coding Game Methods

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.

Code initialization and the end condition

Move over to the Game class to start updating the stubbed methods there.

  1. Enter the full implementation of the initialization code. It will be used to generate a set of player objects using the supplied name array:

    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.

    tip 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.

  2. Update the done? method to provide a real result:

    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.

Code user interface methods

The user interface for the project will display the current round number, the results of the round, and the final winner’s name.

  1. Using your basic string output knowledge, create a simple round indicator:

    def show_round_number

    puts ""

    puts "----> Round #{@rounds}"

    puts ""

    end

  2. Using the player classes ability to generate an appearance for the player, display the results of drawing straws for all players in the current round:

    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.

  3. The winner of the game is represented by the last object in the @players 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.

Coding the main game logic methods

We’re in the home stretch, but we still need to implement the basic game logic methods, so let’s do that now.

  1. The play_round method does the work of preparing the straws for the round and passing them out to the players:

    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

    technicalstuff 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.

  2. The play_round method uses the setup_new_bundle method that we haven’t created yet, so you’ll write that next:

    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.

  3. Finally, code the Ruby to complete a round of the game:

    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!

  4. Save the code and run the project. You should get a lot of output showing the progress of the game and then a final winner just like in Figure 7-4. Try running the program a few times and you’ll see different results.
image

Figure 7-4: The final output should show a winner!

Trying Some Experiments

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?

  • What happens if you change the composition of the bundle of straws? Try having more than one short straw.
  • What happens if you don’t shuffle the straws?
  • Using techniques you learned in the previous project, try adding the ability to type in names of players instead of starting with a hard-coded list.
  • I didn’t use attr_accessor in the Game class. Could I have? Try it out.
  • Come up with some other ways to implement the user interface.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.141.6