diff --git a/src/statistics.py b/src/statistics.py new file mode 100644 index 0000000..f56311e --- /dev/null +++ b/src/statistics.py @@ -0,0 +1,61 @@ +import enum +from typing import List, Tuple + +from hanabi import hanab_game + + +class GameOutcome(enum.Enum): + win = 0 + discard_crit = 1 + bomb_crit = 2 + strikeout = 3 + bottom_deck = 4 + vote_to_kill = 5 + out_of_pace = 6 + + +class GameAnalysisResult: + def __init__(self, outcomes: List[GameOutcome], bdrs: List[Tuple[hanab_game.DeckCard, int]]): + self.outcome = GameOutcome + self.bdrs = bdrs + + +def analyze_game(instance: hanab_game.HanabiInstance, actions: List[hanab_game.Action]) -> GameAnalysisResult: + # List of bdrs + bdrs = [] + # This is the default value if we find no other reason why the game was lost (or won) + outcomes = [] + game = hanab_game.GameState(instance) + + def handle_lost_card(card, game, play: bool): + if not game.is_trash(card): + if game.is_critical(card): + outcomes.append(GameOutcome.bomb_crit if play else GameOutcome.discard_crit) + elif card.rank != 1: + if card in game.deck[game.progress:]: + bdrs.append((card, game.draw_pile_size)) + else: + if game.deck[game.progress:].count(card) == 2: + bdrs.append((card, game.draw_pile_size)) + + for action in actions: + if action.type == hanab_game.ActionType.Discard: + discarded_card = instance.deck[action.target] + handle_lost_card(discarded_card, game, False) + if action.type == hanab_game.ActionType.Play: + played_card = instance.deck[action.target] + if not game.is_playable(played_card) and not game.is_trash(played_card): + bombed_card = instance.deck[action.target] + handle_lost_card(bombed_card, game, True) + game.make_action(action) + if game.pace < 0 and GameOutcome.out_of_pace not in outcomes: + outcomes.append(GameOutcome.out_of_pace) + + if game.strikes == 3: + outcomes.append(GameOutcome.strikeout) + elif actions[-1].type in [hanab_game.ActionType.EndGame, hanab_game.ActionType.VoteTerminate]: + outcomes.append(GameOutcome.vote_to_kill) + if game.score == 5 * instance.num_suits: + outcomes.append(GameOutcome.win) + + return GameAnalysisResult(outcomes, bdrs)