################################################################################################################# Authors: ## Kenny Young ([REDACTED]) ## Tian Tian ([REDACTED]) #################################################################################################################import numpy as np
###################################################################################################################### Constants######################################################################################################################ramp_interval = 100init_spawn_speed = 10init_move_interval = 5shot_cool_down = 5
###################################################################################################################### Env## The player can move freely along the 4 cardinal directions. Enemies and treasure spawn from the sides. A reward of# +1 is given for picking up treasure. Termination occurs if the player makes contact with an enemy. Enemy and# treasure direction are indicated by a trail channel. Difficulty is periodically increased by increasing the speed# and spawn rate of enemies and treasure.######################################################################################################################class Env: def __init__(self, ramping=True): self.channels ={ 'player':0, 'enemy':1, 'trail':2, 'gold':3 } self.action_map = ['n','l','u','r','d','f'] self.ramping = ramping self.random = np.random.RandomState() self.reset()
# Update environment according to agent action def act(self, a): r = 0 if(self.terminal): return r, self.terminal
a = self.action_map[a]
# Spawn enemy if timer is up if(self.spawn_timer==0): self._spawn_entity() self.spawn_timer = self.spawn_speed
# Resolve player action if(a=='l'): self.player_x = max(0, self.player_x-1) elif(a=='r'): self.player_x = min(9, self.player_x+1) elif(a=='u'): self.player_y = max(1, self.player_y-1) elif(a=='d'): self.player_y = min(8, self.player_y+1)
# Update entities for i in range(len(self.entities)): x = self.entities[i] if(x is not None): if(x[0:2]==[self.player_x,self.player_y]): if(self.entities[i][3]): self.entities[i] = None r+=1 else: self.terminal = True if(self.move_timer==0): self.move_timer = self.move_speed for i in range(len(self.entities)): x = self.entities[i] if(x is not None): x[0]+=1 if x[2] else -1 if(x[0]<0 or x[0]>9): self.entities[i] = None if(x[0:2]==[self.player_x,self.player_y]): if(self.entities[i][3]): self.entities[i] = None r+=1 else: self.terminal = True
# Update various timers self.spawn_timer -= 1 self.move_timer -= 1
#Ramp difficulty if interval has elapsed if self.ramping and (self.spawn_speed>1 or self.move_speed>1): if(self.ramp_timer>=0): self.ramp_timer-=1 else: if(self.move_speed>1 and self.ramp_index%2): self.move_speed-=1 if(self.spawn_speed>1): self.spawn_speed-=1 self.ramp_index+=1 self.ramp_timer=ramp_interval return r, self.terminal
# Spawn a new enemy or treasure at a random location with random direction (if all rows are filled do nothing) def _spawn_entity(self): lr = self.random.rand() < 1/2 is_gold = self.random.rand() < 1/3 x = 0 if lr else 9 slot_options = [i for i in range(len(self.entities)) if self.entities[i]==None] if(not slot_options): return slot = slot_options[self.random.randint(len(slot_options))] self.entities[slot] = [x,slot+1,lr,is_gold]
# Query the current level of the difficulty ramp, could be used as additional input to agent for example def difficulty_ramp(self): return self.ramp_index
# Process the game-state into the 10x10xn state provided to the agent and return def state(self): state = np.zeros((10,10,len(self.channels)),dtype=bool) state[self.player_y,self.player_x,self.channels['player']] = 1 for x in self.entities: if(x is not None): c = self.channels['gold'] if x[3] else self.channels['enemy'] state[x[1], x[0],c] = 1 back_x = x[0]-1 if x[2] else x[0]+1 if(back_x>=0 and back_x<=9): state[x[1], back_x, self.channels['trail']] = 1 return state
# Reset to start state for new episode def reset(self): self.player_x = 5 self.player_y = 5 self.entities = [None]*8 self.shot_timer = 0 self.spawn_speed = init_spawn_speed self.spawn_timer = self.spawn_speed self.move_speed = init_move_interval self.move_timer = self.move_speed self.ramp_timer = ramp_interval self.ramp_index = 0 self.terminal = False
# Dimensionality of the game-state (10x10xn) def state_shape(self): return [10,10,len(self.channels)]
# Subset of actions that actually have a unique impact in this environment def minimal_action_set(self): minimal_actions = ['n','l','u','r','d'] return [self.action_map.index(x) for x in minimal_actions]W1[12, 0] = 2.5 # one entry: row 12, column 0W1[0:6, 3] = [1, 0, -1, 0, 2, 0] # explicit values along a range (as many values as entries)b1[0:4] = -1 # fill a range (a:b is half-open; ':' alone = the whole axis)W1[3:600:60, 0] = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1] # column 0 of channel 3, rows 0..9 of the grid -> unit 0W2[0:2, 0:3] = [[1, -1, 0.5], [0, 2, 0]] # a 2-D list for a 2-D block (here 2 x 3)W3[:, 2] *= 2 # scale (also += and -=)# First-layer biases.b1[:] = -8
# Player-conditioned upward hazards.W1[90:570:60, 0:8] = [[8, 0, 0, 0, 0, 0, 0, 0],[6, 0, 0, 0, 0, 0, 0, 0],[3, 4, 0, 0, 0, 0, 0, 0],[0, 3, 4, 0, 0, 0, 0, 0],[0, 0, 3, 4, 0, 0, 0, 0],[0, 0, 0, 3, 4, 0, 0, 0],[0, 0, 0, 0, 3, 4, 0, 0],[0, 0, 0, 0, 0, 3, 4, 0]]
# Player-conditioned downward hazards.W1[90:570:60, 8:16] = [[0, 4, 3, 0, 0, 0, 0, 0],[0, 0, 4, 3, 0, 0, 0, 0],[0, 0, 0, 4, 3, 0, 0, 0],[0, 0, 0, 0, 4, 3, 0, 0],[0, 0, 0, 0, 0, 4, 3, 0],[0, 0, 0, 0, 0, 0, 4, 3],[0, 0, 0, 0, 0, 0, 0, 6],[0, 0, 0, 0, 0, 0, 0, 8]]
# Player-conditioned waiting hazards and edge-row penalties.W1[90:570:60, 16:24] = [[7, 0, 0, 0, 0, 0, 0, 0],[0, 4, 0, 0, 0, 0, 0, 0],[0, 0, 4, 0, 0, 0, 0, 0],[0, 0, 0, 4, 0, 0, 0, 0],[0, 0, 0, 0, 4, 0, 0, 0],[0, 0, 0, 0, 0, 4, 0, 0],[0, 0, 0, 0, 0, 0, 4, 0],[0, 0, 0, 0, 0, 0, 0, 7]]
# Signed distance codes for treasure rows 1 through 8.W1[90:570:60, 24:32] = [[0, -0.8, -1.4, -1.5, -1.6, -1.7, -1.8, -1.9],[0.8, 0, -0.8, -1.4, -1.5, -1.6, -1.7, -1.8],[1.4, 0.8, 0, -0.8, -1.4, -1.5, -1.6, -1.7],[1.5, 1.4, 0.8, 0, -0.8, -1.4, -1.5, -1.6],[1.6, 1.5, 1.4, 0.8, 0, -0.8, -1.4, -1.5],[1.7, 1.6, 1.5, 1.4, 0.8, 0, -0.8, -1.4],[1.8, 1.7, 1.6, 1.5, 1.4, 0.8, 0, -0.8],[1.9, 1.8, 1.7, 1.6, 1.5, 1.4, 0.8, 0]]
# Enemy contributions: upward, downward, and waiting hazard units.W1[61:120:6, 0:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[121:180:6, 1:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[181:240:6, 2:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[241:300:6, 3:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[301:360:6, 4:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[361:420:6, 5:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[421:480:6, 6:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]W1[481:540:6, 7:24:8] = [[0,0,0],[0,0,0],[0,0,3],[5,5,4],[6,6,9],[11,11,10],[6,6,9],[5,5,4],[0,0,3],[0,0,0]]
# Trails cancel receding enemies and already-passed treasure.# Columns within each block: upward hazard, downward hazard, waiting hazard, treasure.W1[64:120:6, 0:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[124:180:6, 1:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[184:240:6, 2:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[244:300:6, 3:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[304:360:6, 4:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[364:420:6, 5:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[424:480:6, 6:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]W1[484:540:6, 7:32:8] = [[0,0,0,0],[0,0,0,-8],[0,0,0,-8],[0,0,-3,-16],[-5,-5,-4,-16],[-6,-6,-9,-24],[-5,-5,-4,-16],[0,0,-3,-16],[0,0,0,-8],[0,0,0,-8]]
# Treasure plus trail equals 8 before interception, and 0 after passing.W1[62:120:6, 24] = [8,8,16,16,24,24,24,16,16,8]W1[122:180:6, 25] = [8,8,16,16,24,24,24,16,16,8]W1[182:240:6, 26] = [8,8,16,16,24,24,24,16,16,8]W1[242:300:6, 27] = [8,8,16,16,24,24,24,16,16,8]W1[302:360:6, 28] = [8,8,16,16,24,24,24,16,16,8]W1[362:420:6, 29] = [8,8,16,16,24,24,24,16,16,8]W1[422:480:6, 30] = [8,8,16,16,24,24,24,16,16,8]W1[482:540:6, 31] = [8,8,16,16,24,24,24,16,16,8]
# Second-layer hazard aggregation.W2[0:8, 0] = 1W2[8:16, 1] = 1W2[16:24, 2] = 1b2[0:3] = 7
# Treasure window decoders.W2[24, 3:6] = [20,20,24]W2[25, 6:10] = [24,20,20,24]W2[26, 10:14] = [24,20,20,24]W2[27, 14:18] = [24,20,20,24]W2[28, 18:22] = [24,20,20,24]W2[29, 22:26] = [24,20,20,24]W2[30, 26:30] = [24,20,20,24]W2[31, 30:32] = [24,20]
b2[3:32:4] = 8b2[4:30:4] = -8b2[5:30:4] = -21.6b2[6:32:4] = 21.6
# Safety penalties.W3[0, 2] = -80W3[1, 4] = -80W3[2, 0] = -80
# Prefer waiting when aligned with incoming treasure.W3[3:32:4, 0] = 16W3[4:30:4, 0] = -16
# Distance-decaying upward attraction.W3[4:30:4, 2] = 6W3[5:30:4, 2] = -6
# Distance-decaying downward attraction.W3[6:32:4, 4] = 6W3[7:32:4, 4] = -6
# Baseline corrections; suppress horizontal movement and redundant fire.b3[:] = [17, -1000, 0.15, -1000, -0.69, -1000]