Skip to content

Commit eba2e32

Browse files
authored
Merge pull request #84 from jjfiv/gridworld
Gridworld and arbitrary-sized envs (#82)
2 parents d94d1cc + 84a2f41 commit eba2e32

File tree

9 files changed

+358
-43
lines changed

9 files changed

+358
-43
lines changed

ctoybox/toybox/toybox/interventions/amidar.py

Lines changed: 287 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from toybox.interventions.base import *
2+
import random
23
import json
34
"""An API for interventions on Amidar."""
45

@@ -8,14 +9,25 @@ def __init__(self, tb, game_name='amidar'):
89
# check that the simulation in tb matches the game name.
910
Intervention.__init__(self, tb, game_name)
1011

12+
def check_position(pdict, ls):
13+
# check that pdict is a dictionary containing the keys in list ls
14+
assert isinstance(pdict, dict)
15+
assert all([k in pdict.keys() for k in ls])
16+
17+
def check_tile_position(tdict):
18+
assert check_position(tdict, ['tx', 'ty'])
19+
# check that the tile is a non-empty tile (i.e., is paintable and walkable)
1120

1221
def num_tiles_unpainted(self):
13-
pass
14-
#return self.query_json('num_tiles_unpainted')
22+
total_unpainted = 0
23+
for i in range(len(self.state['board'])):
24+
total_unpainted += sum([tile == "Unpainted" for tile in self.state['board'][i]])
25+
26+
return total_unpainted
27+
1528

1629
def player_tile(self):
17-
pass
18-
#return self.query_json('player_tile')
30+
return self.state['player']['position']
1931

2032
def num_enemies(self):
2133
return len(self.state['enemies'])
@@ -24,45 +36,290 @@ def jumps_remaining(self):
2436
return self.state['jumps']
2537

2638
def regular_mode(self):
27-
pass
28-
#return self.query_json('regular_mode')
39+
return self.state['jump_timer'] == 0 and self.state['chase_timer'] == 0
2940

3041
def jump_mode(self):
31-
pass
32-
#return self.query_json('jump_mode')
42+
return self.state['jump_timer'] > 0
3343

3444
def chase_mode(self):
35-
pass
36-
#return self.query_json('chase_mode')
45+
return self.state['chase_timer'] > 0
3746

3847
def enemy_tiles(self):
39-
pass
40-
#return self.query_json('enemy_tiles')
48+
return [self.state['enemies'][i]['position'] for i in range(len(self.state['enemies']))]
4149

4250
def enemy_caught(self, eid):
43-
pass
44-
#return self.query_json('enemy_caught', eid)
51+
return self.state['enemies'][eid]['caught']
4552

4653
def any_enemy_caught(self, eid):
47-
pass
48-
#num_enemies = self.amidar_num_enemies()
49-
#return any(self.amidar_enemy_caught(eid) for eid in range(num_enemies))
50-
51-
# paint/unpaint tiles
52-
# paint/unpaint rectangles
53-
# consider logic for tiles, rectangles both filled
54-
# move player to tile, (x,y)
55-
# move enemy(ies) to tile, (x,y)
56-
# begin jump mode
57-
# begin chase mode
58-
# begin regular mode
59-
# set, return enemy protocol
54+
return any([self.state['enemies'][i]['caught'] for i in range(len(self.state['enemies']))])
55+
56+
57+
def set_tile(self, tid, paint=True):
58+
tiles = self.state['board']['tiles']
59+
assert tiles[tid['tx']][tid['ty']] != "Empty"
60+
assert check_position(tid, ['tx', 'ty'])
61+
62+
label = "Painted" if paint else "Unpainted"
63+
self.state['board']['tiles'][tid['tx']][tid['ty']] = label
64+
65+
66+
def set_box(self, bid, paint=True, include_tiles=True, allow_chase=True):
67+
box = self.state['board']['boxes'][bid]
68+
box['painted'] = paint
69+
70+
if allow_chase:
71+
# if we allow the intervention to trigger chasing,
72+
# we only want to do so if it was not already triggered
73+
allow_chase = allow_chase and not self.check_chase_condition()
74+
75+
if include_tiles:
76+
tx_left = box['top_left']['tx']
77+
ty_left = box['top_left']['ty']
78+
tx_right = box['bottom_right']['tx']
79+
ty_right = box['bottom_right']['ty']
80+
81+
for i in range(tx_left, tx_right+1):
82+
for j in range(ty_left, ty_right+1):
83+
if self.state['board']['tiles'][i][j] != "Empty":
84+
self.state['board']['tiles'][i][j] = "Painted"
85+
86+
if allow_chase:
87+
self.chase_react()
88+
89+
90+
def check_chase_condition(self):
91+
chase = False
92+
continue_check = True
93+
94+
for box in self.state['board']['boxes']:
95+
if box['triggers_chase']:
96+
tx_left = box['top_left']['tx']
97+
ty_left = box['top_left']['ty']
98+
tx_right = box['bottom_right']['tx']
99+
ty_right = box['bottom_right']['ty']
100+
101+
all_painted = True
102+
for i in range(tx_left, tx_right+1):
103+
for j in range(ty_left, ty_right+1):
104+
if self.state['board']['tiles'][i][j] != "Empty":
105+
all_painted &= if self.state['board']['tiles'][i][j] == "Painted"
106+
if not all_painted:
107+
continue_check = False
108+
break
109+
if not continue_check:
110+
break
111+
112+
if not continue_check:
113+
break
114+
115+
return all_painted
116+
117+
118+
def chase_react(self):
119+
if self.check_chase_condition():
120+
self.set_mode('chase')
121+
122+
123+
def set_player_tile(self, pos):
124+
assert check_position(pos, ['x','y'])
125+
126+
self.state['player']['position']['x'] = pos['x']
127+
self.state['player']['position']['y'] = pos['y']
128+
129+
130+
def set_enemy_tile(self, eid, pos):
131+
assert check_position(pos, ['x', 'y'])
132+
133+
self.state['enemies'][eid]['position']['x'] = pos['x']
134+
self.state['enemies'][eid]['position']['y'] = pos['y']
135+
136+
137+
def set_enemy_tiles(self, eids, pos_list):
138+
assert len(eids) == len(pos_list)
139+
140+
for i, eid in enumerate(eids):
141+
set_enemy_tile(eid, pos_list[i])
142+
143+
144+
def set_mode(self, mode_id='regular', set_time=None):
145+
assert mode_id in ['jump', 'chase', 'regular']
146+
147+
if mode_id == 'jump':
148+
self.state['jump_timer'] = self.config['jump_time'] if set_time is None else set_time
149+
elif mode_id == 'chase':
150+
self.state['chase_timer'] = self.config['chase_time'] if set_time is None else set_time
151+
else: #mode_id == 'regular'
152+
self.state['jump_timer'] = 0
153+
self.state['chase_timer'] = 0
154+
155+
156+
def get_enemy_protocol(self, eid):
157+
return self.state['enemies'][eid]['ai']
158+
159+
160+
def get_enemy_protocols(self, eids):
161+
return [self.state['enemies'][eid]['ai'] for eid in eids]
162+
163+
164+
def set_enemy_protocol(self, eid, protocol='EnemyAmidarMvmt', kwargs**):
165+
enemy = self.state['enemies'][eid]
166+
167+
enemy['ai'] = {}
168+
enemy['ai'][protocol] = get_default_protocol(protocol, kwargs)
169+
170+
171+
def get_default_protocol(self, protocol, e_pos, kwargs**):
172+
assert protocol in ['EnemyLookupAI', 'EnemyPerimeterAI', 'EnemyAmidarMvmt', 'EnemyTargetPlayer', 'EnemyRandomMvmt']
173+
protocol_ai = {}
174+
175+
if protocol == 'EnemyLookupAI':
176+
protocol_ai['default_route_index'] = eid %% 5 if 'default_route_index' not in kwargs.keys() else kwargs['default_route_index']
177+
protocol_ai['next'] = 0 if 'next' not in kwargs.keys() else kwargs['next']
178+
179+
# add start position
180+
if protocol in ['EnemyPerimeterAI', 'EnemyRandomMvmt', 'EnemyTargetPlayer', 'EnemyAmidarMvmt']:
181+
if 'start' in kwargs.keys():
182+
assert check_position(kwargs['start'], ['tx', 'ty'])
183+
protocol_ai['start'] = kwargs['start']
184+
else:
185+
protocol_ai['start'] = get_random_position()
186+
187+
# add current direction and start direction
188+
if protocol in ['EnemyRandomMvmt', 'EnemyTargetPlayer']:
189+
# choose a valid starting direction
190+
protocol_ai['start_dir'] = get_random_dir_for_tile(protocol_ai['start']) if 'start_dir' not in kwargs.keys() else kwargs['start_dir']
191+
192+
# choose a valid direction to move in
193+
assert check_position(e_pos, ['tx']['ty'])
194+
protocol_ai['dir'] = get_random_dir_for_tile(e_pos) if 'dir' not in kwargs.keys() else kwargs['dir']
195+
196+
if protocol == 'EnemyTargetPlayer':
197+
protocol_ai['vision_distance'] = 15 if 'vision_distance' not in kwargs.keys() else kwargs['vision_distance']
198+
199+
# should have some (Rust?) check to see if enemies move toward the player on the first step after changing protocol
200+
# for now, just assume the first step after setting is False
201+
protocol_ai['player_seen'] = False if 'player_seen' not in kwargs.keys() else kwargs['player_seen']
202+
203+
if protocol == 'EnemyAmidarMvmt'
204+
protocol_ai['vert'] = random.choice(["Up", "Down"]) if 'vert' not in kwargs.keys() else kwargs['vert']
205+
protocol_ai['horiz']= random.choice(["Left", "Right"]) if 'horiz' not in kwargs.keys() else kwargs['horiz']
206+
protocol_ai['start_vert'] = random.choice(["Up", "Down"]) if 'start_vert' not in kwargs.keys() else kwargs['start_vert']
207+
protocol_ai['start_horiz'] = random.choice(["Up", "Down"]) if 'start_horiz' not in kwargs.keys() else kwargs['start_horiz']
208+
209+
210+
return protocol_ai
211+
212+
def get_random_tile_id(self):
213+
tile = {}
214+
215+
tx = random.choice(range(len(self.state['board']['tiles'])))
216+
which_tiles = [i for i in range(len(self.state['board']['tiles'][tx])) if self.state['board']['tiles'][tx][i] != "Empty"]
217+
ty = random.chioce(which_tiles)
218+
219+
tile['tx'] = tx
220+
tile['ty'] = ty
221+
222+
return tile
223+
224+
225+
def get_random_position(self):
226+
rand_tile = self.get_random_tile_id()
227+
228+
# convert random tile to x,y location
229+
pos = {}
230+
231+
return pos
232+
233+
234+
def get_random_dir_for_tile(self, tid):
235+
assert check_position(tid, ['tx', 'ty'])
236+
tile = self.state['board']['tiles'][tid['tx']][tid['ty']]
237+
238+
assert tile != "Empty"
239+
selected = False
240+
dirs = ["Up", "Down", "Left", "Right"]
241+
242+
d = None
243+
while not selected:
244+
next_tid = {}
245+
next_tid['tx'] = tid['tx']
246+
next_tid['ty'] = tid['ty']
247+
248+
if d is not None:
249+
dirs.remove(d)
250+
if dirs.empty():
251+
d = None
252+
253+
d = random.choice(dirs)
254+
if d == "Up":
255+
next_tid['ty'] = next_tid['ty'] - 1
256+
elif d == "Down":
257+
next_tid['ty'] = next_tid['ty'] + 1
258+
elif d == "Left":
259+
next_tid['tx'] = next_tid['tx'] - 1
260+
else: # d == "Right"
261+
next_tid['tx'] = next_tid['tx'] + 1
262+
263+
selected = not selected and check_tile_position(next_tid, ['tx', 'ty'])
264+
265+
if d is None:
266+
raise Exception("No valid direction from this tile:\t\tTile tx:"+str(tid['tx'])+", ty"+str(tid['ty']))
267+
268+
return d
269+
270+
271+
272+
def set_enemy_protocols(self, eids, protocols=None):
273+
if protocols is None:
274+
protocols = ['EnemyAmidarMvmt']*len(eids)
275+
assert len(eids) == len(protocols)
276+
277+
for i, eid in enumerate(eids):
278+
self.set_enemy_protocol[eid, protocols[i]]
279+
280+
281+
def add_enemy(self, pos, ai='EnemyLookupAI', kwargs**):
282+
new_e = {}
283+
284+
# append kwargs, fill in with defaults
285+
new_e['history'] = [] if not 'history' in kwargs.keys() else kwargs['history']
286+
new_e['step'] = None if not 'step' in kwargs.keys() else kwargs['step']
287+
new_e['caught'] = False if not 'caught' in kwargs.keys() else kwargs['caught']
288+
new_e['speed'] = 8 if not 'speed' in kwargs.keys() else kwargs['speed']
289+
290+
assert check_position(pos, ['x', 'y'])
291+
new_e['position'] = pos
292+
self.state['enemies'].append(new_e)
293+
294+
self.set_enemy_protocol(-1, ai, kwargs**)
295+
296+
297+
def remove_enemy(self, eid):
298+
eids = range(len(self.state['enemies']))
299+
300+
assert eid in eids
301+
eids.remove(eid)
302+
303+
self.state['enemies'] = self.state['enemies'][eids]
304+
305+
306+
def set_n_jumps(self, n):
307+
assert n >= 0
308+
self.state['jumps'] = n
309+
310+
311+
def set n_lives(self, n):
312+
assert n > 0
313+
self.state['lives'] = n
314+
315+
# set enemy protocol
60316
# get, set score
61317
# consider logic for score calculation
62-
# add/remove enemy
63-
# set number of jumps
64-
# set number of lives
318+
319+
### difficult interventions ###
65320
# random start state?
321+
# enemy perimeter direction
322+
# tie random selections to Toybox environment seed?
66323

67324

68325
if __name__ == "__main__":
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"start_lives": 3, "rand": {"state": [1817879012901901412, 10917585336602961851]}, "enemies": [{"EnemyLookupAI": {"next": 0, "default_route_index": 0}}, {"EnemyLookupAI": {"next": 0, "default_route_index": 1}}, {"EnemyLookupAI": {"next": 0, "default_route_index": 2}}, {"EnemyLookupAI": {"next": 0, "default_route_index": 3}}, {"EnemyLookupAI": {"next": 0, "default_route_index": 4}}], "bg_color": {"b": 0, "a": 255, "r": 0, "g": 0}, "chase_score_bonus": 100, "player_color": {"b": 153, "a": 255, "r": 255, "g": 255}, "copy_no_score_bug": true, "chase_time": 300, "unpainted_color": {"b": 211, "a": 255, "r": 148, "g": 0}, "start_jumps": 4, "enemy_color": {"b": 100, "a": 255, "r": 255, "g": 50}, "painted_color": {"b": 30, "a": 255, "r": 255, "g": 255}, "box_bonus": 50, "render_images": true, "inner_painted_color": {"b": 0, "a": 255, "r": 255, "g": 255}, "jump_time": 75}
1+
{"box_bonus": 50, "inner_painted_color": {"r": 255, "b": 0, "a": 255, "g": 255}, "jump_time": 75, "render_images": true, "board": ["c========================c======", "= = = = = = = =", "= = = = = = = =", "= = = = = = = =", "= = = = = = = =", "= = = = = = = =", "================================", "= = = = = = = =", "= = = = = = = =", "= = = = = = = =", "= = = = = = = =", "= = = = = = = =", "================================", "= = = = = p", "= = = = = p", "= = = = = p", "= = = = = p", "= = = = = p", "===============================p", "= = = = = =", "= = = = = =", "= = = = = =", "= = = = = =", "= = = = = =", "c========================c======", "= = = = = =", "= = = = = =", "= = = = = =", "= = = = = =", "= = = = = =", "================================"], "enemy_color": {"r": 255, "b": 100, "a": 255, "g": 50}, "chase_time": 300, "rand": {"state": [1817879012901901412, 10917585336602961851]}, "painted_color": {"r": 255, "b": 30, "a": 255, "g": 255}, "enemies": [{"EnemyLookupAI": {"default_route_index": 0, "next": 0}}, {"EnemyLookupAI": {"default_route_index": 1, "next": 0}}, {"EnemyLookupAI": {"default_route_index": 2, "next": 0}}, {"EnemyLookupAI": {"default_route_index": 3, "next": 0}}, {"EnemyLookupAI": {"default_route_index": 4, "next": 0}}], "start_lives": 3, "player_start": {"tx": 31, "ty": 15}, "start_jumps": 4, "default_board_bugs": true, "player_color": {"r": 255, "b": 153, "a": 255, "g": 255}, "bg_color": {"r": 0, "b": 0, "a": 255, "g": 0}, "chase_score_bonus": 100, "unpainted_color": {"r": 148, "b": 211, "a": 255, "g": 0}}

0 commit comments

Comments
 (0)