daniel baird dot com

Ruby bingo cards - part 1

For my wife’s last baby shower, I used Ruby to make cards for a bingo game. This is a walkthrough of that process, which might be interesting if you’re new to Ruby. In this first part, I’ll frame the problem, and work out a reasonable data structure.

The background

In 2012 my wife was pregnant with our third child, and my 11 year old daughter wanted to plan the baby shower. One of the games she planned was “Baby Bingo”, by which she meant bingo where the squares are baby-related pictures. She asked me to help prepare the bingo cards, a task which seems algorithmic and slightly maths-y. Quick! To a text editor!

What’s bingo?

I thought everyone knew what bingo was, but I had to explain bingo to a couple of people this week, so here’s a brief description of the game. Skip this bit if you’re already familiar with the game.

Bingo is a game of chance. Players each get a bingo card, which is a square grid of randomly selected numbers. Like this:

30 62 2 37 11
76 21 90 94 35
80 83 98 84 43
1 96 33 57 27
92 67 49 95 16

All the players listen to the bingo caller who chooses numbers at random, calling each number out. The players mark off a number from their card if it gets called.

Play continues and players mark off more and more numbers from their cards. Eventually some player will have a line across their bing card made out of marked off numbers. The line can be a row, column, or a corner-to-corner diagonal. That player shouts out “bingo!” and wins a prize.

Why write this article?

It’s possible to learn a programming language or tool by working through tutorials and trying stuff out on your own. But I reckon it also helps to watch someone else do the thing you’re trying to learn. It helps you to pick up little idioms that often don’t get covered by tutorials. So, I’ve documented what I did to muddle through this project, and hopefully that can help someone just learning Ruby.

Framing the problem

So let’s use Ruby to make some bingo cards. Here are the features I want in my bingo card maker:

No repeats. I’m not sure how real bingo does it, but I don’t want the same thing showing up in multiple squares on a single bingo card.

No draws. It would be nice to avoid draws, where two players get bingo at the same time. I think real bingo has draws which are broken by who’s first to yell out “bingo”, but that’s for serious bingo players and we want a more casual party atmosphere.

Images, not numbers. The bingo squares should be pictures of things like babies, bottles, rattles etc. rather than numbers.

Printable. Obviously the output needs to be something we can print out.

Let’s get started. Initially I’ll ignore all of those things I just mentioned, but I’ll fix each one up eventually.

First step: a data structure for bingo cards

Data structures are important, and I usually start a project like this by working out a data structure and trying it out.

We need a square grid. Ruby has arrays, and arrays-of-arrays. So we can make a bingo grid as an array of rows, where each row is an array of numbers.

Source code

card = []              # start with a blank array
5.times do             # loop 5 times..
  card << [1,2,3,4,5]  # add an array to the 'cards' array
end                    # ..end of the 5-times loop

puts card              # print the card

Result

1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5

Hmm, a long single column of numbers. Maybe that’s right… it’s quite hard to tell.

Nicer output

Let’s make card output a bit better.

Source code

def print_card a_card     # make a method called 'print_card'
  a_card.each do |row|    # loop thru each row..
    row.each do |square|  # loop thru each square in the row..
      print square        # print the current square
    end                   # ..end of each-square loop
    puts                  # put a newline after each row of squares
  end                     # ..end of each-row loop
end                       # end of the print_card method

card = []
5.times do
  card << [1,2,3,4,5]
end

print_card card

Result

12345
12345
12345
12345
12345

That looks nicer, and confirms our card structure is okay. It would look even cooler with random numbers though, right?

Source code

  def print_card a_card
    # ...
  end

  card = []
  5.times do           # loop 5 times (to make 5 rows)
    row = []           # each row starts as an empty array
    5.times do         # loop 5 times..
      row << rand(25)  # add a random number to the row
    end                # ..end of 5-times loop
    card << row        # add our filled-in row to the card
  end                  # end of the 5-rows loop

  print_card card

Result

1618342
21922114
171410719
242362416
15619421

Whoah, that looks crazy. We’re getting some two-digit numbers, so our output looks jumbled up.

Maybe nicer output THIS time

It’s easy to add a space after each number:

Source code

def print_card a_card
  a_card.each do |row|
    row.each do |square|
      print square + " "
    end
    puts
  end
end

…except that doesn’t work — you’ll see a String can't be coerced into Fixnum error. That’s because Ruby sees you’re trying to add a string to a number, which doesn’t make sense. Ruby will let you do all sorts of useful maths-y things with strings, like puts ("Na" * 2 + " ") * 8 + "Batman!", but here Ruby wants you to be clear about what you mean by “adding” a string and a number.

In this case you want to join them together as strings. So use the to_s method to turn the number into a string:

Source code

def print_card a_card
  a_card.each do |row|
    row.each do |square|
      print square.to_s + " "
    end
    puts
  end
end

Result

1 4 11 12 19
5 8 17 2 11
16 12 3 23 19
9 11 4 17 0
3 14 19 19 17

..and that works, kind of. It’s all irregular though. What we really want is for each number to take up a certain width, irrespective of how many digits it takes. Ruby strings have a center method that does the trick:

Source code

def print_card a_card
  a_card.each do |row|
    row.each do |square|
      print square.to_s.center 5
    end
    puts
  end
end

Result

 20    1   11    6   18
  0    8   21   23   11
 22   17   19   11   14
 22   17   20    1   13
  2    7    6   19   20

Bam! Awesome. Your numbers are random, run it a few times to see them changing. We’re miles away from meeting the features I want, but I’m confident the data structure is working, so it’s time to go make dinner. In Part 2 I’ll use a Ruby class to wrap up all this functionality.

This article

by Daniel Baird first appeared here on 19 July 2013.

Topics