telltale / tests /test_policy.py
smpdl
Deploy ZeroGPU Space snapshot
60daaa6
Raw
History Blame Contribute Delete
8.96 kB
from telltale.game.engine import HoldemEngine
from telltale.game.cards import Card
from telltale.game.holdem import ActionType, PlayerState
from telltale.poker.policy import board_texture
from telltale.poker import PokerPolicyConfig, PokerPolicyWriter, native
class FixedSolver:
def __init__(self, result: native.EquityResult):
self.result = result
def estimate_equity(self, *args, **kwargs):
return self.result
def make_players() -> list[PlayerState]:
players: list[PlayerState] = []
for index in range(3):
players.append(PlayerState(f"p{index}", f"Player {index}", index, 100))
return players
def equity(value: float) -> native.EquityResult:
wins = int(value * 100)
losses = 100 - wins
return native.EquityResult(wins, 0, losses, 100, value, 0.0, 1.0 - value)
def test_recommendation_contains_only_legal_actions():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
writer = PokerPolicyWriter(FixedSolver(equity(0.5)))
decision = writer.choose_action(hand)
assert decision.action in hand.legal_actions()
def test_native_policy_probabilities_sum_to_one():
features = native.DecisionFeatures(
street="flop",
hero_equity=0.5,
pot_size=100,
amount_to_call=30,
pot_odds=30 / 130,
stack_to_pot_ratio=2.0,
players_remaining=3,
can_check=False,
legal_actions=("fold", "call", "raise", "all_in"),
)
recommendation = native.recommend_action(features)
assert abs(sum(recommendation.probabilities.values()) - 1.0) < 1e-12
assert set(recommendation.probabilities) <= set(features.legal_actions)
assert recommendation.amount_options is not None
assert recommendation.board_texture == "dry"
def test_native_policy_returns_richer_advice_fields():
features = native.DecisionFeatures(
street="turn",
hero_equity=0.76,
pot_size=120,
amount_to_call=0,
pot_odds=0.0,
stack_to_pot_ratio=2.0,
players_remaining=2,
can_check=True,
legal_actions=("check", "bet", "all_in"),
hero_stack=200,
minimum_raise_amount=20,
board_texture="two_tone",
street_action_count=1,
previous_aggression_count=0,
)
recommendation = native.recommend_action(features)
assert recommendation.risk_label in {"low", "medium", "high"}
assert recommendation.board_texture == "two_tone"
assert recommendation.decision_margin >= 0
assert recommendation.amount_options == {
"all_in": 200,
"large": 120,
"medium": 60,
"small": 40,
}
assert recommendation.hand_strength_bucket == "strong"
assert recommendation.spr_bucket == "medium"
assert recommendation.abstract_actions == ("check", "bet_33", "bet_66", "bet_100", "all_in")
def test_weak_equity_facing_bad_pot_odds_favors_fold():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
writer = PokerPolicyWriter(FixedSolver(equity(0.05)))
decision = writer.choose_action(hand)
assert decision.action == ActionType.FOLD
def test_weak_equity_with_check_available_favors_check():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
hand.apply_action(ActionType.CALL)
hand.apply_action(ActionType.CALL)
hand.apply_action(ActionType.CHECK)
writer = PokerPolicyWriter(FixedSolver(equity(0.12)))
decision = writer.choose_action(hand)
assert decision.action == ActionType.CHECK
def test_strong_equity_favors_bet_or_raise():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
writer = PokerPolicyWriter(FixedSolver(equity(0.85)), PokerPolicyConfig(simulations=100))
decision = writer.choose_action(hand)
assert decision.action == ActionType.RAISE
def test_suggested_bet_or_raise_amount_is_legal_when_present():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
writer = PokerPolicyWriter(FixedSolver(equity(0.85)), PokerPolicyConfig(simulations=100))
decision = writer.apply_action(hand)
assert decision.action == ActionType.RAISE
assert hand.action_history[-1].amount >= 20
def test_explanation_includes_equity_and_pot_odds():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
writer = PokerPolicyWriter(FixedSolver(equity(0.45)))
decision = writer.choose_action(hand)
assert "equity" in decision.reason
assert "pot odds" in decision.reason
def test_wet_board_reduces_strong_bet_probability():
dry = native.DecisionFeatures(
street="flop",
hero_equity=0.68,
pot_size=100,
amount_to_call=0,
pot_odds=0.0,
stack_to_pot_ratio=2.0,
players_remaining=2,
can_check=True,
legal_actions=("check", "bet", "all_in"),
hero_stack=200,
minimum_raise_amount=10,
board_texture="dry",
)
wet = native.DecisionFeatures(
street="flop",
hero_equity=0.68,
pot_size=100,
amount_to_call=0,
pot_odds=0.0,
stack_to_pot_ratio=2.0,
players_remaining=2,
can_check=True,
legal_actions=("check", "bet", "all_in"),
hero_stack=200,
minimum_raise_amount=10,
board_texture="wet",
)
assert native.recommend_action(wet).probabilities["bet"] < native.recommend_action(dry).probabilities["bet"]
def test_multiway_pot_reduces_marginal_raise_probability():
heads_up = native.DecisionFeatures(
street="turn",
hero_equity=0.74,
pot_size=100,
amount_to_call=20,
pot_odds=20 / 120,
stack_to_pot_ratio=2.0,
players_remaining=2,
can_check=False,
legal_actions=("fold", "call", "raise", "all_in"),
hero_stack=200,
minimum_raise_amount=20,
board_texture="dry",
)
multiway = native.DecisionFeatures(
street="turn",
hero_equity=0.74,
pot_size=100,
amount_to_call=20,
pot_odds=20 / 120,
stack_to_pot_ratio=2.0,
players_remaining=4,
can_check=False,
legal_actions=("fold", "call", "raise", "all_in"),
hero_stack=200,
minimum_raise_amount=20,
board_texture="dry",
)
assert native.recommend_action(multiway).probabilities["raise"] < native.recommend_action(heads_up).probabilities["raise"]
def test_low_spr_strong_equity_can_suggest_all_in():
features = native.DecisionFeatures(
street="river",
hero_equity=0.9,
pot_size=200,
amount_to_call=0,
pot_odds=0.0,
stack_to_pot_ratio=0.8,
players_remaining=2,
can_check=True,
legal_actions=("check", "bet", "all_in"),
hero_stack=160,
minimum_raise_amount=20,
board_texture="dry",
)
recommendation = native.recommend_action(features)
assert recommendation.probabilities["all_in"] > 0
assert recommendation.risk_label == "high"
def test_suggested_amounts_are_clamped_to_stack_and_minimum_raise():
features = native.DecisionFeatures(
street="flop",
hero_equity=0.8,
pot_size=300,
amount_to_call=40,
pot_odds=40 / 340,
stack_to_pot_ratio=0.25,
players_remaining=2,
can_check=False,
legal_actions=("fold", "call", "raise", "all_in"),
hero_stack=75,
minimum_raise_amount=30,
board_texture="dry",
)
recommendation = native.recommend_action(features)
assert recommendation.suggested_amount <= 75
assert recommendation.amount_options is not None
assert recommendation.amount_options["small"] == 75
assert recommendation.amount_options["all_in"] == 75
def test_board_texture_extraction():
hand = HoldemEngine().start_hand(make_players(), seed=42, dealer_button_index=0)
hand.board_cards = [Card.parse("Ah"), Card.parse("7h"), Card.parse("2h")]
assert board_texture(hand) == "monotone"
hand.board_cards = [Card.parse("Ah"), Card.parse("Ad"), Card.parse("7c")]
assert board_texture(hand) == "paired"
hand.board_cards = [Card.parse("9h"), Card.parse("8d"), Card.parse("7h")]
assert board_texture(hand) == "wet"
hand.board_cards = [Card.parse("Kh"), Card.parse("8h"), Card.parse("2c")]
assert board_texture(hand) == "two_tone"
hand.board_cards = [Card.parse("Kh"), Card.parse("8d"), Card.parse("2c")]
assert board_texture(hand) == "dry"
def test_runtime_and_build_references_use_action_policy_name():
checked_paths = [
"telltale/native/poker_solver/bindings.cpp",
"telltale/native/poker_solver/CMakeLists.txt",
"telltale/poker/native.py",
]
for path in checked_paths:
with open(path) as file:
assert "cfr_lite" not in file.read()