|
| 1 | +import re |
| 2 | + |
| 3 | +from codeclash.agents.player import Player |
| 4 | +from codeclash.arenas.arena import CodeArena, RoundStats |
| 5 | +from codeclash.constants import RESULT_TIE |
| 6 | +from codeclash.utils.environment import assert_zero_exit_code |
| 7 | + |
| 8 | +LOG_FILE = "result.log" |
| 9 | + |
| 10 | + |
| 11 | +class TexasHoldemArena(CodeArena): |
| 12 | + name: str = "TexasHoldem" |
| 13 | + submission: str = "main.py" |
| 14 | + variant: str = "classic" # Can be overridden by subclasses |
| 15 | + description: str = """Texas Hold'em is a heads-up (2-player) No-Limit poker game where each player receives 2 private hole cards and shares 5 community cards. |
| 16 | +
|
| 17 | +Players bet based on hand strength across 4 betting rounds (preflop, flop, turn, river). |
| 18 | +The best 5-card hand from the 7 available cards wins the pot. |
| 19 | +
|
| 20 | +Your bot must implement a `get_move(state)` function that returns one of: |
| 21 | +- 'fold': Give up the hand |
| 22 | +- 'check': Pass when no bet to call |
| 23 | +- 'call': Match the current bet |
| 24 | +- 'raise <amount>': Raise to a specified total amount |
| 25 | +- 'all_in': Bet all remaining chips |
| 26 | +
|
| 27 | +The state object contains: |
| 28 | +- hole_cards: Your 2 private cards (e.g., ['As', 'Kh']) |
| 29 | +- community_cards: Current board cards (0-5) |
| 30 | +- pot: Total pot size |
| 31 | +- current_bet: Amount to call |
| 32 | +- player_stack: Your remaining chips |
| 33 | +- opponent_stack: Opponent's chips |
| 34 | +- player_bet: Your bet this round |
| 35 | +- opponent_bet: Opponent's bet this round |
| 36 | +- position: 'button' or 'big_blind' |
| 37 | +- round_name: 'preflop', 'flop', 'turn', 'river' |
| 38 | +- min_raise: Minimum raise amount |
| 39 | +- is_first_action: True if first to act this betting round |
| 40 | +- variant: 'classic' or 'short_deck' |
| 41 | +
|
| 42 | +Cards use 2-character notation: rank (23456789TJQKA) + suit (cdhs). |
| 43 | +Example: 'As' = Ace of spades, 'Th' = Ten of hearts. |
| 44 | +
|
| 45 | +Hand rankings (highest to lowest): |
| 46 | +1. Royal Flush 2. Straight Flush 3. Four of a Kind 4. Full House |
| 47 | +5. Flush 6. Straight 7. Three of a Kind 8. Two Pair 9. Pair 10. High Card |
| 48 | +""" |
| 49 | + |
| 50 | + def __init__(self, config, **kwargs): |
| 51 | + super().__init__(config, **kwargs) |
| 52 | + assert len(config["players"]) == 2, "TexasHoldem requires exactly 2 players" |
| 53 | + |
| 54 | + def execute_round(self, agents: list[Player]) -> None: |
| 55 | + args = [f"/{agent.name}/{self.submission}" for agent in agents] |
| 56 | + variant_arg = f"--variant {self.variant}" if self.variant else "" |
| 57 | + cmd = f"python engine.py {' '.join(args)} -r {self.game_config['sims_per_round']} {variant_arg} > {self.log_env / LOG_FILE};" |
| 58 | + self.logger.info(f"Running game: {cmd}") |
| 59 | + assert_zero_exit_code(self.environment.execute(cmd)) |
| 60 | + |
| 61 | + def get_results(self, agents: list[Player], round_num: int, stats: RoundStats): |
| 62 | + with open(self.log_round(round_num) / LOG_FILE) as f: |
| 63 | + round_log = f.read() |
| 64 | + lines = round_log.split("FINAL_RESULTS")[-1].splitlines() |
| 65 | + |
| 66 | + scores = {} |
| 67 | + for line in lines: |
| 68 | + match = re.search(r"Bot\_(\d)\_main:\s(\d+)\srounds\swon", line) |
| 69 | + if match: |
| 70 | + bot_id = match.group(1) |
| 71 | + rounds_won = int(match.group(2)) |
| 72 | + scores[agents[int(bot_id) - 1].name] = rounds_won |
| 73 | + |
| 74 | + draw_match = re.search(r"Draws:\s(\d+)", round_log) |
| 75 | + if draw_match and int(draw_match.group(1)) > 0: |
| 76 | + scores[RESULT_TIE] = int(draw_match.group(1)) |
| 77 | + |
| 78 | + stats.winner = max(scores, key=scores.get) if scores else "unknown" |
| 79 | + stats.scores = scores |
| 80 | + for player, score in scores.items(): |
| 81 | + if player != RESULT_TIE: |
| 82 | + stats.player_stats[player].score = score |
| 83 | + |
| 84 | + def validate_code(self, agent: Player) -> tuple[bool, str | None]: |
| 85 | + if self.submission not in agent.environment.execute("ls")["output"]: |
| 86 | + return False, f"No {self.submission} file found" |
| 87 | + |
| 88 | + bot_content = agent.environment.execute(f"cat {self.submission}")["output"] |
| 89 | + if "def get_move(" not in bot_content: |
| 90 | + return False, "Missing required function: def get_move(state)" |
| 91 | + |
| 92 | + return True, None |
| 93 | + |
| 94 | + |
| 95 | +class ShortDeckHoldemArena(TexasHoldemArena): |
| 96 | + """Short-deck (Six-plus) Hold'em variant with 36-card deck.""" |
| 97 | + |
| 98 | + name: str = "ShortDeckHoldem" |
| 99 | + variant: str = "short_deck" |
| 100 | + description: str = """Short-deck (Six-plus) Hold'em is a heads-up (2-player) No-Limit poker variant using a 36-card deck (6 through Ace, removing 2-5). |
| 101 | +
|
| 102 | +Key differences from classic Texas Hold'em: |
| 103 | +- 36-card deck (ranks 6-A only, no 2-5) |
| 104 | +- FLUSH BEATS FULL HOUSE (flushes are harder to make with fewer cards per suit) |
| 105 | +- A-6-7-8-9 is the lowest straight (wheel) |
| 106 | +
|
| 107 | +Your bot must implement a `get_move(state)` function that returns one of: |
| 108 | +- 'fold': Give up the hand |
| 109 | +- 'check': Pass when no bet to call |
| 110 | +- 'call': Match the current bet |
| 111 | +- 'raise <amount>': Raise to a specified total amount |
| 112 | +- 'all_in': Bet all remaining chips |
| 113 | +
|
| 114 | +The state object contains: |
| 115 | +- hole_cards: Your 2 private cards (e.g., ['As', 'Kh']) |
| 116 | +- community_cards: Current board cards (0-5) |
| 117 | +- pot: Total pot size |
| 118 | +- current_bet: Amount to call |
| 119 | +- player_stack: Your remaining chips |
| 120 | +- opponent_stack: Opponent's chips |
| 121 | +- player_bet: Your bet this round |
| 122 | +- opponent_bet: Opponent's bet this round |
| 123 | +- position: 'button' or 'big_blind' |
| 124 | +- round_name: 'preflop', 'flop', 'turn', 'river' |
| 125 | +- min_raise: Minimum raise amount |
| 126 | +- is_first_action: True if first to act this betting round |
| 127 | +- variant: 'short_deck' |
| 128 | +
|
| 129 | +Cards use 2-character notation: rank (6789TJQKA) + suit (cdhs). |
| 130 | +Example: 'As' = Ace of spades, 'Th' = Ten of hearts, '6c' = Six of clubs. |
| 131 | +
|
| 132 | +Short-deck hand rankings (highest to lowest): |
| 133 | +1. Royal Flush 2. Straight Flush 3. Four of a Kind 4. FLUSH (beats full house!) |
| 134 | +5. Full House 6. Straight 7. Three of a Kind 8. Two Pair 9. Pair 10. High Card |
| 135 | +""" |
0 commit comments