# BalatroBot
> A bot framework for Balatro
BalatroBot is a Python framework for developing automated bots to play the card game Balatro.
The architecture consists of three main layers: a communication layer using TCP protocol with Lua API,
a Python framework layer for bot development, and comprehensive testing and documentation systems.
The project enables real-time bidirectional communication between the game and bot through TCP sockets.
# Documentation
# BalatroBot API
This page provides comprehensive API documentation for the BalatroBot Python framework. The API enables you to build automated bots that interact with the Balatro card game through a structured TCP communication protocol.
The API is organized into several key components: the `BalatroClient` for managing game connections and sending commands, enums that define game states and actions, exception classes for robust error handling, and data models that structure requests and responses between your bot and the game.
## Client
The `BalatroClient` is the main interface for communicating with the Balatro game through TCP connections. It handles connection management, message serialization, and error handling.
### `balatrobot.client.BalatroClient`
Client for communicating with the BalatroBot game API.
Attributes:
| Name | Type | Description |
| ------------- | -------- | --------------------------- |
| `host` | | Host address to connect to |
| `port` | | Port number to connect to |
| `timeout` | | Socket timeout in seconds |
| `buffer_size` | | Socket buffer size in bytes |
| `_socket` | \`socket | None\` |
Source code in `src/balatrobot/client.py`
```python
class BalatroClient:
"""Client for communicating with the BalatroBot game API.
Attributes:
host: Host address to connect to
port: Port number to connect to
timeout: Socket timeout in seconds
buffer_size: Socket buffer size in bytes
_socket: Socket connection to BalatroBot
"""
host = "127.0.0.1"
timeout = 30.0
buffer_size = 65536
def __init__(self, port: int = 12346):
"""Initialize BalatroBot client
Args:
port: Port number to connect to (default: 12346)
"""
self.port = port
self._socket: socket.socket | None = None
self._connected = False
def __enter__(self) -> Self:
"""Enter context manager and connect to the game."""
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
"""Exit context manager and disconnect from the game."""
self.disconnect()
def connect(self) -> None:
"""Connect to Balatro TCP server
Raises:
ConnectionFailedError: If not connected to the game
"""
if self._connected:
return
logger.info(f"Connecting to BalatroBot API at {self.host}:{self.port}")
try:
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.settimeout(self.timeout)
self._socket.setsockopt(
socket.SOL_SOCKET, socket.SO_RCVBUF, self.buffer_size
)
self._socket.connect((self.host, self.port))
self._connected = True
logger.info(
f"Successfully connected to BalatroBot API at {self.host}:{self.port}"
)
except (socket.error, OSError) as e:
logger.error(f"Failed to connect to {self.host}:{self.port}: {e}")
raise ConnectionFailedError(
f"Failed to connect to {self.host}:{self.port}",
error_code="E008",
context={"host": self.host, "port": self.port, "error": str(e)},
) from e
def disconnect(self) -> None:
"""Disconnect from the BalatroBot game API."""
if self._socket:
logger.info(f"Disconnecting from BalatroBot API at {self.host}:{self.port}")
self._socket.close()
self._socket = None
self._connected = False
def send_message(self, name: str, arguments: dict | None = None) -> dict:
"""Send JSON message to Balatro and receive response
Args:
name: Function name to call
arguments: Function arguments
Returns:
Response from the game API
Raises:
ConnectionFailedError: If not connected to the game
BalatroError: If the API returns an error
"""
if arguments is None:
arguments = {}
if not self._connected or not self._socket:
raise ConnectionFailedError(
"Not connected to the game API",
error_code="E008",
context={
"connected": self._connected,
"socket": self._socket is not None,
},
)
# Create and validate request
request = APIRequest(name=name, arguments=arguments)
logger.debug(f"Sending API request: {name}")
try:
# Send request
message = request.model_dump_json() + "\n"
self._socket.send(message.encode())
# Receive response
data = self._socket.recv(self.buffer_size)
response_data = json.loads(data.decode().strip())
# Check for error response
if "error" in response_data:
logger.error(f"API request {name} failed: {response_data.get('error')}")
raise create_exception_from_error_response(response_data)
logger.debug(f"API request {name} completed successfully")
return response_data
except socket.error as e:
logger.error(f"Socket error during API request {name}: {e}")
raise ConnectionFailedError(
f"Socket error during communication: {e}",
error_code="E008",
context={"error": str(e)},
) from e
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON response from API request {name}: {e}")
raise BalatroError(
f"Invalid JSON response from game: {e}",
error_code="E001",
context={"error": str(e)},
) from e
```
#### `connect()`
Connect to Balatro TCP server
Raises:
| Type | Description |
| ----------------------- | ---------------------------- |
| `ConnectionFailedError` | If not connected to the game |
Source code in `src/balatrobot/client.py`
```python
def connect(self) -> None:
"""Connect to Balatro TCP server
Raises:
ConnectionFailedError: If not connected to the game
"""
if self._connected:
return
logger.info(f"Connecting to BalatroBot API at {self.host}:{self.port}")
try:
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.settimeout(self.timeout)
self._socket.setsockopt(
socket.SOL_SOCKET, socket.SO_RCVBUF, self.buffer_size
)
self._socket.connect((self.host, self.port))
self._connected = True
logger.info(
f"Successfully connected to BalatroBot API at {self.host}:{self.port}"
)
except (socket.error, OSError) as e:
logger.error(f"Failed to connect to {self.host}:{self.port}: {e}")
raise ConnectionFailedError(
f"Failed to connect to {self.host}:{self.port}",
error_code="E008",
context={"host": self.host, "port": self.port, "error": str(e)},
) from e
```
#### `disconnect()`
Disconnect from the BalatroBot game API.
Source code in `src/balatrobot/client.py`
```python
def disconnect(self) -> None:
"""Disconnect from the BalatroBot game API."""
if self._socket:
logger.info(f"Disconnecting from BalatroBot API at {self.host}:{self.port}")
self._socket.close()
self._socket = None
self._connected = False
```
#### `send_message(name, arguments=None)`
Send JSON message to Balatro and receive response
Parameters:
| Name | Type | Description | Default |
| ----------- | ------ | --------------------- | ------------------ |
| `name` | `str` | Function name to call | *required* |
| `arguments` | \`dict | None\` | Function arguments |
Returns:
| Type | Description |
| ------ | -------------------------- |
| `dict` | Response from the game API |
Raises:
| Type | Description |
| ----------------------- | ---------------------------- |
| `ConnectionFailedError` | If not connected to the game |
| `BalatroError` | If the API returns an error |
Source code in `src/balatrobot/client.py`
```python
def send_message(self, name: str, arguments: dict | None = None) -> dict:
"""Send JSON message to Balatro and receive response
Args:
name: Function name to call
arguments: Function arguments
Returns:
Response from the game API
Raises:
ConnectionFailedError: If not connected to the game
BalatroError: If the API returns an error
"""
if arguments is None:
arguments = {}
if not self._connected or not self._socket:
raise ConnectionFailedError(
"Not connected to the game API",
error_code="E008",
context={
"connected": self._connected,
"socket": self._socket is not None,
},
)
# Create and validate request
request = APIRequest(name=name, arguments=arguments)
logger.debug(f"Sending API request: {name}")
try:
# Send request
message = request.model_dump_json() + "\n"
self._socket.send(message.encode())
# Receive response
data = self._socket.recv(self.buffer_size)
response_data = json.loads(data.decode().strip())
# Check for error response
if "error" in response_data:
logger.error(f"API request {name} failed: {response_data.get('error')}")
raise create_exception_from_error_response(response_data)
logger.debug(f"API request {name} completed successfully")
return response_data
except socket.error as e:
logger.error(f"Socket error during API request {name}: {e}")
raise ConnectionFailedError(
f"Socket error during communication: {e}",
error_code="E008",
context={"error": str(e)},
) from e
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON response from API request {name}: {e}")
raise BalatroError(
f"Invalid JSON response from game: {e}",
error_code="E001",
context={"error": str(e)},
) from e
```
______________________________________________________________________
## Enums
### `balatrobot.enums.State`
Game state values representing different phases of gameplay in Balatro, from menu navigation to active card play and shop interactions.
Source code in `src/balatrobot/enums.py`
```python
@unique
class State(Enum):
"""Game state values representing different phases of gameplay in Balatro,
from menu navigation to active card play and shop interactions."""
SELECTING_HAND = 1
HAND_PLAYED = 2
DRAW_TO_HAND = 3
GAME_OVER = 4
SHOP = 5
PLAY_TAROT = 6
BLIND_SELECT = 7
ROUND_EVAL = 8
TAROT_PACK = 9
PLANET_PACK = 10
MENU = 11
TUTORIAL = 12
SPLASH = 13
SANDBOX = 14
SPECTRAL_PACK = 15
DEMO_CTA = 16
STANDARD_PACK = 17
BUFFOON_PACK = 18
NEW_ROUND = 19
```
### `balatrobot.enums.Actions`
Bot action values corresponding to user interactions available in different game states, from card play to shop purchases and inventory management.
Source code in `src/balatrobot/enums.py`
```python
@unique
class Actions(Enum):
"""Bot action values corresponding to user interactions available in
different game states, from card play to shop purchases and inventory
management."""
SELECT_BLIND = 1
SKIP_BLIND = 2
PLAY_HAND = 3
DISCARD_HAND = 4
END_SHOP = 5
REROLL_SHOP = 6
BUY_CARD = 7
BUY_VOUCHER = 8
BUY_BOOSTER = 9
SELECT_BOOSTER_CARD = 10
SKIP_BOOSTER_PACK = 11
SELL_JOKER = 12
USE_CONSUMABLE = 13
SELL_CONSUMABLE = 14
REARRANGE_JOKERS = 15
REARRANGE_CONSUMABLES = 16
REARRANGE_HAND = 17
PASS = 18
START_RUN = 19
SEND_GAMESTATE = 20
```
### `balatrobot.enums.Decks`
Starting deck types in Balatro, each providing unique starting conditions, card modifications, or special abilities that affect gameplay throughout the run.
Source code in `src/balatrobot/enums.py`
```python
@unique
class Decks(Enum):
"""Starting deck types in Balatro, each providing unique starting
conditions, card modifications, or special abilities that affect gameplay
throughout the run."""
RED = "Red Deck"
BLUE = "Blue Deck"
YELLOW = "Yellow Deck"
GREEN = "Green Deck"
BLACK = "Black Deck"
MAGIC = "Magic Deck"
NEBULA = "Nebula Deck"
GHOST = "Ghost Deck"
ABANDONED = "Abandoned Deck"
CHECKERED = "Checkered Deck"
ZODIAC = "Zodiac Deck"
PAINTED = "Painted Deck"
ANAGLYPH = "Anaglyph Deck"
PLASMA = "Plasma Deck"
ERRATIC = "Erratic Deck"
```
### `balatrobot.enums.Stakes`
Difficulty stake levels in Balatro that increase game difficulty through various modifiers and restrictions, with higher stakes providing greater challenges and rewards.
Source code in `src/balatrobot/enums.py`
```python
@unique
class Stakes(Enum):
"""Difficulty stake levels in Balatro that increase game difficulty through
various modifiers and restrictions, with higher stakes providing greater
challenges and rewards."""
WHITE = 1
RED = 2
GREEN = 3
BLACK = 4
BLUE = 5
PURPLE = 6
ORANGE = 7
GOLD = 8
```
### `balatrobot.enums.ErrorCode`
Standardized error codes used in BalatroBot API that match those defined in src/lua/api.lua for consistent error handling across the entire system.
Source code in `src/balatrobot/enums.py`
```python
@unique
class ErrorCode(Enum):
"""Standardized error codes used in BalatroBot API that match those defined in src/lua/api.lua for consistent error handling across the entire system."""
# Protocol errors (E001-E005)
INVALID_JSON = "E001"
MISSING_NAME = "E002"
MISSING_ARGUMENTS = "E003"
UNKNOWN_FUNCTION = "E004"
INVALID_ARGUMENTS = "E005"
# Network errors (E006-E008)
SOCKET_CREATE_FAILED = "E006"
SOCKET_BIND_FAILED = "E007"
CONNECTION_FAILED = "E008"
# Validation errors (E009-E012)
INVALID_GAME_STATE = "E009"
INVALID_PARAMETER = "E010"
PARAMETER_OUT_OF_RANGE = "E011"
MISSING_GAME_OBJECT = "E012"
# Game logic errors (E013-E016)
DECK_NOT_FOUND = "E013"
INVALID_CARD_INDEX = "E014"
NO_DISCARDS_LEFT = "E015"
INVALID_ACTION = "E016"
```
______________________________________________________________________
## Exceptions
### Connection and Socket Errors
#### `balatrobot.exceptions.SocketCreateFailedError`
Socket creation failed (E006).
#### `balatrobot.exceptions.SocketBindFailedError`
Socket bind failed (E007).
#### `balatrobot.exceptions.ConnectionFailedError`
Connection failed (E008).
### Game State and Logic Errors
#### `balatrobot.exceptions.InvalidGameStateError`
Invalid game state for requested action (E009).
#### `balatrobot.exceptions.InvalidActionError`
Invalid action for current context (E016).
#### `balatrobot.exceptions.DeckNotFoundError`
Deck not found (E013).
#### `balatrobot.exceptions.InvalidCardIndexError`
Invalid card index (E014).
#### `balatrobot.exceptions.NoDiscardsLeftError`
No discards remaining (E015).
### API and Parameter Errors
#### `balatrobot.exceptions.InvalidJSONError`
Invalid JSON in request (E001).
#### `balatrobot.exceptions.MissingNameError`
Message missing required 'name' field (E002).
#### `balatrobot.exceptions.MissingArgumentsError`
Message missing required 'arguments' field (E003).
#### `balatrobot.exceptions.UnknownFunctionError`
Unknown function name (E004).
#### `balatrobot.exceptions.InvalidArgumentsError`
Invalid arguments provided (E005).
#### `balatrobot.exceptions.InvalidParameterError`
Invalid or missing required parameter (E010).
#### `balatrobot.exceptions.ParameterOutOfRangeError`
Parameter value out of valid range (E011).
#### `balatrobot.exceptions.MissingGameObjectError`
Required game object missing (E012).
______________________________________________________________________
## Models
The BalatroBot API uses Pydantic models to provide type-safe data structures that exactly match the game's internal state representation. All models inherit from `BalatroBaseModel` which provides consistent validation and serialization.
#### Base Model
#### `balatrobot.models.BalatroBaseModel`
Base model for all BalatroBot API models.
### Request Models
These models define the structure for specific API requests:
#### `balatrobot.models.StartRunRequest`
Request model for starting a new run.
#### `balatrobot.models.BlindActionRequest`
Request model for skip or select blind actions.
#### `balatrobot.models.HandActionRequest`
Request model for playing hand or discarding cards.
#### `balatrobot.models.ShopActionRequest`
Request model for shop actions.
### Game State Models
The game state models provide comprehensive access to all Balatro game information, structured hierarchically to match the Lua API:
#### Root Game State
#### `balatrobot.models.G`
Root game state response matching G in Lua types.
##### `state_enum`
Get the state as an enum value.
##### `convert_empty_list_to_none_for_hand(v)`
Convert empty list to None for hand field.
#### Game Information
#### `balatrobot.models.GGame`
Game state matching GGame in Lua types.
##### `convert_empty_list_to_dict(v)`
Convert empty list to empty dict.
##### `convert_empty_list_to_none(v)`
Convert empty list to None for optional nested objects.
#### `balatrobot.models.GGameCurrentRound`
Current round info matching GGameCurrentRound in Lua types.
##### `convert_empty_list_to_dict(v)`
Convert empty list to empty dict.
#### `balatrobot.models.GGameLastBlind`
Last blind info matching GGameLastBlind in Lua types.
#### `balatrobot.models.GGamePreviousRound`
Previous round info matching GGamePreviousRound in Lua types.
#### `balatrobot.models.GGameProbabilities`
Game probabilities matching GGameProbabilities in Lua types.
#### `balatrobot.models.GGamePseudorandom`
Pseudorandom data matching GGamePseudorandom in Lua types.
#### `balatrobot.models.GGameRoundBonus`
Round bonus matching GGameRoundBonus in Lua types.
#### `balatrobot.models.GGameRoundScores`
Round scores matching GGameRoundScores in Lua types.
#### `balatrobot.models.GGameSelectedBack`
Selected deck info matching GGameSelectedBack in Lua types.
#### `balatrobot.models.GGameShop`
Shop configuration matching GGameShop in Lua types.
#### `balatrobot.models.GGameStartingParams`
Starting parameters matching GGameStartingParams in Lua types.
#### `balatrobot.models.GGameTags`
Game tags model matching GGameTags in Lua types.
#### Hand Management
#### `balatrobot.models.GHand`
Hand structure matching GHand in Lua types.
#### `balatrobot.models.GHandCards`
Hand card matching GHandCards in Lua types.
#### `balatrobot.models.GHandCardsBase`
Hand card base properties matching GHandCardsBase in Lua types.
##### `convert_int_to_string(v)`
Convert integer values to strings.
#### `balatrobot.models.GHandCardsConfig`
Hand card configuration matching GHandCardsConfig in Lua types.
#### `balatrobot.models.GHandCardsConfigCard`
Hand card config card data matching GHandCardsConfigCard in Lua types.
#### `balatrobot.models.GHandConfig`
Hand configuration matching GHandConfig in Lua types.
#### Joker Information
#### `balatrobot.models.GJokersCards`
Joker card matching GJokersCards in Lua types.
#### `balatrobot.models.GJokersCardsConfig`
Joker card configuration matching GJokersCardsConfig in Lua types.
### Communication Models
These models handle the communication protocol between your bot and the game:
#### `balatrobot.models.APIRequest`
Model for API requests sent to the game.
#### `balatrobot.models.APIResponse`
Model for API responses from the game.
#### `balatrobot.models.ErrorResponse`
Model for API error responses matching Lua ErrorResponse.
#### `balatrobot.models.JSONLLogEntry`
Model for JSONL log entries that record game actions.
## Usage Examples
For practical implementation examples:
- Follow the [Developing Bots](../developing-bots/) guide for complete bot setup
- Understand the underlying [Protocol API](../protocol-api/) for advanced usage
- Reference the [Installation](../installation/) guide for environment setup
# Contributing to BalatroBot
Welcome to BalatroBot! We're excited that you're interested in contributing to this Python framework and Lua mod for creating automated bots to play Balatro.
BalatroBot uses a dual-architecture approach with a Python framework that communicates with a Lua mod running inside Balatro via TCP sockets. This allows for real-time bot automation and game state analysis.
## Project Status & Priorities
We track all development work using the [BalatroBot GitHub Project](https://github.com/users/S1M0N38/projects/7). This is the best place to see current priorities, ongoing work, and opportunities for contribution.
## Getting Started
### Prerequisites
Before contributing, ensure you have:
- **Balatro**: Version 1.0.1o-FULL
- **SMODS (Steamodded)**: Version 1.0.0-beta-0711a or newer
- **Python**: 3.13+ (managed via uv)
- **uv**: Python package manager ([Installation Guide](https://docs.astral.sh/uv/))
- **OS**: macOS, Linux. Windows is not currently supported
- **[DebugPlus](https://github.com/WilsontheWolf/DebugPlus) (optional)**: useful for Lua API development and debugging
### Development Environment Setup
1. **Fork and Clone**
```bash
git clone https://github.com/YOUR_USERNAME/balatrobot.git
cd balatrobot
```
1. **Install Dependencies**
```bash
make install-dev
```
1. **Start Balatro with Mods**
```bash
./balatro.sh -p 12346
```
1. **Verify Balatro is Running**
```bash
# Check if Balatro is running
./balatro.sh --status
# Monitor startup logs
tail -n 100 logs/balatro_12346.log
```
Look for these success indicators:
- "BalatrobotAPI initialized"
- "BalatroBot loaded - version X.X.X"
- "TCP socket created on port 12346"
## How to Contribute
### Types of Contributions Welcome
- **Bug Fixes**: Issues tracked in our GitHub project
- **Feature Development**: New bot strategies, API enhancements
- **Performance Improvements**: Optimization of TCP communication or game interaction
- **Documentation**: Improvements to guides, API documentation, or examples
- **Testing**: Additional test coverage, edge case handling
### Contribution Workflow
1. **Check Issues First** (Highly Encouraged)
- Browse the [BalatroBot GitHub Project](https://github.com/users/S1M0N38/projects/7)
- Comment on issues you'd like to work on
- Create new issues for bugs or feature requests
1. **Fork & Branch**
```bash
git checkout -b feature/your-feature-name
```
1. **Make Changes**
- Follow our code style guidelines (see below)
- Add tests for new functionality
- Update documentation as needed
1. **Create Pull Request**
- **Important**: Enable "Allow edits from maintainers" when creating your PR
- Link to related issues
- Provide clear description of changes
- Include tests for new functionality
### Commit Messages
We highly encourage following [Conventional Commits](https://www.conventionalcommits.org/) format:
```text
feat(api): add new game state detection
fix(tcp): resolve connection timeout issues
docs(readme): update setup instructions
test(api): add shop booster validation tests
```
## Development & Testing
### Makefile Commands
BalatroBot includes a comprehensive Makefile that provides a convenient interface for all development tasks. Use `make help` to see all available commands:
```bash
# Show all available commands with descriptions
make help
```
#### Installation & Setup
```bash
make install # Install package dependencies
make install-dev # Install with development dependencies
```
#### Code Quality & Formatting
```bash
make lint # Run ruff linter (check only)
make lint-fix # Run ruff linter with auto-fixes
make format # Run ruff formatter and stylua
make format-md # Run markdown formatter
make typecheck # Run type checker
make quality # Run all code quality checks
make dev # Quick development check (format + lint + typecheck, no tests)
```
### Testing Requirements
#### Testing with Makefile
```bash
make test # Run tests with single instance (auto-starts if needed)
make test-parallel # Run tests on 4 instances (auto-starts if needed)
make test-teardown # Kill all Balatro instances
# Complete workflow including tests
make all # Run format + lint + typecheck + test
```
The testing system automatically handles Balatro instance management:
- **`make test`**: Runs tests with a single instance, auto-starting if needed
- **`make test-parallel`**: Runs tests on 4 instances for ~4x speedup, auto-starting if needed
- **`make test-teardown`**: Cleans up all instances when done
Both test commands keep instances running after completion for faster subsequent runs.
**Manual Setup for Advanced Testing:**
```bash
# Check/manage Balatro instances
./balatro.sh --status # Show running instances
./balatro.sh --kill # Kill all instances
# Start instances manually
./balatro.sh -p 12346 -p 12347 # Two instances
./balatro.sh --headless --fast -p 12346 -p 12347 -p 12348 -p 12349 # Full setup
# Manual parallel testing
pytest -n 4 --port 12346 --port 12347 --port 12348 --port 12349 tests/lua/
```
**Performance Modes:**
- **`--headless`**: No graphics, ideal for servers
- **`--fast`**: 10x speed, disabled effects, optimal for testing
### Documentation
```bash
make docs-serve # Serve documentation locally
make docs-build # Build documentation
make docs-clean # Clean built documentation
```
### Build & Maintenance
```bash
make build # Build package for distribution
make clean # Clean build artifacts and caches
```
## Technical Guidelines
### Python Development
- **Style**: Follow modern Python 3.13+ patterns
- **Type Hints**: Use pipe operator for unions (`str | int | None`)
- **Type Aliases**: Use `type` statement
- **Docstrings**: Google-style without type information (types in annotations)
- **Generics**: Modern syntax (`class Container[T]:`)
### Lua Development
- **Focus Area**: Primary development is on `src/lua/api.lua`
- **Communication**: TCP protocol on port 12346
- **Debugging**: Use DebugPlus mod for enhanced debugging capabilities
### Environment Variables
Configure BalatroBot behavior with these environment variables:
- **`BALATROBOT_HEADLESS=1`**: Disable graphics for server environments
- **`BALATROBOT_FAST=1`**: Enable 10x speed with disabled effects for testing
- **`BALATROBOT_PORT`**: TCP communication port (default: "12346")
## Communication & Community
### Preferred Channels
- **GitHub Issues**: Primary communication for bugs, features, and project coordination
- **Discord**: Join us at the [Balatro Discord](https://discord.com/channels/1116389027176787968/1391371948629426316) for real-time discussions
Happy contributing!
# Developing Bots
BalatroBot allows you to create automated players (bots) that can play Balatro by implementing decision-making logic in Python. Your bot communicates with the game through a TCP socket connection, sending actions to perform and receiving back the game state.
## Bot Architecture
A bot is a finite state machine that implements a sequence of actions to play the game. The bot can be in one state at a time and has access to a set of functions that can move the bot to other states.
| **State** | **Description** | **Functions** |
| ---------------- | -------------------------------------------- | ---------------------------------------- |
| `MENU` | The main menu | `start_run` |
| `BLIND_SELECT` | Selecting or skipping the blind | `skip_or_select_blind` |
| `SELECTING_HAND` | Selecting cards to play or discard | `play_hand_or_discard`, `rearrange_hand` |
| `ROUND_EVAL` | Evaluating the round outcome and cashing out | `cash_out` |
| `SHOP` | Buy items and move to the next round | `shop` |
| `GAME_OVER` | Game has ended | – |
Developing a bot boils down to providing the action name and its parameters for each state.
### State Diagram
The following diagram illustrates the possible states of the game and how the functions can be used to move the bot between them:
- Start (◉) and End (⦾) states
- States are written in uppercase (e.g., `MENU`, `BLIND_SELECT`, ...)
- Functions are written in lowercase (e.g., `start_run`, `skip_or_select_blind`, ...)
- Function parameters are written in italics (e.g., `action = play_hand`). Not all parameters are reported in the diagram.
- Comments are reported in parentheses (e.g., `(win round)`, `(lose round)`).
- Abstract groups are written with capital letters (e.g., `Run`, `Round`, ...)
```
stateDiagram-v2
direction TB
BLIND_SELECT_1:BLIND_SELECT
[*] --> MENU: go_to_menu
MENU --> BLIND_SELECT: start_run
state Run{
BLIND_SELECT --> skip_or_select_blind
skip_or_select_blind --> BLIND_SELECT: *action = skip*(small or big blind)
skip_or_select_blind --> SELECTING_HAND: *action = select*
state Round {
SELECTING_HAND --> play_hand_or_discard
play_hand_or_discard --> SELECTING_HAND: *action = play_hand*
play_hand_or_discard --> SELECTING_HAND: *action = discard*
play_hand_or_discard --> ROUND_EVAL: *action = play_hand*
(win round)
play_hand_or_discard --> GAME_OVER: *action = play_hand*
(lose round)
}
state RoundEval {
ROUND_EVAL --> SHOP: *cash_out*
}
state Shop {
SHOP --> shop
shop --> BLIND_SELECT_1: *action = next_round*
}
state GameOver {
GAME_OVER --> [*]
}
}
state skip_or_select_blind <>
state play_hand_or_discard <>
state shop <>
```
## Development Environment Setup
The BalatroBot project provides a complete development environment with all necessary tools and resources for developing bots.
### Environment Setup
Before developing or running bots, you need to set up the development environment by configuring the `.envrc` file:
```sh
cd %AppData%/Balatro/Mods/balatrobot
copy .envrc.example .envrc
.envrc
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro/Mods/balatrobot"
cp .envrc.example .envrc
source .envrc
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods/balatrobot
cp .envrc.example .envrc
source .envrc
```
Always Source Environment
Remember to source the `.envrc` file every time you start a new terminal session before developing or running bots. The environment variables are essential for proper bot functionality.
Automatic Environment Loading with direnv
For a better development experience, consider using [direnv](https://direnv.net/) to automatically load and unload environment variables when entering and leaving the project directory.
After installing direnv and hooking it into your shell:
```sh
# Allow direnv to load the .envrc file automatically
direnv allow .
```
This eliminates the need to manually source `.envrc` every time you work on the project.
### Bot File Location
When developing new bots, place your files in the `bots/` directory using one of these recommended patterns:
- **Single file bots**: `bots/my_new_bot.py`
- **Complex bots**: `bots/my_new_bot/main.py` (for bots with multiple modules)
## Next Steps
After setting up your development environment:
- Explore the [BalatroBot API](../balatrobot-api/) for detailed client and model documentation
- Learn about the underlying [Protocol API](../protocol-api/) for TCP communication details
# Installation Guide
This guide will walk you through installing and setting up BalatroBot.
## Prerequisites
Before installing BalatroBot, ensure you have:
- **[balatro](https://store.steampowered.com/app/2379780/Balatro/)**: Steam version (>= 1.0.1)
- **[git](https://git-scm.com/downloads)**: for cloning the repository
- **[uv](https://docs.astral.sh/uv/)**: for managing Python installations, environments, and dependencies
- **[lovely](https://github.com/ethangreen-dev/lovely-injector)**: for injecting Lua code into Balatro (>= 0.8.0)
- **[steamodded](https://github.com/Steamodded/smods)**: for loading and injecting mods (>= 1.0.0)
## Step 1: Install BalatroBot
BalatroBot is installed like any other Steamodded mod.
```sh
cd %AppData%/Balatro
mkdir -p Mods
cd Mods
git clone https://github.com/S1M0N38/balatrobot.git
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro"
mkdir -p Mods
cd Mods
git clone https://github.com/S1M0N38/balatrobot.git
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro
mkdir -p Mods
cd Mods
git clone https://github.com/S1M0N38/balatrobot.git
```
Tip
You can also clone the repository somewhere else and then provide a symlink to the `balatrobot` directory in the `Mods` directory.
```sh
# Clone repository to a custom location
cd C:\your\custom\path
git clone https://github.com/S1M0N38/balatrobot.git
# Create symlink in Mods directory
cd %AppData%/Balatro/Mods
mklink /D balatrobot C:\your\custom\path\balatrobot
```
```sh
# Clone repository to a custom location
cd /your/custom/path
git clone https://github.com/S1M0N38/balatrobot.git
# Create symlink in Mods directory
cd "/Users/$USER/Library/Application Support/Balatro/Mods"
ln -s /your/custom/path/balatrobot balatrobot
```
```sh
# Clone repository to a custom location
cd /your/custom/path
git clone https://github.com/S1M0N38/balatrobot.git
# Create symlink in Mods directory
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods
ln -s /your/custom/path/balatrobot balatrobot
```
Update BalatroBot
Updating BalatroBot is as simple as pulling the latest changes from the repository.
```sh
cd %AppData%/Balatro/Mods/balatrobot
git pull
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro/Mods/balatrobot"
git pull
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods/balatrobot
git pull
```
Uninstall BalatroBot
Simply delete the balatrobot mod directory.
```sh
cd %AppData%/Balatro/Mods
rmdir /S /Q balatrobot
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro/Mods"
rm -rf balatrobot
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods
rm -rf balatrobot
```
## Step 2: Set Up Python Environment
Uv takes care of managing Python installations, virtual environment creation, and dependency installation. To set up the Python environment for running BalatroBot bots, simply run:
```sh
cd %AppData%/Balatro/Mods/balatrobot
uv sync
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro/Mods/balatrobot"
uv sync
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods/balatrobot
uv sync
```
The same command can be used to update the Python environment and dependencies in the future.
Remove Python Environment
To uninstall the Python environment and dependencies, simply remove the `.venv` directory.
```sh
cd %AppData%/Balatro/Mods/balatrobot
rmdir /S /Q .venv
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro/Mods/balatrobot"
rm -rf .venv
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods/balatrobot
rm -rf .venv
```
## Step 3: Test Installation
### Launch Balatro with Mods
1. Start Balatro through Steam
1. In the main menu, click "Mods"
1. Verify "BalatroBot" appears in the mod list
1. Enable the mod if it's not already enabled and restart the game
macOS Steam Client Issue
On macOS, you cannot start Balatro through the Steam App due to a bug in the Steam client. Instead, you must use the `run_lovely_macos.sh` script.
```sh
cd "/Users/$USER/Library/Application Support/Steam/steamapps/common/Balatro"
./run_lovely_macos.sh
```
**First-time setup:** If this is your first time running the script, macOS Security & Privacy settings will prevent it from executing. Open **System Preferences** → **Security & Privacy** and click "Allow" when prompted, then run the script again.
### Quick Test with Example Bot
With Balatro running and the mod enabled, you can quickly test if everything is set up correctly using the provided example bot.
```sh
cd %AppData%/Balatro/Mods/balatrobot
uv run bots/example.py
```
```sh
cd "/Users/$USER/Library/Application Support/Balatro/Mods/balatrobot"
uv run bots/example.py
```
```sh
cd ~/.local/share/Steam/steamapps/compatdata/2379780/pfx/drive_c/users/steamuser/AppData/Roaming/Balatro/Mods/balatrobot
uv run bots/example.py
```
Tip
You can also navigate to the `balatrobot` directory, activate the Python environment and run the bot with `python bots/example.py` if you prefer. However, remember to always activate the virtual environment first.
The bot is working correctly if:
1. Game starts automatically
1. Cards are played/discarded automatically
1. Win the first blind
1. Game progresses through blinds
## Troubleshooting
If you encounter issues during installation or testing:
- **Discord Support**: Join our community at for real-time help
- **GitHub Issues**: Report bugs or request features by [opening an issue](https://github.com/S1M0N38/balatrobot/issues) on GitHub
______________________________________________________________________
*Once installation is complete, proceed to the [Developing Bots](../developing-bots/) to create your first bot!*
# Protocol API
This document provides the TCP API protocol reference for developers who want to interact directly with the BalatroBot game interface using raw socket connections.
## Protocol
The BalatroBot API establishes a TCP socket connection to communicate with the Balatro game through the BalatroBot Lua mod. The protocol uses a simple JSON request-response model for synchronous communication.
- **Host:** `127.0.0.1` (localhost)
- **Port:** `12346` (default)
- **Message Format:** JSON
### Communication Sequence
The typical interaction follows a game loop where clients continuously query the game state, analyze it, and send appropriate actions:
```
sequenceDiagram
participant Client
participant BalatroBot
loop Game Loop
Client->>BalatroBot: {"name": "get_game_state", "arguments": {}}
BalatroBot->>Client: {game state JSON}
Note over Client: Analyze game state and decide action
Client->>BalatroBot: {"name": "function_name", "arguments": {...}}
alt Valid Function Call
BalatroBot->>Client: {updated game state}
else Error
BalatroBot->>Client: {"error": "description", ...}
end
end
```
### Message Format
All communication uses JSON messages with a standardized structure. The protocol defines three main message types: function call requests, successful responses, and error responses.
**Request Format:**
```json
{
"name": "function_name",
"arguments": {
"param1": "value1",
"param2": ["array", "values"]
}
}
```
**Response Format:**
```json
{
"state": 7,
"game": { ... },
"hand": [ ... ],
"jokers": [ ... ]
}
```
**Error Response Format:**
```json
{
"error": "Error message description",
"error_code": "E001",
"state": 7,
"context": {
"additional": "error details"
}
}
```
## Game States
The BalatroBot API operates as a finite state machine that mirrors the natural flow of playing Balatro. Each state represents a distinct phase where specific actions are available.
### Overview
The game progresses through these states in a typical flow: `MENU` → `BLIND_SELECT` → `SELECTING_HAND` → `ROUND_EVAL` → `SHOP` → `BLIND_SELECT` (or `GAME_OVER`).
| State | Value | Description | Available Functions |
| ---------------- | ----- | ---------------------------- | ------------------------------------------------------------------------------------------- |
| `MENU` | 11 | Main menu screen | `start_run` |
| `BLIND_SELECT` | 7 | Selecting or skipping blinds | `skip_or_select_blind`, `sell_joker`, `sell_consumable`, `use_consumable` |
| `SELECTING_HAND` | 1 | Playing or discarding cards | `play_hand_or_discard`, `rearrange_hand`, `sell_joker`, `sell_consumable`, `use_consumable` |
| `ROUND_EVAL` | 8 | Round completion evaluation | `cash_out`, `sell_joker`, `sell_consumable`, `use_consumable` |
| `SHOP` | 5 | Shop interface | `shop`, `sell_joker`, `sell_consumable`, `use_consumable` |
| `GAME_OVER` | 4 | Game ended | `go_to_menu` |
### Validation
Functions can only be called when the game is in their corresponding valid states. The `get_game_state` function is available in all states.
Game State Reset
The `go_to_menu` function can be used in any state to reset a run. However, run resuming is not supported by BalatroBot. So performing a `go_to_menu` is effectively equivalent to resetting the run. This can be used to restart the game to a clean state.
## Game Functions
The BalatroBot API provides core functions that correspond to the main game actions. Each function is state-dependent and can only be called in the appropriate game state.
### Overview
| Name | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `get_game_state` | Retrieves the current complete game state |
| `go_to_menu` | Returns to the main menu from any game state |
| `start_run` | Starts a new game run with specified configuration |
| `skip_or_select_blind` | Handles blind selection - either select the current blind to play or skip it |
| `play_hand_or_discard` | Plays selected cards or discards them |
| `rearrange_hand` | Reorders the current hand according to the supplied index list |
| `rearrange_consumables` | Reorders the consumables according to the supplied index list |
| `cash_out` | Proceeds from round completion to the shop phase |
| `shop` | Performs shop actions: proceed to next round (`next_round`), purchase a card (`buy_card`), or reroll shop (`reroll`) |
| `sell_joker` | Sells a joker from the player's collection for money |
| `sell_consumable` | Sells a consumable from the player's collection for money |
| `use_consumable` | Uses a consumable card from the player's collection (Tarot, Planet, or Spectral cards) |
### Parameters
The following table details the parameters required for each function. Note that `get_game_state` and `go_to_menu` require no parameters:
| Name | Parameters |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `start_run` | `deck` (string): Deck name `stake` (number): Difficulty level 1-8 `seed` (string, optional): Seed for run generation `challenge` (string, optional): Challenge name `log_path` (string, optional): Full file path for run log (must include .jsonl extension) |
| `skip_or_select_blind` | `action` (string): Either "select" or "skip" |
| `play_hand_or_discard` | `action` (string): Either "play_hand" or "discard" `cards` (array): Card indices (0-indexed, 1-5 cards) |
| `rearrange_hand` | `cards` (array): Card indices (0-indexed, exactly `hand_size` elements) |
| `rearrange_consumables` | `consumables` (array): Consumable indices (0-indexed, exactly number of consumables in consumable area) |
| `shop` | `action` (string): Shop action ("next_round", "buy_card", "reroll", or **"redeem_voucher"**) `index` (number, required when `action` = "buy_card" or `action` = **"redeem_voucher"**): 0-based card index to purchase / redeem |
| `sell_joker` | `index` (number): 0-based index of the joker to sell from the player's joker collection |
| `sell_consumable` | `index` (number): 0-based index of the consumable to sell from the player's consumable collection |
| `use_consumable` | `index` (number): 0-based index of the consumable to use from the player's consumable collection |
### Shop Actions
The `shop` function supports multiple in-shop actions. Use the `action` field inside the `arguments` object to specify which of these to execute.
| Action | Description | Additional Parameters |
| ---------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `next_round` | Leave the shop and proceed to the next blind selection. | — |
| `buy_card` | Purchase the card at the supplied `index` in `shop_jokers`. | `index` *(number)* – 0-based position of the card to buy |
| `reroll` | Spend dollars to refresh the shop offer (cost shown in-game). | — |
| `redeem_voucher` | Redeem the voucher at the supplied `index` in `shop_vouchers`, applying its discount or effect. | `index` *(number)* – 0-based position of the voucher to redeem |
Future actions
Additional shop actions such as `buy_and_use_card` and `open_pack` are planned.
### Errors
All API functions validate their inputs and game state before execution. Error responses include an `error` message, standardized `error_code`, current `state` value, and optional `context` with additional details.
| Code | Category | Error |
| ------ | ---------- | ------------------------------------------ |
| `E001` | Protocol | Invalid JSON in request |
| `E002` | Protocol | Message missing required 'name' field |
| `E003` | Protocol | Message missing required 'arguments' field |
| `E004` | Protocol | Unknown function name |
| `E005` | Protocol | Arguments must be a table |
| `E006` | Network | Socket creation failed |
| `E007` | Network | Socket bind failed |
| `E008` | Network | Connection failed |
| `E009` | Validation | Invalid game state for requested action |
| `E010` | Validation | Invalid or missing required parameter |
| `E011` | Validation | Parameter value out of valid range |
| `E012` | Validation | Required game object missing |
| `E013` | Game Logic | Deck not found |
| `E014` | Game Logic | Invalid card index |
| `E015` | Game Logic | No discards remaining |
| `E016` | Game Logic | Invalid action for current context |
## Implementation
For higher-level integration:
- Use the [BalatroBot API](../balatrobot-api/) `BalatroClient` for managed connections
- See [Developing Bots](../developing-bots/) for complete bot implementation examples