def actions(self, current_state): ''' RECEIVES: the current state RETURNS: a list containing all the valid actions for the state Calculates all the valid actions for a state: -> Receives a dictionary containing the current state -> For every direction in "wasd" calculates the next state (positions of the keeper and boxes) -> Verifies wether the boxes are in a deadlock -> Return a list of valid actions (directions) ''' valid_directions = [] for direction in DIRECTIONS: next_state = calc_next_state(current_state, direction) keeper = next_state['keeper'] boxes = next_state['boxes'] valid_directions.append(direction) # Check wether we are placing a box outside of the map or # placing a box on top of another box if Map.is_blocked(self.level_map, keeper) or len(list(set(boxes))) < len(boxes): valid_directions.remove(direction) continue for box in boxes: if box in self.deadlocks_pos: valid_directions.remove(direction) continue elif self.isDeadlock(box): self.deadlocks_pos.append(box) self.deadlocks.append(next_state) valid_directions.remove(direction) return list(set(valid_directions))
class Game: """Representation of a Game run.""" def __init__(self, level=1, timeout=TIMEOUT, player=None): logger.info("Game(level=%s)", level) self.puzzles = 0 #puzzles completed self.level = level if player: self._running = True self._player_name = player else: self._running = False self._timeout = timeout self._step = 0 self._total_steps = 0 self._state = {} self._papertrail = "" # keeps track of all steps made by the player self._moves = 0 self._pushes = 0 self.map = None self._lastkeypress = "" self.next_level(self.level) def info(self): """Initial Static information about the game.""" return { "fps": GAME_SPEED, "timeout": self._timeout, "map": f"levels/{self.level}.xsb", } @property def papertrail(self): """String containing all pressed keys by agent.""" return self._papertrail @property def running(self): """Status on game.""" return self._running @property def score(self): """Calculus of the current score.""" return self.puzzles, self._moves, self._pushes, self._total_steps + self._step, self.map.on_goal def stop(self): """Stop the game.""" if self._step: logger.info("GAME OVER at %s", self._step) self._running = False def next_level(self, level): """Update all state variables to a new level.""" self.puzzles += 1 self._total_steps += self._step self._step = 0 self._lastkeypress = "" self._papertrail += "," self.level = level try: self.map = Map(f"levels/{level}.xsb") logger.info("NEXT LEVEL: %s", level) except FileNotFoundError: logger.info("No more levels... You WIN!") self.stop() return def keypress(self, key): """Update locally last key pressed.""" self._lastkeypress = key def move(self, cur, direction): """Move an entity in the game.""" assert direction in "wasd", f"Can't move in {direction} direction" cx, cy = cur ctile = self.map.get_tile(cur) npos = cur if direction == "w": npos = cx, cy - 1 if direction == "a": npos = cx - 1, cy if direction == "s": npos = cx, cy + 1 if direction == "d": npos = cx + 1, cy # test blocked if self.map.is_blocked(npos): logger.debug("Blocked ahead") return False if self.map.get_tile(npos) in [ Tiles.BOX, Tiles.BOX_ON_GOAL, ]: # next position has a box? if ctile & Tiles.MAN == Tiles.MAN: # if you are the keeper you can push if not self.move(npos, direction): # as long as the pushed box can move return False else: # you are not the Keeper, so no pushing return False self._moves += 1 # actually update map self.map.set_tile(npos, ctile) self.map.clear_tile(cur) return True def update_keeper(self): """Update the location of the Keeper.""" if self._lastkeypress == "": return GameStatus.NO_OPERATION try: # Update position self.move(self.map.keeper, self._lastkeypress) self._papertrail += self._lastkeypress except AssertionError: logger.error( "Invalid key <%s> pressed. Valid keys: w,a,s,d", self._lastkeypress ) finally: self._lastkeypress = "" # remove inertia if self.map.completed: logger.info("Level %s completed", self.level) self.next_level(self.level + 1) return GameStatus.NEW_MAP return GameStatus.RUNNING async def next_frame(self): """Calculate next frame.""" await asyncio.sleep(1.0 / GAME_SPEED) if not self._running: logger.info("Waiting for player 1") return self._step += 1 if self._step >= self._timeout: self.stop() if self._step % 100 == 0: logger.debug("[%s] SCORE %s", self._step, self.score) game_status = self.update_keeper() self._state = { "player": self._player_name, "level": self.level, "step": self._step, "score": self.score, "keeper": self.map.keeper, "boxes": self.map.boxes, } return game_status @property def state(self): """Contains the state of the Game.""" # logger.debug(self._state) return json.dumps(self._state)
class Game: def __init__(self, level=1, lives=LIVES, timeout=TIMEOUT, size=MAP_SIZE): logger.info(f"Game(level={level}, lives={lives})") self.initial_level = level self._running = False self._timeout = timeout self._score = 0 self._step = 0 self._total_steps = 0 self._state = {} self._initial_lives = lives self.map = Map(size=size, empty=True) self._enemies = [] def info(self): return { "size": self.map.size, "map": self.map.map, "fps": GAME_SPEED, "timeout": TIMEOUT, "lives": LIVES, "score": self.score, } @property def running(self): return self._running @property def score(self): return self._score @property def total_steps(self): return self._total_steps def start(self, player_name): logger.debug("Reset world") self._player_name = player_name self._running = True self._total_steps = 0 self._score = INITIAL_SCORE self._bomberman = Bomberman(self.map.bomberman_spawn, self._initial_lives) for powerup in range(1, self.initial_level): self._bomberman.powerup(LEVEL_POWERUPS[powerup]) logger.debug("Bomberman Powerups: %s", self._bomberman.powers) self.next_level(self.initial_level) def stop(self): logger.info("GAME OVER") self._total_steps += self._step self._running = False def next_level(self, level): if level > len(LEVEL_ENEMIES): logger.info("You WIN!") self.stop() return logger.info("NEXT LEVEL") self.map = Map(level=level, size=self.map.size, enemies=len(LEVEL_ENEMIES[level])) self._bomberman.respawn() self._total_steps += self._step self._step = 0 self._bombs = [] self._powerups = [] self._bonus = [] self._exit = [] self._lastkeypress = "" self._enemies = [ t(p) for t, p in zip(LEVEL_ENEMIES[level], self.map.enemies_spawn) ] logger.debug("Enemies: %s", [(e._name, e.pos) for e in self._enemies]) logger.debug("Walls: %s", self.map.walls) def quit(self): logger.debug("Quit") self._running = False def keypress(self, key): self._lastkeypress = key def update_bomberman(self): try: if self._lastkeypress.isupper(): # Parse action if self._lastkeypress == "A" and len(self._bombs) > 0: self._bombs[0].detonate( ) # always detonate the oldest bomb elif (self._lastkeypress == "B" and len(self._bombs) < self._bomberman.powers.count(Powerups.Bombs) + 1 and not self.map.is_blocked(self._bomberman.pos)): self._bombs.append( Bomb( self._bomberman.pos, self.map, MIN_BOMB_RADIUS + self._bomberman.flames(), detonator=Powerups.Detonator in self._bomberman.powers, )) # must be dependent of powerup else: # Update position new_pos = self.map.calc_pos( self._bomberman.pos, self._lastkeypress, self._bomberman.wallpass) # don't bump into stones/walls if self._bomberman.bombpass or new_pos not in [ b.pos for b in self._bombs ]: # don't pass over bombs self._bomberman.pos = new_pos for pos, _type in self._powerups: # consume powerups if new_pos == pos: self._bomberman.powerup(_type) self._powerups.remove((pos, _type)) except AssertionError: logger.error("Invalid key <%s> pressed. Valid keys: w,a,s,d A B", self._lastkeypress) finally: self._lastkeypress = "" # remove inertia if len(self._enemies) == 0 and self._bomberman.pos == self._exit: logger.info(f"Level {self.map.level} completed") #self._score += self._timeout - self._step self.next_level(self.map.level + 1) def kill_bomberman(self): logger.info(f"bomberman has died on step: {self._step}") self._bomberman.kill() logger.debug(f"bomberman has now {self._bomberman.lives} lives") if self._bomberman.lives > 0: logger.debug("RESPAWN") self._bomberman.respawn() for e in self._enemies: if distance(self._bomberman.pos, e.pos) < VITAL_SPACE: logger.debug("respawn camper") e.respawn() self._bombs = [] else: self.stop() def collision(self): for e in self._enemies: if e.pos == self._bomberman.pos: self.kill_bomberman() e.respawn() def explode_bomb(self): for bomb in self._bombs[:]: bomb.update() if bomb.exploded(): logger.debug("BOOM") if bomb.in_range( self._bomberman) and not self._bomberman.flamepass: self.kill_bomberman() for wall in self.map.walls[:]: if bomb.in_range(wall): logger.debug(f"Destroying wall @{wall}") self.map.remove_wall(wall) if self.map.exit_door == wall: self._exit = wall if self.map.powerup == wall: self._powerups.append( (wall, LEVEL_POWERUPS[self.map.level])) for enemy in self._enemies[:]: if bomb.in_range(enemy): logger.debug(f"killed enemy @{enemy}") self._score += enemy.points() self._enemies.remove(enemy) if bomb in self._bombs: self._bombs.remove(bomb) async def next_frame(self): await asyncio.sleep(1.0 / GAME_SPEED) if not self._running: logger.info("Waiting for player 1") return self._step += 1 if self._step == self._timeout: self.stop() if self._step % 100 == 0: logger.debug( f"[{self._step}] SCORE {self._score} - LIVES {self._bomberman.lives}" ) self.explode_bomb() self.update_bomberman() self.collision() if (self._step % (self._bomberman.powers.count(Powerups.Speed) + 1) == 0): # increase speed of bomberman by moving enemies less often for enemy in self._enemies: enemy.move(self.map, self._bomberman, self._bombs, self._enemies) self.collision() #sanity check assert all([ ep not in self.map.walls for ep in [e.pos for e in self._enemies if not e._wallpass] ]) self._state = { "level": self.map.level, "step": self._step, "timeout": self._timeout, "player": self._player_name, "score": self._score, "lives": self._bomberman.lives, "bomberman": self._bomberman.pos, "bombs": [(b.pos, b.timeout, b.radius) for b in self._bombs], "enemies": [{ "name": str(e), "id": str(e.id), "pos": e.pos } for e in self._enemies], "walls": self.map.walls, "powerups": [(p, Powerups(n).name) for p, n in self._powerups], "bonus": self._bonus, "exit": self._exit, } @property def state(self): # logger.debug(self._state) return json.dumps(self._state)
class AI_Agent(): def __init__(self, game_properties): self.logger = logging.getLogger("AI AGENT") self.logger.setLevel(logging.WARN) self.logger.info("AI Agent created.") self.map = Map(size=game_properties["size"], mapa=game_properties["map"]) self.logger.debug(self.map) self.update_wallpass_next = False self.cur_pos = None self.walls = None self.enemies = None self.powerups = None self.bonus = None self.exit = None self.have_powerup = None self.level = None self.lives = 3 #this shouldnt be hardcoded # self.enemy_past_pos = {} self.pursuing_enemy = None # self.eval_enemy = None self.depth_limit = 100 self.loop = 0 self.search_domain = BombermanSearch(self.map) self.decisions_queue = [] self.rounds_pursuing_limit = 3 # Limit of rounds we can be pursuing the same enemy self.wait_time = 200 # time to wait when in loop pursuing enemy self.last_enemy_dir = None self.perform_last_resort = False def dist(self, pos1, pos2): return math.hypot(pos2[0] - pos1[0], pos2[1] - pos1[1]) def update_pursuing_enemy(self, closest_enemy): if (self.pursuing_enemy is None or self.pursuing_enemy['id'] != closest_enemy['id']): self.pursuing_enemy = closest_enemy self.pursuing_enemy['last_pos'] = None self.pursuing_enemy['rounds_pursuing'] = 0 self.reset_waiting_limit() else: last_pos = self.pursuing_enemy[ 'pos'] # keep track of enemy movement rounds_pursuing = self.pursuing_enemy['rounds_pursuing'] self.pursuing_enemy = closest_enemy self.pursuing_enemy['last_pos'] = last_pos self.pursuing_enemy['rounds_pursuing'] = rounds_pursuing def incr_round(self): rounds_pursuing = self.pursuing_enemy['rounds_pursuing'] self.pursuing_enemy['rounds_pursuing'] = rounds_pursuing + 1 def reset_life(self): self.logger.info( "I Died, will restart decisionQueue------------------------------------------" ) #self.cur_pos = [1,1] self.decisions_queue = [] def reset_level(self): self.logger.info("NEW LEVEL") #self.cur_pos = [1,1] self.decisions_queue = [] self.have_powerup = False self.pursuing_enemy = None self.search_domain.remove_destroyed_walls() def reset_waiting_limit(self): if self.level > 2: self.rounds_pursuing_limit = 4 # Limit of rounds we can be pursuing the same enemy self.wait_time = 70 # time to wait when in loop pursuing enemy elif self.level > 6: self.wait_time = 50 self.rounds_pursuing = 5 def update_level(self, level): if self.level is None: self.level = level if self.level != level: self.reset_level() self.reset_map_walls() self.level = level def reset_map_walls(self): self.logger.debug("Updating walls because NEXT LEVEL") self.search_domain.set_walls(self.walls) def closest_enemy(self, pos=None): if pos is None: pos = self.cur_pos closest = None for enemy in self.enemies: d = self.dist(pos, enemy['pos']) if closest is None or d < closest[1]: closest = (enemy, d) return closest[0] if closest != None else None #Importante para nao perder tempo a procura de paredes def closest_wall(self, i=0): closest_list = [] if not self.walls: return None for wall in self.walls: d = self.dist(self.cur_pos, wall) closest_list.append((wall, d)) # if closest is None or d < closest[1]: # closest = (wall, d) closest_list = sorted(closest_list, key=lambda x: x[1]) return closest_list[i][0] def calculate_path(self, origin, goal): problem = SearchProblem(self.search_domain, origin, goal) tree = SearchTree(problem, strategy='greedy') self.logger.debug("Searching path from " + str(origin) + " to " + str(goal)) # self.logger.debug(self.walls) path, moves = tree.search(depth_limit=self.depth_limit) self.logger.debug("Path found.") return (path, moves) def find_direction(self, prev_pos, pos): #change this if prev_pos is None: return '' if prev_pos[0] < pos[0]: return 'd' elif prev_pos[0] > pos[0]: return 'a' elif prev_pos[1] > pos[1]: return 'w' elif prev_pos[1] < pos[1]: return 's' def dir_in_x(self, d): if d in ['a', 'd']: return True return False def dir_in_y(self, d): if d in ['w', 's']: return True return False def opposite_move(self, move): if move == 'w': return 's' elif move == 's': return 'w' elif move == 'a': return 'd' elif move == 'd': return 'a' def running_away(self, move): # self.logger.debug(str(self.pursuing_enemy)) enemy_dir = self.find_direction(self.pursuing_enemy['last_pos'], self.pursuing_enemy['pos']) cond1 = self.dir_in_x(enemy_dir) and (self.cur_pos[1] == self.pursuing_enemy['pos'][1]) cond2 = self.dir_in_y(enemy_dir) and (self.cur_pos[0] == self.pursuing_enemy['pos'][0]) if enemy_dir == move and (cond1 or cond2): self.logger.debug("Enemy running away from me.") return True return False def running_towards(self, move): if (not 'last_pos' in self.pursuing_enemy or self.pursuing_enemy['last_pos'] is None or self.pursuing_enemy is None): return False # self.logger.debug(str(self.pursuing_enemy)) if (self.find_direction(self.pursuing_enemy['last_pos'], self.pursuing_enemy['pos']) == self.opposite_move(move) and ((self.cur_pos[0] == self.pursuing_enemy['pos'][0]) or (self.cur_pos[1] == self.pursuing_enemy['pos'][1]))): self.logger.debug("Enemy running towards me.") return True return False def select_bomb_point(self, target, i=0): closest = None for pos in [ self.search_domain.result(target, mov) for mov in self.search_domain.actions(target) ]: if pos in self.walls: continue d = self.dist(self.cur_pos, pos) if closest is None or d < closest[1]: closest = (pos, d) if closest is None: # did not find place to put bomb self.logger.debug(f'Current wall {target} is blocked') target = self.closest_wall(i=i + 1) return self.select_bomb_point(target, i + 1) self.logger.debug("Closest wall: " + str(target) + ". Going to " + str(closest[0])) self.bomb_point = closest[0] path, moves = self.calculate_path(self.cur_pos, closest[0]) return (path, moves) def hide(self, path, moves): # hide in nearby position # but choose the one closest to next wall ? not yet quad1 = [['w', 'd'], ['d', 'w'], ['w', 'w', 'd'], ['d', 'd', 'w'], ['w', 'w', 'w', 'd'], ['d', 'd', 'd', 'w']] quad2 = [['w', 'a'], ['a', 'w'], ['w', 'w', 'a'], ['a', 'a', 'w'], ['a', 'a', 'a', 'w'], ['w', 'w', 'w', 'a']] quad3 = [['s', 'a'], ['a', 's'], ['s', 's', 'a'], ['a', 'a', 's'], ['a', 'a', 'a', 's'], ['s', 's', 's', 'a']] quad4 = [['s', 'd'], ['d', 's'], ['s', 's', 'd'], ['d', 'd', 's'], ['s', 's', 's', 'd'], ['d', 'd', 'd', 's']] last_pos = path[-1] #Instead of none, we have a possibility to stay in place if there is no exit best = last_pos, [''] quad1n = 0 quad2n = 0 quad3n = 0 quad4n = 0 for e in self.enemies: epos = e['pos'] if self.search_domain.dist(last_pos, epos) < 8: #only the closests if last_pos[0] > epos[0]: #esquerda if last_pos[1] < epos[1]: #baixo quad3n += 2 if last_pos[1] > epos[1]: #cima quad2n += 2 else: quad2n += 1 quad3n += 1 if last_pos[0] < epos[0]: #direita if last_pos[1] < epos[1]: #baixo quad4n += 2 if last_pos[1] > epos[1]: #cima quad1n += 2 else: quad1n += 1 quad4n += 1 possible_moves = [] if quad1n != 0 and quad2n == 0 and quad3n == 0 and quad4n == 0: possible_moves += quad3 + quad4 + quad2 + quad1 if quad1n == 0 and quad2n != 0 and quad3n == 0 and quad4n == 0: possible_moves += quad4 + quad1 + quad3 + quad2 if quad1n == 0 and quad2n == 0 and quad3n != 0 and quad4n == 0: possible_moves += quad1 + quad2 + quad4 + quad3 if quad1n == 0 and quad2n == 0 and quad3n == 0 and quad4n != 0: possible_moves += quad2 + quad3 + quad1 + quad4 else: helper = { 'quad1': quad1n, 'quad2': quad2n, 'quad3': quad3n, 'quad4': quad4n } for i in sorted(helper.items(), key=lambda e: (e[1], e[0])): if i[0] == 'quad1': possible_moves += quad1 elif i[0] == 'quad2': possible_moves += quad2 elif i[0] == 'quad3': possible_moves += quad3 elif i[0] == 'quad4': possible_moves += quad4 for possible_move in possible_moves: p = [last_pos] this_works = True hide_path = self.result(possible_move) if len(self.enemies) > 0: for e in self.enemies: if e['pos'] in hide_path: this_works = False if not this_works: continue this_works = True #print(last_pos,possible_move) if self.can_i_do_this(last_pos, possible_move): for move in possible_move: p.append(self.search_domain.result(p[-1], move)) best = (p, possible_move) break #aqui verificar se best == None e se for talvez andar para tras conforme o radius da bomab? self.logger.debug("Hiding from bomb in " + str(best[0][-1]) + " (" + str(best[1]) + ")") path += best[0] moves += best[1] def result(self, moves, origin=None): if origin is None: origin = self.cur_pos path = [origin] for mov in moves: r = self.search_domain.result(path[-1], mov) path.append(r) return path def can_i_do_this(self, pos, possible_move): if possible_move == []: return True enemies_pos = [] for e in self.enemies: enemies_pos.append(e['pos']) letter = possible_move[0] #print(pos, letter, letter in self.search_domain.actions(pos)) return ((letter in self.search_domain.actions(pos)) and self.can_i_do_this(self.search_domain.result(pos, letter), possible_move[1:])) def predict_enemy(self, n_moves, enemy=None): if enemy is None: enemy = self.pursuing_enemy if 'last_pos' in enemy: direction = self.find_direction(enemy['last_pos'], enemy['pos']) moves = [direction for _ in range(n_moves)] result = self.result(moves, enemy['pos']) return result else: return None def allBalloms(self): for e in self.enemies: if e['name'] not in ['Balloom', 'Doll']: return False return True def decide_move(self): if self.update_wallpass_next: self.logger.info("I can walk through walls now!") self.search_domain.wallpass = True if self.powerups: # powerup to pick up powerup_popped = self.powerups.pop(0) # 0 - pos, 1 - type powerup = powerup_popped[0] powerup_name = powerup_popped[1] # [[15, 16], 'Flames'] self.have_powerup = True self.logger.debug("Going for powerup: " + str(powerup)) path, moves = self.calculate_path(self.cur_pos, powerup) if len(self.enemies) > 0: self.powerups.append(powerup) if str(powerup_name) == "Wallpass": self.update_wallpass_next = True return [moves[0]] return moves if self.exit and not self.enemies and self.have_powerup: path, moves = self.calculate_path(self.cur_pos, self.exit) return moves #Does this solve? closest_enemy = self.closest_enemy() closest_wall = self.closest_wall() if (closest_enemy is not None and (not self.walls or self.dist(self.cur_pos, closest_enemy['pos']) <= self.dist(self.cur_pos, closest_wall) #or (self.have_powerup and self.exit) ) and not closest_enemy['pos'] in self.walls): self.update_pursuing_enemy(closest_enemy) #se distancia for maior que o metade do tamanho do mapa, vamos para uma posiçao no meio e tentamos procurar a partir de la if False: #melhorar esta porcaria xd #if self.search_domain.dist(self.cur_pos, closest_enemy['pos']) >= self.map._size[1]/2: chosen_pos = [ int(self.map._size[0] / 2), int(self.map._size[1] / 2) ] self.logger.debug("Going for %s" % (chosen_pos)) while self.map.is_blocked(chosen_pos) or self.map.is_stone(chosen_pos) \ or chosen_pos not in self.search_domain.destroyed_walls or chosen_pos!=[self.map._size[0]-1,self.map._size[1]-1]: chosen_pos = [ele + 1 for ele in chosen_pos] self.logger.debug("Going for %s" % (chosen_pos)) path, moves = self.calculate_path(self.cur_pos, chosen_pos) return moves else: path, moves = self.calculate_path(self.cur_pos, closest_enemy['pos']) if (len(moves) > 0 and self.search_domain.dist( self.cur_pos, closest_enemy['pos']) <= 2 and not self.running_away(moves[-1])): if self.walls == [] and self.allBalloms(): self.perform_last_resort = True moves = ['B'] self.incr_round() self.hide([self.cur_pos], moves) elif self.search_domain.dist(self.cur_pos, closest_enemy['pos']) <= 1: if self.walls == [] and self.allBalloms(): self.perform_last_resort = True moves = ['B'] self.incr_round() self.hide([self.cur_pos], moves) elif ((self.pursuing_enemy['name'] == 'Balloom' or self.pursuing_enemy['name'] == 'Doll') and self.pursuing_enemy['rounds_pursuing'] >= self.rounds_pursuing_limit): enemy_direction = self.find_direction( self.pursuing_enemy['last_pos'], self.pursuing_enemy['pos']) if enemy_direction is None: enemy_direction = self.last_enemy_dir if enemy_direction == 'w': self.last_enemy_dir = 'w' if not self.map.is_stone( (self.cur_pos[0] - 1, self.cur_pos[1])): return ['a'] elif not self.map.is_stone( (self.cur_pos[0], self.cur_pos[1] - 1)): return ['w'] else: self.pursuing_enemy['rounds_pursuing'] = 0 return [moves[0]] elif enemy_direction == 'a': self.last_enemy_dir = 'a' if not self.map.is_stone( (self.cur_pos[0], self.cur_pos[1] + 1)): return ['s'] elif not self.map.is_stone( (self.cur_pos[0] - 1, self.cur_pos[1])): return ['a'] else: self.pursuing_enemy['rounds_pursuing'] = 0 return [moves[0]] elif enemy_direction == 's': self.last_enemy_dir = 's' if not self.map.is_stone( (self.cur_pos[0] + 1, self.cur_pos[1])): return ['d'] elif not self.map.is_stone( (self.cur_pos[0], self.cur_pos[1] + 1)): return ['s'] else: self.pursuing_enemy['rounds_pursuing'] = 0 return [moves[0]] elif enemy_direction == 'd': self.last_enemy_dir = 'd' if not self.map.is_stone( (self.cur_pos[0], self.cur_pos[1] - 1)): return ['w'] elif not self.map.is_stone( (self.cur_pos[0] + 1, self.cur_pos[1])): return ['d'] else: self.pursuing_enemy['rounds_pursuing'] = 0 return [moves[0]] elif self.perform_last_resort: '''pos = [1,1] while self.map.is_blocked(pos) or self.map.is_stone(pos): pos = [x+1 for x in pos] if self.cur_pos == pos: return '' self.logger.info("Chosen position to camp: %s" % (pos)) path, moves = self.calculate_path(self.cur_pos, pos) return [moves[0]]''' if not self.map.is_stone( (self.cur_pos[0], self.cur_pos[1] - 1)): return ['w'] elif not self.map.is_stone( (self.cur_pos[0] - 1, self.cur_pos[1])): return ['a'] else: return [''] elif self.pursuing_enemy['name'] in [ 'Oneal', 'Minvo', 'Ovapi', 'Kondoria', 'Pass' ] and self.pursuing_enemy['rounds_pursuing'] > 10: self.pursuing_enemy['rounds_pursuing'] = 0 if self.cur_pos[0] > self.pursuing_enemy['pos'][0]: return ['a', 's', 'a', 's', 'a'] else: return ['d', 's', 'd', 's', 'd'] else: return [moves[0]] elif closest_wall != None: # self.eval_enemy = False path, moves = self.select_bomb_point(closest_wall) if not self.enemies or self.search_domain.dist( self.cur_pos, closest_wall) <= 1: moves.append('B') # leave a bomb at the end self.hide(path, moves) else: return [moves[0]] if (self.level == 3 and self.have_powerup or self.level > 3): moves.append('A') return moves def next_move(self, state): if self.enemies != None and len(self.enemies) != len( state['enemies']): #Enemy killed self.perform_last_resort = False self.cur_pos = state['bomberman'] self.enemies = state['enemies'] self.powerups = state['powerups'] self.bonus = state['bonus'] self.exit = state['exit'] #self.walls = state['walls'] ##Keep track of enemies past positions to check for loops #for enemy in self.enemies: # if enemy['id'] in self.enemy_past_pos: # old_pos = self.enemy_past_pos[enemy['id']] # new_pos = enemy['pos'] # old_pos.append(new_pos) # self.enemy_past_pos[enemy['id']] = old_pos # else: # self.enemy_past_pos[enemy['id']] = [enemy['pos']] # self.logger.info("Past enemies positions: %s" % (self.enemy_past_pos)) # Workaround to compare previous and current walls new_walls = state['walls'] if self.walls is not None: for wall in [w for w in self.walls if w not in new_walls]: self.search_domain.set_destroyed_wall(wall) self.walls = new_walls lost_life = self.lives != state['lives'] self.lives = state['lives'] #Clear after death if lost_life: self.reset_life() level = state['level'] self.update_level(level) # if queue is empty and there are no bombs placed if ((not self.decisions_queue) and not state['bombs']): self.decisions_queue = self.decide_move() if not self.decisions_queue: return [''] self.logger.debug("Path: " + str(self.decisions_queue)) next_move = self.decisions_queue.pop(0) self.logger.debug("Next move: " + str(next_move)) return next_move
class SokobanDomain(SearchDomain): def __init__(self, filename): self.level = filename self.map = Map(filename) self.states = [] self.emptyMap() def fillMap(self, state): for box in state["boxes"]: self.map.set_tile(box, Tiles.BOX) self.map.set_tile(state["player"], Tiles.MAN) def emptyMap(self): self.map.clear_tile(self.map.keeper) boxs = self.map.boxes for box in boxs: self.map.clear_tile(box) def actions(self, state): self.fillMap(state) actions = [] for direction in ["w", "a", "s", "d"]: if (self.can_move(self.map.keeper, direction)): actions += [direction] self.emptyMap() return actions def can_move(self, cur, direction): """Move an entity in the game.""" assert direction in "wasd", f"Can't move in {direction} direction" cx, cy = cur ctile = self.map.get_tile(cur) npos = cur if direction == "w": npos = cx, cy - 1 if direction == "a": npos = cx - 1, cy if direction == "s": npos = cx, cy + 1 if direction == "d": npos = cx + 1, cy # test blocked if self.map.is_blocked(npos): return False if self.map.get_tile(npos) in [ Tiles.BOX, Tiles.BOX_ON_GOAL, ]: # next position has a box? if ctile & Tiles.MAN == Tiles.MAN: # if you are the keeper you can push if not self.move( npos, direction): # as long as the pushed box can move return False else: # you are not the Keeper, so no pushing return False return True def move(self, cur, direction): """Move an entity in the game.""" assert direction in "wasd", f"Can't move in {direction} direction" cx, cy = cur ctile = self.map.get_tile(cur) npos = cur if direction == "w": npos = cx, cy - 1 if direction == "a": npos = cx - 1, cy if direction == "s": npos = cx, cy + 1 if direction == "d": npos = cx + 1, cy # test blocked if self.map.is_blocked(npos): return False if self.map.get_tile(npos) in [ Tiles.BOX, Tiles.BOX_ON_GOAL, ]: # next position has a box? if ctile & Tiles.MAN == Tiles.MAN: # if you are the keeper you can push if not self.move( npos, direction): # as long as the pushed box can move return False else: # you are not the Keeper, so no pushing return False # actually update map self.map.set_tile(npos, ctile) self.map.clear_tile(cur) return True def result(self, state, action): self.fillMap(state) self.move(self.map.keeper, action) newstate = {} newstate["player"] = self.map.keeper newstate["boxes"] = self.map.boxes self.emptyMap() return newstate def cost(self, state, action): return 1 def heuristic(self, state, goal): sum = 0 list1 = state["boxes"] list2 = goal["boxes"] for i in range(len(list1)): sum += minimal_distance(list1[i], list2[i]) return sum def satisfies(self, state, goal): self.fillMap(state) return self.map.completed def satisfies_box(self, box, goal): boxes = goal["boxes"] if box in boxes: return True return False