# Euchre Benchmark API You are a Euchre-playing agent. Your goal is to play as many hands as possible against a trained CFR bot and accumulate points for your team. ## How Euchre works (essentials) - 4 players in 2 fixed teams: seats (0, 2) vs seats (1, 3). - 24-card deck: 9, T, J, Q, K, A in each of 4 suits (s=spades, c=clubs, h=hearts, d=diamonds). Each player is dealt 5 cards; one card is turned face up after the deal. - Game flow: 1. **Pickup phase**: each player (starting left of dealer) chooses to have the dealer pick up the face-up card as trump (`T`) or pass (`P`). If picked up, the dealer must discard one card. 2. **Trump-call phase**: if all 4 passed, each player may declare a different suit as trump or pass. Can't call the rejected suit. 3. **Alone phase**: the trump caller decides whether to go alone (`L`) — the partner sits out — or play with the partner (`P`). 4. **Play phase**: 5 tricks. Must follow suit if possible. Highest trump wins; otherwise highest card of the led suit wins. - Trump ranking: Right Bower (J of trump), Left Bower (J of same color), A, K, Q, T, 9. - Scoring per hand: - Caller takes 3-4 tricks: +1 - Caller takes 5 (march): +2 - Going alone and taking 5: +4 - Caller euchred (takes <= 2): defending team +2 - Match ends when one team reaches 10 points. ## Your role You control **both seats of one team** (seats 0 and 2). The bench agent controls seats 1 and 3. To prevent cheating, you only see one player's information at a time and you don't know which game/seat you're in — the server interleaves N concurrent games and serves your requests in random order. ## Workflow 1. List agents: GET /bench/agents Returns: ["random", "easy", "medium", "hard"] Difficulty tiers: - random: picks uniformly from legal actions - easy: PIMCTS (open-hand Monte Carlo), no training - medium: CFR trained on bidding phase only - hard: CFR trained through 3 cards played 2. Start a session: POST /bench/sessions {"challenger_id": "your_unique_name", "agent_name": "easy", "num_games": 200} Returns 200: {"session_id": "...", "num_games": 200, "agent_name": "easy"} - num_games must be in 1..=1000. - Only ONE active session per challenger_id at a time. If one is already active, the server returns: 409 Conflict {"error": "you already have an active session; resume it", "session_id": "", "agent_name": "", "num_games": } Use that session_id to resume — the existing agent_name and num_games are authoritative; the values you posted are ignored. - To recover the in-flight istate after resuming, call POST /bench/sessions/{session_id}/move with action=null. The server returns the current Turn response without advancing state. 3. Play loop. Repeat until you receive a Complete response: POST /bench/sessions/{session_id}/move First call: {"challenger_id": "your_unique_name", "action": null} Subsequent: {"challenger_id": "your_unique_name", "action": } Sending action=null on any call is a no-op probe: it returns the current Turn (or Complete) without applying anything. Use it after resuming to learn the in-flight istate. Response shapes (untagged JSON): Turn: {"istate": "", "legal_actions": [, ...], "games_done": , "games_total": } Complete: {"complete": true, "challenger_score": , "agent_score": , "challenger_match_wins": , "agent_match_wins": , "hands_played": } *_score is total Euchre points (1, 2, or 4 per hand). *_match_wins is the count of to-10 matches won by each team within the session. With num_games=N, challenger_match_wins + agent_match_wins = N. Each submitted `action` must appear in the previous response's `legal_actions` list. 4. Inspect the leaderboard: GET /bench (HTML) GET /bench/results (JSON, every session, newest first) GET /bench/results?challenger_id=X (filter) GET /bench/results?agent_name=Y (filter) GET /bench/results?challenger_id=X&agent_name=Y (both filters) GET /bench/history/{challenger_id}/{agent_name} (HTML chart over time) ## Information state format Pipe-delimited segments. Cards are ``, e.g. `Js` = Jack of spades, `Td` = Ten of diamonds. Example istate (player 0, post-bidding): "9cTcJcQcKc|Js|T|0S|9cAcKsJs|" Segments: 1. Your 5 cards (sorted), e.g. "9cTcJcQcKc" 2. Face-up card, e.g. "Js" 3. Pickup phase actions: 'T'=Pickup, 'P'=Pass (one char per player) 4. Trump caller + trump: e.g. "0S" = player 0 called Spades 5. (Dealer only) Discarded card after pickup 6. Cards played in tricks so far, in play order ## Action encoding Each action is a `u8` (the `Action.0` field). Always choose from the `legal_actions` list returned by the server. You don't need to know the exact integer mapping — parse the istate to understand the position. ## Available agents - easy - hard - medium - random ## Errors - 400 Bad Request: invalid action, bad num_games, unknown agent_name, malformed UUID. - 403 Forbidden: challenger_id does not match the session. - 404 Not Found: session_id does not exist. - 409 Conflict: you already have an active session. The response body carries its session_id, agent_name, and num_games — resume that session instead of starting a new one. ## Example client (pseudocode) resp = POST /bench/sessions body = {"challenger_id": "mybot", "agent_name": "easy", "num_games": 50} sid = resp["session_id"] action = None while True: r = POST /bench/sessions/{sid}/move body = {"challenger_id": "mybot", "action": action} if r.get("complete"): print("done", r["challenger_score"], "-", r["agent_score"]) break action = pick_action(r["istate"], r["legal_actions"])