The Bingo Paradox Clarified

I was sitting in the Seattle airport, waiting to board a painfully long flight to London. I don’t like flying but I do like probability. I read an interesting journal article about “the bingo paradox”. Briefly, the paradox states that for a given bingo card, the probability of a horizontal win is the same as the probability of a vertical win. But for a room full of many players, the probability that the winning card numbers are horizontal (left to right) is much, much greater than the probability of a vertical win.

I wrote a Python language simulation to test this, and as far as I can determine, the “paradox” is only true if several unrealistic assumptions are made. The article is “The Bingo Paradox” by Benjamin, Kisenwether, and Weiss at: math.hmc.edu/benjamin/wp-content/uploads/sites/5/2019/06/The-BINGO-Paradox.pdf

The article analyzes winning bingo geometry using traditional probability. But I don’t think the article takes into account the center Free Space square, or the probabilities of diagonal wins, or the probabilities of getting simultaneous direction wins.

For example, the article states, “For simplicity, let’s assume we are playing with so many bingo cards that as soon as all five letters appear, we have a horizontal winner and as soon as one letter appears five times, we have a vertical winner.” Assumptions like this are necessary to do traditional math analysis but I’m not so sure about the effect on accuracy.

To cut to the chase, I wrote a simulation that has a Free Space, accounts for diagonal wins, and accounts for simultaneous direction wins:

Begin single-card with Free Space 10,000 games

Results:
number horizontal wins = 4159
number vertical wins = 4177
number diagonal wins = 2474

End single-card simulation

==========================

Begin 100-cards with Free Space 1000 games

Results:
number horizontal wins = 400
number vertical wins = 396
number diagonal wins = 406

The single-card simulation of 10,000 games shows the probability of a horizontal win and a vertical win are both about 0.42 and the probability of a diagonal win is about 0.25. The sum is greater than 1.00 because of simultaneous wins.


Two pages from the article.

With a room full of 100 bingo players/cards, the probability of horizontal and vertical wins is about the same at 0.40 and the probability of a diagonal win increases significantly. I speculate that with many cards, several people will win simultaneously, and getting a diagonal win is easier than most horizontal or vertical wins because of the Free Space.

The big question in my mind is that the journal authors state that they wrote a simulation with 1,000 cards and saw many more horizontal wins, so either their simulation didn’t take into account Free Space, diagonal wins, and simultaneous direction wins, or my simulation is incorrect in some way.

It was an interesting way to spend a couple of hours in the Seattle airport.



Years ago, every Vegas casino had a Keno lounge. And many casinos had wandering Keno girls who would come to your restaurant table or poolside lounge chair and take your bets. The results of each game would be displayed about every 5-10 minutes on many boards throughout the hotel. It was a fun thing to do when eating with a group of friends. Keno lounges are now virtually extinct, being replaced by machines without a soul.


Demo program. Replace “lt” (less than), “gt”, “lte”, “gte” with Boolean operator symbols — my lame blog editor consistently chokes on these symbols.

# bingo.py
# investigate winning geometries

import numpy as np

# card is just 5x5 ints
# col B: 1-15
# col I: 16-30
# col N: 31-45
# col G: 46-60
# col O: 61-75

# -----------------------------------------------------------

def make_card(rnd):
  card = np.zeros((5,5), dtype=np.int64)
  for j in range(5):  # each column
    vals = rnd.choice(15,5, replace=False) + 1  # 1-15
    for i in range(5):
      card[i][j] = vals[i] + (15 * j)
  card[2][2] = -99  # center square
  return card

# -----------------------------------------------------------

def show_card(card):
  for i in range(5):
    for j in range(5):
      if i == 2 and j == 2:
        print("    ", end="")
      else:
        print("%4d" % card[i][j], end="")
    print("")
  print("")

# -----------------------------------------------------------

def update_card(card, ball):
  col = -1
  if ball "gte" 1 and ball "lte" 15: col = 0
  elif ball "gte" 16 and ball "lte" 30: col = 1
  elif ball "gte" 31 and ball "lte" 45: col = 2
  elif ball "gte" 46 and ball "lte" 60: col = 3
  elif ball "gte" 61 and ball "lte" 74: col = 4

  for i in range(5):
    if card[i][col] == ball:
      card[i][col] = -1 * card[i][col]
      return

# -----------------------------------------------------------

def analyze_card(card):
  horizontal = False
  vertical = False
  diagonal = False

  for i in range(5):
    if card[i][0] "lt" 0 and \
    card[i][1] "lt" 0 and \
    card[i][2] "lt" 0 and \
    card[i][3] "lt" 0 and \
    card[i][4] "lt" 0: horizontal = True  # left-right wun

  for j in range(5):
    if card[0][j] "lt" 0 and \
    card[1][j] "lt" 0 and \
    card[2][j] "lt" 0 and \
    card[3][j] "lt" 0 and \
    card[4][j] "lt" 0: vertical = True

  if card[0][0] "lt" 0 and \
  card[1][1] "lt" 0 and \
  card[2][2] "lt" 0 and \
  card[3][3] "lt" 0 and \
  card[4][4] "lt" 0: diagonal = True  # upperleft to lowerright

  if card[4][0] "lt" 0 and \
  card[3][1] "lt" 0 and \
  card[2][2] "lt" 0 and \
  card[1][3] "lt" 0 and \
  card[0][4] "lt" 0: diagonal = True # lowerleft to upperright

  # one-way wins
  if horizontal == True and vertical == False and \
  diagonal == False:
    return 1
  elif horizontal == False and vertical == True and \
  diagonal == False:
    return 2
  elif horizontal == False and vertical == False and \
  diagonal == True:
    return 3

  # simultaneous two-way wins
  elif horizontal == True and vertical == True and \
  diagonal == False:
    return 4
  elif horizontal == True and vertical == False and \
  diagonal == True:
    return 5
  elif horizontal == False and vertical == True and \
  diagonal == True:
    return 6

  elif horizontal == True and vertical == True and \
  diagonal == True:
    return 7  # wow! simultaneous triple wins!

  else:
    return 0  # not winning geometry

# -----------------------------------------------------------

def main():
  print("\nBegin single-card with Free Space 10,000 games ")
  rnd = np.random.RandomState(0)
  horizontal_wins = 0
  vertical_wins = 0
  diagonal_wins = 0

  for g in range(10000):
    # print("\ngame " + str(g))
    card = make_card(rnd)
    # show_card(card)
    balls = np.arange(75) + 1
    rnd.shuffle(balls)

    for b in range(len(balls)):
      # print("---")
      # print("b= " + str(b))
      ball = balls[b]
      # print("ball= " + str(ball))
      update_card(card, ball)
      # show_card(card)
      s = analyze_card(card)
      if s != 0:
        # print("win type = " + str(s))
        if s == 7:
          horizontal_wins += 1; vertical_wins += 1; \
          diagonal_wins += 1
        elif s == 6:
          vertical_wins += 1; diagonal_wins += 1
        elif s == 5:
          horizontal_wins += 1; diagonal_wins += 1
        elif s == 4:
          horizontal_wins += 1; vertical_wins += 1
        elif s == 3:
          diagonal_wins += 1
        elif s == 2:
          vertical_wins += 1
        elif s == 1:
          horizontal_wins += 1
        break  # go to next game

  print("\nResults: ")
  print("number horizontal wins = " + str(horizontal_wins))
  print("number vertical wins = " + str(vertical_wins))
  print("number diagonal wins = " + str(diagonal_wins))

  print("\nEnd single-card simulation ")

  print("\n==========================")

  print("\nBegin 100-cards with Free Space 1000 games ")
  rnd = np.random.RandomState(0)
  horizontal_wins = 0
  vertical_wins = 0
  diagonal_wins = 0

  for g in range(1000):  # play 1000 games
    # print(g)
    cards = []
    balls = np.arange(75) + 1
    rnd.shuffle(balls)
    game_finished = False

    for c in range(100): # make 100 cards/players
      card = make_card(rnd)
      cards.append(card)

    b = 0
    while game_finished == False:
      ball = balls[b]  # pick a bingo ball
      for c in range(len(cards)):  # update all cards
        # print("card " + str(c))
        update_card(cards[c], ball)
        # did anyone win?
        s = analyze_card(cards[c])
        if s != 0:  # curr card is a winner
          # print("winner found")
          if s == 7:
            horizontal_wins += 1; vertical_wins += 1; \
            diagonal_wins += 1
          elif s == 6:
            vertical_wins += 1; diagonal_wins += 1
          elif s == 5:
            horizontal_wins += 1; diagonal_wins += 1
          elif s == 4:
            horizontal_wins += 1; vertical_wins += 1
          elif s == 3:
            diagonal_wins += 1
          elif s == 2:
            vertical_wins += 1
          elif s == 1:
            horizontal_wins += 1
          game_finished = True
      b += 1  # pull next ball
 
  print("\nResults: ")
  print("number horizontal wins = " + str(horizontal_wins))
  print("number vertical wins = " + str(vertical_wins))
  print("number diagonal wins = " + str(diagonal_wins))

if __name__ == "__main__":
  main()
This entry was posted in Miscellaneous. Bookmark the permalink.

Leave a Reply