balatrobot 1.3.0__tar.gz → 1.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.github/workflows/release_please.yml +0 -17
- balatrobot-1.3.3/.github/workflows/release_pypi.yml +53 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/CHANGELOG.md +22 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/PKG-INFO +1 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/balatrobot.json +1 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/pyproject.toml +1 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/__init__.py +1 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/buy.lua +9 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/next_round.lua +20 -6
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/pack.lua +27 -10
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/openrpc.json +1 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/fixtures/fixtures.json +146 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_buy.py +29 -1
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_pack.py +63 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/uv.lock +1 -1
- balatrobot-1.3.0/.github/workflows/release_pypi.yml +0 -28
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.claude/settings.json +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.editorconfig +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.github/workflows/code_quality.yml +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.github/workflows/commit_lint.yml +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.github/workflows/deploy_docs.yml +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.gitignore +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.mdformat.toml +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.mux/init +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.mux/mcp.jsonc +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.mux/tool_env +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.mux/tool_post +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/.python-version +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/CLAUDE.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/LICENSE +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/Makefile +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/README.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/balatrobot.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/api.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/assets/balatrobench.svg +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/assets/balatrobot-white.svg +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/assets/balatrobot.svg +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/assets/balatrollm.svg +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/cli.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/contributing.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/example-bot.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/index.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/docs/installation.md +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/mkdocs.yml +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/__main__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/cli.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/config.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/manager.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/platforms/__init__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/platforms/base.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/platforms/macos.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/platforms/native.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/balatrobot/platforms/windows.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/core/dispatcher.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/core/server.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/core/validator.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/add.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/cash_out.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/discard.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/gamestate.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/health.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/load.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/menu.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/play.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/rearrange.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/reroll.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/save.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/screenshot.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/select.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/sell.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/set.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/skip.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/start.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/tests/echo.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/tests/endpoint.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/tests/error.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/tests/state.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/tests/validation.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/endpoints/use.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/settings.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/debugger.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/enums.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/errors.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/gamestate.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/logger.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/src/lua/utils/types.lua +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/__init__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/cli/__init__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/cli/conftest.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/cli/test_config.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/cli/test_integration.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/cli/test_manager.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/cli/test_platforms.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/conftest.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/fixtures/generate.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/__init__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/conftest.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/core/__init__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/core/test_dispatcher.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/core/test_server.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/core/test_validator.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/__init__.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_add.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_cash_out.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_discard.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_gamestate.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_health.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_load.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_menu.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_next_round.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_play.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_rearrange.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_reroll.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_save.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_screenshot.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_select.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_sell.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_set.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_skip.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_start.py +0 -0
- {balatrobot-1.3.0 → balatrobot-1.3.3}/tests/lua/endpoints/test_use.py +0 -0
|
@@ -69,20 +69,3 @@ jobs:
|
|
|
69
69
|
else
|
|
70
70
|
echo "No changes to version files"
|
|
71
71
|
fi
|
|
72
|
-
- name: Notify consumer repo of new balatrobot release
|
|
73
|
-
if: ${{ steps.release.outputs.release_created }}
|
|
74
|
-
env:
|
|
75
|
-
BALATROLLM_REPO: "coder/balatrollm"
|
|
76
|
-
BALATROLLM_TOKEN: ${{ secrets.BALATROLLM_TOKEN }}
|
|
77
|
-
BALATRO_REPO: "S1M0N38/balatro"
|
|
78
|
-
BALATRO_TOKEN: ${{ secrets.BALATRO_TOKEN }}
|
|
79
|
-
run: |
|
|
80
|
-
VERSION="${{ steps.release.outputs.version }}"
|
|
81
|
-
curl -X POST -H "Accept: application/vnd.github+json" \
|
|
82
|
-
-H "Authorization: token $BALATROLLM_TOKEN" \
|
|
83
|
-
https://api.github.com/repos/$BALATROLLM_REPO/dispatches \
|
|
84
|
-
-d "{\"event_type\":\"balatrobot_release\",\"client_payload\":{\"version\":\"$VERSION\"}}"
|
|
85
|
-
curl -X POST -H "Accept: application/vnd.github+json" \
|
|
86
|
-
-H "Authorization: token $BALATRO_TOKEN" \
|
|
87
|
-
https://api.github.com/repos/$BALATRO_REPO/dispatches \
|
|
88
|
-
-d "{\"event_type\":\"balatrobot_release\",\"client_payload\":{\"version\":\"$VERSION\"}}"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Release PyPI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags:
|
|
5
|
+
- v*
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
jobs:
|
|
8
|
+
pypi:
|
|
9
|
+
name: Publish to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment:
|
|
12
|
+
name: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
contents: read
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v5
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v7
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version-file: ".python-version"
|
|
25
|
+
- name: Build
|
|
26
|
+
run: uv build
|
|
27
|
+
- name: Publish
|
|
28
|
+
run: uv publish
|
|
29
|
+
- name: Wait for PyPI availability and notify balatrollm
|
|
30
|
+
env:
|
|
31
|
+
BALATROLLM_TOKEN: ${{ secrets.BALATROLLM_TOKEN }}
|
|
32
|
+
run: |
|
|
33
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
34
|
+
echo "Waiting for version $VERSION to appear on PyPI..."
|
|
35
|
+
|
|
36
|
+
# Poll PyPI until version is available (max 5 minutes)
|
|
37
|
+
for i in {1..30}; do
|
|
38
|
+
PYPI_VERSION=$(curl -s https://pypi.org/pypi/balatrobot/json | jq -r .info.version)
|
|
39
|
+
if [ "$PYPI_VERSION" = "$VERSION" ]; then
|
|
40
|
+
echo "✓ Version $VERSION is available on PyPI"
|
|
41
|
+
curl -s -X POST -H "Accept: application/vnd.github+json" \
|
|
42
|
+
-H "Authorization: token $BALATROLLM_TOKEN" \
|
|
43
|
+
https://api.github.com/repos/coder/balatrollm/dispatches \
|
|
44
|
+
-d "{\"event_type\":\"balatrobot_release\",\"client_payload\":{\"version\":\"$VERSION\"}}"
|
|
45
|
+
echo "✓ Dispatched to balatrollm"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
echo "Attempt $i/30: PyPI shows $PYPI_VERSION, waiting for $VERSION..."
|
|
49
|
+
sleep 10
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
echo "ERROR: Version $VERSION not available on PyPI after 5 minutes"
|
|
53
|
+
exit 1
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.3](https://github.com/coder/balatrobot/compare/v1.3.2...v1.3.3) (2026-01-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Continuous Integration
|
|
7
|
+
|
|
8
|
+
* **release:** move release notification to pypi workflow ([2274781](https://github.com/coder/balatrobot/commit/2274781d66cdc32a09f618b5058542dc1e4dc2b4))
|
|
9
|
+
|
|
10
|
+
## [1.3.2](https://github.com/coder/balatrobot/compare/v1.3.1...v1.3.2) (2026-01-12)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **lua.endpoint:** add nil checks for race condition in `next_round` endpoint ([ce08dcc](https://github.com/coder/balatrobot/commit/ce08dcc5a13b102ecd0ac56631791903487f7fea))
|
|
16
|
+
* **lua.endpoints:** fix error message for invalid voucher/pack index in buy ([4af3d39](https://github.com/coder/balatrobot/commit/4af3d39c31a2575c7986c4cda81618ee5aa652fe))
|
|
17
|
+
|
|
18
|
+
## [1.3.1](https://github.com/coder/balatrobot/compare/v1.3.0...v1.3.1) (2026-01-11)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **lua.endpoints:** fix `pack` with mega packs with double target selection ([ba3e270](https://github.com/coder/balatrobot/commit/ba3e2700bb31ec90c16cbc1316ee2dc5771dbf7e))
|
|
24
|
+
|
|
3
25
|
## [1.3.0](https://github.com/coder/balatrobot/compare/v1.2.2...v1.3.0) (2026-01-06)
|
|
4
26
|
|
|
5
27
|
|
|
@@ -101,8 +101,16 @@ return {
|
|
|
101
101
|
|
|
102
102
|
-- Validate card index is in range
|
|
103
103
|
if not area.cards[pos] then
|
|
104
|
+
local msg
|
|
105
|
+
if args.card then
|
|
106
|
+
msg = "Card index out of range. Index: " .. args.card .. ", Available cards: " .. area.count
|
|
107
|
+
elseif args.voucher then
|
|
108
|
+
msg = "Voucher index out of range. Index: " .. args.voucher .. ", Available: " .. area.count
|
|
109
|
+
else
|
|
110
|
+
msg = "Pack index out of range. Index: " .. args.pack .. ", Available: " .. area.count
|
|
111
|
+
end
|
|
104
112
|
send_response({
|
|
105
|
-
message =
|
|
113
|
+
message = msg,
|
|
106
114
|
name = BB_ERROR_NAMES.BAD_REQUEST,
|
|
107
115
|
})
|
|
108
116
|
return
|
|
@@ -32,14 +32,28 @@ return {
|
|
|
32
32
|
trigger = "condition",
|
|
33
33
|
blocking = false,
|
|
34
34
|
func = function()
|
|
35
|
-
|
|
35
|
+
-- Wait for state transition and UI to be fully initialized
|
|
36
|
+
if G.STATE ~= G.STATES.BLIND_SELECT then
|
|
37
|
+
return false
|
|
38
|
+
end
|
|
39
|
+
if not G.blind_select_opts then
|
|
40
|
+
return false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local blind_key = string.lower(G.GAME.blind_on_deck)
|
|
44
|
+
local blind_pane = G.blind_select_opts[blind_key]
|
|
45
|
+
if not blind_pane then
|
|
46
|
+
return false
|
|
47
|
+
end
|
|
48
|
+
|
|
36
49
|
local select_button = blind_pane:get_UIE_by_ID("select_blind_button")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
sendDebugMessage("Return next_round() - reached BLIND_SELECT state", "BB.ENDPOINTS")
|
|
40
|
-
send_response(BB_GAMESTATE.get_gamestate())
|
|
50
|
+
if not select_button then
|
|
51
|
+
return false
|
|
41
52
|
end
|
|
42
|
-
|
|
53
|
+
|
|
54
|
+
sendDebugMessage("Return next_round() - reached BLIND_SELECT state", "BB.ENDPOINTS")
|
|
55
|
+
send_response(BB_GAMESTATE.get_gamestate())
|
|
56
|
+
return true
|
|
43
57
|
end,
|
|
44
58
|
}))
|
|
45
59
|
end,
|
|
@@ -196,12 +196,12 @@ return {
|
|
|
196
196
|
|
|
197
197
|
-- Highlight the target cards in hand
|
|
198
198
|
if args.targets and #args.targets > 0 then
|
|
199
|
-
-- Clear existing highlights
|
|
200
|
-
for
|
|
201
|
-
|
|
199
|
+
-- Clear existing highlights using proper CardArea method
|
|
200
|
+
for i = #G.hand.highlighted, 1, -1 do
|
|
201
|
+
G.hand:remove_from_highlighted(G.hand.highlighted[i], true)
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
-
-- Highlight target cards
|
|
204
|
+
-- Highlight target cards using proper CardArea method
|
|
205
205
|
for _, target_idx in ipairs(args.targets) do
|
|
206
206
|
local hand_pos = target_idx + 1 -- Convert 0-based to 1-based
|
|
207
207
|
if not G.hand.cards[hand_pos] then
|
|
@@ -211,8 +211,7 @@ return {
|
|
|
211
211
|
})
|
|
212
212
|
return true
|
|
213
213
|
end
|
|
214
|
-
G.hand.cards[hand_pos]
|
|
215
|
-
G.hand.highlighted[#G.hand.highlighted + 1] = G.hand.cards[hand_pos]
|
|
214
|
+
G.hand:add_to_highlighted(G.hand.cards[hand_pos], true)
|
|
216
215
|
end
|
|
217
216
|
end
|
|
218
217
|
end
|
|
@@ -320,17 +319,35 @@ return {
|
|
|
320
319
|
trigger = "condition",
|
|
321
320
|
blocking = false,
|
|
322
321
|
func = function()
|
|
323
|
-
--
|
|
324
|
-
|
|
322
|
+
-- Wait for state transition to complete (ensures hand is fully dealt)
|
|
323
|
+
if not G.STATE_COMPLETE then
|
|
324
|
+
return false
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
-- Calculate expected hand size for initial load
|
|
328
|
+
-- After cards are destroyed (mega packs), hand may have fewer cards
|
|
325
329
|
local hand_limit = G.hand.config and G.hand.config.card_limit or 8
|
|
326
330
|
local deck_size = G.deck and G.deck.config and G.deck.config.card_count or 52
|
|
327
331
|
local expected_hand_size = math.min(deck_size, hand_limit)
|
|
328
332
|
|
|
329
|
-
--
|
|
333
|
+
-- Calculate minimum required cards based on target indices
|
|
334
|
+
local min_required = 1
|
|
335
|
+
if args.targets and #args.targets > 0 then
|
|
336
|
+
for _, target_idx in ipairs(args.targets) do
|
|
337
|
+
local required = target_idx + 1 -- 0-based to 1-based
|
|
338
|
+
if required > min_required then
|
|
339
|
+
min_required = required
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
-- Wait for hand to be ready:
|
|
345
|
+
-- - At least expected_hand_size cards (initial load), OR
|
|
346
|
+
-- - At least min_required cards (for mega packs after cards destroyed)
|
|
330
347
|
local hand_ready = G.hand
|
|
331
348
|
and not G.hand.REMOVED
|
|
332
349
|
and G.hand.cards
|
|
333
|
-
and #G.hand.cards
|
|
350
|
+
and (#G.hand.cards >= expected_hand_size or #G.hand.cards >= min_required)
|
|
334
351
|
and G.hand.T -- Table area exists
|
|
335
352
|
and G.hand.T.x -- Positioned
|
|
336
353
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "BalatroBot API",
|
|
5
5
|
"description": "JSON-RPC 2.0 API for Balatro bot development. This API allows external clients to control the Balatro game, query game state, and execute actions through an HTTP server.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.3.2",
|
|
7
7
|
"license": {
|
|
8
8
|
"name": "MIT"
|
|
9
9
|
}
|
|
@@ -1612,6 +1612,152 @@
|
|
|
1612
1612
|
"pack": 1
|
|
1613
1613
|
}
|
|
1614
1614
|
}
|
|
1615
|
+
],
|
|
1616
|
+
"seed-VEBROR8--state-SMODS_BOOSTER_OPENED--pack.key-p_arcana_mega_1": [
|
|
1617
|
+
{
|
|
1618
|
+
"method": "menu",
|
|
1619
|
+
"params": {}
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
"method": "start",
|
|
1623
|
+
"params": {
|
|
1624
|
+
"deck": "RED",
|
|
1625
|
+
"stake": "WHITE",
|
|
1626
|
+
"seed": "VEBROR8"
|
|
1627
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
"method": "select",
|
|
1631
|
+
"params": {}
|
|
1632
|
+
},
|
|
1633
|
+
{
|
|
1634
|
+
"method": "set",
|
|
1635
|
+
"params": {
|
|
1636
|
+
"chips": 1000,
|
|
1637
|
+
"money": 100
|
|
1638
|
+
}
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
"method": "play",
|
|
1642
|
+
"params": {
|
|
1643
|
+
"cards": [
|
|
1644
|
+
0
|
|
1645
|
+
]
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
"method": "cash_out",
|
|
1650
|
+
"params": {}
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
"method": "buy",
|
|
1654
|
+
"params": {
|
|
1655
|
+
"pack": 0
|
|
1656
|
+
}
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
"method": "pack",
|
|
1660
|
+
"params": {
|
|
1661
|
+
"skip": true
|
|
1662
|
+
}
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
"method": "buy",
|
|
1666
|
+
"params": {
|
|
1667
|
+
"pack": 0
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
"method": "pack",
|
|
1672
|
+
"params": {
|
|
1673
|
+
"skip": true
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
"method": "add",
|
|
1678
|
+
"params": {
|
|
1679
|
+
"key": "p_arcana_mega_1"
|
|
1680
|
+
}
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
"method": "buy",
|
|
1684
|
+
"params": {
|
|
1685
|
+
"pack": 0
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
],
|
|
1689
|
+
"seed-7IDNRIV--state-SMODS_BOOSTER_OPENED--pack.cards[2].key-c_black_hole": [
|
|
1690
|
+
{
|
|
1691
|
+
"method": "menu",
|
|
1692
|
+
"params": {}
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
"method": "start",
|
|
1696
|
+
"params": {
|
|
1697
|
+
"deck": "RED",
|
|
1698
|
+
"stake": "WHITE",
|
|
1699
|
+
"seed": "7IDNRIV"
|
|
1700
|
+
}
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
"method": "select",
|
|
1704
|
+
"params": {}
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
"method": "set",
|
|
1708
|
+
"params": {
|
|
1709
|
+
"chips": 1000,
|
|
1710
|
+
"money": 100
|
|
1711
|
+
}
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
"method": "play",
|
|
1715
|
+
"params": {
|
|
1716
|
+
"cards": [
|
|
1717
|
+
0
|
|
1718
|
+
]
|
|
1719
|
+
}
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
"method": "cash_out",
|
|
1723
|
+
"params": {}
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
"method": "buy",
|
|
1727
|
+
"params": {
|
|
1728
|
+
"pack": 0
|
|
1729
|
+
}
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
"method": "pack",
|
|
1733
|
+
"params": {
|
|
1734
|
+
"skip": true
|
|
1735
|
+
}
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
"method": "buy",
|
|
1739
|
+
"params": {
|
|
1740
|
+
"pack": 0
|
|
1741
|
+
}
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
"method": "pack",
|
|
1745
|
+
"params": {
|
|
1746
|
+
"skip": true
|
|
1747
|
+
}
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
"method": "add",
|
|
1751
|
+
"params": {
|
|
1752
|
+
"key": "p_celestial_mega_1"
|
|
1753
|
+
}
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
"method": "buy",
|
|
1757
|
+
"params": {
|
|
1758
|
+
"pack": 0
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1615
1761
|
]
|
|
1616
1762
|
},
|
|
1617
1763
|
"sell": {
|
|
@@ -49,7 +49,7 @@ class TestBuyEndpoint:
|
|
|
49
49
|
"No jokers/consumables/cards in the shop. Reroll to restock the shop",
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
def
|
|
52
|
+
def test_buy_invalid_card_index(self, client: httpx.Client) -> None:
|
|
53
53
|
"""Test buy endpoint with invalid card index."""
|
|
54
54
|
gamestate = load_fixture(client, "buy", "state-SHOP--shop.cards[0].set-JOKER")
|
|
55
55
|
assert gamestate["state"] == "SHOP"
|
|
@@ -60,6 +60,34 @@ class TestBuyEndpoint:
|
|
|
60
60
|
"Card index out of range. Index: 999, Available cards: 2",
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
def test_buy_invalid_voucher_index(self, client: httpx.Client) -> None:
|
|
64
|
+
"""Test buy endpoint with invalid voucher index."""
|
|
65
|
+
gamestate = load_fixture(
|
|
66
|
+
client, "buy", "state-SHOP--voucher.cards[0].set-VOUCHER"
|
|
67
|
+
)
|
|
68
|
+
assert gamestate["state"] == "SHOP"
|
|
69
|
+
assert gamestate["vouchers"]["cards"][0]["set"] == "VOUCHER"
|
|
70
|
+
assert_error_response(
|
|
71
|
+
api(client, "buy", {"voucher": 999}),
|
|
72
|
+
"BAD_REQUEST",
|
|
73
|
+
"Voucher index out of range. Index: 999, Available: 1",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def test_buy_invalid_pack_index(self, client: httpx.Client) -> None:
|
|
77
|
+
"""Test buy endpoint with invalid pack index."""
|
|
78
|
+
gamestate = load_fixture(
|
|
79
|
+
client,
|
|
80
|
+
"buy",
|
|
81
|
+
"state-SHOP--packs.cards[0].label-Buffoon+Pack--packs.cards[1].label-Standard+Pack",
|
|
82
|
+
)
|
|
83
|
+
assert gamestate["state"] == "SHOP"
|
|
84
|
+
assert gamestate["packs"]["cards"][0]["label"] == "Buffoon Pack"
|
|
85
|
+
assert_error_response(
|
|
86
|
+
api(client, "buy", {"pack": 999}),
|
|
87
|
+
"BAD_REQUEST",
|
|
88
|
+
"Pack index out of range. Index: 999, Available: 2",
|
|
89
|
+
)
|
|
90
|
+
|
|
63
91
|
def test_buy_insufficient_funds(self, client: httpx.Client) -> None:
|
|
64
92
|
"""Test buy endpoint when player has insufficient funds."""
|
|
65
93
|
gamestate = load_fixture(client, "buy", "state-SHOP--money-0")
|
|
@@ -372,6 +372,37 @@ class TestPackEndpointSelection:
|
|
|
372
372
|
after = assert_gamestate_response(result)
|
|
373
373
|
assert before["jokers"]["count"] + 1 == after["jokers"]["count"]
|
|
374
374
|
|
|
375
|
+
def test_pack_celestial_black_hole(self, client: httpx.Client) -> None:
|
|
376
|
+
"""Test selecting Black Hole from a celestial mega pack levels up all hands.
|
|
377
|
+
|
|
378
|
+
Black Hole is a special planet card that levels up all poker hands by 1.
|
|
379
|
+
Mega packs allow 2 selections, so we also select a second planet card.
|
|
380
|
+
"""
|
|
381
|
+
load_fixture(
|
|
382
|
+
client,
|
|
383
|
+
"pack",
|
|
384
|
+
"seed-7IDNRIV--state-SMODS_BOOSTER_OPENED--pack.cards[2].key-c_black_hole",
|
|
385
|
+
)
|
|
386
|
+
before = api(client, "gamestate", {})["result"]
|
|
387
|
+
|
|
388
|
+
# First selection: Black Hole at index 2
|
|
389
|
+
result = api(client, "pack", {"card": 2})
|
|
390
|
+
after_first = assert_gamestate_response(result, state="SMODS_BOOSTER_OPENED")
|
|
391
|
+
|
|
392
|
+
# Black Hole levels up ALL hands by 1
|
|
393
|
+
for hand_name in before["hands"]:
|
|
394
|
+
assert (
|
|
395
|
+
after_first["hands"][hand_name]["level"]
|
|
396
|
+
== before["hands"][hand_name]["level"] + 1
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Second selection: any planet card at index 0
|
|
400
|
+
result = api(client, "pack", {"card": 0})
|
|
401
|
+
after_second = assert_gamestate_response(result, state="SHOP")
|
|
402
|
+
|
|
403
|
+
# Pack should be closed after second selection
|
|
404
|
+
assert "pack" not in after_second
|
|
405
|
+
|
|
375
406
|
|
|
376
407
|
# =============================================================================
|
|
377
408
|
# Mega Pack Multi-Selection Tests
|
|
@@ -401,6 +432,38 @@ class TestPackEndpointMegaPack:
|
|
|
401
432
|
gamestate = assert_gamestate_response(result, state="SHOP")
|
|
402
433
|
assert "pack" not in gamestate
|
|
403
434
|
|
|
435
|
+
def test_mega_pack_both_selections_with_targets(self, client: httpx.Client) -> None:
|
|
436
|
+
"""Test mega pack where both selections require targets.
|
|
437
|
+
|
|
438
|
+
Pack contents (seed VEBROR8):
|
|
439
|
+
[0] c_wheel_of_fortune
|
|
440
|
+
[1] c_sun
|
|
441
|
+
[2] c_star
|
|
442
|
+
[3] c_hanged_man - requires 2 targets (first selection)
|
|
443
|
+
[4] c_chariot - requires 1 target (second selection)
|
|
444
|
+
"""
|
|
445
|
+
load_fixture(
|
|
446
|
+
client,
|
|
447
|
+
"pack",
|
|
448
|
+
"seed-VEBROR8--state-SMODS_BOOSTER_OPENED--pack.key-p_arcana_mega_1",
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
result = api(client, "pack", {"card": 3, "targets": [0, 1]})
|
|
452
|
+
gamestate = assert_gamestate_response(result, state="SMODS_BOOSTER_OPENED")
|
|
453
|
+
|
|
454
|
+
# After first selection, pack should still be open (mega packs allow 2 selections)
|
|
455
|
+
# The Hanged Man was removed, so cards shifted:
|
|
456
|
+
# [0] c_wheel_of_fortune, [1] c_sun, [2] c_star, [3] c_chariot
|
|
457
|
+
assert "pack" in gamestate
|
|
458
|
+
assert len(gamestate["pack"]["cards"]) == 4
|
|
459
|
+
|
|
460
|
+
# Second selection: card index 3 is now c_chariot (requires 1 target)
|
|
461
|
+
result = api(client, "pack", {"card": 3, "targets": [0]})
|
|
462
|
+
gamestate = assert_gamestate_response(result, state="SHOP")
|
|
463
|
+
|
|
464
|
+
# After second selection, pack should be closed
|
|
465
|
+
assert "pack" not in gamestate
|
|
466
|
+
|
|
404
467
|
|
|
405
468
|
# =============================================================================
|
|
406
469
|
# Skip Tests
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
name: Release PyPI
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
tags:
|
|
5
|
-
- v*
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
jobs:
|
|
8
|
-
pypi:
|
|
9
|
-
name: Publish to PyPI
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
environment:
|
|
12
|
-
name: pypi
|
|
13
|
-
permissions:
|
|
14
|
-
id-token: write
|
|
15
|
-
contents: read
|
|
16
|
-
steps:
|
|
17
|
-
- name: Checkout
|
|
18
|
-
uses: actions/checkout@v5
|
|
19
|
-
- name: Install uv
|
|
20
|
-
uses: astral-sh/setup-uv@v7
|
|
21
|
-
- name: Set up Python
|
|
22
|
-
uses: actions/setup-python@v5
|
|
23
|
-
with:
|
|
24
|
-
python-version-file: ".python-version"
|
|
25
|
-
- name: Build
|
|
26
|
-
run: uv build
|
|
27
|
-
- name: Publish
|
|
28
|
-
run: uv publish
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|