Programming Fundamentals - Lab 6 - Solution

The problem

From labs/6-craps.rb in your copy of ruby-intro:

Craps: the whole game. The rules:

  • The first roll: 7 or 11 wins. 2, 3 or 12 loses. Anything else becomes the point.
  • Once there is a point, keep rolling until:
    • the point comes up again: the player wins
    • a 7 comes up: the player loses
  • After the point is set, nothing else matters. Any other total means roll again.

Show each roll ("Roll 1: 8"), the point once it's set, then "YOU WIN!" or "YOU LOSE!".

Run it from the top folder of the repo:

bin/rails runner labs/6-craps.rb

The first roll is already written. It's lab 2 with each roll numbered:

roll_number = 1
total = rand(1..6) + rand(1..6)
puts "Roll #{roll_number}: #{total}"

if total == 7 || total == 11
  puts "YOU WIN!"
elsif total == 2 || total == 3 || total == 12
  puts "YOU LOSE!"
else
  point = total
  puts "THE POINT IS #{point}"

  # what happens after the point is set goes here

end

The only part left is the else branch, after the point is set. That's the new idea: until now every program ran top to bottom once. This one has to keep going until something happens, and you can't know in advance how many rolls that will take.

1. Pseudo-code first

Before any code, write what happens after the point in plain English, one step per line, as comments:

# 1. roll again, and show the total
# 2. if it's the point, the player wins: stop
# 3. if it's a 7, the player loses: stop
# 4. anything else: nothing happens, go back to step 1

Check yours against the rules. The gaps that show up most often:

  • No "keep rolling." One more roll, then done. The rules say roll until.
  • No way to stop. Rolling forever is the opposite mistake.
  • Nothing about the other totals. What happens to an 11 after the point? A 2? The rules say nothing: they mean roll again. A first-roll rule (11 wins, 2 loses) creeping into the point phase is the most common bug in this lab, written by people and by chats alike.

2. Ask a chat for the code

Give a chat the rules, your pseudo-code and the file, and say exactly what you want back. Something like:

I'm writing a craps game in plain Ruby. The rules:
- First roll: 7 or 11 wins. 2, 3 or 12 loses. Anything else becomes the point.
- Once there is a point, keep rolling until the point comes up again (the player wins) or a 7 comes up (the player loses).
- After the point is set, nothing else matters. Any other total, including 2, 3, 11 or 12, means roll again.

My pseudo-code for what happens after the point:
# 1. roll again, and show the total
# 2. if it's the point, the player wins: stop
# 3. if it's a 7, the player loses: stop
# 4. anything else: nothing happens, go back to step 1

My file is below. The first roll is already written. Fill in only the else branch, under my pseudo-code, with the code for each step under its comment.

Use only what the file already uses: variables, if/elsif/else, loop do ... end with break, and puts. No methods, no classes, no gets. Print each roll the way the first roll does ("Roll 2: 9"), then "YOU WIN!" or "YOU LOSE!". Give me just the code for the else branch.

[paste labs/6-craps.rb here]

The last paragraph matters. Without it, a chat will often hand back a method, a class, or code that waits for you to press Enter between rolls (gets). None of that is wrong, but it's not what you asked for, and it's harder to read.

3. The loop

Each line of pseudo-code becomes a line or two of code under its comment.

loop do ... end repeats everything inside it, forever, until it reaches break. So the two ways the game ends are the two places we break:

  loop do
    # 1. roll again, and show the total
    roll_number = roll_number + 1
    total = rand(1..6) + rand(1..6)
    puts "Roll #{roll_number}: #{total}"

    # 2. if it's the point, the player wins: stop
    if total == point
      puts "YOU WIN!"
      break
    # 3. if it's a 7, the player loses: stop
    elsif total == 7
      puts "YOU LOSE!"
      break
    end
    # 4. anything else: nothing happens, go back to step 1
  end

A few things to notice:

  • roll_number = roll_number + 1 comes before the roll, so the second roll prints as "Roll 2". roll_number was set to 1 above, outside the loop, so it keeps counting up.
  • total is reused. We don't need the first roll's total any more; point remembers it.
  • Step 4 has no code. If the total is neither the point nor a 7, nothing in the if runs, we reach the bottom of the loop, and it goes around again. That's how an 11 or a 2 after the point "does nothing." Saying it in the pseudo-code is still worth it: it's the step that keeps the first-roll rules out.

If you run it and it never stops, press Ctrl+C. Most likely a break is missing.

Final code

roll_number = 1
total = rand(1..6) + rand(1..6)
puts "Roll #{roll_number}: #{total}"

if total == 7 || total == 11
  puts "YOU WIN!"
elsif total == 2 || total == 3 || total == 12
  puts "YOU LOSE!"
else
  point = total
  puts "THE POINT IS #{point}"

  loop do
    # 1. roll again, and show the total
    roll_number = roll_number + 1
    total = rand(1..6) + rand(1..6)
    puts "Roll #{roll_number}: #{total}"

    # 2. if it's the point, the player wins: stop
    if total == point
      puts "YOU WIN!"
      break
    # 3. if it's a 7, the player loses: stop
    elsif total == 7
      puts "YOU LOSE!"
      break
    end
    # 4. anything else: nothing happens, go back to step 1
  end
end

One run:

Roll 1: 9
THE POINT IS 9
Roll 2: 8
Roll 3: 6
Roll 4: 9
YOU WIN!

Your chat may have written it differently: a while loop, two loops, a method. Same rules, different code. If it follows the rules, it's right.

So, is it right?

How do you know? Most of us ran it a few times, and it seemed to work. But each run, the dice decided which case you got to see. Did you see a point followed by an 11? A point followed by a 2? Running a random program again isn't checking it. Lab 7 takes the dice out so the game can be checked.