Setup¶

In [1]:
import math
import matplotlib as mpl
import matplotlib.colors as MC
from matplotlib.patches import Polygon as mpP
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

Constants & Functions¶

In [2]:
#Colors.
viridis = plt.get_cmap('viridis_r')
newcolors = viridis(np.linspace(0, 1, 181))





def getScore(game) :
    
    """
    Return the score given bowling pins knocked down over bowls and frames.

    Parameters:
    game : A list of 12 lists.
    Each of the first 10 lists is an ordered pair representing the two bowls in a frame.
    Last two lists are single numbers.
    
    Returns:
    Total score.
    """
    
    score = 0
    
    for i in range(10) :
        
        frame = sum(game[i])
        #Get points for current frame.
        frame_score = frame
        
        #If strike [10, 0] or spare [n, 10-n].
        if frame == 10 :
            
            #Add the next bowl.
            frame_score += game[i+1][0]
            
            #Strike for i?
            if game[i][0] == 10 :
                
                #Strike for i+1?
                if game[i+1][0] == 10 :
                    
                    #Add i+2 (Could be a strike?) :
                    frame_score += game[i+2][0]
                
                #Non-strike for i+1 and not last frame.
                elif i != 9 :
                    #Add the second bowl (Could be a spare for i+1?) :
                    frame_score += game[i+1][1]
                    
                #Non-strike for i+1 and last frame.
                elif i == 9 :
                    #Add the second bowl (Could be a spare for i+1?) :
                    frame_score += game[i+2][0]
     
        score += frame_score
    
    return(score)





def getPins(game) :
    
    """
    Return the number of pins.

    Parameters:
    game : A list of 12 lists.  Each of the 12 lists is an ordered pair representing a frame.
    
    Returns:
    Total pins dropped.
    """
    pins = 0
    
    for i in range(12) :

        pins += sum(game[i])
        
    return(pins)





def createMinMax(p) :
    
    """
    Return the minimum scoring and maximum scoring game given a number of pins.

    Parameters:
    p : number of pins dropped in a game.
    
    Returns:
    List of two scores.
    The first is the min and the second is the max.
    """
    
    mins = []
    maxs = []
   
    #12 frames to add.
    for i in range(12) :
        
        #First ten frames have two bowls each.
        if i < 10 :
            
            if p > 10 :
                #Append a spare.
                mins.append([0, 10])

                #Append a strike.
                maxs.append([10, 0])
                
                p = p-10

            else :
                #Append remaining.
                mins.append([0, p])

                #Append remaining.
                maxs.append([p, 0])
                
                p = 0
                
        #11th Frame.        
        elif i == 10 :
            
            if p > 10 :
                #Append a spare.
                mins.append([10])

                #Append a strike.
                maxs.append([10])
                
                p = p-10

            else :
                #Append remaining.
                mins.append([p])

                #Append remaining.
                maxs.append([p])
            
                p = 0
        
        #12th Frame.        
        elif i == 11 :
            
            #Change frame 9 spare to strike to allow frame 11 to happen!
            if p > 0 :
                mins[9] = [10, 0]
            
            
            #Append remaining.
            mins.append([p])

            #Append remaining.
            maxs.append([p])
            
    return(mins, maxs)





def createDF() :
    
    """
    Create the main dataframe.

    Parameters:
    None.
    
    Returns:
    df : 180 rows, columns are Pins, Min_Score, Max_Score, and Difference.
    """
    
    mins = []
    maxs = []

    for i in range(121) :
        a, b = (createMinMax(i))
        mins.append(getScore(a))
        maxs.append(getScore(b))


    df = pd.DataFrame({'Pins': list(range(121)),
                       'Min_Score': mins,
                       'Max_Score': maxs})

    df['Difference'] = df['Max_Score'] - df['Min_Score']
    
    return(df)





def diffRegion(df, p) :
    
    """
    Create the trapezoid of width 1 between min and max scores for a particular number of pins.

    Parameters:
    df - Main dataframe.
    p - # of pins dropped, or index of df.
    
    Returns:
    Polygon patch that is a trapezoid of width 1, filled by color representing the difference.
    """
    
    #Previous pin data
    data1 = df.loc[p-1]
    #Current pin data
    data2 = df.loc[p]
    
    #Color represents difference.
    c = MC.to_hex(newcolors[data2[3]])
    
    return(mpP([(data1[0], data1[1]),
                (data1[0], data1[2]),
                (data2[0], data2[2]),
                (data2[0], data2[1])],
               edgecolor = c,
               facecolor = c,
               lw = 1,
               zorder = 2))
In [3]:
a = [[10, 0],
     [10, 0],
     [10, 0],
     [10, 0],
     [4, 0],
     [0, 0],
     [0, 0],
     [0, 0],
     [0, 0],
     [0, 0],
     [0],
     [0]]

print(getScore(a))
print(getPins(a))
102
44

Extra Credit Plot¶

In [4]:
df = createDF()
sns.set()

#Plot.    
fig = plt.figure(figsize = (14, 7))
ax = fig.add_axes([0, 0, 25/28 , 1], xlim = (0, 120), ylim = (0, 300))

#Title.
ax.set_title("Score Range Given Number of Pins Dropped", fontsize = 24)

#x-axis.
ax.set_xlabel("Pins Dropped", fontsize = 24)

#y-axis.
ax.set_ylabel("Score", fontsize = 24)

ax.tick_params(axis = 'both', labelsize = 20)

#0-10 has difference = 0
ax.plot(list(range(11)), list(range(11)), lw = 1, c = newcolors[0], label = "Minimum Score")

#Trapezoidal strips.
for p in range(11, 121) :
    ax.add_patch(diffRegion(df, p))

#Second axes for manual colorbar.    
ax1 = fig.add_axes([27/28, 0, 1/28 , 1])
norm = mpl.colors.Normalize(vmin = 0, vmax = 180)
cb = mpl.colorbar.ColorbarBase(ax1, cmap = 'viridis_r', norm = norm, orientation='vertical')
cb.set_label('Difference Between Max and Min Scores',
             labelpad = -100,
             rotation = 90,
             fontsize = 24)

cb.ax.tick_params(labelsize = 20)

fig.savefig('2025.07.11.png', bbox_inches = "tight");
Rohan Lewis¶

2025.07.14¶