Simulating bingo for fun
The other day, I was playing bingo with the family. The rules are simple, each person has a 5x5 card with the center number “free”. You pick numbers from 1 to 75 without replacement until some matches 5 in a row on their card (diagonal matches are allowed).
Playing with young kids, I knew there was a clock ticking on how long their attention would last. We had three bingo cards going.
How many numbers would we have to pick for someone to win?
There may actually a closed for solution estimate the mean number of picks for someone to get Bingo with n cards in play. Alas, that is far beyond my remaining math skills. Luckily, I can simply write a Montecarlo simulation. This is about the same difficulty of an Advent of Code problem.
My solution ended up with three classes: board, game, and experiment.
Working on a project like this, I’m still frustrated with code reloading in `irb`. I should probably be able to wire zeitwerk (code loader used in Rails) to accomplish this in the future.
Final solution: playing by yourself you’ll need to pick ~41 numbers on average. And with the three of us, this drops to ~35. So now I have a good idea of how long these games will go on for.
Maybe I'll come back later and add the variance to know how often I’m going to have to wait a lot longer.
Here is the code I wrote to simulate bingo.
Playing with young kids, I knew there was a clock ticking on how long their attention would last. We had three bingo cards going.
How many numbers would we have to pick for someone to win?
There may actually a closed for solution estimate the mean number of picks for someone to get Bingo with n cards in play. Alas, that is far beyond my remaining math skills. Luckily, I can simply write a Montecarlo simulation. This is about the same difficulty of an Advent of Code problem.
My solution ended up with three classes: board, game, and experiment.
Working on a project like this, I’m still frustrated with code reloading in `irb`. I should probably be able to wire zeitwerk (code loader used in Rails) to accomplish this in the future.
Final solution: playing by yourself you’ll need to pick ~41 numbers on average. And with the three of us, this drops to ~35. So now I have a good idea of how long these games will go on for.

Maybe I'll come back later and add the variance to know how often I’m going to have to wait a lot longer.
Here is the code I wrote to simulate bingo.
module Bingo class Experiment def run(epsilon = 2, boards=1) @games = [] last_mean = 0 while true do @games.push Game.new.play(boards) mean = @games.sum.fdiv(@games.size) if (mean-last_mean).abs < epsilon return mean end last_mean = mean end end end class Game def initialize reset @i = -1 end def play(board_count=1) boards = board_count.times.map { Board.new } 5.times { draw } while(boards.none? { |b| b.win?(drawn) }) do draw end @i end def drawn return [] if @i < 0 @pulls[0..@i] end def draw @i+=1 drawn end def reset @pulls = pulls end def pulls 1.upto(75).to_a.shuffle end end class Board attr_reader :board def initialize # order col, row @board = init_board end def win?(picks) full_picks = picks + [0] return true if match?(full_picks, diag_1) return true if match?(full_picks, diag_2) 0.upto(4).each do |i| return true if match?(full_picks, col_vals(i)) return true if match?(full_picks, row_vals(i)) end false end def match?(a1, a2) (a1 & a2).length == 5 end def to_s @board end def pretty 0.upto(4).each do |i| puts row_vals(i).map { |c| c.to_s.rjust(3) }.join end end def col_vals(j) @board[j] end def row_vals(i) @board.map { |c| c[i] } end def diag_1 @board.map.with_index { |c, i| c[i] } end def diag_2 @board.map.with_index { |c, i| c[4-i] } end private def init_board 5.times.map do |i| if i == 2 (i*15+1).upto(i*15+15).to_a.shuffle[0..3].insert(2, 0) # center is always a match else (i*15+1).upto(i*15+15).to_a.shuffle[0..4] end end end end end # write out a CSV to stdout puts "cards,mean win round" 1.upto(50) do |i| result = Bingo::Experiment.new.run(0.001, i) puts "#{i},#{result}" end