55import socket
66import time
77from enum import Enum
8+ from gamestates import cache_state
9+ import subprocess
10+ import random
11+
812
913class State (Enum ):
1014 SELECTING_HAND = 1
@@ -27,6 +31,7 @@ class State(Enum):
2731 BUFFOON_PACK = 18
2832 NEW_ROUND = 19
2933
34+
3035class Actions (Enum ):
3136 SELECT_BLIND = 1
3237 SKIP_BLIND = 2
@@ -49,137 +54,192 @@ class Actions(Enum):
4954 START_RUN = 19
5055 SEND_GAMESTATE = 20
5156
52- class Bot :
5357
54- def __init__ (self , deck : str , stake : int = 1 , seed : str = None , challenge : str = None ):
58+ class Bot :
59+ def __init__ (
60+ self ,
61+ deck : str ,
62+ stake : int = 1 ,
63+ seed : str = None ,
64+ challenge : str = None ,
65+ bot_port : int = 12346 ,
66+ ):
5567 self .G = None
5668 self .deck = deck
5769 self .stake = stake
5870 self .seed = seed
5971 self .challenge = challenge
6072
61- self .addr = ("127.0.0.1" , 12345 )
73+ self .bot_port = bot_port
74+
75+ self .addr = ("localhost" , self .bot_port )
6276 self .running = False
77+ self .balatro_instance = None
78+
79+ self .sock = None
80+
81+ self .state = {}
6382
64- self .state = { }
65-
6683 def skip_or_select_blind (self ):
67- raise NotImplementedError ("Error: Bot.skip_or_select_blind must be implemented." )
84+ raise NotImplementedError (
85+ "Error: Bot.skip_or_select_blind must be implemented."
86+ )
6887
6988 def select_cards_from_hand (self ):
70- raise NotImplementedError ("Error: Bot.select_cards_from_hand must be implemented." )
89+ raise NotImplementedError (
90+ "Error: Bot.select_cards_from_hand must be implemented."
91+ )
7192
7293 def select_shop_action (self ):
7394 raise NotImplementedError ("Error: Bot.select_shop_action must be implemented." )
74-
95+
7596 def select_booster_action (self ):
76- raise NotImplementedError ("Error: Bot.select_booster_action must be implemented." )
77-
97+ raise NotImplementedError (
98+ "Error: Bot.select_booster_action must be implemented."
99+ )
100+
78101 def sell_jokers (self ):
79102 raise NotImplementedError ("Error: Bot.sell_jokers must be implemented." )
80103
81104 def rearrange_jokers (self ):
82105 raise NotImplementedError ("Error: Bot.rearrange_jokers must be implemented." )
83-
106+
84107 def use_or_sell_consumables (self ):
85- raise NotImplementedError ("Error: Bot.use_or_sell_consumables must be implemented." )
86-
108+ raise NotImplementedError (
109+ "Error: Bot.use_or_sell_consumables must be implemented."
110+ )
111+
87112 def rearrange_consumables (self ):
88- raise NotImplementedError ("Error: Bot.rearrange_consumables must be implemented." )
89-
113+ raise NotImplementedError (
114+ "Error: Bot.rearrange_consumables must be implemented."
115+ )
116+
90117 def rearrange_hand (self ):
91118 raise NotImplementedError ("Error: Bot.rearrange_hand must be implemented." )
92119
120+ def start_balatro_instance (self ):
121+ balatro_exec_path = (
122+ r"C:\Program Files (x86)\Steam\steamapps\common\Balatro\Balatro.exe"
123+ )
124+ self .balatro_instance = subprocess .Popen (
125+ [balatro_exec_path , str (self .bot_port )]
126+ )
127+
128+ def stop_balatro_instance (self ):
129+ if self .balatro_instance :
130+ self .balatro_instance .kill ()
131+
93132 def sendcmd (self , cmd , ** kwargs ):
94- msg = bytes (cmd , ' utf-8' )
133+ msg = bytes (cmd , " utf-8" )
95134 self .sock .sendto (msg , self .addr )
96135
97136 def actionToCmd (self , action ):
98- result = [ ]
137+ result = []
99138
100139 for x in action :
101140 if isinstance (x , Actions ):
102141 result .append (x .name )
103142 elif type (x ) is list :
104- result .append (',' .join ([str (y ) for y in x ]))
143+ result .append ("," .join ([str (y ) for y in x ]))
105144 else :
106- result .append (str (x ))
145+ result .append (str (x ))
107146
108- return '|' .join (result )
147+ return "|" .join (result )
109148
110149 def verifyimplemented (self ):
111150 try :
112- self .skip_or_select_blind (self , { })
113- self .select_cards_from_hand (self , { })
114- self .select_shop_action (self , { })
115- self .select_booster_action (self , { })
116- self .sell_jokers (self , { })
117- self .rearrange_jokers (self , { })
118- self .use_or_sell_consumables (self , { })
119- self .rearrange_consumables (self , { })
120- self .rearrange_hand (self , { })
151+ self .skip_or_select_blind (self , {})
152+ self .select_cards_from_hand (self , {})
153+ self .select_shop_action (self , {})
154+ self .select_booster_action (self , {})
155+ self .sell_jokers (self , {})
156+ self .rearrange_jokers (self , {})
157+ self .use_or_sell_consumables (self , {})
158+ self .rearrange_consumables (self , {})
159+ self .rearrange_hand (self , {})
121160 except NotImplementedError as e :
122161 print (e )
123162 sys .exit (0 )
124163 except :
125164 pass
126165
166+ def random_seed (self ):
167+ # e.g. 1OGB5WO
168+ return "" .join (random .choices ("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" , k = 7 ))
169+
127170 def chooseaction (self ):
128- if self .G [' state' ] == State .GAME_OVER :
171+ if self .G [" state" ] == State .GAME_OVER :
129172 self .running = False
130173
131- match self .G ['waitingFor' ]:
132- case 'start_run' :
133- return [ Actions .START_RUN , self .stake , self .deck , self .seed , self .challenge ]
134- case 'skip_or_select_blind' :
174+ match self .G ["waitingFor" ]:
175+ case "start_run" :
176+ seed = self .seed
177+ if seed is None :
178+ seed = self .random_seed ()
179+ return [
180+ Actions .START_RUN ,
181+ self .stake ,
182+ self .deck ,
183+ seed ,
184+ self .challenge ,
185+ ]
186+ case "skip_or_select_blind" :
135187 return self .skip_or_select_blind (self , self .G )
136- case ' select_cards_from_hand' :
188+ case " select_cards_from_hand" :
137189 return self .select_cards_from_hand (self , self .G )
138- case ' select_shop_action' :
190+ case " select_shop_action" :
139191 return self .select_shop_action (self , self .G )
140- case ' select_booster_action' :
192+ case " select_booster_action" :
141193 return self .select_booster_action (self , self .G )
142- case ' sell_jokers' :
194+ case " sell_jokers" :
143195 return self .sell_jokers (self , self .G )
144- case ' rearrange_jokers' :
196+ case " rearrange_jokers" :
145197 return self .rearrange_jokers (self , self .G )
146- case ' use_or_sell_consumables' :
198+ case " use_or_sell_consumables" :
147199 return self .use_or_sell_consumables (self , self .G )
148- case ' rearrange_consumables' :
200+ case " rearrange_consumables" :
149201 return self .rearrange_consumables (self , self .G )
150- case ' rearrange_hand' :
202+ case " rearrange_hand" :
151203 return self .rearrange_hand (self , self .G )
152204
153- def run (self ):
154- self .verifyimplemented ()
155- self .state = { }
205+ def run_step (self ):
206+ if self .sock is None :
207+ self .verifyimplemented ()
208+ self .state = {}
209+ self .G = None
156210
157- self .running = True
211+ self .running = True
212+ self .sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
213+ self .sock .settimeout (1 )
214+ self .sock .connect (self .addr )
158215
159- self .sock = socket . socket ( socket . AF_INET , socket . SOCK_DGRAM )
160- self .sock . settimeout ( 1.0 )
216+ if self .running :
217+ self .sendcmd ( "HELLO" )
161218
162- while self .running :
163- self .sendcmd ('HELLO' )
164-
165- jsondata = { }
219+ jsondata = {}
166220 try :
167- data = self .sock .recv (4096 )
221+ data = self .sock .recv (65536 )
168222 jsondata = json .loads (data )
169223
170- if ' response' in jsondata :
171- print (jsondata [' response' ])
224+ if " response" in jsondata :
225+ print (jsondata [" response" ])
172226 else :
173227 self .G = jsondata
174- if self .G [' waitingForAction' ]:
175- # Choose next action
228+ if self .G [" waitingForAction" ]:
229+ cache_state ( self . G [ "waitingFor" ], self . G )
176230 action = self .chooseaction ()
177231 if action == None :
178232 raise ValueError ("All actions must return a value!" )
179-
233+
180234 cmdstr = self .actionToCmd (action )
181- print (f'CMD: { cmdstr } ' )
182235 self .sendcmd (cmdstr )
183- except socket .error :
236+ except socket .error as e :
237+ print (e )
238+ print ("Socket error, reconnecting..." )
184239 self .sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
185- self .sock .settimeout (1.0 )
240+ self .sock .settimeout (1 )
241+ self .sock .connect (self .addr )
242+
243+ def run (self ):
244+ while self .running :
245+ self .run_step ()
0 commit comments