Skip to content

Commit 790c1ab

Browse files
committed
refactor(api): simplify BalatroClient to core TCP communication
- Remove high-level API methods (get_game_state, start_run, etc.) - Simplify constructor to use class attributes with defaults - Keep only core TCP methods: connect, disconnect, send_message - Remove model imports and game-specific abstractions
1 parent 4714656 commit 790c1ab

File tree

1 file changed

+27
-134
lines changed

1 file changed

+27
-134
lines changed

src/balatrobot/client.py

Lines changed: 27 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,36 @@
33
import json
44
import logging
55
import socket
6-
from typing import Any, Literal, Self
6+
from typing import Self
77

88
from .exceptions import (
99
BalatroError,
1010
ConnectionFailedError,
1111
create_exception_from_error_response,
1212
)
13-
from .models import (
14-
APIRequest,
15-
BlindActionRequest,
16-
GameState,
17-
HandActionRequest,
18-
ShopActionRequest,
19-
StartRunRequest,
20-
)
13+
from .models import APIRequest
2114

2215
logger = logging.getLogger(__name__)
2316

2417

2518
class BalatroClient:
26-
"""Client for communicating with the BalatroBot game API."""
27-
28-
def __init__(
29-
self,
30-
host: str = "127.0.0.1",
31-
port: int = 12346,
32-
timeout: float = 10.0,
33-
buffer_size: int = 65536,
34-
) -> None:
35-
"""Initialize the BalatroBot client.
36-
37-
Args:
38-
host: Host address to connect to
39-
port: Port number to connect to
40-
timeout: Socket timeout in seconds
41-
buffer_size: Socket buffer size in bytes
42-
"""
43-
self.host = host
44-
self.port = port
45-
self.timeout = timeout
46-
self.buffer_size = buffer_size
19+
"""Client for communicating with the BalatroBot game API.
20+
21+
Attributes:
22+
host: Host address to connect to
23+
port: Port number to connect to
24+
timeout: Socket timeout in seconds
25+
buffer_size: Socket buffer size in bytes
26+
_socket: Socket connection to BalatroBot
27+
"""
28+
29+
host = "127.0.0.1"
30+
port = 12346
31+
timeout = 10.0
32+
buffer_size = 65536
33+
34+
def __init__(self):
35+
"""Initialize BalatroBot client"""
4736
self._socket: socket.socket | None = None
4837
self._connected = False
4938

@@ -56,8 +45,12 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
5645
"""Exit context manager and disconnect from the game."""
5746
self.disconnect()
5847

59-
def connect(self) -> None:
60-
"""Connect to the BalatroBot game API."""
48+
def connect(self):
49+
"""Connect to Balatro TCP server
50+
51+
Raises:
52+
ConnectionFailedError: If not connected to the game
53+
"""
6154
if self._connected:
6255
return
6356

@@ -89,8 +82,8 @@ def disconnect(self) -> None:
8982
self._socket = None
9083
self._connected = False
9184

92-
def _send_request(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
93-
"""Send a request to the game API and return the response.
85+
def send_message(self, name: str, arguments: dict) -> dict:
86+
"""Send JSON message to Balatro and receive response
9487
9588
Args:
9689
name: Function name to call
@@ -148,103 +141,3 @@ def _send_request(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
148141
error_code="E001",
149142
context={"error": str(e)},
150143
) from e
151-
152-
def get_game_state(self) -> GameState:
153-
"""Get the current game state.
154-
155-
Returns:
156-
Current game state
157-
"""
158-
response = self._send_request("get_game_state", {})
159-
return GameState.model_validate(response)
160-
161-
def go_to_menu(self) -> GameState:
162-
"""Navigate to the main menu.
163-
164-
Returns:
165-
Game state after navigation
166-
"""
167-
response = self._send_request("go_to_menu", {})
168-
return GameState.model_validate(response)
169-
170-
def start_run(
171-
self,
172-
deck: str,
173-
stake: int = 1,
174-
seed: str | None = None,
175-
challenge: str | None = None,
176-
) -> GameState:
177-
"""Start a new game run.
178-
179-
Args:
180-
deck: Name of the deck to use
181-
stake: Stake level (1-8)
182-
seed: Optional seed for the run
183-
challenge: Optional challenge name
184-
185-
Returns:
186-
Game state after starting the run
187-
"""
188-
request = StartRunRequest(
189-
deck=deck,
190-
stake=stake,
191-
seed=seed,
192-
challenge=challenge,
193-
)
194-
if request.seed is None:
195-
logger.warning(
196-
"Seed not provided, using random seed. This run cannot be replayed."
197-
)
198-
response = self._send_request("start_run", request.model_dump())
199-
return GameState.model_validate(response)
200-
201-
def skip_or_select_blind(self, action: Literal["skip", "select"]) -> GameState:
202-
"""Skip or select the current blind.
203-
204-
Args:
205-
action: Either "skip" or "select"
206-
207-
Returns:
208-
Game state after the action
209-
"""
210-
request = BlindActionRequest(action=action)
211-
response = self._send_request("skip_or_select_blind", request.model_dump())
212-
return GameState.model_validate(response)
213-
214-
def play_hand_or_discard(
215-
self, action: Literal["play_hand", "discard"], cards: list[int]
216-
) -> GameState:
217-
"""Play selected cards or discard them.
218-
219-
Args:
220-
action: Either "play_hand" or "discard"
221-
cards: List of card indices (0-indexed)
222-
223-
Returns:
224-
Game state after the action
225-
"""
226-
request = HandActionRequest(action=action, cards=cards)
227-
response = self._send_request("play_hand_or_discard", request.model_dump())
228-
return GameState.model_validate(response)
229-
230-
def cash_out(self) -> GameState:
231-
"""Cash out from the current round to enter the shop.
232-
233-
Returns:
234-
Game state after cashing out
235-
"""
236-
response = self._send_request("cash_out", {})
237-
return GameState.model_validate(response)
238-
239-
def shop(self, action: Literal["next_round"]) -> GameState:
240-
"""Perform a shop action.
241-
242-
Args:
243-
action: Shop action to perform (currently only "next_round")
244-
245-
Returns:
246-
Game state after the action
247-
"""
248-
request = ShopActionRequest(action=action)
249-
response = self._send_request("shop", request.model_dump())
250-
return GameState.model_validate(response)

0 commit comments

Comments
 (0)