diff --git a/codeclash/arenas/__init__.py b/codeclash/arenas/__init__.py index 7ffbb95f..e3c9cec9 100644 --- a/codeclash/arenas/__init__.py +++ b/codeclash/arenas/__init__.py @@ -9,6 +9,7 @@ from codeclash.arenas.huskybench.huskybench import HuskyBenchArena from codeclash.arenas.robocode.robocode import RoboCodeArena from codeclash.arenas.robotrumble.robotrumble import RobotRumbleArena +from codeclash.arenas.texasholdem.texasholdem import ShortDeckHoldemArena, TexasHoldemArena ARENAS = [ BattleCodeArena, @@ -21,6 +22,8 @@ HuskyBenchArena, RoboCodeArena, RobotRumbleArena, + ShortDeckHoldemArena, + TexasHoldemArena, ] diff --git a/codeclash/arenas/texasholdem/TexasHoldem.Dockerfile b/codeclash/arenas/texasholdem/TexasHoldem.Dockerfile new file mode 100644 index 00000000..433904d1 --- /dev/null +++ b/codeclash/arenas/texasholdem/TexasHoldem.Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + python3.10 python3-pip python-is-python3 wget git build-essential \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone https://github.com/CodeClash-ai/TexasHoldem.git /workspace \ + && cd /workspace \ + && git remote set-url origin https://github.com/CodeClash-ai/TexasHoldem.git + +WORKDIR /workspace diff --git a/codeclash/arenas/texasholdem/__init__.py b/codeclash/arenas/texasholdem/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/codeclash/arenas/texasholdem/texasholdem.py b/codeclash/arenas/texasholdem/texasholdem.py new file mode 100644 index 00000000..279c5ae8 --- /dev/null +++ b/codeclash/arenas/texasholdem/texasholdem.py @@ -0,0 +1,135 @@ +import re + +from codeclash.agents.player import Player +from codeclash.arenas.arena import CodeArena, RoundStats +from codeclash.constants import RESULT_TIE +from codeclash.utils.environment import assert_zero_exit_code + +LOG_FILE = "result.log" + + +class TexasHoldemArena(CodeArena): + name: str = "TexasHoldem" + submission: str = "main.py" + variant: str = "classic" # Can be overridden by subclasses + 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. + +Players bet based on hand strength across 4 betting rounds (preflop, flop, turn, river). +The best 5-card hand from the 7 available cards wins the pot. + +Your bot must implement a `get_move(state)` function that returns one of: +- 'fold': Give up the hand +- 'check': Pass when no bet to call +- 'call': Match the current bet +- 'raise ': Raise to a specified total amount +- 'all_in': Bet all remaining chips + +The state object contains: +- hole_cards: Your 2 private cards (e.g., ['As', 'Kh']) +- community_cards: Current board cards (0-5) +- pot: Total pot size +- current_bet: Amount to call +- player_stack: Your remaining chips +- opponent_stack: Opponent's chips +- player_bet: Your bet this round +- opponent_bet: Opponent's bet this round +- position: 'button' or 'big_blind' +- round_name: 'preflop', 'flop', 'turn', 'river' +- min_raise: Minimum raise amount +- is_first_action: True if first to act this betting round +- variant: 'classic' or 'short_deck' + +Cards use 2-character notation: rank (23456789TJQKA) + suit (cdhs). +Example: 'As' = Ace of spades, 'Th' = Ten of hearts. + +Hand rankings (highest to lowest): +1. Royal Flush 2. Straight Flush 3. Four of a Kind 4. Full House +5. Flush 6. Straight 7. Three of a Kind 8. Two Pair 9. Pair 10. High Card +""" + + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) + assert len(config["players"]) == 2, "TexasHoldem requires exactly 2 players" + + def execute_round(self, agents: list[Player]) -> None: + args = [f"/{agent.name}/{self.submission}" for agent in agents] + variant_arg = f"--variant {self.variant}" if self.variant else "" + cmd = f"python engine.py {' '.join(args)} -r {self.game_config['sims_per_round']} {variant_arg} > {self.log_env / LOG_FILE};" + self.logger.info(f"Running game: {cmd}") + assert_zero_exit_code(self.environment.execute(cmd)) + + def get_results(self, agents: list[Player], round_num: int, stats: RoundStats): + with open(self.log_round(round_num) / LOG_FILE) as f: + round_log = f.read() + lines = round_log.split("FINAL_RESULTS")[-1].splitlines() + + scores = {} + for line in lines: + match = re.search(r"Bot\_(\d)\_main:\s(\d+)\srounds\swon", line) + if match: + bot_id = match.group(1) + rounds_won = int(match.group(2)) + scores[agents[int(bot_id) - 1].name] = rounds_won + + draw_match = re.search(r"Draws:\s(\d+)", round_log) + if draw_match and int(draw_match.group(1)) > 0: + scores[RESULT_TIE] = int(draw_match.group(1)) + + stats.winner = max(scores, key=scores.get) if scores else "unknown" + stats.scores = scores + for player, score in scores.items(): + if player != RESULT_TIE: + stats.player_stats[player].score = score + + def validate_code(self, agent: Player) -> tuple[bool, str | None]: + if self.submission not in agent.environment.execute("ls")["output"]: + return False, f"No {self.submission} file found" + + bot_content = agent.environment.execute(f"cat {self.submission}")["output"] + if "def get_move(" not in bot_content: + return False, "Missing required function: def get_move(state)" + + return True, None + + +class ShortDeckHoldemArena(TexasHoldemArena): + """Short-deck (Six-plus) Hold'em variant with 36-card deck.""" + + name: str = "ShortDeckHoldem" + variant: str = "short_deck" + 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). + +Key differences from classic Texas Hold'em: +- 36-card deck (ranks 6-A only, no 2-5) +- FLUSH BEATS FULL HOUSE (flushes are harder to make with fewer cards per suit) +- A-6-7-8-9 is the lowest straight (wheel) + +Your bot must implement a `get_move(state)` function that returns one of: +- 'fold': Give up the hand +- 'check': Pass when no bet to call +- 'call': Match the current bet +- 'raise ': Raise to a specified total amount +- 'all_in': Bet all remaining chips + +The state object contains: +- hole_cards: Your 2 private cards (e.g., ['As', 'Kh']) +- community_cards: Current board cards (0-5) +- pot: Total pot size +- current_bet: Amount to call +- player_stack: Your remaining chips +- opponent_stack: Opponent's chips +- player_bet: Your bet this round +- opponent_bet: Opponent's bet this round +- position: 'button' or 'big_blind' +- round_name: 'preflop', 'flop', 'turn', 'river' +- min_raise: Minimum raise amount +- is_first_action: True if first to act this betting round +- variant: 'short_deck' + +Cards use 2-character notation: rank (6789TJQKA) + suit (cdhs). +Example: 'As' = Ace of spades, 'Th' = Ten of hearts, '6c' = Six of clubs. + +Short-deck hand rankings (highest to lowest): +1. Royal Flush 2. Straight Flush 3. Four of a Kind 4. FLUSH (beats full house!) +5. Full House 6. Straight 7. Three of a Kind 8. Two Pair 9. Pair 10. High Card +""" diff --git a/configs/test/shortdeckholdem.yaml b/configs/test/shortdeckholdem.yaml new file mode 100644 index 00000000..b74004e9 --- /dev/null +++ b/configs/test/shortdeckholdem.yaml @@ -0,0 +1,40 @@ +tournament: + rounds: 3 +game: + name: ShortDeckHoldem + sims_per_round: 50 +players: +- agent: dummy + name: p1 +- agent: dummy + name: p2 +prompts: + game_description: | + You are a software developer ({{player_id}}) competing in a Short-deck (Six-plus) Hold'em poker coding game. + + The game is played in {{rounds}} rounds. For every round, you (and your competitor) edit program code that controls your poker bot. This is round {{round}}. + After you and your competitor finish editing your codebases, the game is run automatically. + + IMPORTANT: This is SHORT-DECK Hold'em with key differences from classic: + - 36-card deck (only 6 through Ace, no 2-5) + - FLUSH BEATS FULL HOUSE (flushes are harder to make) + - A-6-7-8-9 is the lowest straight (wheel) + + Your bot must implement a `get_move(state)` function that receives the game state and returns an action: + - 'fold': Give up the hand + - 'check': Pass when no bet to call + - 'call': Match the current bet + - 'raise ': Raise to a specified total amount + - 'all_in': Bet all remaining chips + + The state object contains: + - hole_cards: Your 2 private cards (e.g., ['As', 'Kh']) + - community_cards: Current board cards (0-5) + - pot: Total pot size + - current_bet: Amount to call + - player_stack/opponent_stack: Chip counts + - position: 'button' or 'big_blind' + - round_name: 'preflop', 'flop', 'turn', 'river' + - variant: 'short_deck' + + Adjust your strategy for the modified hand rankings! diff --git a/configs/test/texasholdem.yaml b/configs/test/texasholdem.yaml new file mode 100644 index 00000000..38823ab6 --- /dev/null +++ b/configs/test/texasholdem.yaml @@ -0,0 +1,34 @@ +tournament: + rounds: 3 +game: + name: TexasHoldem + sims_per_round: 50 +players: +- agent: dummy + name: p1 +- agent: dummy + name: p2 +prompts: + game_description: | + You are a software developer ({{player_id}}) competing in a Texas Hold'em poker coding game. + + The game is played in {{rounds}} rounds. For every round, you (and your competitor) edit program code that controls your poker bot. This is round {{round}}. + After you and your competitor finish editing your codebases, the game is run automatically. + + Your bot must implement a `get_move(state)` function that receives the game state and returns an action: + - 'fold': Give up the hand + - 'check': Pass when no bet to call + - 'call': Match the current bet + - 'raise ': Raise to a specified total amount + - 'all_in': Bet all remaining chips + + The state object contains: + - hole_cards: Your 2 private cards (e.g., ['As', 'Kh']) + - community_cards: Current board cards (0-5) + - pot: Total pot size + - current_bet: Amount to call + - player_stack/opponent_stack: Chip counts + - position: 'button' or 'big_blind' + - round_name: 'preflop', 'flop', 'turn', 'river' + + Focus on hand selection, position awareness, and proper bet sizing.