Setup¶

In [3]:
from random import choice as rC
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

Functions¶

In [74]:
def bigBoard(N) :
    """
    Create a list of 2N+2 lists representing the Big NxN Board.  N rows, N columns, and 2 diagonals

    Parameters:
    N - Dimension of Big Board
    
    Returns:
    bbl - Big Board List of 2N+2 lists.
    """
    
    #Get M² random numbers from the first N² numbers.
    bbl = [[]*N]*(2*N+2)
    
    for r in range(N) :
        for c in range(N) :
            
            num = r*N + c
            
            #Enter random number in appropriate row list.
            bbl[r] = bbl[r].copy() + [num]
            
            #Enter random number in appropriate column list.
            bbl[N+c] = bbl[N+c].copy() + [num]
            
            #Enter number in diagonal.
            if r == c :
                bbl[2*N] = bbl[2*N].copy() + [num]
            
            if r+c+1 == N :
                bbl[2*N+1] = bbl[2*N+1].copy() + [num]

    #Reverse the order of the last diagonal
    bbl[2*N+1] = bbl[2*N+1][::-1]                
    
    return(bbl)


def lilBoard(M, N) :
    """
    Create a list of 2M+2 lists representing the Lil MxM Board.  M rows, M columns, and 2 diagonals

    Parameters:
    M - Dimension of Lil Board.
    N - Dimension of Big Board.
    
    Returns:
    lbl - Lil Board List of 2M+2 lists.
    """   
    
    bbl = bigBoard(N)
    
    squares = list(range(0, N**2))

    #Get M² random numbers from the first N² numbers.
    lbl = [[]*M]*(2*M+2)
    
    for r in range(M) :
        for c in range(M) :
            
            #Lil Board creation.
            rand = rC(squares)
            squares.remove(rand)
            
            #Enter random number in appropriate row list.
            lbl[r] = lbl[r].copy() + [rand]
            
            #Enter random number in appropriate column list.
            lbl[M+c] = lbl[M+c].copy() + [rand]
            
            #Enter number in diagonal.
            if r == c :
                lbl[2*M] = lbl[2*M].copy() + [rand]
            
            if r+c+1 == M :
                lbl[2*M+1] = lbl[2*M+1].copy() + [rand]
            
            #Big Board destruction.  If the random number is in one of the lists in 
            #Big Board, clear that list.
            for l in range(len(bbl)) :
                if rand in bbl[l] :
                    bbl[l] = []
            
            #Big Board cleanup.  Remove empty lists.
            bbl = [sl for sl in bbl if sl != []]

    lbl[2*M+1] = lbl[2*M+1][::-1]                
    
    return(lbl, bbl)



def playAsymmetricBingo(lbl, bbl, K, M, N) :
    
    """
    Determine the result of K trials of a lbl & bbl Asymmetric Bingo game.

    Parameters:
    lbl - Lil Board list of lists.
    bbl - Big Board list of lists.
    K - number of trials.
    
    Returns:
    ev - Expected value of points you would gain.
    """
    
    ev = 0
    count = 0

    #Sequence of M or N in a row, respectively.
    LIL_BINGO = [1]*M
    BIG_BINGO = [1]*N

    #Numbers on the Little Board and
    #*useful numbers on the Big Board.
    LIL_LIST = [i for sublist in lbl for i in sublist]
    BIG_LIST = [i for sublist in bbl for i in sublist]
    
    for k in range(K) :

        #Initiate the marker boards on top of the Little Board and Big Board.
        #For each iteration that follows, a 1 will be placed in the corresponding
        #location of a random number called out.  
        lil_potential = [[]*M]*(2*M+2)
        big_potential = [[]*M]*(len(bbl))

        squares = list(range(0, N**2))

        
        for n in range(N**2) :
            
            rand = rC(squares)
            squares.remove(rand)
            
            #Is the new random number in a useful line on the big bingo board?
            if rand in BIG_LIST :
                
                #For each useful line in the big bingo board...
                for b in range(len(bbl)) :
                    
                    #If new random number is in that line, update potential. 
                    if rand in bbl[b] :
                        
                        big_potential[b] = big_potential[b].copy() + [1]
                        
                        #That line could be bingo!
                        if big_potential[b] == BIG_BINGO :
                            count += 1
                            #No score update!
                            break
                
                #Break again, if true.
                if big_potential[b] == BIG_BINGO :
                    count += 1
                    #No score update!
                    break
            
            #Is the new random number in the little bingo board?
            elif rand in LIL_LIST :
                
                #For each line in the little bingo board...
                for m in range(2*M+2) :

                    #If new random number is in that line, update potential. 
                    if rand in lbl[m] :

                        lil_potential[m] = lil_potential[m].copy() + [1]

                        #That line could be bingo!
                        if lil_potential[m] == LIL_BINGO :
                            ev += 1/K
                            count += 1
                            break
                
                #Break again, if true.
                if lil_potential[m] == LIL_BINGO :
                    count += 1
                    #No score update!
                    break
        
            #The new random number is in a nonuseful space on the big bingo board.
            else :
                pass
               
    return(ev)



def monteCarlo(J, K, M, N) :

    """
    Determine the result of K trials of J lbl & bbl Asymmetric Bingo.

    Parameters:
    J - number of games.
    K - number of trials per game.
    M - Dimension of Lil Board.
    N - Dimension of Big Board.
    
    Returns:
    ev - Expected value of points you would gain.
    """
    bingo = 0
    
    for i in range(J) :
        lbl, bbl = lilBoard(M, N)

        #Big board may have no winning moves left.
        if bbl == [] :
            bingo += 1
            
        #But if it does...
        elif K != 0:
            bingo += playAsymmetricBingo(lbl, bbl, K, M, N)
    return(bingo/J)    

monteCarlo(1000000, 0, 5, 8)
Out[74]:
0.768803
In [3]:
monteCarlo(1000000, 1000, 5, 8)
Out[3]:
0.9915768839999936
In [11]:
M = []
N = []
Z = []

for m in range(2, 11) :  
    for n in range(m, 13) :
        
        #Lil Board has annihilated Big Board.
        if m == n :
            z = 1
            
        else : 
            z = monteCarlo(10000, 2000, m, n)
            
        M.append(m)
        N.append(n)
        Z.append(z)
        print(m, n, z)
2 2 1
2 3 0.8985031999999604
2 4 0.855537949999962
2 5 0.8574679999999593
2 6 0.8704201999999581
2 7 0.8857126499999506
2 8 0.9001836499999551
2 9 0.9128208499999593
2 10 0.9236308999999594
2 11 0.9329776999999584
2 12 0.9407672499999459
3 3 1
3 4 0.9816528499999897
3 5 0.9416706499999634
3 6 0.920546549999956
3 7 0.9136119999999502
3 8 0.9155759499999585
3 9 0.9208201999999533
3 10 0.9269709499999553
3 11 0.9339633999999533
3 12 0.9404953499999475
4 4 1
4 5 0.997557749999999
4 6 0.9850222999999867
4 7 0.9708415499999684
4 8 0.96126454999995
4 9 0.9565198499999479
4 10 0.9549583499999474
4 11 0.9555362499999402
4 12 0.9575705999999456
5 5 1
5 6 0.9997935499999998
5 7 0.9968296499999962
5 8 0.9915901999999891
5 9 0.9862413499999789
5 10 0.9814396999999617
5 11 0.9784805999999564
5 12 0.9764621999999413
6 6 1
6 7 0.9999884
6 8 0.999453049999999
6 9 0.9980123499999967
6 10 0.9955823999999908
6 11 0.9928570499999821
6 12 0.9909379999999681
7 7 1
7 8 1.0
7 9 0.99992925
7 10 0.9995439499999998
7 11 0.9987577499999967
7 12 0.9977273999999939
8 8 1
8 9 1.0
8 10 1.0
8 11 0.9998778999999995
8 12 0.9997090499999988
9 9 1
9 10 1.0
9 11 1.0
9 12 0.99997605
10 10 1
10 11 1.0
10 12 1.0
In [75]:
data = pd.DataFrame({'M': M,
                     'N': N,
                     'Z': Z})
data.loc[(data['Z'] > 0.9998) & (data['M'] != data['N']), 'Z'] = 0.9999
In [78]:
def heatMapHelper():
    
    fig = plt.figure(figsize = (10, 7))
    ax = fig.add_subplot(xlim = (1.5, 12.5),
                         ylim = (1.5, 10.5))
   
    heatmap = plt.scatter(x = data['N'],
                          y = data['M'],
                          c = data['Z'],
                          cmap = 'viridis_r',
                          marker = "s",
                          s = 1700,
                          alpha = 0.8,
                          vmin = data['Z'].min(),
                          vmax = data['Z'].max())
                          
    #Title setup.
    ax.set_title("Asymmetric Bingo‽", fontsize = 24)
    ax.set_xlabel("Opponent Board Dimension", fontsize = 18)
    ax.set_ylabel("Your Board Dimension", fontsize = 18)

    ax.set_facecolor('#999999FF')
    
    #Borders between cells.
    for i in range(2, 13) :
        ax.axhline(y = i-0.5, xmin = 0, xmax = 12, c = 'k', lw = 1)
        ax.axvline(x = i-0.5, ymin = 0, ymax = 10, c = 'k', lw = 1)

    #Print P in each territory
    for row in data.iterrows():
        plt.annotate(round(row[1]['Z'], 4),
                     (row[1]['N'], row[1]['M']),
                     c = "k",
                     fontsize = 10,
                     ha = "center",
                     va = "center")
    
    #Colorbar.
    cb = plt.colorbar(heatmap, format = '%.3f')
    #cb.set_ticklabels(labels)
    cb.set_label('Probability of Winning',
                 labelpad = -95,
                 rotation = 90,
                 fontsize = 20)
    cb.ax.tick_params(labelsize = 16)
    
    fig.savefig("2026.09.04EC.png",
                bbox_inches = 'tight')
In [79]:
heatMapHelper()
No description has been provided for this image

Rohan Lewis¶

2026.09.09¶