Bunco Simulator

bunco.py at [d1c9ab]

File bunco.py artifact 314493 part of check-in d1c9ab


# Test
import csv, random

teammate = {
    1: 3,
    2: 4,
    3: 1,
    4: 2
}

def TurnInProgress():
    return -1

def roll_dice(name, current_round, roll_count):
    dice = []
    result = 0

    desired_num = current_round % 6
    desired_num = 6 if desired_num == 0 else desired_num
    
    for _ in range(3):
        dice.append(random.randint(1,6))
    
    if all(die == desired_num for die in dice):
        # Bunco!
        result = 21
    elif all(die == dice[0] for die in dice):
        # All three dice match, but not Bunco
        result = 5
    else:
        for die in dice:
            result += 1 if die == desired_num else 0
    
    print(f"In round {current_round} {name} rolled {dice} = {result}")
    return result


class Player:
    fuzzydie_holder = None

    def __init__(self, name, dex, math, speed):
        self.name = name
        self.dex = dex
        self.math_comprehension = math
        self.roll_speed = speed
        self.max_streak = 0
        self.bunco_count = 0
        self.round_scores = [0]
        self.round_roll_counts = [0]
        self.personal_roll_scores = [0]
        self.turn_progress = 0
        self.current_streak = 0
        self.max_fuzzydie_streak = 0
        self.current_fuzzydie_streak = 0
        self.rolled_bunco = False
    
    def __repr__(self):
        return f"<Player {self.name}:\t\t" \
            + f"\tscores\t\t{self.round_scores}>" \
            + f"\troll counts\t{self.round_roll_counts}>"

    def prep_new_round(self):
        self.round_scores.append(0)
        self.round_roll_counts.append(0)
        self.personal_roll_scores.append(0)
        self.turn_progress = 0
        self.current_streak = 0

    def tick(self, current_round):
        result = TurnInProgress()

        if self.fuzzydie_holder is self:
            self.current_fuzzydie_streak += 1
            self.max_fuzzydie_streak = max(self.max_fuzzydie_streak, self.current_fuzzydie_streak)
        else:
            self.current_fuzzydie_streak = 0

        if self.turn_progress < 25:
            # Grabbing the dice
            # TODO: Incorporate DEXTERITY stat
            self.turn_progress += random.randint(12,25)
        elif self.turn_progress < 50:
            # Rolling the dice
            # TODO: Incorporate ROLL SPEED stat
            self.turn_progress += random.randint(12,25)
        elif self.turn_progress < 75:
            # Reading the numbers
            # TODO: Incorporate MATH COMPREHENSION stat
            self.turn_progress += random.randint(12,25)
        else:
            # Finished rolling
            self.round_roll_counts[current_round - 1] += 1
            roll_result = roll_dice(self.name, current_round, self.round_roll_counts[current_round - 1])
            
            if roll_result > 0:
                self.current_streak += 1
                self.max_streak = max(self.current_streak, self.max_streak)
                self.round_scores[current_round - 1] += roll_result
                self.personal_roll_scores[current_round - 1] += roll_result
                if roll_result == 21:
                    self.bunco_count += 1
                    self.rolled_bunco = True
            else:
                self.current_streak = 0
            
            result = roll_result
            self.turn_progress = 0
        return result

class Table:
    def __init__(self):
        self.team1_score = 0
        self.team2_score = 0
        self.players = []
        self.active_player = -1

    def __repr__(self):
        names = ", ".join([p.name for p in self.players])
        return "<Table: " + names + ">"

    def tick(self, current_round):
        if self.active_player == -1:
            # First tick for the table this round
            self.active_player = random.randint(1, len(self.players)) - 1

        result = self.players[self.active_player].tick(current_round)

        if result != TurnInProgress():
            # Player rolled
            if result > 0:
                if (self.active_player % 2) == 0:
                    self.team1_score += result
                else:
                    self.team2_score += result

                self.players[teammate[self.active_player + 1]-1].round_scores[current_round - 1] += result
            else:
                self.active_player += 1
                if self.active_player > (len(self.players) - 1): self.active_player = 0
    
    def losers(self):
        if self.team1_score > self.team2_score:
            return self.players[1::2]
        else:
            return self.players[0::2]
    
    def winners(self):
        if self.team1_score > self.team2_score:
            return self.players[0::2]
        else:
            return self.players[1::2]
    
    def swap_for_winners(self, new_players):
        if self.team1_score > self.team2_score:
            # Team 1 (evens) won
            winners = self.players[0::2]
            # Player 1 move to spot 0
            self.players[0] = self.players[1]
        else:
            # Team 2 (odds) won
            winners = self.players[1::2]
            # Player 2 move to spot 3
            self.players[3] = self.players[2]
        # Replace middle two players
        self.players[1:3] = new_players
        return winners

    def swap_for_losers(self, new_players):
        if self.team1_score < self.team2_score:
            # Team 1 (evens) lost
            losers = self.players[0::2]
            # Player 1 move to spot 0
            self.players[0] = self.players[1]
        else:
            # Team 2 (odds) lost
            losers = self.players[1::2]
            # Player 2 move to spot 3
            self.players[3] = self.players[2]
        # Replace middle two players
        self.players[1:3] = new_players
        return losers
    
    def prep_new_round(self):
        self.team1_score = 0
        self.team2_score = 0
        self.active_player = -1
        for player in self.players:
            player.prep_new_round()


def assign_teams(player_list):
    players_per_table = 4
    tables = []
    random.seed()

    if len(player_list) % players_per_table != 0:
        print("Wrong number of players!")
        return tables
    else:
        table_count = len(player_list) // players_per_table
        random.shuffle(player_list)
        tables = [Table() for _ in range(table_count)]

        for n in range(table_count):
            first = n * players_per_table
            stop_before = first + players_per_table
            tables[n].players = player_list[first:stop_before]
        
        return tables

def load_players(filename):
    players = []

    with open(filename) as tsvfile:
        tsvreader = csv.reader(tsvfile)
        for line in tsvreader:
            players.append(Player(line[0],0,0,0))
    
    return players

class Game:
    def __init__(self, playerfile):
        self.players = load_players(playerfile)
        self.tables = assign_teams(self.players)
        self.current_round = 1

    def tick(self):
        for table in self.tables:
            table.tick(self.current_round)
        
        bunco_rollers = [p for p in self.players if p.rolled_bunco is True]
        
        # If multiple people rolled Bunco this tick, and one of them already has the
        # fuzzy die, they retain it.
        # Otherwise, the last person in the list gets the fuzzy die.
        if bunco_rollers and Player.fuzzydie_holder not in bunco_rollers:
            Player.fuzzydie_holder = bunco_rollers[-1]
            for player in bunco_rollers:
                player.rolled_bunco = False # Reset flag

    def print_status(self):
        for n, table in enumerate(self.tables):
            print(f"== TABLE {n+1} == Team 1:{table.team1_score} pts, Team 2:{table.team2_score} pts")
            for player in table.players:
                print(f"    {player.name} {player.round_scores[self.current_round - 1]} points, streak {player.max_streak} buncos {player.bunco_count}")

    def prep_next_round(self):
        # losers from head table move to next table
        headtable_losers = self.tables[0].losers()
        round_winners = self.tables[1].swap_for_winners(headtable_losers)

        # winners from other tables move to next table
        for n in range(2, len(self.tables)):
            round_winners = self.tables[n].swap_for_winners(round_winners)
        # last set of winners moves to head table
        self.tables[0].swap_for_losers(round_winners)

        for table in self.tables:
            table.prep_new_round()

        self.current_round += 1

    def play_one_round(self):
        # Go until one of the head table teams reaches 21 pts
        while max(self.tables[0].team1_score, self.tables[0].team2_score) < 21:
            self.tick()

        print("\n\n\t\tB U N C O !!!\n\n")
        
        self.print_status()