From 4509054f484625c3dd23c0f72db39410ef55d252 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 5 Dec 2024 14:03:01 +0000 Subject: [PATCH] - Add in AStar pathfinding --- Routes/Gold_Silver_Route.py | 1 + Routes/pokemon_game_desc.py | 135 +++++++++++++++++++++++++++++++++++- convert_to_pddl.py | 2 +- red_blue_goal_path.py | 9 ++- 4 files changed, 143 insertions(+), 4 deletions(-) diff --git a/Routes/Gold_Silver_Route.py b/Routes/Gold_Silver_Route.py index 8e309ea..310a1dc 100644 --- a/Routes/Gold_Silver_Route.py +++ b/Routes/Gold_Silver_Route.py @@ -104,6 +104,7 @@ def get_gold_silver_desc() -> PokemonGameDesc: desc.badges = [ZEPHYR_BADGE, HIVE_BADGE, PLAIN_BADGE, FOG_BADGE, STORM_BADGE, MINERAL_BADGE, GLACIER_BADGE, RISING_BADGE, BOULDER_BADGE, CASCADE_BADGE, THUNDER_BADGE, RAINBOW_BADGE, MARSH_BADGE, SOUL_BADGE, VOLCANO_BADGE, EARTH_BADGE] desc.hms = [CUT, SURF, FLASH, STRENGTH, FLY, WATERFALL, WHIRLPOOL] desc.additional_goals = [WOKE_SNORLAX] + #desc.must_visit = set([MT_MOON]) desc.starting_town = NEW_BARK_TOWN desc.end_goal = MT_SILVER diff --git a/Routes/pokemon_game_desc.py b/Routes/pokemon_game_desc.py index 653eee4..a429c24 100644 --- a/Routes/pokemon_game_desc.py +++ b/Routes/pokemon_game_desc.py @@ -1,6 +1,33 @@ +import heapq import networkx as nx from typing import List, Set +FLY_OUT_OF_BATTLE = 'Fly out of battle' + +class State: + def __init__(self, location, conditions, cost, path, visited_required_nodes): + self.location = location + self.conditions = conditions # A frozenset of conditions + self.cost = cost + self.path = path # List of locations visited in order + self.visited_required_nodes = visited_required_nodes # A frozenset of required nodes visited + + def __lt__(self, other): + return self.cost < other.cost # For priority queue + +def heuristic(state, goal_conditions, required_nodes): + # Since we don't have actual distances, we can use the number of badges remaining as the heuristic + remaining_conditions = goal_conditions - state.conditions + remaining_nodes = required_nodes - state.visited_required_nodes + return len(remaining_conditions) + len(remaining_nodes) + +def is_goal_state(state, goal_location, goals, required_nodes): + return ( + state.location == goal_location and + goals.issubset(state.conditions) and + required_nodes.issubset(state.visited_required_nodes) + ) + class PokemonGameDesc: def __init__(self): self.game_name: str = "" @@ -13,7 +40,113 @@ class PokemonGameDesc: self.flying_badge: str self.additional_goals: List[str] = [] self.one_way_routes: List[str] = [] - self.has_visited: List[str] = [] + self.must_visit: Set[str] = set() self.graph: nx.Graph = nx.Graph() + def astar_search(self): + from collections import deque + + self.goals = set(self.badges + self.additional_goals) + + # Priority queue for open states + open_list = [] + heapq.heappush(open_list, (0, State( + location=self.starting_town, + conditions=frozenset(), # Start with no conditions + cost=0, + path=[self.starting_town], + visited_required_nodes=frozenset([self.starting_town]) if self.starting_town in self.must_visit else frozenset() + ))) + + # Closed set to keep track of visited states + closed_set = {} + + while open_list: + _, current_state = heapq.heappop(open_list) + + # Check if we've reached the goal location with all required conditions + if is_goal_state(current_state, self.end_goal, self.goals, self.must_visit): + return current_state.path, current_state.cost, current_state.conditions + + # Check if we've already visited this state with equal or better conditions + state_key = (current_state.location, current_state.conditions, current_state.visited_required_nodes) + if state_key in closed_set and closed_set[state_key] <= current_state.cost: + continue # Skip this state + + closed_set[state_key] = current_state.cost + + # Expand neighbors via normal moves + for neighbor in self.graph.neighbors(current_state.location): + edge_data = self.graph.get_edge_data(current_state.location, neighbor) + edge_condition = edge_data.get('condition', []) + + if edge_condition is None: + edge_requires = set() + else: + edge_requires = set(edge_condition) + + # Check if we have the required conditions to traverse this edge + if not edge_requires.issubset(current_state.conditions): + continue # Can't traverse this edge + + # Update conditions based on grants at the neighbor node + neighbor_data = self.graph.nodes[neighbor] + new_conditions = set(current_state.conditions) + + # Check if the neighbor grants any conditions + grants = neighbor_data.get('grants_conditions', []) + for grant in grants: + required_for_grant = set(grant.get('required_conditions', [])) + if required_for_grant.issubset(new_conditions): + # We can acquire the condition + new_conditions.add(grant['condition']) + + # Update visited required nodes + new_visited_required_nodes = set(current_state.visited_required_nodes) + if neighbor in self.must_visit: + new_visited_required_nodes.add(neighbor) + + new_state = State( + location=neighbor, + conditions=frozenset(new_conditions), + cost=current_state.cost + 1, # Assuming uniform cost; adjust if needed + path=current_state.path + [neighbor], + visited_required_nodes=frozenset(new_visited_required_nodes) + ) + + estimated_total_cost = new_state.cost + heuristic(new_state, self.goals, self.must_visit) + + heapq.heappush(open_list, (estimated_total_cost, new_state)) + + # Expand neighbors via FLY if applicable + if FLY_OUT_OF_BATTLE in current_state.conditions and current_state.location in self.towns_and_cities: + for fly_target in self.towns_and_cities: + if fly_target != current_state.location and fly_target in current_state.path: + # You can fly to this location + new_conditions = set(current_state.conditions) + neighbor_data = self.graph.nodes[fly_target] + grants = neighbor_data.get('grants_conditions', []) + for grant in grants: + required_for_grant = set(grant.get('required_conditions', [])) + if required_for_grant.issubset(new_conditions): + new_conditions.add(grant['condition']) + + # Update visited required nodes + new_visited_required_nodes = set(current_state.visited_required_nodes) + if fly_target in self.must_visit: + new_visited_required_nodes.add(fly_target) + + fly_state = State( + location=fly_target, + conditions=frozenset(new_conditions), + cost=current_state.cost + 1, # Adjust cost if flying is different + path=current_state.path + [fly_target], + visited_required_nodes=frozenset(new_visited_required_nodes) + ) + estimated_total_cost = fly_state.cost + heuristic(fly_state, self.goals, self.must_visit) + heapq.heappush(open_list, (estimated_total_cost, fly_state)) + + return None # No path found + + __all__ = ["PokemonGameDesc"] diff --git a/convert_to_pddl.py b/convert_to_pddl.py index abbbd21..68b4080 100644 --- a/convert_to_pddl.py +++ b/convert_to_pddl.py @@ -158,7 +158,7 @@ def generate_pddl_problem(desc: PokemonGameDesc): # Goal state badges = desc.badges additional_conditions = desc.additional_goals - visited = desc.has_visited + visited = desc.must_visit problem_pddl += " (:goal\n" problem_pddl += " (and\n" problem_pddl += f" (at {format_name(desc.end_goal)})\n" diff --git a/red_blue_goal_path.py b/red_blue_goal_path.py index ba15f76..5bf656d 100644 --- a/red_blue_goal_path.py +++ b/red_blue_goal_path.py @@ -184,5 +184,10 @@ if __name__ == "__main__": red_blue = get_red_blue_desc() gold_silver = get_gold_silver_desc() - generate_pddl_problem(red_blue) - generate_pddl_problem(gold_silver) + path, _, _ = red_blue.astar_search() + print(path) + path, _, _ = gold_silver.astar_search() + print(path) + + #generate_pddl_problem(red_blue) + #generate_pddl_problem(gold_silver)