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