# How to make repeat winners unlikely in B.I.N.G.O.

My three-year-old son loves BINGO (U.S. version). This morning I took him to observe "senior BINGO" at a local fast food joint, and something interesting (though not surprising) happened:

After a certain amount of numbers had been announced, senior "Paul" exclaimed that he had finally got one (1) to match his board. Fast forward a few more turns of the ball cage, and another player wins. Now, the caller spins the cage until 5-6 balls are out, THEN replaces all of the balls that had been called, and immediately starts the next round with the 5-6 queued balls that were not replaced.

Paul wins the next game, and I start thinking:

How many balls would you have to queue between rounds/games (from those not chosen) to ensure, with a certain confidence C (or a certain probability P?) that there is not a back-to-back winner?

The problem gets complicated quickly, at least the way I look at it. This would surely depend on the number of players, and on the number of spaces on each board that match the winner's board. If the problem is too tricky, I'd certainly be satisfied with a computer output for a few combinations of players, number of balls announced before a win, etc. (or with a description of how/why it is so complicated).

-
Shoot the winner before proceeding to the next shuffle. – Asaf Karagila Jul 13 '13 at 14:08
I don't understand the problem (I had better things to do on my ship cruises). Aen't the cards cleared before a new round with fresh numbers is started? – Hagen von Eitzen Jul 13 '13 at 23:01
@Hagen: sorry I missed this earlier! The cards are cleared and the balls are (all) returned to the cage between rounds. I'm asking how things would look if you selected $n$ of the "non-winning" numbers first in the next round, which you could guarantee by physically removing them from the cage after a winner was announced (but before the balls are returned to the cage). – The Chaz 2.0 Jul 17 '13 at 22:02
Is anyone else working on this? I've sort of started to write a program that simply plays BINGO games. I also have half of an idea how to do this analytically, but I haven't explicitly tried it yet. – Mr. G Jul 20 '13 at 4:49
@PeterTaylor This is my understanding of it (which may be wrong.) Imagine that you're Paul, you play an entire round, several numbers are drawn, and you only get one number. Someone else wins, the caller takes out $x = 5$ of the remaining balls, and starts the next round by calling those balls. Since you went so many balls without a number in the last round, the likelihood of these numbers being on your card is very high, so you win. Since you have an advantage, the previous winner must have a disadvantage. OP is looking to understand how $x$ affects the game. – Mr. G Jul 20 '13 at 14:16

Okay, so I went with the computational approach and wrote a Python script. The program plays two games in a row $N$ times and returns $n/N$, where $n$ is the number of times a player won back-to-back games (in the limit $N\to\infty$ this should be exactly the probability of a back-to-back win.) I didn't really spend too much time on optimization, so it can probably be made a little faster. To use the program yourself, simply call the function P_btb_win(Ntrials, Nplayers, Nhands, Nremoves) with whatever parameters you want (Nhands is the number of cards each player has and Nremoves is the number of balls removed before each round.) I believe that $10^5$ trials reliably gets you $2$ digits of accuracy on the resulting probability. Here is the program:

from random import shuffle, sample, choice
from time import clock
from copy import deepcopy

# General plan: card represented as 5x5 arrays of ints, with
# filled spaces represented by 0's and called numbers being
# represented by [column number, actual number].
# Bingo is obtained with straight lines (including diagonals)

def generate_card(): # makes fresh card
b = sample(range(1, 16), 5)
i = sample(range(16, 31), 5)
n = sample(range(31, 46), 5)
n[2] = 0 #free space
g = sample(range(46, 61), 5)
o = sample(range(61, 76), 5)
return [b, i, n, g, o]

def generate_draws(): # makes randomized list of balls to be drawn
draws = []
for x in range(0, 5):
for y in range(1 + x*15, 16 + x*15):
draws.append([x, y])
shuffle(draws)
return draws

def did_i_win(card, filled): # simulates player checking
# whether or not they won after adding "filled". Player
# only checks the row an column of the last number he filled,
# or the diagonal if necessary.
for column in range(0, 5):
if card[filled[0]][column] != 0:
for row in range(0, 5):
if card[row][filled[1]] != 0:
if filled[0] == filled[1] or filled[0] + filled[1] == 4:
for x in range(0, 5):
if card[x][x] != 0:
for y in range(0, 5):
if card[y][4 - y] != 0:
return 0
return 1
return 1
else:
return 0
return 1
return 1

def update_cards(mycards, called): # simulates player
# updating all their cards with the most
# recently called number; returns 1 if BINGO
newcards = mycards
for card in range(0, len(mycards)):
for row in range(0, 5):
if newcards[card][called[0]][row] == called[1]:
newcards[card][called[0]][row] = 0
if did_i_win(newcards[card], [called[0], row]) == 1:
return 1
return newcards

def grid_print(card): # Prints a card out as a grid, only used for debugging
for x in range(0, 5):
print card[0][x], card[1][x], card[2][x], card[3][x], card[4][x]

def play_two_rounds(Nplayers, Nhands, Nremoves): # Simulates the players playing
# two games; returns 1 if a player wins back to back and 0 otherwise.
hands = [] # the original unfilled cards
for x in range(0, Nplayers):
hand = []
for y in range(0, Nhands):
hand.append(generate_card())
hands.append(hand)
play = deepcopy(hands) # the played cards
undrawn = generate_draws() # balls in wheel
drawn = [] # balls discarded
winner1 = -1
while winner1 == -1: # while no one has won
card = undrawn.pop(0) # Ball drawn and discarded
drawn.append(card)
for x in range(0, Nplayers): # Players update their hands
play[x] = update_cards(play[x], card)
winners = [] # list of BINGO-getters
for y in range(0, Nplayers):
if play[y] == 1: # If someone just won
winners.append(y)
if len(winners) > 1: # If more than one BINGO, select random winner
winner1 = choice(winners)
if len(winners) == 1:
winner1 = winners[0]
if len(undrawn) < Nremoves: # If there are less balls remaining than we need to draw, use as many as possible
shuffle(undrawn)
shuffle(drawn)
undrawn = undrawn + drawn
drawn = []
play = deepcopy(hands)
else: # There are enough balls for the queue length
nextballs = []  # List of balls that will be up next
samples = sample(range(0, len(undrawn)), Nremoves) # Random list of Nremoves positions of undrawn
for x in samples: # Selects Nremoves random balls from the undrawn pile and puts them in nextballs
nextballs.append(undrawn[x])
undrawn_remains = [] # List of undrawn balls remaining
for x in range(0, len(undrawn)):
if x not in samples:
undrawn_remains.append(undrawn[x])
others = undrawn_remains + drawn # every other ball
shuffle(others)
undrawn = nextballs + others
drawn = []
play = deepcopy(hands)
# At this point, the player in position "winner1" has just won, and we've reset the cards. Play again:
winner2 = -1
while winner2 == -1:
card = undrawn.pop(0)
drawn.append(card)
for x in range(0, Nplayers):
play[x] = update_cards(play[x], card)
winners = []
for y in range(0, Nplayers):
if play[y] == 1:
winners.append(y)
if len(winners) > 1: # If more than one BINGO, select random winner
winner2 = choice(winners)
if len(winners) == 1:
winner2 = winners[0]
if winner1 == winner2: # Player won back-to-back
return 1
else:
return 0

def P_btb_win(Ntrials, Nplayers, Nhands, Nremoves): # Runs Ntrials simulations and returns the experimental
# probability of a back-to-back win with the input parameters.
wins = 0
t = clock()
for x in range(0, Ntrials):
wins = wins + play_two_rounds(Nplayers, Nhands, Nremoves)
print "Run time =", clock() - t
print "For", Nplayers, "players with", Nhands, "card(s) each and", Nremoves, "ball(s) removed between rounds, the probability of a back-to-back winner is", float(wins)/float(Ntrials)
return


It does take a fair amount of time to run these trials, but I performed a few and obtained the following results in the case of one card per player. Let $f(x, y)$ be the probability of a back-to-back win occurring with $x$ players and $y$ balls removed before the second turn. Note that if $y$ is greater than the number of balls remaining in the wheel, as many as possible are removed. Then:

\begin{array}{|c|c|c|c|} \hline x& y & f(x,y) \\ \hline 2& 0& 0.50\\ \hline 2& 15& 0.47\\ \hline 2& 30& 0.43\\ \hline 2& 75& 0.40\\ \hline 6& 0& 0.17\\ \hline 6& 15& 0.14\\ \hline 6& 30& 0.12\\ \hline 6& 75& 0.10\\ \hline 15& 0& 0.065\\ \hline 15& 15& 0.054\\ \hline 15& 30& 0.042\\ \hline 15& 75& 0.040\\ \hline \end{array}

Based on these results, it seems as though one can't make the probability of a back-to-back win arbitrarily small, which I guess makes sense. It's entirely possible for someone to win in $10$ draws and then win in the next $10$ draws, no matter how many balls you remove. In fact, the number of balls removed seems to have only a marginal (but clearly visible) effect on the probability of a back-to-back win. Paul's case is extreme: to get only one number in an entire game is highly unlikely. One would, of course, wish for a more comprehensive probability table to confirm these ideas, but I lack the computational resources for such a task.

-
Wow, thanks for your attention to this! Quick question - the first line of the table seems to indicate that the winner of game 1 has a .50 chance to win game 2. That can' be right...? – The Chaz 2.0 Jul 22 '13 at 1:59
@TheChaz2.0 Well, $y=0$, so the number of balls removed before the second game is zero ie. the second game is totally independent from the first. – Mr. G Jul 22 '13 at 2:05
I missed the part about there being only two players in that game. Carry on! – The Chaz 2.0 Jul 22 '13 at 2:11
@TheChaz2.0 No worries :) – Mr. G Jul 22 '13 at 2:31