feat: add human http agent

This commit is contained in:
qianrui.mmmy
2026-05-11 19:53:40 +08:00
parent e46b2b84c5
commit 6014ec0707
5 changed files with 335 additions and 41 deletions
+39 -1
View File
@@ -110,10 +110,14 @@ class TableGame:
board=list(self.board),
actions=list(self.action_history),
awards=awards,
showdown_hands=self._collect_showdown_hands(),
started_at=started_at,
finished_at=time(),
)
self.hand_summaries.append(summary)
# Notify every agent so HTTP-backed clients can render the just
# finished hand. Failures here must never abort the table.
self._broadcast_game_update()
return summary
def run_hands(self, max_hands: int, until_one_left: bool = False) -> list[HandSummary]:
@@ -140,7 +144,10 @@ class TableGame:
"big_blind": self.big_blind,
"starting_stack": self.starting_stack,
"players": [player.public_dict() for player in self.players],
"last_hand": self.hand_summaries[-1].to_dict() if self.hand_summaries else None,
# ``hands`` exposes every finished hand (each entry is the same
# dict that was previously returned as ``last_hand``). Callers
# that only want the most recent one can do ``hands[-1]``.
"hands": [summary.to_dict() for summary in self.hand_summaries],
}
def _advance_button(self) -> None:
@@ -448,6 +455,37 @@ class TableGame:
)
return awards
def _collect_showdown_hands(self) -> dict[str, list]:
"""Snapshot hole cards of every player still eligible at showdown.
We treat a hand as having reached showdown iff at least two players
remain ``in_hand`` and unfolded after the river. Returning an empty
dict for the one-player-left case keeps the wire format compact and
avoids leaking hole cards when there was no real comparison.
"""
live_players = [player for player in self.players if self._is_live(player)]
if len(live_players) < 2:
return {}
return {
player.player_id: list(player.hole_cards) for player in live_players
}
def _broadcast_game_update(self) -> None:
"""Push the post-hand game snapshot to every agent's optional hook.
Agents may opt into receiving game updates by overriding
:meth:`PokerAgent.on_game_update`. The default implementation is a
no-op, so this loop is essentially free for non-HTTP agents. We
swallow individual exceptions so a flaky remote endpoint cannot
break the table flow.
"""
snapshot = self.to_dict()
for agent in self.agents.values():
try:
agent.on_game_update(snapshot)
except Exception:
continue
def _record_action(
self,
player: PlayerState,