Files
2026-05-11 15:46:30 +08:00

129 lines
4.6 KiB
Python

import unittest
from random import Random
from texas_holdem.agents import CallingStationAgent, PokerAgent
from texas_holdem.cards import Card
from texas_holdem.engine import TableGame
from texas_holdem.models import Observation, PlayerAction
class RecordingAgent(PokerAgent):
def __init__(self, seen: list[tuple[str, str, int]]) -> None:
self.seen = seen
def decide(self, observation: Observation) -> PlayerAction:
self.seen.append((observation.street, observation.player_id, observation.to_call))
for action in observation.legal_actions:
if action["action"] == "check":
return PlayerAction("check")
return PlayerAction("call")
class ScriptedAgent(PokerAgent):
def __init__(
self,
actions: list[PlayerAction],
seen: list[tuple[str, str, list[str]]],
) -> None:
self.actions = actions
self.seen = seen
def decide(self, observation: Observation) -> PlayerAction:
self.seen.append(
(
observation.street,
observation.player_id,
[str(action["action"]) for action in observation.legal_actions],
)
)
if self.actions:
return self.actions.pop(0)
for action in observation.legal_actions:
if action["action"] == "check":
return PlayerAction("check")
return PlayerAction("call")
class EngineTests(unittest.TestCase):
def test_full_hand_preserves_total_chips(self) -> None:
players = [
("p1", "Player 1", CallingStationAgent()),
("p2", "Player 2", CallingStationAgent()),
("p3", "Player 3", CallingStationAgent()),
("p4", "Player 4", CallingStationAgent()),
]
game = TableGame("g1", players, starting_stack=1000, small_blind=5, big_blind=10, rng=Random(7))
summary = game.run_hand()
self.assertEqual(sum(player.stack for player in game.players), 4000)
self.assertEqual(len(summary.board), 5)
self.assertGreaterEqual(len(summary.awards), 1)
def test_preflop_observations_follow_table_order(self) -> None:
seen: list[tuple[str, str, int]] = []
players = [
("p1", "Button", RecordingAgent(seen)),
("p2", "Small Blind", RecordingAgent(seen)),
("p3", "Big Blind", RecordingAgent(seen)),
]
game = TableGame("g2", players, starting_stack=100, small_blind=5, big_blind=10, rng=Random(3))
game.run_hand()
preflop = [player_id for street, player_id, _ in seen if street == "preflop"]
self.assertEqual(preflop[:3], ["p1", "p2", "p3"])
def test_side_pots_are_awarded_to_eligible_players(self) -> None:
players = [
("p1", "Short", CallingStationAgent()),
("p2", "Middle", CallingStationAgent()),
("p3", "Deep", CallingStationAgent()),
]
game = TableGame("g3", players, starting_stack=0 + 100, small_blind=5, big_blind=10, rng=Random(1))
board = [Card.parse(value) for value in "2h 7d 9c Js 3h".split()]
holes = {
"p1": "Ah Ac",
"p2": "Kh Kc",
"p3": "Qh Qc",
}
bets = {"p1": 50, "p2": 100, "p3": 100}
for player in game.players:
player.stack = 0
player.in_hand = True
player.folded = False
player.hole_cards = [Card.parse(value) for value in holes[player.player_id].split()]
player.total_bet = bets[player.player_id]
game.board = board
game.button_index = 0
awards = game._award_pots()
self.assertEqual([award.amount for award in awards], [150, 100])
self.assertEqual(game.players[0].stack, 150)
self.assertEqual(game.players[1].stack, 100)
self.assertEqual(game.players[2].stack, 0)
def test_short_all_in_does_not_reopen_raising_to_prior_actor(self) -> None:
seen: list[tuple[str, str, list[str]]] = []
players = [
("p1", "Button", ScriptedAgent([PlayerAction("raise", 20)], seen)),
("p2", "Short Blind", ScriptedAgent([PlayerAction("all_in", 25)], seen)),
("p3", "Big Blind", ScriptedAgent([PlayerAction("call")], seen)),
]
game = TableGame("g4", players, starting_stack=100, small_blind=5, big_blind=10, rng=Random(13))
game.players[1].stack = 25
game.run_hand()
p1_second_preflop = [
legal
for street, player_id, legal in seen
if street == "preflop" and player_id == "p1"
][1]
self.assertEqual(p1_second_preflop, ["fold", "call"])
if __name__ == "__main__":
unittest.main()