diff --git a/pluribus/poker/card.py b/pluribus/poker/card.py index cdec7234..5fe30a61 100644 --- a/pluribus/poker/card.py +++ b/pluribus/poker/card.py @@ -1,4 +1,4 @@ -from typing import List, Set, Union +from typing import Dict, List, Set, Union from pluribus.poker.evaluation.eval_card import EvaluationCard @@ -48,6 +48,11 @@ def __init__(self, rank: Union[str, int], suit: str): suit_char = self.suit.lower()[0] self._eval_card = EvaluationCard.new(f"{rank_char}{suit_char}") + def __repr__(self): + """Pretty printing the object.""" + icon = self._suit_to_icon(self.suit) + return f"" + def __int__(self): return self._eval_card @@ -152,7 +157,25 @@ def _suit_to_icon(self, suit: str) -> str: """Icons for pretty printing.""" return {"hearts": "♥", "diamonds": "♦", "clubs": "♣", "spades": "♠"}[suit] - def __repr__(self): - """Pretty printing the object.""" - icon = self._suit_to_icon(self.suit) - return f"" + def to_dict(self) -> Dict[str, Union[int, str]]: + """Turn into dict.""" + return dict(rank=self._rank, suit=self._suit) + + @staticmethod + def from_dict(x: Dict[str, Union[int, str]]): + """From dict turn into class.""" + if set(x) != {"rank", "suit"}: + raise NotImplementedError(f"Unrecognised dict {x}") + return Card(rank=x["rank"], suit=x["suit"]) + + def to_dict(self) -> Dict[str, Union[int, str]]: + """Turn into dict.""" + return dict(rank=self._rank, suit=self._suit) + + @staticmethod + def from_dict(x: Dict[str, Union[int, str]]): + """From dict turn into class.""" + if set(x) != {"rank", "suit"}: + raise NotImplementedError(f"Unrecognised dict {x}") + return Card(rank=x["rank"], suit=x["suit"]) + diff --git a/pluribus/terminal/results.py b/pluribus/terminal/results.py index f25c7ed7..e30b4681 100644 --- a/pluribus/terminal/results.py +++ b/pluribus/terminal/results.py @@ -1,5 +1,8 @@ +import collections +import os from typing import Dict, Any +import numpy as np import yaml from pluribus.games.short_deck.state import ShortDeckPokerState @@ -12,18 +15,72 @@ def __init__(self, file_path: str = "results.yaml"): """""" self._file_path = file_path try: - with open(self._file_path, "w") as stream: + with open(self._file_path, "r") as stream: self._results: Dict[str, Any] = yaml.safe_load(stream=stream) except FileNotFoundError: self._results: Dict[str, Any] = { "stats": {}, + "results": [], } - def add_result(self, state: ShortDeckPokerState, **players): + def add_result( + self, + strategy_path: str, + agent: str, + state: ShortDeckPokerState, + og_name_to_name: Dict[str, str], + ): """Adds results to file.""" + ai_key = f"{agent}_{os.path.basename(strategy_path)}" + players = [] + for player_i, player in enumerate(state.players): + name = og_name_to_name[player.name] + player_info_dict = dict( + name=name, + args=dict( + cards=[c.to_dict() for c in player.cards], + value=state.payout[player_i], + is_big_blind=player.is_big_blind, + is_small_blind=player.is_small_blind, + is_dealer=player.is_dealer, + ), + ) + players.append(player_info_dict) + result_entry = dict( + ai_key=ai_key, + players=players, + community_cards=[c.to_dict() for c in state.community_cards], + ) + self._results["results"].append(result_entry) + self._compute_human_stats() self._write_to_file() + def _compute_human_stats(self): + """""" + values = collections.defaultdict(lambda: collections.defaultdict(list)) + for result_entry in self._results["results"]: + ai_key = result_entry["ai_key"] + for player in result_entry["players"]: + if player["name"].lower() == "human": + if player["args"]["is_big_blind"]: + key = "BB" + elif player["args"]["is_small_blind"]: + key = "SB" + elif player["args"]["is_dealer"]: + key = "D" + else: + raise NotImplementedError("") + values[ai_key][key].append(player["args"]["value"]) + break + self._results["stats"] = { + ai_key: { + p: {"mean": float(np.mean(v)), "std": float(np.std(v))} + for p, v in positions_to_values.items() + } + for ai_key, positions_to_values in values.items() + } + def _write_to_file(self): """""" - with open(self._file_path, "r") as stream: + with open(self._file_path, "w") as stream: yaml.safe_dump(self._results, stream=stream, default_flow_style=False) diff --git a/pluribus/terminal/runner.py b/pluribus/terminal/runner.py index d18a2aa8..4581743c 100644 --- a/pluribus/terminal/runner.py +++ b/pluribus/terminal/runner.py @@ -12,6 +12,7 @@ from pluribus.terminal.ascii_objects.player import AsciiPlayer from pluribus.terminal.ascii_objects.logger import AsciiLogger from pluribus.terminal.render import print_footer, print_header, print_log, print_table +from pluribus.terminal.results import UserResults from pluribus.utils.algos import rotate_list @@ -56,6 +57,7 @@ def run_terminal_app( offline_strategy = joblib.load(strategy_path) else: offline_strategy = {} + user_results: UserResults = UserResults() with term.cbreak(), term.hidden_cursor(): while True: # Construct ascii objects to be rendered later. @@ -88,6 +90,7 @@ def run_terminal_app( if state.is_terminal: legal_actions = ["quit", "new game"] human_should_interact = True + user_results.add_result(strategy_path, agent, state, og_name_to_name) else: og_current_name = state.current_player.name human_should_interact = og_name_to_position[og_current_name] == "right"