1
1
from toybox .interventions .base import *
2
+ import random
2
3
import json
3
4
"""An API for interventions on Amidar."""
4
5
@@ -8,14 +9,25 @@ def __init__(self, tb, game_name='amidar'):
8
9
# check that the simulation in tb matches the game name.
9
10
Intervention .__init__ (self , tb , game_name )
10
11
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)
11
20
12
21
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
+
15
28
16
29
def player_tile (self ):
17
- pass
18
- #return self.query_json('player_tile')
30
+ return self .state ['player' ]['position' ]
19
31
20
32
def num_enemies (self ):
21
33
return len (self .state ['enemies' ])
@@ -24,45 +36,290 @@ def jumps_remaining(self):
24
36
return self .state ['jumps' ]
25
37
26
38
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
29
40
30
41
def jump_mode (self ):
31
- pass
32
- #return self.query_json('jump_mode')
42
+ return self .state ['jump_timer' ] > 0
33
43
34
44
def chase_mode (self ):
35
- pass
36
- #return self.query_json('chase_mode')
45
+ return self .state ['chase_timer' ] > 0
37
46
38
47
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' ]))]
41
49
42
50
def enemy_caught (self , eid ):
43
- pass
44
- #return self.query_json('enemy_caught', eid)
51
+ return self .state ['enemies' ][eid ]['caught' ]
45
52
46
53
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 \t Tile 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
60
316
# get, set score
61
317
# consider logic for score calculation
62
- # add/remove enemy
63
- # set number of jumps
64
- # set number of lives
318
+
319
+ ### difficult interventions ###
65
320
# random start state?
321
+ # enemy perimeter direction
322
+ # tie random selections to Toybox environment seed?
66
323
67
324
68
325
if __name__ == "__main__" :
0 commit comments