33import json
44import logging
55import socket
6- from typing import Any , Literal , Self
6+ from typing import Self
77
88from .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
2215logger = logging .getLogger (__name__ )
2316
2417
2518class 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