Skip to content

Commit c87b8d3

Browse files
committed
Python API MVP
1 parent f32bec6 commit c87b8d3

File tree

8 files changed

+565
-192
lines changed

8 files changed

+565
-192
lines changed

bot.py

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
import json
55
import socket
6+
import time
67
from enum import Enum
78

89
class State(Enum):
@@ -51,26 +52,32 @@ class Actions(Enum):
5152
class Bot:
5253

5354
def __init__(self, deck: str, stake: int = 1, seed: str = None, challenge: str = None):
55+
self.G = None
5456
self.deck = deck
5557
self.stake = stake
5658
self.seed = seed
5759
self.challenge = challenge
5860

5961
self.addr = ("127.0.0.1", 12345)
6062
self.running = False
63+
64+
self.state = { }
6165

6266
def skip_or_select_blind(self):
6367
raise NotImplementedError("Error: Bot.skip_or_select_blind must be implemented.")
6468

65-
def play_or_discard_hand(self):
66-
raise NotImplementedError("Error: Bot.play_or_discard_hand must be implemented.")
69+
def select_cards_from_hand(self):
70+
raise NotImplementedError("Error: Bot.select_cards_from_hand must be implemented.")
6771

6872
def select_shop_action(self):
6973
raise NotImplementedError("Error: Bot.select_shop_action must be implemented.")
7074

7175
def select_booster_action(self):
7276
raise NotImplementedError("Error: Bot.select_booster_action must be implemented.")
7377

78+
def sell_jokers(self):
79+
raise NotImplementedError("Error: Bot.sell_jokers must be implemented.")
80+
7481
def rearrange_jokers(self):
7582
raise NotImplementedError("Error: Bot.rearrange_jokers must be implemented.")
7683

@@ -84,45 +91,95 @@ def rearrange_hand(self):
8491
raise NotImplementedError("Error: Bot.rearrange_hand must be implemented.")
8592

8693
def sendcmd(self, cmd, **kwargs):
87-
msg = bytes(cmd.name, 'utf-8')
94+
msg = bytes(cmd, 'utf-8')
8895
self.sock.sendto(msg, self.addr)
8996

97+
def actionToCmd(self, action):
98+
result = [ ]
99+
100+
for x in action:
101+
if isinstance(x, Actions):
102+
result.append(x.name)
103+
elif type(x) is list:
104+
result.append(','.join([str(y) for y in x]))
105+
else:
106+
result.append(str(x))
107+
108+
return '|'.join(result)
109+
90110
def verifyimplemented(self):
91111
try:
92-
self.skip_or_select_blind()
93-
self.play_or_discard_hand()
94-
self.select_shop_action()
95-
self.select_booster_action()
96-
self.rearrange_jokers()
97-
self.use_or_sell_consumables()
98-
self.rearrange_consumables()
99-
self.rearrange_hand()
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, { })
100121
except NotImplementedError as e:
101122
print(e)
102123
sys.exit(0)
103124
except:
104125
pass
105126

127+
def chooseaction(self):
128+
if self.G['state'] == State.GAME_OVER:
129+
self.running = False
130+
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':
135+
return self.skip_or_select_blind(self, self.G)
136+
case 'select_cards_from_hand':
137+
return self.select_cards_from_hand(self, self.G)
138+
case 'select_shop_action':
139+
return self.select_shop_action(self, self.G)
140+
case 'select_booster_action':
141+
return self.select_booster_action(self, self.G)
142+
case 'sell_jokers':
143+
return self.sell_jokers(self, self.G)
144+
case 'rearrange_jokers':
145+
return self.rearrange_jokers(self, self.G)
146+
case 'use_or_sell_consumables':
147+
return self.use_or_sell_consumables(self, self.G)
148+
case 'rearrange_consumables':
149+
return self.rearrange_consumables(self, self.G)
150+
case 'rearrange_hand':
151+
return self.rearrange_hand(self, self.G)
152+
106153
def run(self):
107154
self.verifyimplemented()
155+
self.state = { }
108156

109-
running = True
157+
self.running = True
110158

111159
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
112160
self.sock.settimeout(1.0)
113161

114-
self.sendcmd(Actions.SEND_GAMESTATE)
162+
while self.running:
163+
self.sendcmd('HELLO')
115164

116-
while running:
165+
jsondata = { }
117166
try:
118167
data = self.sock.recv(4096)
119-
G = json.loads(data)
120-
self.G = G
121-
122-
if State(G.state) == State.GAME_OVER:
123-
running = False
124-
125-
except:
126-
print('Request Timed Out')
127-
128-
running = False
168+
jsondata = json.loads(data)
169+
170+
if 'response' in jsondata:
171+
print(jsondata['response'])
172+
else:
173+
self.G = jsondata
174+
if self.G['waitingForAction']:
175+
# Choose next action
176+
action = self.chooseaction()
177+
if action == None:
178+
raise ValueError("All actions must return a value!")
179+
180+
cmdstr = self.actionToCmd(action)
181+
print(f'CMD: {cmdstr}')
182+
self.sendcmd(cmdstr)
183+
except socket.error:
184+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
185+
self.sock.settimeout(1.0)

bot_example.py

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,68 @@
1-
from bot import Bot
1+
from bot import Bot, Actions
22

3-
if __name__=='__main__':
4-
mybot = Bot(deck ="Plasma Deck",
5-
stake = 1,
6-
seed = "1OGB5WO")
3+
def skip_or_select_blind(self, G):
4+
if G['ante']['blinds']['ondeck'] == "Small" or G['ante']['blinds']['ondeck'] == "Big":
5+
return [ Actions.SKIP_BLIND ]
6+
else:
7+
return [ Actions.SELECT_BLIND ]
8+
9+
def select_cards_from_hand(self, G):
10+
if 'hands_played' not in self.state:
11+
self.state['hands_played'] = 0
12+
13+
self.state['hands_played'] += 1
14+
15+
if self.state['hands_played'] == 1:
16+
return [ Actions.DISCARD_HAND, [ 2, 3, 6, 7 ] ]
17+
elif self.state['hands_played'] == 2:
18+
return [ Actions.PLAY_HAND, [ 1, 3, 4, 5, 8 ] ]
19+
20+
return [ Actions.PLAY_HAND, [ 1 ] ]
21+
22+
def select_shop_action(self, G):
23+
if 'num_shops' not in self.state:
24+
self.state['num_shops'] = 0
25+
26+
self.state['num_shops'] += 1
27+
28+
if self.state['num_shops'] == 1:
29+
return [ Actions.BUY_CARD, [ 2 ] ]
30+
elif self.state['num_shops'] == 5:
31+
return [ Actions.BUY_CARD, [ 2 ] ]
732

8-
def skip_or_select_blind(self):
9-
pass
33+
return [ Actions.END_SHOP ]
34+
35+
def select_booster_action(self, G):
36+
return [ Actions.SKIP_BOOSTER_PACK ]
1037

11-
def play_or_discard_hand(self):
12-
pass
38+
def sell_jokers(self, G):
39+
if len(G['jokers']) > 1:
40+
return [ Actions.SELL_JOKER, [ 2 ] ]
1341

14-
def select_shop_action(self):
15-
pass
42+
return [ Actions.SELL_JOKER, [] ]
1643

17-
def select_booster_action(self):
18-
pass
44+
def rearrange_jokers(self, G):
45+
return [ Actions.REARRANGE_JOKERS, [] ]
1946

20-
def rearrange_jokers(self):
21-
pass
47+
def use_or_sell_consumables(self, G):
48+
return [ Actions.USE_CONSUMABLE, [] ]
2249

23-
def use_or_sell_consumables(self):
24-
pass
50+
def rearrange_consumables(self, G):
51+
return [ Actions.REARRANGE_CONSUMABLES, [] ]
2552

26-
def rearrange_consumables(self):
27-
pass
53+
def rearrange_hand(self, G):
54+
return [ Actions.REARRANGE_HAND, [] ]
2855

29-
def rearrange_hand(self):
30-
pass
56+
if __name__=='__main__':
57+
mybot = Bot(deck ="Plasma Deck",
58+
stake = 1,
59+
seed = "1OGB5WO")
3160

3261
mybot.skip_or_select_blind = skip_or_select_blind
33-
mybot.play_or_discard_hand = play_or_discard_hand
62+
mybot.select_cards_from_hand = select_cards_from_hand
3463
mybot.select_shop_action = select_shop_action
3564
mybot.select_booster_action = select_booster_action
65+
mybot.sell_jokers = sell_jokers
3666
mybot.rearrange_jokers = rearrange_jokers
3767
mybot.use_or_sell_consumables = use_or_sell_consumables
3868
mybot.rearrange_consumables = rearrange_consumables

lib/hook.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function _clearfunc(obj, which, func_index)
109109
return obj
110110
end
111111

112-
local function ishooked(obj)
112+
function Hook.ishooked(obj)
113113
if type(obj) == 'table' and obj.__inithook then return true end
114114
return false
115115
end
@@ -131,7 +131,7 @@ function Hook.addonwrite(obj, func, ephemeral)
131131
end
132132

133133
function Hook.clear(obj)
134-
if ishooked(obj) then
134+
if Hook.ishooked(obj) then
135135
for i = 1, #Hook.FUNCTYPES, 1 do
136136
obj[Hook.FUNCTYPES[i]] = { }
137137
end

src/api.lua

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,29 @@ local data, msg_or_ip, port_or_nil
66
BalatrobotAPI = { }
77
BalatrobotAPI.socket = nil
88

9-
function BalatrobotAPI.notifyapiclient(...)
9+
BalatrobotAPI.waitingFor = nil
10+
BalatrobotAPI.waitingForAction = true
11+
12+
function BalatrobotAPI.notifyapiclient()
1013
-- TODO Generate gamestate json object
1114
local _gamestate = Utils.getGamestate()
15+
_gamestate.waitingFor = BalatrobotAPI.waitingFor
16+
sendDebugMessage('WaitingFor '..tostring(BalatrobotAPI.waitingFor))
17+
_gamestate.waitingForAction = BalatrobotAPI.waitingFor ~= nil and BalatrobotAPI.waitingForAction or false
1218
local _gamestateJsonString = json.encode(_gamestate)
1319

14-
if BalatrobotAPI.socket then
20+
if BalatrobotAPI.socket and port_or_nil ~= nil then
21+
sendDebugMessage(_gamestate.waitingFor)
1522
BalatrobotAPI.socket:sendto(string.format("%s", _gamestateJsonString), msg_or_ip, port_or_nil)
1623
end
1724
end
1825

1926
function BalatrobotAPI.respond(str)
20-
if BalatrobotAPI.socket then
27+
sendDebugMessage('respond')
28+
if BalatrobotAPI.socket and port_or_nil ~= nil then
29+
response = { }
30+
response.response = str
31+
str = json.encode(response)
2132
BalatrobotAPI.socket:sendto(string.format("%s\n", str), msg_or_ip, port_or_nil)
2233
end
2334
end
@@ -29,6 +40,7 @@ end
2940

3041
function BalatrobotAPI.update(dt)
3142
if not BalatrobotAPI.socket then
43+
sendDebugMessage('new socket')
3244
BalatrobotAPI.socket = socket.udp()
3345
BalatrobotAPI.socket:settimeout(0)
3446
BalatrobotAPI.socket:setsockname('*', 12345)
@@ -37,17 +49,20 @@ function BalatrobotAPI.update(dt)
3749
data, msg_or_ip, port_or_nil = BalatrobotAPI.socket:receivefrom()
3850
if data then
3951

40-
if data == 'SEND_GAMESTATE' then
41-
sendDebugMessage("SEND_GAMESTATE")
52+
if data == 'HELLO\n' or data == 'HELLO' then
4253
BalatrobotAPI.notifyapiclient()
4354
else
4455
local _action = Utils.parseaction(data)
56+
local _err = Utils.validateAction(_action)
4557

46-
if _action and #_action > 1 and #_action > Bot.ACTIONPARAMS[_action[1]].num_args then
47-
BalatrobotAPI.respond("Error: Incorrect number of params for action " .. action)
48-
elseif not _action then
58+
if _err == Utils.ERROR.NUMPARAMS then
59+
BalatrobotAPI.respond("Error: Incorrect number of params for action " .. _action[1])
60+
elseif _err == Utils.ERROR.MSGFORMAT then
4961
BalatrobotAPI.respond("Error: Incorrect message format. Should be ACTION|arg1|arg2")
62+
elseif _err == Utils.ERROR.INVALIDACTION then
63+
BalatrobotAPI.respond("Error: Action invalid for action " .. _action[1])
5064
else
65+
BalatrobotAPI.waitingForAction = false
5166
BalatrobotAPI.queueaction(_action)
5267
end
5368
end
@@ -63,12 +78,49 @@ function BalatrobotAPI.init()
6378

6479
love.update = Hook.addcallback(love.update, BalatrobotAPI.update)
6580

81+
sendDebugMessage('init api')
6682
if Bot.SETTINGS.api == true then
67-
for k,v in pairs(Bot) do
68-
if type(Bot[k]) == 'function' then
69-
Bot[k] = Hook.addbreakpoint(Bot[k], BalatrobotAPI.notifyapiclient)
70-
end
71-
end
83+
Middleware.c_play_hand = Hook.addbreakpoint(Middleware.c_play_hand, function()
84+
BalatrobotAPI.waitingFor = 'select_cards_from_hand'
85+
BalatrobotAPI.waitingForAction = true
86+
end)
87+
Middleware.c_select_blind = Hook.addbreakpoint(Middleware.c_select_blind, function()
88+
BalatrobotAPI.waitingFor = 'skip_or_select_blind'
89+
BalatrobotAPI.waitingForAction = true
90+
end)
91+
Middleware.c_choose_booster_cards = Hook.addbreakpoint(Middleware.c_choose_booster_cards, function()
92+
BalatrobotAPI.waitingFor = 'select_booster_action'
93+
BalatrobotAPI.waitingForAction = true
94+
end)
95+
Middleware.c_shop = Hook.addbreakpoint(Middleware.c_shop, function()
96+
sendDebugMessage('SELECT SHOP ACTION')
97+
BalatrobotAPI.waitingFor = 'select_shop_action'
98+
BalatrobotAPI.waitingForAction = true
99+
end)
100+
Middleware.c_rearrange_hand = Hook.addbreakpoint(Middleware.c_rearrange_hand, function()
101+
BalatrobotAPI.waitingFor = 'rearrange_hand'
102+
BalatrobotAPI.waitingForAction = true
103+
end)
104+
Middleware.c_rearrange_consumables = Hook.addbreakpoint(Middleware.c_rearrange_consumables, function()
105+
BalatrobotAPI.waitingFor = 'rearrange_consumables'
106+
BalatrobotAPI.waitingForAction = true
107+
end)
108+
Middleware.c_use_or_sell_consumables = Hook.addbreakpoint(Middleware.c_use_or_sell_consumables, function()
109+
BalatrobotAPI.waitingFor = 'use_or_sell_consumables'
110+
BalatrobotAPI.waitingForAction = true
111+
end)
112+
Middleware.c_rearrange_jokers = Hook.addbreakpoint(Middleware.c_rearrange_jokers, function()
113+
BalatrobotAPI.waitingFor = 'rearrange_jokers'
114+
BalatrobotAPI.waitingForAction = true
115+
end)
116+
Middleware.c_sell_jokers = Hook.addbreakpoint(Middleware.c_sell_jokers, function()
117+
BalatrobotAPI.waitingFor = 'sell_jokers'
118+
BalatrobotAPI.waitingForAction = true
119+
end)
120+
Middleware.c_start_run = Hook.addbreakpoint(Middleware.c_start_run, function()
121+
BalatrobotAPI.waitingFor = 'start_run'
122+
BalatrobotAPI.waitingForAction = true
123+
end)
72124
end
73125
end
74126

0 commit comments

Comments
 (0)