def auto_command_finish(self): # If the traceback does not exist then it means the bot has not found a path. if not self.traceback: return False new_player, old_player = self.traceback[-1] if self.player_location == old_player: # Push the boulder. proximal = new_player self.traceback.pop() else: # Move next to the boulder. slow_neighbor_transition = SlowNeighborTransition(self, self) loc_to_dist = AscDP.measure_all_targets([old_player], [self.player_location], slow_neighbor_transition) assert self.player_location in loc_to_dist, error_message path = AscDP.get_path(self.player_location, slow_neighbor_transition, loc_to_dist) assert len(path) > 1, error_message proximal = path[1] row, col = self.player_location nrow, ncol = proximal drow = nrow - row dcol = ncol - col delta = (drow, dcol) move_result = self.move_player(delta) if not self.traceback: self.invalidate() return move_result
def do_curses_demo(stdscr): # do not wait for a keypress when using getch() stdscr.nodelay(1) # create the subscreen to leave room at the top for a message sokoban_screen = stdscr.subpad(1, 0) # solve each level for level_string, level_name in all_level_strings_and_names: # load the list of boulder pushes from the file push_list = load_push_sequence(level_name) # all of the soko files should exist when this function is called assert push_list # load the level level = SokoMap(level_string, level_name) # show the level name on the top row stdscr.addstr(0, 0, level_name) stdscr.refresh() # play the level automatically push_index = 0 while True: # quit if a key is pressed during the demo # note that getch() usually waits for a keypress, # but this has been disabled on stdscr by the nodelay(1) call. if stdscr.getch() != -1: return # show the initial move or the result of the last move level.draw_to_curses_screen(sokoban_screen) sokoban_screen.refresh() # pause between animation frames time.sleep(.1) # if we win then go to the next level if level.is_solved(): break # are we at the square that allows us to push yet? rowa, cola, rowb, colb = push_list[push_index] drow = rowb - rowa dcol = colb - cola target_player_location = (rowa, cola) player_location = level.player_location if player_location == target_player_location: # push the boulder delta = (drow, dcol) level.move_player(delta) push_index += 1 else: # move the player in the direction of the target player location slow_neighbor_transition = SlowNeighborTransition(level, level) loc_to_dist = AscDP.measure_all_targets([target_player_location], [level.player_location], slow_neighbor_transition) path = AscDP.get_path(level.player_location, slow_neighbor_transition, loc_to_dist) best_loc = path[1] row, col = player_location nrow, ncol = best_loc delta = (nrow - row, ncol - col) level.move_player(delta) # clear the screen to prepare to show the new level map and map name stdscr.clear() stdscr.refresh()
def get_best_action(self, location, targets): """ @param location: current location @param targets: an iterable container of equally desirable target locations @return: the first action in the shortest path """ best_actions = AscDP.get_best_actions(location, targets, self.transition_table.backwards, self.transition_table.forwards) if best_actions: return list(best_actions)[0]
def __call__(self, source): """ Interesting moves include pushing the boulder and moving to a different side of the boulder to push it. """ boulder_location, player_location = source # Keep a set of the next states to return at the end of the function. next_states = set() # Make sure that the state is valid. assert self.rect.is_inbounds(boulder_location) assert self.rect.is_inbounds(player_location) assert self.level[boulder_location] not in '0-|^', (boulder_location, self.level[boulder_location]) assert self.level[player_location] not in '0-|^', (player_location, self.leve[player_location]) # Put the boulder on the floor. self.level[boulder_location] = '0' # Keep a set of the locations we want to visit to push the boulder in a different direction. target_locations = set() # This set includes all target locations and may include the current location. all_desirable_sides = set() for back, front in self.location_to_back_front_pairs[boulder_location]: # You cannot push a boulder while standing on a trap or solid object. if self.level[back] in '0^': continue # You cannot push a boulder into a solid object if self.level[front] == '0': continue # At this point the boulder is pushable if we can get behind it. all_desirable_sides.add(back) if player_location == back: # If we are already behind it then add a boulder push to the list of next states. next_states.add((front, boulder_location)) else: # Otherwise add the location behind the boulder to a list of places we want to reach. target_locations.add(back) # See which of the target locations we can reach while the boulder is on the floor. if target_locations: reachable_desirable_sides = self.side_cache.get(source, None) if not reachable_desirable_sides: reachable_desirable_sides = AscDP.flood_all_targets([player_location], all_desirable_sides, self.manhattan_transition) for back in reachable_desirable_sides: adjacent_state = (boulder_location, back) self.side_cache[adjacent_state] = reachable_desirable_sides if adjacent_state != source: next_states.add(adjacent_state) # Take the boulder back off of the floor. self.level[boulder_location] = '.' return next_states
def invalidate(self): """ A boulder has been moved so the traceback must be recalculated. """ # calling this function means something has caused the old traceback to become invalid self.traceback = None # see where the boulders are boulder_locations = [loc for loc, c in self.level.items() if c == '0'] # create solvers associated with boulder locations pq = [] distance_solver_location_triples = [] for boulder_location in boulder_locations: self.level[boulder_location] = '.' boulder_transition = BoulderTransition(self, self.level, self.floor_neighbors, self.location_to_back_front_pairs) boulder_heuristic = BoulderTransitionHeuristic(self, self.level, self.location_to_back_front_pairs) initial_state = (boulder_location, self.player_location) solver = AscDP.MeasureInformedTraceback([initial_state], boulder_transition, boulder_heuristic) solver.step() self.level[boulder_location] = '0' distance = solver.get_distance() if distance is not None: heappush(pq, (distance, solver, boulder_location)) # Keep going until a solver has finished or until they have all failed to find a solution. best_path = None while pq: distance, solver, boulder_location = heappop(pq) solution = solver.get_solution() if solution: best_path = AscDP.traceback_to_path(*solution) break self.level[boulder_location] = '.' solver.step() self.level[boulder_location] = '0' distance = solver.get_distance() if distance is not None: heappush(pq, (distance, solver, boulder_location)) # Set the path if one was found. if best_path: # Convert the path to boulder pushes. assert len(best_path) > 1 self.traceback = [] for (new_boulder, new_player), (old_boulder, old_player) in zip(best_path[0:-1], best_path[1:]): if new_boulder != old_boulder: self.traceback.append((new_player, old_player))
def __init__(self, rect, level, location_to_back_front_pairs): relaxed_reverse_transition = RelaxedBoulderReverseTransition(rect, level, location_to_back_front_pairs) relaxed_targets = set(loc for loc, c in level.items() if c == '^') self.boulder_location_to_lower_bound = AscDP.measure_all_states(relaxed_targets, relaxed_reverse_transition)