Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
c501b89
chore; add .vscode/cursor settings for pytest
S1M0N38 Jul 8, 2025
11e0af4
chore: add pytest to dev deps
S1M0N38 Jul 8, 2025
548e2ff
test: add test for connection api and function api
S1M0N38 Jul 8, 2025
1d893b5
feat: init lua code socket server
S1M0N38 Jul 8, 2025
fb073e0
feat: update mod entry point to new lua code
S1M0N38 Jul 8, 2025
a11059c
chore: add old implementation to gitignore
S1M0N38 Jul 8, 2025
c9cbc9e
feat: implement game speed up
S1M0N38 Jul 8, 2025
e15d9cb
refactor(test): group test in class and setup/teardown methods
S1M0N38 Jul 8, 2025
6b113b0
feat: add blind_on_deck to game_state and ftm code
S1M0N38 Jul 9, 2025
6fece79
chore: add editorconfig for consistent formatting
S1M0N38 Jul 9, 2025
10add4d
style: format *.lua using editorconfig specs
S1M0N38 Jul 9, 2025
b1c84fe
test(api): add assert for skip blind test
S1M0N38 Jul 9, 2025
779d962
chore: add suggested extensions for vscode-like editors
S1M0N38 Jul 9, 2025
e8568b9
test: use "EXAMPLE" seed for all tests
S1M0N38 Jul 9, 2025
8008430
fix: key for G.GAME.skips
S1M0N38 Jul 9, 2025
fd5d9ad
fix: lua type for BalatrobotConfig
S1M0N38 Jul 9, 2025
fe3458c
feat(api): implement play_hand_or_discard action
S1M0N38 Jul 9, 2025
a917b1d
test(api): add tests for play_hand_or_discard action
S1M0N38 Jul 9, 2025
9cbefe6
chore: add simple script to gitignore
S1M0N38 Jul 9, 2025
8316588
chore(api): remove discard funciton
S1M0N38 Jul 9, 2025
fd7240a
feat(api): handle winning a round in play_hand_or_discard
S1M0N38 Jul 9, 2025
248eec8
test(api): add test for play winning hand
S1M0N38 Jul 9, 2025
7c68130
fix: reduce default mod dt to 4/60
S1M0N38 Jul 9, 2025
34ba7c8
feat(api): improve logging for function calls
S1M0N38 Jul 9, 2025
0acf22f
feat(api): game over and no discard left edge cases
S1M0N38 Jul 9, 2025
6177566
test(api): add test for game over and no discards left
S1M0N38 Jul 9, 2025
26c99f7
chore: remove unused import
S1M0N38 Jul 9, 2025
988fc4a
chore: add symbolic link for starting balatro to gitignore
S1M0N38 Jul 10, 2025
517f314
feat(api): add cashout API function
S1M0N38 Jul 10, 2025
d1fa001
test(api): add tests for cash_out API function
S1M0N38 Jul 10, 2025
8a07733
docs: add CLAUDE.md with development guidelines and commands
S1M0N38 Jul 10, 2025
5f6ce84
ci: add claude code hooks for formatters
S1M0N38 Jul 10, 2025
65c4605
ci: add stylua formatter to code_quality workflow
S1M0N38 Jul 10, 2025
5e1723f
docs: update CLAUDE.md with test prerequisites and workflow
S1M0N38 Jul 10, 2025
284b361
ci: use latest version of stylua action
S1M0N38 Jul 10, 2025
3039d63
refactor: remove unused arguments from functions
S1M0N38 Jul 10, 2025
20f82ff
refactor(tests): use helper function to assert error responses
S1M0N38 Jul 10, 2025
a5a5d8e
docs: remove redundant commands and refine existing ones
S1M0N38 Jul 10, 2025
9a9280f
refactor(api): use helper function for standard error responses
S1M0N38 Jul 10, 2025
fdded27
feat(api): validate state in the usage of API functions
S1M0N38 Jul 10, 2025
683ea07
test(api): add test for validation error responses
S1M0N38 Jul 10, 2025
fe64c88
docs: renamed log file and provide suggestion for Timeout errors
S1M0N38 Jul 10, 2025
a8135fe
style(test): sort imports
S1M0N38 Jul 10, 2025
4ab569a
feat(dev): add commit command with conventional commits spec
S1M0N38 Jul 10, 2025
1aaa7b7
docs(dev): refine commit command workflow instructions
S1M0N38 Jul 11, 2025
b61507f
docs(dev): clarify commit command formatting guidelines
S1M0N38 Jul 11, 2025
bd4f535
feat(dev): add test command and improve process detection
S1M0N38 Jul 11, 2025
3aab283
refactor(api): add comprehensive type definitions and documentation
S1M0N38 Jul 11, 2025
14c4f68
chore: add dump folder to gitignore
S1M0N38 Jul 11, 2025
b20da2c
feat(api): add shop action support with next_round functionality
S1M0N38 Jul 11, 2025
34ec39d
test(api): add comprehensive test suite for shop API endpoint
S1M0N38 Jul 11, 2025
e312881
docs(dev): update test suite metrics after shop API addition
S1M0N38 Jul 11, 2025
7588ae0
style(test): reformat multi-line function call to single line
S1M0N38 Jul 11, 2025
f2c5b12
chore(dev): add tests directory to ruff format command
S1M0N38 Jul 11, 2025
7d9d1d5
refactor(api): improve code quality and documentation
S1M0N38 Jul 11, 2025
10bc0ee
docs(dev): update commit command co-author handling
S1M0N38 Jul 11, 2025
e4891a7
refactor(api): move debug functionality to utils module
S1M0N38 Jul 11, 2025
bf6509e
style(api): update log tags from BALATROBOT to API
S1M0N38 Jul 11, 2025
7bd99e3
test(api): fix error context field name in test
S1M0N38 Jul 11, 2025
3b1ab6d
docs(dev): improve code documentation and comments
S1M0N38 Jul 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .claude/commands/commit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Generate a conventional commit message for the current staged changes.

Analyze the git diff of staged files and create a commit message following conventional commits specification:

**Format:** `<type>(<scope>): <description>`

**Types:**
- feat: new feature
- fix: bug fix
- docs: documentation
- style: formatting, missing semicolons, etc.
- refactor: code change that neither fixes a bug nor adds a feature
- test: adding or correcting tests
- chore: maintenance tasks
- ci: continuous integration changes
- revert: reverts a previous commit

**Scopes:**
- api: Lua API and Python API communication
- bot: Python bot framework and base classes
- examples: Example bots and usage samples
- dev: Development tools and environment

**Workflow:**
1. Run `git status` to see overall repository state. If there are are no staged changes, exit.
2. Run `git diff --staged` to analyze the actual changes
3. Run `git diff --stat --staged` for summary of changed files
4. Run `git log --oneline -10` to review recent commit patterns
5. Choose appropriate type and scope based on changes
6. Write concise description (50 chars max for first line)
7. Include body if changes are complex
8. Commit the staged changes with the generated message

**Co-authors**
Add the following co-authors: $ARGUMENTS
If the list is empty, do not add any co-authors.
Here is a list of co-authors (name, co-authored-by):
- claude: `Co-Authored-By: Claude <noreply@anthropic.com>`

**Notes**
- Do not include emojis in the commit message.
- Do not include `🤖 Generated with [Claude Code](https://claude.ai/code)` in the commit message.
3 changes: 3 additions & 0 deletions .claude/commands/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Run tests following the testing guidelines in CLAUDE.md.

See the Testing section in CLAUDE.md for complete instructions on prerequisites, workflow, and troubleshooting.
19 changes: 19 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "stylua src/lua"
},
{
"type": "command",
"command": "ruff format -s src/balatrobot tests/"
}
]
}
]
}
}
20 changes: 20 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.py]
indent_style = space
indent_size = 4
charset = utf-8

[*.lua]
indent_style = space
indent_size = 2
continuation_indent = 2
quote_style = double
max_line_length = 120
charset = utf-8
call_parentheses = true
11 changes: 11 additions & 0 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,14 @@ jobs:
run: |
source .venv/bin/activate
basedpyright
stylua:
name: StyLua
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Lua formatting
uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
args: --check src/lua
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ gamestate_cache
.envrc
.luarc.json
*.log
src/lua_old
balatrobot_old.lua
scripts
balatro.sh
dump
11 changes: 11 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"recommendations": [
"editorconfig.editorconfig",
"sumneko.lua",
"charliermarsh.ruff",
"detachhead.basedpyright",
"ms-python.vscode-pylance",
"ms-python.python",
"ms-python.debugpy"
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
125 changes: 125 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Development Commands

### Linting and Type Checking

```bash
# Run ruff linter and formatter
ruff check .
ruff format .

# Run type checker
basedpyright
```

### Testing

**IMPORTANT**: Tests require Balatro to be running in the background. Always start the game before running tests.

```bash
# Run all tests (requires Balatro to be running)
pytest

# Run specific test file
pytest tests/test_api_functions.py

# Run tests with verbose output
pytest -v
```

#### Test Prerequisites and Workflow

1. **Always start Balatro first**:

```bash
# Check if game is running
ps aux | grep -E "(Balatro\.app|balatro\.sh)" | grep -v grep

# Start if not running
./balatro.sh > balatro.log 2>&1 & sleep 10 && echo "Balatro started and ready"
```

2. **Monitor game startup**:

```bash
# Check logs for successful mod loading
tail -n 100 balatro.log

# Look for these success indicators:
# - "BalatrobotAPI initialized"
# - "BalatroBot loaded - version X.X.X"
# - "UDP socket created on port 12346"
```

3. **Common startup issues and fixes**:
- **Game crashes on mod load**: Review full log for Lua stack traces
- **Steam connection warnings**: Can be ignored - game works without Steam in development
- **JSON metadata errors**: Normal for development files (.vscode, .luarc.json) - can be ignored

4. **Test execution**:
- **Test suite**: 38 tests covering API functions and UDP communication
- **Execution time**: ~110 seconds (includes game state transitions)
- **Coverage**: API function calls, socket communication, error handling, edge cases

5. **Troubleshooting test failures**:
- **Connection timeouts**: Ensure UDP port 12346 is available
- **Game state errors**: Check if game is responsive and not crashed
- **Invalid responses**: Verify mod loaded correctly by checking logs
- **If test/s fail for timeout the reasons is that Balatro crash because there was an error in the Balatro mod (i.e. @balatrobot.lua and @src/lua/ ). The error should be logged in the `balatro.log` file.**

### Documentation

```bash
# Serve documentation locally
mkdocs serve

# Build documentation
mkdocs build
```

## Architecture Overview

BalatroBot is a Python framework for developing automated bots to play the card game Balatro. The architecture consists of three main layers:

### 1. Communication Layer (UDP Protocol)

- **Lua API** (`src/lua/api.lua`): Game-side mod that handles socket communication
- **UDP Socket Communication**: Real-time bidirectional communication between game and bot
- **Protocol**: Bot sends "HELLO" → Game responds with JSON state → Bot sends action strings

### 2. Python Framework Layer (`src/balatrobot/`)

**NOTE**: This is the old implementation that is being heavily refactored without backwards compatibility.
It will be drastically simplified in the future. For the moment I'm just focusing on the Lua API (`src/lua/api.lua`).
I keep the old code around for reference.

- **Bot Base Class** (`base.py`): Abstract base class defining the bot interface
- **ActionSchema**: TypedDict defining structured action format with `action` (enum) and `args` (list)
- **Enums** (`enums.py`): Game state enums (Actions, Decks, Stakes, State)
- **Socket Management**: Automatic reconnection, timeout handling, JSON parsing

## Development Standards

### Python Code Style (from `.cursor/rules/`)

- Use modern Python 3.12+ syntax with built-in collection types
- Type annotations with pipe operator for unions: `str | int | None`
- Use `type` statement for type aliases
- Google-style docstrings without type information (since type annotations are present)
- Modern generic class syntax: `class Container[T]:`

## Project Structure Context

- **Dual Implementation**: Both Python framework and Lua game mod
- **UDP Communication**: Port 12346 for real-time game interaction
- **MkDocs Documentation**: Comprehensive guides with Material theme
- **Pytest Testing**: UDP socket testing with fixtures
- **Development Tools**: Ruff, basedpyright, modern Python tooling

### Testing Best Practices

- **Always check that Balatro is running before running tests**
- After starting Balatro, check the `balatro.log` to confirm successful startup
44 changes: 13 additions & 31 deletions balatrobot.lua
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
---@meta balatrobot
---Main entry point for the BalatroBot mod

---@class BalatrobotConfig
---@field enabled boolean Disables ALL mod functionality if false
---@field port string Port for the bot to listen on
---@field dt number Tells the game that every update is dt seconds long
---@field uncap_fps boolean Whether to uncap the frame rate
---@field instant_move boolean Whether to enable instant card movement
---@field disable_vsync boolean Whether to disable vertical sync
---@field disable_card_eval_status_text boolean Whether to disable card evaluation status text (e.g. +10 when scoring a queen)
---@field frame_ratio integer Draw every nth frame, set to 1 for normal rendering

---Global configuration for the BalatroBot mod
---@type BalatrobotConfig
BALATRO_BOT_CONFIG = {
enabled = true,
port = "12346",
dt = 1.0 / 60.0,
uncap_fps = false,
instant_move = false,
disable_vsync = false,
disable_card_eval_status_text = true,
frame_ratio = 1,
port = "12346",
dt = 4.0 / 60.0, -- value >= 4.0 make mod instable
max_fps = 60,
vsync_enabled = false,

-- -- Default values for the original game
-- port = "12346",
-- dt = 1.0 / 60.0,
-- vsync_enabled = true,
-- max_fps = nil,
}

assert(SMODS.load_file("src/lua/list.lua"))()
assert(SMODS.load_file("src/lua/hook.lua"))()
-- Load minimal required files
assert(SMODS.load_file("src/lua/utils.lua"))()
assert(SMODS.load_file("src/lua/bot.lua"))()
assert(SMODS.load_file("src/lua/middleware.lua"))()
assert(SMODS.load_file("src/lua/api.lua"))()

-- Init middleware
Middleware.hookbalatro()
sendDebugMessage("Middleware loaded", "BALATROBOT")

-- Init API (includes queue initialization)
BalatrobotAPI.init()
sendDebugMessage("API loaded", "BALATROBOT")
-- Initialize API
API.init()

sendInfoMessage("BalatroBot loaded - version " .. SMODS.current_mod.version, "BALATROBOT")
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ lint.task-tags = ["FIX", "TODO", "HACK", "WARN", "PERF", "NOTE", "TEST"]
typeCheckingMode = "basic"

[dependency-groups]
dev = ["basedpyright>=1.29.5", "mkdocs-material>=9.6.15", "ruff>=0.12.2"]
dev = [
"basedpyright>=1.29.5",
"mkdocs-material>=9.6.15",
"pytest>=8.4.1",
"ruff>=0.12.2",
]
1 change: 0 additions & 1 deletion src/balatrobot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import logging
import sys
from pathlib import Path
from typing import Any


def setup_logging(
Expand Down
Loading