|
| 1 | +# Balatro Bot API Protocol |
| 2 | + |
| 3 | +This document provides the UDP protocol reference for developing bots that interface with Balatro via raw UDP sockets. |
| 4 | + |
| 5 | +## UDP Communication |
| 6 | + |
| 7 | +The Balatro Bot API uses UDP sockets for real-time communication between the game and external bot clients. |
| 8 | + |
| 9 | +**Connection Details:** |
| 10 | +- **Host:** `127.0.0.1` (localhost) |
| 11 | +- **Port:** `12346` (default) |
| 12 | +- **Protocol:** UDP |
| 13 | + |
| 14 | +## Communication Sequence |
| 15 | + |
| 16 | +```mermaid |
| 17 | +sequenceDiagram |
| 18 | + participant Bot |
| 19 | + participant Game |
| 20 | +
|
| 21 | + Bot->>Game: "HELLO" (UDP) |
| 22 | + Game->>Bot: JSON Game State |
| 23 | +
|
| 24 | + loop Game Loop |
| 25 | + Game->>Bot: Updated JSON Game State (when waitingForAction changes) |
| 26 | +
|
| 27 | + Note over Bot: Bot analyzes game state and decides action |
| 28 | +
|
| 29 | + Bot->>Game: "ACTION_NAME|param1,param2|param3" |
| 30 | +
|
| 31 | + alt Valid Action |
| 32 | + Game->>Bot: {"response": "Action executed successfully"} |
| 33 | + else Invalid Action |
| 34 | + Game->>Bot: {"response": "Error: [description]"} |
| 35 | + end |
| 36 | + end |
| 37 | +``` |
| 38 | + |
| 39 | +## Message Formats |
| 40 | + |
| 41 | +**Handshake** |
| 42 | +``` |
| 43 | +Bot -> Game: "HELLO" |
| 44 | +Game -> Bot: {JSON game state with waitingFor field} |
| 45 | +``` |
| 46 | + |
| 47 | +**Action Request** |
| 48 | +``` |
| 49 | +Bot -> Game: "ACTION_NAME|param1,param2|param3,param4" |
| 50 | +``` |
| 51 | + |
| 52 | +**Responses** |
| 53 | +``` |
| 54 | +Success: {"response": "Action executed successfully"} |
| 55 | +Error: {"response": "Error: [description]"} |
| 56 | +``` |
| 57 | + |
| 58 | +## Game State JSON |
| 59 | + |
| 60 | +The game sends JSON containing: |
| 61 | + |
| 62 | +- **waitingFor**: What action type the game expects |
| 63 | +- **waitingForAction**: Boolean indicating if bot input is needed |
| 64 | +- **gamestate**: Current game phase and data |
| 65 | +- **hand**: Player's current hand cards |
| 66 | +- **jokers**: Active joker cards |
| 67 | +- **consumables**: Available consumable cards |
| 68 | +- **shop**: Shop items and costs |
| 69 | +- **blinds**: Current blind information |
| 70 | +- **round**: Round data (hands/discards remaining, etc.) |
| 71 | + |
| 72 | +Key field for bots: **waitingForAction** - only send actions when this is `true`. |
| 73 | + |
| 74 | +## Available Actions |
| 75 | + |
| 76 | +Actions use pipe-separated format: `ACTION_NAME|param1,param2|param3,param4` |
| 77 | + |
| 78 | + |
| 79 | +| Action | Parameters | Description | Valid When waitingFor | |
| 80 | +|--------|------------|-------------|------------| |
| 81 | +| `SELECT_BLIND` | `1` | Select the current blind | `skip_or_select_blind` | |
| 82 | +| `SKIP_BLIND` | `2` | Skip the current blind | `skip_or_select_blind` | |
| 83 | +| `PLAY_HAND` | `3|card_indices` | Play selected cards | `select_cards_from_hand` | |
| 84 | +| `DISCARD_HAND` | `4|card_indices` | Discard selected cards | `select_cards_from_hand` | |
| 85 | +| `END_SHOP` | `5` | Leave the shop | `select_shop_action` | |
| 86 | +| `REROLL_SHOP` | `6` | Reroll shop items | `select_shop_action` | |
| 87 | +| `BUY_CARD` | `7|shop_index` | Buy joker from shop | `select_shop_action` | |
| 88 | +| `BUY_VOUCHER` | `8|voucher_index` | Buy voucher from shop | `select_shop_action` | |
| 89 | +| `BUY_BOOSTER` | `9|booster_index` | Buy booster pack | `select_shop_action` | |
| 90 | +| `SELECT_BOOSTER_CARD` | `10|pack_index|hand_indices` | Use consumable on cards | `select_booster_action` | |
| 91 | +| `SKIP_BOOSTER_PACK` | `11` | Skip booster pack | `select_booster_action` | |
| 92 | +| `SELL_JOKER` | `12|joker_indices` | Sell joker cards | `sell_jokers` | |
| 93 | +| `USE_CONSUMABLE` | `13|consumable_indices` | Use consumable cards | `use_or_sell_consumables` | |
| 94 | +| `SELL_CONSUMABLE` | `14|consumable_indices` | Sell consumable cards | `use_or_sell_consumables` | |
| 95 | +| `REARRANGE_JOKERS` | `15|new_order` | Reorder joker positions | `rearrange_jokers` | |
| 96 | +| `REARRANGE_CONSUMABLES` | `16|new_order` | Reorder consumable positions | `rearrange_consumables` | |
| 97 | +| `REARRANGE_HAND` | `17|new_order` | Reorder hand cards | `rearrange_hand` | |
| 98 | +| `PASS` | `18` | Do nothing (always valid) | Any state | |
| 99 | +| `START_RUN` | `19|stake|deck|seed|challenge` | Start new run | `start_run` | |
| 100 | + |
| 101 | + |
| 102 | +**Card Indices:** 1-based array of card positions |
| 103 | + |
| 104 | +- Example: `PLAY_HAND|3|1,3,5` (play cards at positions 1, 3, and 5) |
| 105 | + |
| 106 | +**Shop Indices:** 1-based position in shop |
| 107 | + |
| 108 | +- Example: `BUY_CARD|7|2` (buy second joker in shop) |
| 109 | + |
| 110 | +**Reorder Arrays:** Complete new ordering (all positions) |
| 111 | + |
| 112 | +- Example: `REARRANGE_JOKERS|15|3,1,2,4` (move 3rd joker to 1st position, etc.) |
| 113 | + |
| 114 | +**Run Parameters:** |
| 115 | + |
| 116 | +- stake: 1-8 (difficulty) |
| 117 | +- deck: deck name string |
| 118 | +- seed: seed string or empty |
| 119 | +- challenge: challenge name or empty |
| 120 | + |
| 121 | +## Game States |
| 122 | + |
| 123 | + |
| 124 | +| waitingFor State | Description | Valid Actions | |
| 125 | +|------------------|-------------|---------------| |
| 126 | +| `start_run` | Main menu | `START_RUN` | |
| 127 | +| `skip_or_select_blind` | Blind selection screen | `SELECT_BLIND`, `SKIP_BLIND` | |
| 128 | +| `select_cards_from_hand` | Playing/discarding phase | `PLAY_HAND`, `DISCARD_HAND` | |
| 129 | +| `select_shop_action` | In shop | `END_SHOP`, `REROLL_SHOP`, `BUY_CARD`, `BUY_VOUCHER`, `BUY_BOOSTER` | |
| 130 | +| `select_booster_action` | Opening booster packs | `SELECT_BOOSTER_CARD`, `SKIP_BOOSTER_PACK` | |
| 131 | +| `sell_jokers` | Joker management | `SELL_JOKER` | |
| 132 | +| `use_or_sell_consumables` | Consumable management | `USE_CONSUMABLE`, `SELL_CONSUMABLE` | |
| 133 | +| `rearrange_jokers` | Joker positioning | `REARRANGE_JOKERS` | |
| 134 | +| `rearrange_consumables` | Consumable positioning | `REARRANGE_CONSUMABLES` | |
| 135 | +| `rearrange_hand` | Hand card positioning | `REARRANGE_HAND` | |
| 136 | + |
| 137 | + |
| 138 | +## Error Handling |
| 139 | + |
| 140 | +The game validates all actions and returns errors for: |
| 141 | + |
| 142 | +- **Invalid format:** Incorrect action syntax |
| 143 | +- **Invalid parameters:** Wrong number or type of parameters |
| 144 | +- **Invalid state:** Action not allowed in current game state |
| 145 | +- **Invalid indices:** References to non-existent cards/items |
| 146 | +- **Insufficient resources:** Not enough money, hands, discards, etc. |
| 147 | + |
| 148 | +## Example Bot Communication |
| 149 | + |
| 150 | +``` |
| 151 | +1. Bot connects and sends handshake: |
| 152 | + Bot -> Game: "HELLO" |
| 153 | +
|
| 154 | +2. Game responds with initial state: |
| 155 | + Game -> Bot: {"waitingFor": "start_run", "waitingForAction": true, ...} |
| 156 | +
|
| 157 | +3. Bot starts a run: |
| 158 | + Bot -> Game: "START_RUN|19|1|Red Deck||" |
| 159 | + Game -> Bot: {"response": "Action executed successfully"} |
| 160 | +
|
| 161 | +4. Game state updates automatically: |
| 162 | + Game -> Bot: {"waitingFor": "skip_or_select_blind", "waitingForAction": true, ...} |
| 163 | +
|
| 164 | +5. Bot selects blind: |
| 165 | + Bot -> Game: "SELECT_BLIND|1" |
| 166 | + Game -> Bot: {"response": "Action executed successfully"} |
| 167 | +
|
| 168 | +6. Continue until run ends... |
| 169 | +``` |
| 170 | + |
| 171 | +## Implementation Notes |
| 172 | + |
| 173 | +- **Wait for waitingForAction:** Only send actions when `waitingForAction` is `true` |
| 174 | +- **One action per request:** Send one action string per UDP message |
| 175 | +- **Monitor responses:** Check for error messages and handle appropriately |
| 176 | +- **State changes:** Game automatically sends updated state when ready for next action |
| 177 | +- **Index validation:** All card/item indices are 1-based and must refer to existing items |
0 commit comments