Bunco Simulator

Artifact Content

Artifact b91166ec316051ac8fb6511badaf7d1af1d3ee132753db99c58e15d6c5817263:


import bunco
import xlsxwriter
import creds # local
from blessings import Terminal
from twilio.rest import Client

t = Terminal()
g = bunco.Game("players.csv")

enable_sms = False
last_status = 'Off to a great start!'

def plural(str,n,suffix="s"):
    return str + suffix if n > 1 else str

def print_center(before, string, after):
    col = int((t.width - len(string)) / 2) - 1
    print(f"{t.move_x(col)}{before}{string}{after}")

def status(text):
    global last_status
    row = 6 + (len(g.tables) * 5)
    print(t.move(row, 0), end='')
    print(t.clear_eol, end='')
    print_center(f"{t.bold_green}",f"[ {text} ]", t.normal)
    print(t.clear_eos)
    last_status = text

def display_dashboard():
    print(t.clear,t.move(0,0))
    print_center(t.bold_red,"B U N C O   S I M U L A T O R", t.normal)
    print(f"{t.red}{'-'*t.width}{t.normal}\n")
    
    # Print column headings for rounds
    rounds = [str(n + 1).rjust(4) for n in range(g.current_round())]
    round_str = "".join(rounds)
    print(f"{t.move_x(18)}{t.bold}{round_str}{t.normal}")

    # Print table of players and their scores each round
    for table in g.tables:
        print(f"  {t.blue}{table}")
        for player in table.players:
            if player is bunco.fuzzydie_holder:
                print(f"🎲",end='')
            print(f"{t.move_x(3)}{t.cyan}{player}{t.normal}",end='')
            for n in range(g.current_round()):
                print(t.move_x(18+(4*n)),end='')
                print(str(player.round_scores[n]).rjust(4),end='')
            print('')
    status(last_status)

def do1():
    g.play_one_round()
    if enable_sms:
        status("Sending results by SMS to players…")
        for player in g.players:
            sms(player.smsnumber, summary_text(player))
    
    display_dashboard()
    status("Prepping for next round…")
    g.prep_next_round()
    status("Ready for another round!")

def summary(n):
    return summary_text(g.players[n])

def sms(number, message):
    if enable_sms and number:
        cli = Client(creds.SID, creds.token)
        cli.messages.create(to=number, from_=creds.sender, body=message)

def summary_text(player):
    for table in g.tables:
        if player in table.players:
            curtable = table
            break
    
    sitch = curtable.get_player_situation(player)

    if player.round_scores[g.current_round()-1] > sitch['opponent_score']:
        winloss = "beating"
    else:
        winloss = "losing to"
    foe_names = " & ".join([str(opp) for opp in sitch['opponents']])

    bunco_ct = player.round_bunco_counts[g.current_round()-1]
    if bunco_ct > 0:
        buncos = f" (& {bunco_ct} {plural('bunco',bunco_ct)}!)"
    else:
        buncos = ""

    ts = [f"Round {g.current_round()} @ {curtable} with {sitch['teammate']}:",
          f"You made {player.round_roll_counts[g.current_round()-1]} rolls,",
          f"adding {player.personal_roll_scores[g.current_round()-1]} pts{buncos}",
          f"to your team score of {player.round_scores[g.current_round()-1]},",
          f"{winloss} {foe_names}’s score of {sitch['opponent_score']}."]
    return " ".join(ts)

def sms_table_move(player, wonlost, table_from, table_to):
    if player.smsnumber:
        message = f"Having {wonlost} the last round, you move from {table_from} to {table_to}."
        sms(player.smsnumber, message)

bunco.table_move_callback = sms_table_move

def sms_all_situations():
    for table in g.tables:
        for player in table.players:
            if player.smsnumber:
                sitch = table.get_player_situation(player)
                msg = f"You are sitting at {table} with {sitch['teammate']} for a teammate. " \
                    + f"{sitch['opponents'][0]} and {sitch['opponents'][1]} form the opposition."
                sms(player.smsnumber, msg)

def sms_all(message):
    if enable_sms:
        status("Sending SMS to all players…")
        for player in g.players:
            if player.smsnumber:
                sms(player.smsnumber, message)
    else:
        status("SMS messaging is disabled!")

display_dashboard()

def excel_sheet(title):
    wb = xlsxwriter.Workbook('bunco.xlsx')
    ws = wb.add_worksheet('Overview')
    style = {}

    col_heading = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 12,
                                 'align': 'center'})
    title_style = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 14,
                                 'bold': True})
    row_heading = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 12})
    score_style = wb.add_format({'font_name': 'Milk Mustache BB', 
                                 'font_size': 14,
                                 'align': 'right',
                                 'num_format': '_(* #,##0_);_(* (#,##0);_(* "-"??_);_(@_)',
                                 'color': '#0070C0'})
    total_style = wb.add_format({'font_name': 'Milk Mustache BB', 
                                 'font_size': 14,
                                 'align': 'center',
                                 'bold': 'true'})
    prizename_style = wb.add_format({'font_name': 'Milk Mustache BB', 
                                 'font_size': 14})
    prizewinner_style = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 12,
                                 'color': '#E26B0A'})

    style['player_title'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 28,
                                 'bold': True,
                                 'color': '#0096FF'})
    style['player_subtitle'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 16})
    style['score_col_head'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 12,
                                 'bold': 'true',
                                 'align': 'center'})
    style['score_row_head'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 16,
                                 'bold': 'true',
                                 'align': 'center'})
    style['score_num'] = wb.add_format({'font_name': 'Milk Mustache BB', 
                                 'font_size': 18,
                                 'align': 'right',
                                 'num_format': '_(* #,##0_);_(* (#,##0);_(* "-"??_);_(@_)',
                                 'color': '#833C0C'})
    style['score_total'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 18,
                                 'align': 'right',
                                 'top': 1,
                                 'num_format': '_(* #,##0_);_(* (#,##0);_(* "-"??_);_(@_)'})
    style['wins'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 18,
                                 'color': '#548235',
                                 'align': 'center'})
    style['losses'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 18,
                                 'color': '#FF0000',
                                 'align': 'center'})
    style['wins_t'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 18,
                                 'color': '#548235',
                                 'top': 1,
                                 'align': 'center'})
    style['losses_t'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 18,
                                 'color': '#FF0000',
                                 'top': 1,
                                 'align': 'center'})
    style['stat_name'] = wb.add_format({'font_name': 'Milk Mustache BB', 
                                 'bold': 'true',
                                 'font_size': 16})
    style['stat_val'] = wb.add_format({'font_name': 'Back Issues BB', 
                                 'font_size': 18,
                                 'align': 'right'})
    
    ws.set_column('A:A', 30)
    
    ws.write('A1', title, title_style)
    ws.set_landscape()
    ws.fit_to_pages(1, 1)

    # Column headings for each round
    for n in range(g.current_round()):
        ws.write(2, n + 1, n + 1, col_heading)
    
    end_col = g.current_round()
    ws.set_column(1, end_col, 5)
    ws.set_column(end_col + 1, end_col + 5, 10)

    ws.write(2, end_col + 1, "Total", col_heading)
    ws.write(2, end_col + 2, "Wins", col_heading)
    ws.write(2, end_col + 3, "Losses", col_heading)
    ws.write(2, end_col + 4, "Buncos", col_heading)
    ws.write(2, end_col + 5, "Rolls", col_heading)

    # Write players and their scores for each round
    for n, player in enumerate(g.players):
        row = n + 3
        ws.write(row, 0, player.name, row_heading)

        for r in range(g.current_round()):
            col = r + 1
            ws.write(row, col, player.round_scores[r], score_style)
        
        # derive losses from wins
        losses = [int(not x) for x in player.round_wins]

        ws.write(row, end_col + 1, sum(player.round_scores), total_style)
        ws.write(row, end_col + 2, sum(player.round_wins), total_style)
        ws.write(row, end_col + 3, sum(losses), total_style)
        ws.write(row, end_col + 4, sum(player.round_bunco_counts), total_style)
        ws.write(row, end_col + 5, sum(player.round_roll_counts), total_style)

        excel_score_sheet(wb, style, player, title)

    end_row = 3 + len(g.players)
    ws.write(end_row + 1, 0, "Prizes", title_style)
    prizelist = g.prizes()

    for n, (prizename, winners) in enumerate(prizelist.items()):
        ws.write(end_row + 2 + n, 0, prizename, prizename_style)
        ws.write(end_row + 2 + n, 1, winners, prizewinner_style)
    wb.close()

def excel_score_sheet(workbook, styles, player, title):
    ws = workbook.add_worksheet(player.name)

    ws.set_column('A:A', 14)
    ws.set_column('B:C', 13)
    ws.set_column('D:D', 19)
    ws.set_column('E:F', 12)
    ws.set_portrait()
    ws.fit_to_pages(1, 1)

    ws.write('A1', player.name, styles['player_title'])
    ws.write('A2', f"{title} Score Sheet", styles['player_subtitle'])
    
    cols = ['Round', 'Rolls', 'Buncos?', 'Team Score', 'Win', 'Loss']
    for n, col in enumerate(cols):
        ws.write(3, n, col, styles['score_col_head'])

    for n in range(g.current_round()):
        row = 4 + n
        ws.write(row, 0, n + 1, styles['score_col_head'])
        ws.write(row, 1, player.round_roll_counts[n], styles['score_num'])
        ws.write(row, 3, player.round_scores[n], styles['score_num'])

        if player.round_bunco_counts[n] > 0:
            ws.write(row, 2, player.round_bunco_counts[n], styles['score_num'])
        if player.round_wins[n] > 0:
            ws.write(row, 4, "âś“", styles['wins'])
        else:
            ws.write(row, 5, "âś“", styles['losses'])

    end_row = 3 + g.current_round()

    # derive losses from wins
    losses = [int(not x) for x in player.round_wins]
    ws.write(end_row + 1, 0, '', styles['score_total'])
    ws.write(end_row + 1, 1, sum(player.round_roll_counts), styles['score_total'])
    ws.write(end_row + 1, 2, sum(player.round_bunco_counts), styles['score_total'])
    ws.write(end_row + 1, 3, sum(player.round_scores), styles['score_total'])
    ws.write(end_row + 1, 4, sum(player.round_wins), styles['wins_t'])
    ws.write(end_row + 1, 5, sum(losses), styles['losses_t'])

    stats = ['Longest time with fuzzy die',
             'Highest roll streak',
             f"Difference from average score of {g.average_total_score():.2f}",
             f"Difference from median score of {g.median_total_score():.2f}",
             "Average % of team score"]

    for n, stat in enumerate(stats):
        ws.write(end_row + 3 + n, 0, stat, styles['stat_name'])
    
    ws.write(end_row + 3, 5, f"{player.max_fuzzydie_streak // 60}:{player.max_fuzzydie_streak % 60:02}",
        styles['stat_val'])
    ws.write(end_row + 4, 5, player.max_streak, styles['stat_val'])
    ws.write(end_row + 5, 5, f"{sum(player.round_scores) - g.average_total_score():.2f}", styles['stat_val'])
    ws.write(end_row + 6, 5, f"{sum(player.round_scores) - g.median_total_score():.2f}", styles['stat_val'])
    ws.write(end_row + 7, 5, f"{player.average_contrib_pct():.2%}", styles['stat_val'])