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.
Ah, so "the cards are cleared" means "people keep the same cards, but reset their contents"?I believe so. I originally thought the same as you: that everyone started over with totally new cards after each round (thus keeping the game random) but I suppose that they play this way to "give everyone a chance." – Mr. G Jul 20 '13 at 15:16