← The Stack
· ORIGINAL #benchmarks #llm #example #poker #sdk

One-Shot Example: Gemini's Poker Bot

A worked example for The Benchmark That Fights Back: Gemini's full, unedited one-shot response to our poker-bot prompt - its requirements.txt and bot.py.

This is a companion to The Benchmark That Fights Back. To show what the prompt actually produces, here is one model's complete one-shot reply, unedited: Gemini's requirements.txt and bot.py, generated from the prompt in that post. Want to add your own model to the arena? Hand it the same prompt and upload the bot it writes.

requirements.txt

# No external dependencies required. Pure Python implementation for speed and stability.

bot.py

import random
import itertools
from chipzen import Bot, GameState, Action

# Mapping card ranks to integer values for mathematical comparisons
RANK_MAP = {
    "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
    "T": 10, "J": 11, "Q": 12, "K": 13, "A": 14
}

class CardMock:
    """Lightweight card object used during Monte Carlo simulations."""
    def __init__(self, rank: str, suit: str):
        self.rank = rank
        self.suit = suit


def eval_5_cards(cards) -> tuple:
    """
    Evaluates exactly 5 cards and returns a scoring tuple: (hand_type, details).
    Higher tuples natively beat lower tuples in Python comparisons.
    """
    ranks = sorted([RANK_MAP[c.rank] for c in cards], reverse=True)
    suits = [c.suit for c in cards]
    is_flush = len(set(suits)) == 1

    unique_ranks = sorted(list(set(ranks)), reverse=True)
    is_straight = False
    straight_high = 0

    if len(unique_ranks) == 5:
        if unique_ranks[0] - unique_ranks[4] == 4:
            is_straight = True
            straight_high = unique_ranks[0]
        elif unique_ranks == [14, 5, 4, 3, 2]:  # Ace-low straight
            is_straight = True
            straight_high = 5

    if is_flush and is_straight:
        return (8, straight_high)

    counts = {}
    for r in ranks:
        counts[r] = counts.get(r, 0) + 1
    freq = sorted([(cnt, r) for r, cnt in counts.items()], reverse=True)

    if freq[0][0] == 4:  # Four of a Kind
        return (7, freq[0][1], freq[1][1])
    if freq[0][0] == 3 and freq[1][0] == 2:  # Full House
        return (6, freq[0][1], freq[1][1])
    if is_flush:  # Flush
        return (5, ranks)
    if is_straight:  # Straight
        return (4, straight_high)
    if freq[0][0] == 3:  # Three of a Kind
        return (3, freq[0][1], [f[1] for f in freq[1:]])
    if freq[0][0] == 2 and freq[1][0] == 2:  # Two Pair
        return (2, freq[0][1], freq[1][1], freq[2][1])
    if freq[0][0] == 2:  # Pair
        return (1, freq[0][1], [f[1] for f in freq[1:]])

    return (0, ranks)  # High Card


def eval_best_hand(cards) -> tuple:
    """Finds the strongest 5-card combination from 5, 6, or 7 available cards."""
    if len(cards) < 5:
        return (0, [])
    best_score = None
    for combo in itertools.combinations(cards, 5):
        score = eval_5_cards(combo)
        if best_score is None or score > best_score:
            best_score = score
    return best_score


class MyBot(Bot):
    def __init__(self):
        super().__init__()
        # Seed the RNG for game theory mix-ins (bluffs)
        random.seed()

    def get_preflop_strength(self, c1, c2) -> float:
        """
        Calculates a robust preflop equity heuristic based on card ranks,
        pairs, connectivity, and suitedness. Returns a score between 0.0 and 1.0.
        """
        r1, r2 = RANK_MAP[c1.rank], RANK_MAP[c2.rank]
        high, low = max(r1, r2), min(r1, r2)
        suited = (c1.suit == c2.suit)
        pair = (r1 == r2)

        if pair:
            return 0.58 + (high / 14.0) * 0.27  # AA = 0.85, 22 = 0.61

        # Non-paired hands
        base_eq = (high * 2 + low) / 42.0
        score = 0.38 + base_eq * 0.16
        if suited:
            score += 0.05

        gap = high - low
        if gap == 1:
            score += 0.03  # Connected cards
        elif gap > 4:
            score -= 0.04  # Gapped cards

        return min(0.85, max(0.25, score))

    def run_monte_carlo(self, hole, board, iterations=180) -> float:
        """Runs a fast Monte Carlo simulation against a random opponent range."""
        used_cards = set((c.rank, c.suit) for c in hole + board)
        deck = []
        for r in RANK_MAP.keys():
            for s in ["h", "d", "c", "s"]:
                if (r, s) not in used_cards:
                    deck.append(CardMock(r, s))

        wins, ties, count = 0, 0, 0
        cards_needed_on_board = 5 - len(board)
        sample_size = cards_needed_on_board + 2

        for _ in range(iterations):
            if len(deck) < sample_size:
                break
            sampled = random.sample(deck, sample_size)
            opp_hole = sampled[:2]
            extra_board = sampled[2:]

            sim_board = board + extra_board
            my_score = eval_best_hand(hole + sim_board)
            opp_score = eval_best_hand(opp_hole + sim_board)

            if my_score > opp_score:
                wins += 1
            elif my_score == opp_score:
                ties += 1
            count += 1

        return (wins + 0.5 * ties) / count if count > 0 else 0.5

    def decide(self, state: GameState) -> Action:
        try:
            # --- Strategic Context Gathering ---
            hole = state.hole_cards
            board = state.board
            phase = state.phase
            valid = state.valid_actions

            # Absolute safety net: if no actions are available, just check/fold
            if not valid:
                return Action.check()

            # Calculate mathematical Pot Odds
            pot = state.pot
            to_call = state.to_call
            pot_odds = to_call / (pot + to_call) if (pot + to_call) > 0 else 0.0

            # Determine position
            is_button = (state.your_seat == state.dealer_seat)

            # Stack sizing metrics
            opp_stack = state.opponent_stacks[0] if state.opponent_stacks else state.your_stack
            effective_stack = min(state.your_stack, opp_stack)

            # --- Hand Strength Evaluation ---
            if phase == "preflop":
                equity = self.get_preflop_strength(hole[0], hole[1])
            else:
                # 180 iterations completes comfortably within 20-40ms, well under the 5000ms budget
                equity = self.run_monte_carlo(hole, board, iterations=180)

            # --- Semi-Bluff Detection (Postflop Draw Tracking) ---
            has_draw = False
            if phase in ["flop", "turn"]:
                suits = [c.suit for c in hole + board]
                # 4-card flush draw detection
                if any(suits.count(s) == 4 for s in set(suits)):
                    has_draw = True

            # --- Action Execution Sub-Routing ---
            def fallback_action():
                """Returns a verified passive action to avoid illegal moves."""
                if "check" in valid: return Action.check()
                if "call" in valid: return Action.call()
                return Action.fold()

            def raise_action(target_amount: int):
                """Safely scales and clamps values within the allowed server range."""
                if "raise" in valid:
                    clamped = max(state.min_raise, min(state.max_raise, int(target_amount)))
                    return Action.raise_to(clamped)
                if "all_in" in valid and equity > 0.55:
                    return Action.all_in()
                return fallback_action()

            # --- Decision Engine Matrix ---

            # 1. Short Stack / High-Leverage Shove Engine
            # If the pot is large relative to stacks, simplify game tree to push/fold
            if phase == "preflop" and effective_stack < 15 * max(1, to_call):
                if equity > 0.54:
                    return Action.all_in() if "all_in" in valid else raise_action(state.max_raise)
                if to_call == 0: return fallback_action()
                return Action.call() if (equity >= pot_odds and "call" in valid) else fallback_action()

            # 2. Preflop Strategy Tree
            if phase == "preflop":
                if is_button:  # We act first on the button (Small Blind)
                    if to_call <= 2:  # Pot is unraised/limped
                        if equity > 0.48:  # Value raise open
                            return raise_action(max(state.min_raise, 3 * 2))
                        if equity > 0.38:  # Flat call/complete
                            return Action.call() if "call" in valid else fallback_action()
                        return fallback_action()
                    else:  # Facing a 3-bet from the Big Blind
                        if equity > 0.64: return raise_action(to_call * 2.5)  # 4-bet
                        if equity > 0.50 and (equity >= pot_odds): return Action.call() if "call" in valid else fallback_action()
                        return fallback_action()
                else:  # We are in the Big Blind
                    if to_call == 0:  # Button limped
                        if equity > 0.58: return raise_action(state.min_raise * 3)  # Punish limpers
                        return fallback_action()
                    else:  # Button opened a raise
                        if equity > 0.65: return raise_action(to_call * 3)  # 3-bet
                        if equity >= pot_odds: return Action.call() if "call" in valid else fallback_action()
                        return fallback_action()

            # 3. Postflop Strategy Tree (Flop, Turn, River)
            else:
                # Value Betting Range
                if equity > 0.62:
                    # Bet larger on wet boards/draw heavy situations (~70% pot), smaller on dry (~50%)
                    bet_size = pot * 0.65 if equity > 0.75 else pot * 0.50
                    # Standardize open sizing or incremental response sizing
                    target = (to_call + bet_size) if to_call > 0 else bet_size
                    return raise_action(target)

                # Tactical Bluff / Semi-Bluff Range
                if equity < 0.42:
                    # 12% pure frequency bluff or 30% semi-bluff on draws when checked to
                    bluff_roll = random.random()
                    if to_call == 0 and ((has_draw and bluff_roll < 0.30) or bluff_roll < 0.12):
                        return raise_action(pot * 0.55)

                # Defensive Checking / Calling Range (Bluff Caching & Pot Odds Checks)
                if to_call == 0:
                    return fallback_action()
                else:
                    # Add a minor buffer to pot odds for safe calling margins
                    buffer = 0.03 if phase != "river" else 0.0
                    if equity >= (pot_odds + buffer):
                        return Action.call() if "call" in valid else fallback_action()
                    return fallback_action()

        except Exception:
            # Complete fail-safe guard against unforeseen internal logic runtime exceptions
            if not state or not state.valid_actions:
                return Action.check()
            if "check" in state.valid_actions: return Action.check()
            if "call" in state.valid_actions: return Action.call()
            if "fold" in state.valid_actions: return Action.fold()
            return Action.check()