From c955a02e33e8fc05f92eb1827d04056a84c5169e Mon Sep 17 00:00:00 2001 From: Quildra Date: Thu, 5 Dec 2024 19:53:05 +0000 Subject: [PATCH] - Add route generation to the app --- routes/Gold_Silver_Route.py | 386 ++++++++++++++++++++++++++++ routes/Red_Blue_Route.py | 231 +++++++++++++++++ routes/__init__.py | 0 routes/pokemon_game_desc.py | 154 +++++++++++ ui/main_window_controller.py | 18 ++ ui/main_window_view.py | 42 ++- ui/workers/generate_PDDLs_worker.py | 30 +++ ui/workers/solve_pddl_route.py | 214 +++++++++++++++ utility/convert_to_pddl.py | 176 +++++++++++++ 9 files changed, 1247 insertions(+), 4 deletions(-) create mode 100644 routes/Gold_Silver_Route.py create mode 100644 routes/Red_Blue_Route.py create mode 100644 routes/__init__.py create mode 100644 routes/pokemon_game_desc.py create mode 100644 ui/workers/generate_PDDLs_worker.py create mode 100644 ui/workers/solve_pddl_route.py create mode 100644 utility/convert_to_pddl.py diff --git a/routes/Gold_Silver_Route.py b/routes/Gold_Silver_Route.py new file mode 100644 index 0000000..6270be8 --- /dev/null +++ b/routes/Gold_Silver_Route.py @@ -0,0 +1,386 @@ +import networkx as nx +from networkx import Graph + +from routes.pokemon_game_desc import PokemonGameDesc + +NEW_BARK_TOWN = 'New Bark Town' +CHERRYGROVE_CITY = 'Cherrygrove City' +VIOLET_CITY = 'Violet City' +SPROUT_TOWER = 'Sprout Tower' +RUINS_OF_ALPH = 'Ruins of Alph' +UNION_CAVE = 'Union Cave' +AZALEA_TOWN = 'Azalea Town' +SLOWPOKE_WELL = 'Slowpoke Well' +ILEX_FOREST = 'Ilex Forest' +GOLDENROD_CITY = 'Goldenrod City' +NATIONAL_PARK = 'National Park' +ECRUTEAK_CITY = 'Ecruteak City' +MOOMOO_FARM = 'MooMoo Farm' +OLIVINE_CITY = 'Olivine City' +CIANWOOD_CITY = 'Cianwood City' +MAHOGANY_TOWN = 'Mahogany Town' +LAKE_OF_RAGE = 'Lake of Rage' +TEAM_ROCKET_HQ = 'Team Rocket H.Q.' +ICE_PATH = 'Ice Path' +BLACKTHORN_CITY = 'Blackthorn City' +MT_MORTAR = 'Mt. Mortar' +TIN_TOWER = 'Tin Tower' +WHIRL_ISLANDS = 'Whirl Islands' +DARK_CAVE = 'Dark Cave' +VICTORY_ROAD = 'Victory Road' +INDIGO_PLATEAU = 'Indigo Plateau' +SS_AQUA = 'S.S. Aqua' +VERMILION_CITY = 'Vermilion City' +SAFFRON_CITY = 'Saffron City' +LAVENDER_TOWN = 'Lavender Town' +ROCK_TUNNEL = 'Rock Tunnel' +CERULEAN_CITY = 'Cerulean City' +CELADON_CITY = 'Celadon City' +FUCHSIA_CITY = 'Fuchsia City' +DIGLETTS_CAVE = "Diglett's Cave" +PEWTER_CITY = 'Pewter City' +MT_MOON = 'Mt. Moon' +VIRIDIAN_CITY = 'Viridian City' +PALLET_TOWN = 'Pallet Town' +CINNABAR_ISLAND = 'Cinnabar Island' +MT_SILVER = 'Mt. Silver' +SAFARI_ZONE = 'Safari Zone' +TOHJO_FALLS = 'Tohjo Falls' +POWER_PLANT = 'Power Plant' +LEAGUE_RECEPTION_GATE = 'League Reception Gate' + +CUT = 'Cut' +SURF = 'Surf' +FLASH = 'Flash' +STRENGTH = 'Strength' +ROCK_SMASH = 'Rock Smash' +WHIRLPOOL = 'Whirlpool' +WATERFALL = 'Waterfall' + +ZEPHYR_BADGE = 'Zephyr Badge' +HIVE_BADGE = 'Hive Badge' +PLAIN_BADGE = 'Plain Badge' +FOG_BADGE = 'Fog Badge' +STORM_BADGE = 'Storm Badge' +MINERAL_BADGE = 'Mineral Badge' +GLACIER_BADGE = 'Glacier Badge' +RISING_BADGE = 'Rising Badge' + +BOULDER_BADGE = 'Boulder Badge' +CASCADE_BADGE = 'Cascade Badge' +THUNDER_BADGE = 'Thunder Badge' +RAINBOW_BADGE = 'Rainbow Badge' +MARSH_BADGE = 'Marsh Badge' +SOUL_BADGE = 'Soul Badge' +VOLCANO_BADGE = 'Volcano Badge' +EARTH_BADGE = 'Earth Badge' + +SQUIRTBOTTLE = 'Squirt bottle' +MEDICINE = 'Medicine' +CATCH_RED_GYRADOS = 'Catch Red Gryados' +ROCKET_DEAFEATED = 'Rocket Defeated' +PKMN_WING = 'Gold Silver Wing' +JOHTO_CHAMPION = 'Johto Champion' +S_S_TICKET = 'S.S. Ticket' +WOKE_SNORLAX = 'Woke Snorlax' +EXPN_CARD = 'Expn card' +POWER_RESTORED = 'Power restored' +MACHINE_PART = 'Machine Part' +CLEFAIRY_DOLL = 'Clefairy Doll' +RAIL_PASS = 'Rail Pass' +MISTY_FOUND = 'Misty Found' +FLUTE_CHANNEL = 'Flute Channel' +ROUTE_28_UNLOCKED = 'Rotue 28 Unlocked' +POWER_PLANT_VISITED = 'POWER_PLANT_VISITED' + +FLY = 'Fly' +FLY_OUT_OF_BATTLE = 'Fly out of battle' + +def get_gold_silver_desc() -> PokemonGameDesc: + desc: PokemonGameDesc = PokemonGameDesc() + desc.graph = get_gold_silver_route() + desc.game_name = "Gold_Silver" + desc.towns_and_cities = [NEW_BARK_TOWN, CHERRYGROVE_CITY, VIOLET_CITY, AZALEA_TOWN, GOLDENROD_CITY, ECRUTEAK_CITY, OLIVINE_CITY, CIANWOOD_CITY, MAHOGANY_TOWN, BLACKTHORN_CITY, PALLET_TOWN, VIRIDIAN_CITY, PEWTER_CITY, CERULEAN_CITY, SAFFRON_CITY, CELADON_CITY, VERMILION_CITY, FUCHSIA_CITY, CINNABAR_ISLAND] + 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.starting_town = NEW_BARK_TOWN + desc.end_goal = MT_SILVER + desc.must_visit = set([MT_MOON]) + desc.games_covered = ["Gold", "Silver", "Crystal"] + desc.file_name = "gold_silver_crystal_problem.pddl" + + desc.one_way_routes.append("Route_29 -> Route_46") + + return desc + +def get_gold_silver_route() -> Graph: + G = nx.Graph() + + for i in range(1,46): + G.add_node(f'Route {i}', node_type='route') + + G.add_node(NEW_BARK_TOWN, node_type='location') + G.add_node(CHERRYGROVE_CITY, node_type='location') + G.add_node(VIOLET_CITY, node_type='location') + G.add_node(SPROUT_TOWER, node_type='location') + G.add_node(RUINS_OF_ALPH, node_type='location') + G.add_node(UNION_CAVE, node_type='location') + G.add_node(AZALEA_TOWN, node_type='location') + G.add_node(SLOWPOKE_WELL, node_type='location') + G.add_node(ILEX_FOREST, node_type='location') + G.add_node(GOLDENROD_CITY, node_type='location') + G.add_node(NATIONAL_PARK, node_type='location') + G.add_node(ECRUTEAK_CITY, node_type='location') + G.add_node(MOOMOO_FARM, node_type='location') + G.add_node(OLIVINE_CITY, node_type='location') + G.add_node(CIANWOOD_CITY, node_type='location') + G.add_node(MAHOGANY_TOWN, node_type='location') + G.add_node(LAKE_OF_RAGE, node_type='location') + G.add_node(TEAM_ROCKET_HQ, node_type='location') + G.add_node(ICE_PATH, node_type='location') + G.add_node(BLACKTHORN_CITY, node_type='location') + G.add_node(MT_MORTAR, node_type='location') + G.add_node(TIN_TOWER, node_type='location') + G.add_node(WHIRL_ISLANDS, node_type='location') + G.add_node(DARK_CAVE, node_type='location') + G.add_node(VICTORY_ROAD, node_type='location') + G.add_node(INDIGO_PLATEAU, node_type='location') + G.add_node(SS_AQUA, node_type='location') + G.add_node(VERMILION_CITY, node_type='location') + G.add_node(SAFFRON_CITY, node_type='location') + G.add_node(LAVENDER_TOWN, node_type='location') + G.add_node(ROCK_TUNNEL, node_type='location') + G.add_node(CERULEAN_CITY, node_type='location') + G.add_node(CELADON_CITY, node_type='location') + G.add_node(FUCHSIA_CITY, node_type='location') + G.add_node(DIGLETTS_CAVE, node_type='location') + G.add_node(PEWTER_CITY, node_type='location') + G.add_node(MT_MOON, node_type='location') + G.add_node(VIRIDIAN_CITY, node_type='location') + G.add_node(PALLET_TOWN, node_type='location') + G.add_node(CINNABAR_ISLAND, node_type='location') + G.add_node(MT_SILVER, node_type='location') + G.add_node(SAFARI_ZONE, node_type='location') + G.add_node(TOHJO_FALLS, node_type='location') + G.add_node(POWER_PLANT, node_type='location') + + G.add_edge(NEW_BARK_TOWN, 'Route 29', condition=None) + G.add_edge(CHERRYGROVE_CITY, 'Route 29', condition=None) + G.add_edge(CHERRYGROVE_CITY, 'Route 30', condition=None) + G.add_edge('Route 31', 'Route 30', condition=None) + G.add_edge('Route 30', DARK_CAVE, condition=None) + G.add_edge('Route 31', VIOLET_CITY, condition=None) + G.add_edge(VIOLET_CITY, 'Route 32', condition=None) + G.add_edge(VIOLET_CITY, SPROUT_TOWER, condition=None) + G.add_edge('Route 32', RUINS_OF_ALPH, condition=None) + G.add_edge('Route 32', UNION_CAVE, condition=None) + G.add_edge(UNION_CAVE, 'Route 33', condition=None) + G.add_edge(UNION_CAVE, RUINS_OF_ALPH, condition=None) + G.add_edge('Route 33', AZALEA_TOWN, condition=None) + G.add_edge('Route 33', SLOWPOKE_WELL, condition=None) + G.add_edge(AZALEA_TOWN, ILEX_FOREST, condition=None) + G.add_edge(ILEX_FOREST, 'Route 34', condition=None) + G.add_edge(GOLDENROD_CITY, 'Route 34', condition=None) + G.add_edge(GOLDENROD_CITY, 'Route 35', condition=None) + G.add_edge(NATIONAL_PARK, 'Route 35', condition=None) + G.add_edge(NATIONAL_PARK, 'Route 36', condition=[SQUIRTBOTTLE]) + G.add_edge(VIOLET_CITY, 'Route 36', condition=None) + G.add_edge('Route 37', 'Route 36', condition=[SQUIRTBOTTLE]) + G.add_edge('Route 37', ECRUTEAK_CITY, condition=None) + G.add_edge('Route 38', ECRUTEAK_CITY, condition=None) + G.add_edge('Route 38', 'Route 39', condition=None) + G.add_edge('Route 39', OLIVINE_CITY, condition=None) + G.add_edge('Route 40', OLIVINE_CITY, condition=[SURF]) + G.add_edge(OLIVINE_CITY, SS_AQUA, condition=[S_S_TICKET]) + G.add_edge('Route 40', 'Route 41', condition=[SURF]) + G.add_edge('Route 40', WHIRL_ISLANDS, condition=[SURF, WHIRLPOOL]) + G.add_edge('Route 41', WHIRL_ISLANDS, condition=[SURF, WHIRLPOOL]) + G.add_edge('Route 41', CIANWOOD_CITY, condition=[SURF]) + G.add_edge('Route 47', CIANWOOD_CITY, condition=None) + G.add_edge('Route 47', 'Route 48', condition=None) + G.add_edge('Route 47', SAFARI_ZONE, condition=None) + G.add_edge('Route 42', ECRUTEAK_CITY, condition=None) + G.add_edge(ECRUTEAK_CITY, TIN_TOWER, condition=None) + G.add_edge('Route 42', MT_MORTAR, condition=[SURF, WATERFALL]) + G.add_edge('Route 42', MAHOGANY_TOWN, condition=[SURF]) + G.add_edge('Route 43', MAHOGANY_TOWN, condition=None) + G.add_edge('Route 43', LAKE_OF_RAGE, condition=None) + G.add_edge('Route 44', MAHOGANY_TOWN, condition=[ROCKET_DEAFEATED]) + G.add_edge(TEAM_ROCKET_HQ, MAHOGANY_TOWN, condition=[CATCH_RED_GYRADOS]) + G.add_edge('Route 44', ICE_PATH, condition=[STRENGTH]) + G.add_edge(BLACKTHORN_CITY, ICE_PATH, condition=[STRENGTH]) + G.add_edge('Route 45', BLACKTHORN_CITY, condition=None) + G.add_edge('Route 45', DARK_CAVE, condition=[SURF]) + G.add_edge('Route 45', 'Route 46', condition=None) + G.add_edge('Route 46', 'Route 29', condition=None) + G.add_edge('Route 46', DARK_CAVE, condition=[ROCK_SMASH]) + G.add_edge('Route 27', NEW_BARK_TOWN, condition=[SURF, WATERFALL, WHIRLPOOL]) + G.add_edge('Route 26', 'Route 27', condition=None) + G.add_edge('Route 27', TOHJO_FALLS, condition=None) + + G.add_edge(LEAGUE_RECEPTION_GATE, 'Route 26', condition=None) + G.add_edge(LEAGUE_RECEPTION_GATE, 'Route 28', condition=[ROUTE_28_UNLOCKED]) + G.add_edge(LEAGUE_RECEPTION_GATE, 'Route 22', condition=[WOKE_SNORLAX]) + G.add_edge(LEAGUE_RECEPTION_GATE, 'Route 23', condition=None) + + G.add_edge('Route 28', MT_SILVER, condition=None) + G.add_edge('Route 23', VICTORY_ROAD, condition=[ZEPHYR_BADGE, HIVE_BADGE, PLAIN_BADGE, FOG_BADGE, STORM_BADGE, MINERAL_BADGE, GLACIER_BADGE, RISING_BADGE]) + + G.add_edge(SS_AQUA, VERMILION_CITY, condition=None) + G.add_edge(SAFFRON_CITY, GOLDENROD_CITY, condition=[POWER_RESTORED, RAIL_PASS]) + G.add_edge(DIGLETTS_CAVE, 'Route 11', condition=[WOKE_SNORLAX]) + + G.add_edge(PALLET_TOWN, 'Route 1', condition=None) + G.add_edge(PALLET_TOWN, 'Route 21', condition=[SURF]) + G.add_edge('Route 1', VIRIDIAN_CITY, condition=None) + G.add_edge(VIRIDIAN_CITY, 'Route 2', condition=None) + G.add_edge('Route 2', 'Viridian Forest', condition=None) + G.add_edge('Route 2', 'Route 3', condition=[CUT]) + G.add_edge('Viridian Forest', PEWTER_CITY, condition=None) + G.add_edge(PEWTER_CITY, 'Route 3', condition=None) + G.add_edge('Route 3', MT_MOON, condition=None) + G.add_edge(MT_MOON, 'Route 4', condition=None) + G.add_edge('Route 4', CERULEAN_CITY, condition=None) + G.add_edge(CERULEAN_CITY, 'Route 24', condition=None) + G.add_edge(CERULEAN_CITY, 'Route 9', condition=[CUT]) + G.add_edge(CERULEAN_CITY, 'Route 5', condition=None) + G.add_edge('Route 5', SAFFRON_CITY, condition=None) + G.add_edge(SAFFRON_CITY, 'Route 6', condition=None) + G.add_edge(SAFFRON_CITY, 'Route 7', condition=None) + G.add_edge(SAFFRON_CITY, 'Route 8', condition=None) + G.add_edge('Route 6', VERMILION_CITY, condition=None) + G.add_edge(VERMILION_CITY, 'Route 11', condition=None) + G.add_edge('Route 11', 'Route 12', condition=None) + G.add_edge('Route 11', DIGLETTS_CAVE, condition=None) + G.add_edge('Route 2', DIGLETTS_CAVE, condition=[CUT]) + G.add_edge('Route 12', 'Route 13', condition=None) + G.add_edge('Route 12', LAVENDER_TOWN, condition=None) + G.add_edge('Route 7', LAVENDER_TOWN, condition=None) + G.add_edge(LAVENDER_TOWN, 'Route 10', condition=None) + G.add_edge('Route 9', ROCK_TUNNEL, condition=[FLASH]) + G.add_edge('Route 10', ROCK_TUNNEL, condition=[FLASH]) + G.add_edge(CELADON_CITY, 'Route 8', condition=None) + G.add_edge(CELADON_CITY, 'Route 16', condition=None) + G.add_edge('Route 16', 'Route 17', condition=None) + G.add_edge('Route 17', 'Route 18', condition=None) + G.add_edge('Route 18', FUCHSIA_CITY, condition=None) + G.add_edge(FUCHSIA_CITY,'Route 19', condition=None) + G.add_edge(FUCHSIA_CITY,'Route 15', condition=None) + G.add_edge('Route 19', 'Seafoam Islands', condition=[SURF]) + G.add_edge('Seafoam Islands', 'Route 20', condition=[SURF]) + G.add_edge('Route 20', CINNABAR_ISLAND, condition=[SURF]) + G.add_edge('Route 21', CINNABAR_ISLAND, condition=[SURF]) + G.add_edge('Route 22', VIRIDIAN_CITY, condition=None) + G.add_edge(VICTORY_ROAD, INDIGO_PLATEAU, condition=None) + G.add_edge('Route 12', 'Route 13', condition=None) + G.add_edge('Route 13', 'Route 14', condition=None) + G.add_edge('Route 14', 'Route 15', condition=None) + G.add_edge('Route 24', 'Route 25', condition=None) + G.add_edge('Route 10', POWER_PLANT, condition=[SURF]) + G.add_edge('Route 5', 'Underground Path', condition=None) + G.add_edge('Underground Path', 'Route 6', condition=None) + + G.nodes[NEW_BARK_TOWN]['grants_conditions'] = [ + {'condition': S_S_TICKET, 'required_conditions': [JOHTO_CHAMPION]} + ] + G.nodes[VIOLET_CITY]['grants_conditions'] = [ + {'condition': ZEPHYR_BADGE, 'required_conditions': []} + ] + G.nodes[SPROUT_TOWER]['grants_conditions'] = [ + {'condition': FLASH, 'required_conditions': []} + ] + G.nodes[AZALEA_TOWN]['grants_conditions'] = [ + {'condition': HIVE_BADGE, 'required_conditions': []} + ] + G.nodes[ILEX_FOREST]['grants_conditions'] = [ + {'condition': CUT, 'required_conditions': []} + ] + G.nodes[GOLDENROD_CITY]['grants_conditions'] = [ + {'condition': PLAIN_BADGE, 'required_conditions': []}, + {'condition': SQUIRTBOTTLE, 'required_conditions': []}, + {'condition': PKMN_WING, 'required_conditions': [GLACIER_BADGE]}, + ] + G.nodes[ECRUTEAK_CITY]['grants_conditions'] = [ + {'condition': SURF, 'required_conditions': []}, + {'condition': FOG_BADGE, 'required_conditions': []} + ] + G.nodes['Route 36']['grants_conditions'] = [ + {'condition': ROCK_SMASH, 'required_conditions': [SQUIRTBOTTLE]} + ] + G.nodes[OLIVINE_CITY]['grants_conditions'] = [ + {'condition': MINERAL_BADGE, 'required_conditions': [MEDICINE]}, + {'condition': STRENGTH, 'required_conditions': []}, + ] + G.nodes[CIANWOOD_CITY]['grants_conditions'] = [ + {'condition': STORM_BADGE, 'required_conditions': []}, + {'condition': MEDICINE, 'required_conditions': []}, + {'condition': FLY, 'required_conditions': [STORM_BADGE]}, + {'condition': FLY_OUT_OF_BATTLE, 'required_conditions': [STORM_BADGE]}, + ] + G.nodes[MAHOGANY_TOWN]['grants_conditions'] = [ + {'condition': GLACIER_BADGE, 'required_conditions': [ROCKET_DEAFEATED]} + ] + G.nodes[TEAM_ROCKET_HQ]['grants_conditions'] = [ + {'condition': ROCKET_DEAFEATED, 'required_conditions': []}, + {'condition': WHIRLPOOL, 'required_conditions': []} + ] + G.nodes[LAKE_OF_RAGE]['grants_conditions'] = [ + {'condition': CATCH_RED_GYRADOS, 'required_conditions': []} + ] + G.nodes[ICE_PATH]['grants_conditions'] = [ + {'condition': WATERFALL, 'required_conditions': []} + ] + G.nodes[BLACKTHORN_CITY]['grants_conditions'] = [ + {'condition': RISING_BADGE, 'required_conditions': []} + ] + G.nodes[INDIGO_PLATEAU]['grants_conditions'] = [ + {'condition': JOHTO_CHAMPION, 'required_conditions': []} + ] + G.nodes[VERMILION_CITY]['grants_conditions'] = [ + {'condition': THUNDER_BADGE, 'required_conditions': []}, + {'condition': FLUTE_CHANNEL, 'required_conditions': [EXPN_CARD]}, + {'condition': CLEFAIRY_DOLL, 'required_conditions': [POWER_RESTORED]}, + ] + G.nodes[SAFFRON_CITY]['grants_conditions'] = [ + {'condition': MARSH_BADGE, 'required_conditions': []}, + {'condition': RAIL_PASS, 'required_conditions': [CLEFAIRY_DOLL]}, + ] + G.nodes[LAVENDER_TOWN]['grants_conditions'] = [ + {'condition': EXPN_CARD, 'required_conditions': [POWER_RESTORED]} + ] + G.nodes[POWER_PLANT]['grants_conditions'] = [ + {'condition': POWER_PLANT_VISITED, 'required_conditions': []}, + {'condition': POWER_RESTORED, 'required_conditions': [MACHINE_PART]} + ] + G.nodes[CERULEAN_CITY]['grants_conditions'] = [ + {'condition': CASCADE_BADGE, 'required_conditions': [MISTY_FOUND]}, + {'condition': MACHINE_PART, 'required_conditions': [POWER_PLANT_VISITED]} + ] + G.nodes['Route 25']['grants_conditions'] = [ + {'condition': MISTY_FOUND, 'required_conditions': []} + ] + G.nodes[CELADON_CITY]['grants_conditions'] = [ + {'condition': RAINBOW_BADGE, 'required_conditions': []} + ] + G.nodes[FUCHSIA_CITY]['grants_conditions'] = [ + {'condition': SOUL_BADGE, 'required_conditions': []} + ] + G.nodes['Route 11']['grants_conditions'] = [ + {'condition': WOKE_SNORLAX, 'required_conditions': [FLUTE_CHANNEL]} + ] + G.nodes[PEWTER_CITY]['grants_conditions'] = [ + {'condition': BOULDER_BADGE, 'required_conditions': []}, + {'condition': PKMN_WING, 'required_conditions': []}, + ] + G.nodes[VIRIDIAN_CITY]['grants_conditions'] = [ + {'condition': EARTH_BADGE, 'required_conditions': [VOLCANO_BADGE]}, + ] + G.nodes[PALLET_TOWN]['grants_conditions'] = [ + {'condition': ROUTE_28_UNLOCKED, 'required_conditions': [BOULDER_BADGE, CASCADE_BADGE, THUNDER_BADGE, RAINBOW_BADGE, MARSH_BADGE, SOUL_BADGE, VOLCANO_BADGE, EARTH_BADGE]}, + ] + G.nodes[CINNABAR_ISLAND]['grants_conditions'] = [ + {'condition': VOLCANO_BADGE, 'required_conditions': []}, + ] + + return G \ No newline at end of file diff --git a/routes/Red_Blue_Route.py b/routes/Red_Blue_Route.py new file mode 100644 index 0000000..af8a269 --- /dev/null +++ b/routes/Red_Blue_Route.py @@ -0,0 +1,231 @@ +import networkx as nx +from networkx import Graph + +from routes.pokemon_game_desc import PokemonGameDesc + +PALLET_TOWN = 'Pallet Town' +VIRIDIAN_CITY = 'Viridian City' +VIRIDIAN_FOREST = 'Viridian Forest' +PEWTER_CITY = 'Pewter City' +MT_MOON = 'Mt. Moon' +CERULEAN_CITY = 'Cerulean City' +POWER_PLANT = 'Power Plant' +SAFFRON_CITY = 'Saffron City' +CELADON_CITY = 'Celadon City' +CERULEAN_CAVE = 'Cerulean Cave' +VERMILLION_CITY = 'Vermillion City' +BILLS_HOUSE = 'Bill\'s House' +FUCHSIA_CITY = 'Fuchsia City' +SS_ANNE = 'S.S. Anne' +CINNABAR_ISLAND = 'Cinnabar Island' +SEAFOAM_ISLANDS ='Seafoam Islands' +VICTORY_ROAD = 'Victory Road' +INDIGO_PLATEAU = 'Indigo Plateau' +ROCK_TUNNEL = 'Rock Tunnel' +UNDERGROUND_PATH = 'Underground Path' +UNDERGROUND_PASSAGE = 'Underground Passage' +DIGLETT_CAVE = 'Diglett Cave' +POKEMON_TOWER = 'Pokemon Tower' +LAVENDER_TOWN = 'Lavender Town' + +BOULDER_BADGE = 'Boulder Badge' +CASCADE_BADGE = 'Cascade Badge' +THUNDER_BADGE = 'Thunder Badge' +RAINBOW_BADGE = 'Rainbow Badge' +MARSH_BADGE = 'Marsh Badge' +SOUL_BADGE = 'Soul Badge' +VOLCANO_BADGE = 'Volcano Badge' +EARTH_BADGE = 'Earth Badge' + +BIKE = 'Bike' +BIKE_VOUCHER = 'Bike Voucer' +SS_ANNE_TICKET = 'S.S. Anne Ticket' +QUENCHED_THURST = 'Quenched Thrust' +POKE_FLUTE = 'Poke Flute' +SILPH_SCOPE = 'Silph Scope' +GIOVANNI_FIGHT = 'Giovanni Fight' +CHAMPION = 'Champion' +MEWTWO = 'Mewtwo' +ARCTICUNO = 'Arcticuno' +ZAPDOS = 'Zapdos' +MOLTRES = 'Moltres' + +CUT = 'Cut' +SURF = 'Surf' +FLASH = 'Flash' +STRENGTH = 'Strength' +FLY = 'Fly' + +FLY_OUT_OF_BATTLE = 'Fly out of battle' + +def get_red_blue_desc() -> PokemonGameDesc: + desc: PokemonGameDesc = PokemonGameDesc() + desc.graph = get_red_blue_route() + desc.game_name = "Red_Blue" + desc.towns_and_cities = [PALLET_TOWN, VIRIDIAN_CITY, PEWTER_CITY, CERULEAN_CITY, SAFFRON_CITY, CELADON_CITY, VERMILLION_CITY, FUCHSIA_CITY, CINNABAR_ISLAND] + desc.badges = [BOULDER_BADGE, CASCADE_BADGE, THUNDER_BADGE, RAINBOW_BADGE, MARSH_BADGE, SOUL_BADGE, VOLCANO_BADGE, EARTH_BADGE] + desc.hms = [CUT, SURF, FLASH, STRENGTH, FLY] + desc.starting_town = PALLET_TOWN + desc.end_goal = CERULEAN_CAVE + + desc.file_name = "red_blue_yellow_problem.pddl" + desc.games_covered = ["Red", "Blue", "Yellow"] + + return desc + +def get_red_blue_route() -> Graph: + G = nx.Graph() + + for i in range(1,25): + G.add_node(f'Route {i}', node_type='route') + + G.nodes['Route 2']['grants_conditions'] = [ + {'condition': FLASH, 'required_conditions': [CUT]} + ] + + G.nodes['Route 16']['grants_conditions'] = [ + {'condition': FLY, 'required_conditions': [CUT]} + ] + + G.add_node(PALLET_TOWN, node_type='location') + G.add_node(VIRIDIAN_CITY, node_type='location') + G.nodes[VIRIDIAN_CITY]['grants_conditions'] = [ + {'condition': EARTH_BADGE, 'required_conditions': [GIOVANNI_FIGHT]} + ] + G.add_node(VIRIDIAN_FOREST, node_type='location') + G.add_node(PEWTER_CITY, node_type='location') + G.nodes[PEWTER_CITY]['grants_conditions'] = [ + {'condition': BOULDER_BADGE, 'required_conditions': []} + ] + G.add_node(MT_MOON, node_type='location') + G.add_node(CERULEAN_CITY, node_type='location') + G.nodes[CERULEAN_CITY]['grants_conditions'] = [ + {'condition': CASCADE_BADGE, 'required_conditions': []}, + {'condition': BIKE, 'required_conditions': [BIKE_VOUCHER]}, + ] + G.add_node(BILLS_HOUSE, node_type='location') + G.nodes[BILLS_HOUSE]['grants_conditions'] = [ + {'condition': SS_ANNE_TICKET, 'required_conditions': []} + ] + G.add_node(POWER_PLANT, node_type='location') + G.nodes[POWER_PLANT]['grants_conditions'] = [ + {'condition': ZAPDOS, 'required_conditions': []} + ] + G.add_node(SAFFRON_CITY, node_type='location') + G.nodes[SAFFRON_CITY]['grants_conditions'] = [ + {'condition': MARSH_BADGE, 'required_conditions': []}, + {'condition': GIOVANNI_FIGHT, 'required_conditions': []}, + ] + G.add_node(SS_ANNE, node_type='location') + G.nodes[SS_ANNE]['grants_conditions'] = [ + {'condition': CUT, 'required_conditions': []} + ] + G.add_node(CERULEAN_CAVE, node_type='location') + G.nodes[CERULEAN_CAVE]['grants_conditions'] = [ + {'condition': MEWTWO, 'required_conditions': []} + ] + G.add_node(VERMILLION_CITY, node_type='location') + G.nodes[VERMILLION_CITY]['grants_conditions'] = [ + {'condition': BIKE_VOUCHER, 'required_conditions': []}, + {'condition': THUNDER_BADGE, 'required_conditions': [CUT]}, + {'condition': FLY_OUT_OF_BATTLE, 'required_conditions': [CUT]} + ] + G.add_node(CELADON_CITY, node_type='location') + G.nodes[CELADON_CITY]['grants_conditions'] = [ + {'condition': QUENCHED_THURST, 'required_conditions': []}, + {'condition': RAINBOW_BADGE, 'required_conditions': []}, + {'condition': SILPH_SCOPE, 'required_conditions': []} + ] + G.add_node(FUCHSIA_CITY, node_type='location') + G.nodes[FUCHSIA_CITY]['grants_conditions'] = [ + {'condition': SURF, 'required_conditions': []}, + {'condition': STRENGTH, 'required_conditions': []}, + {'condition': SOUL_BADGE, 'required_conditions': []}, + ] + G.add_node(CINNABAR_ISLAND, node_type='location') + G.nodes[CINNABAR_ISLAND]['grants_conditions'] = [ + {'condition': VOLCANO_BADGE, 'required_conditions': []} + ] + G.add_node(SEAFOAM_ISLANDS, node_type='location') + G.nodes[SEAFOAM_ISLANDS]['grants_conditions'] = [ + {'condition': ARCTICUNO, 'required_conditions': []} + ] + G.add_node(VICTORY_ROAD, node_type='location') + G.nodes[VICTORY_ROAD]['grants_conditions'] = [ + {'condition': MOLTRES, 'required_conditions': []} + ] + G.add_node(INDIGO_PLATEAU, node_type='location') + G.nodes[INDIGO_PLATEAU]['grants_conditions'] = [ + {'condition': CHAMPION, 'required_conditions': []} + ] + G.add_node(ROCK_TUNNEL, node_type='location') + G.add_node(UNDERGROUND_PATH, node_type='location') + G.add_node(DIGLETT_CAVE, node_type='location') + G.add_node(POKEMON_TOWER, node_type='location') + G.nodes[POKEMON_TOWER]['grants_conditions'] = [ + {'condition': POKE_FLUTE, 'required_conditions': []} + ] + G.add_node(LAVENDER_TOWN, node_type='location') + G.add_node(UNDERGROUND_PASSAGE, node_type='location') + + + G.add_edge('Pallet Town', 'Route 1', condition=None) + G.add_edge('Pallet Town', 'Route 21', condition=[SURF]) + G.add_edge('Route 1', 'Viridian City', condition=None) + G.add_edge('Viridian City', 'Route 2', condition=None) + G.add_edge('Route 2', 'Viridian Forest', condition=None) + G.add_edge('Route 2', 'Route 3', condition=[CUT]) + G.add_edge('Viridian Forest', 'Pewter City', condition=None) + G.add_edge('Pewter City', 'Route 3', condition=None) + G.add_edge('Route 3', 'Mt. Moon', condition=None) + G.add_edge('Mt. Moon', 'Route 4', condition=None) + G.add_edge('Route 4', 'Cerulean City', condition=None) + G.add_edge('Cerulean City', 'Route 24', condition=None) + G.add_edge('Cerulean City', 'Route 9', condition=[CUT]) + G.add_edge('Cerulean City', 'Route 5', condition=None) + G.add_edge('Route 5', 'Saffron City', condition=[QUENCHED_THURST]) + G.add_edge('Saffron City', 'Route 6', condition=[QUENCHED_THURST]) + G.add_edge('Saffron City', 'Route 7', condition=[QUENCHED_THURST]) + G.add_edge('Saffron City', 'Route 8', condition=[QUENCHED_THURST]) + G.add_edge('Route 6', 'Vermillion City', condition=None) + G.add_edge('Vermillion City', 'Route 11', condition=None) + G.add_edge('Vermillion City', SS_ANNE, condition=[SS_ANNE_TICKET]) + G.add_edge('Route 11', 'Route 12', condition=[POKE_FLUTE]) + G.add_edge('Route 11', DIGLETT_CAVE, condition=None) + G.add_edge('Route 2', DIGLETT_CAVE, condition=[CUT]) + G.add_edge('Route 12', 'Route 13', condition=None) + G.add_edge('Route 12', LAVENDER_TOWN, condition=None) + G.add_edge('Route 7', LAVENDER_TOWN, condition=None) + G.add_edge(LAVENDER_TOWN, 'Route 10', condition=None) + G.add_edge('Route 9', 'Rock Tunnel', condition=[FLASH]) + G.add_edge('Route 10', 'Rock Tunnel', condition=[FLASH]) + G.add_edge('Celadon City', 'Route 8', condition=None) + G.add_edge('Celadon City', 'Route 16', condition=None) + G.add_edge('Route 16', 'Route 17', condition=[POKE_FLUTE]) + G.add_edge('Route 17', 'Route 18', condition=[BIKE]) + G.add_edge('Route 18', 'Fuchsia City', condition=[BIKE]) + G.add_edge('Fuchsia City','Route 19', condition=None) + G.add_edge('Fuchsia City','Route 15', condition=None) + G.add_edge('Fuchsia City','Safari Zone', condition=None) + G.add_edge('Route 19', 'Seafoam Islands', condition=[SURF]) + G.add_edge('Seafoam Islands', 'Route 20', condition=[SURF]) + G.add_edge('Route 20', 'Cinnabar Island', condition=[SURF]) + G.add_edge('Route 21', 'Cinnabar Island', condition=[SURF]) + G.add_edge('Route 22', 'Viridian City', condition=None) + G.add_edge('Route 22', 'Route 23', condition=[SURF]) + G.add_edge('Route 23', 'Victory Road', condition=[BOULDER_BADGE, CASCADE_BADGE, THUNDER_BADGE, RAINBOW_BADGE, MARSH_BADGE, SOUL_BADGE, VOLCANO_BADGE, EARTH_BADGE]) + G.add_edge('Victory Road', 'Indigo Plateau', condition=None) + G.add_edge('Route 12', 'Route 13', condition=None) + G.add_edge('Route 13', 'Route 14', condition=None) + G.add_edge('Route 14', 'Route 15', condition=None) + G.add_edge('Route 24', 'Cerulean Cave', condition=[SURF, CHAMPION]) + G.add_edge('Route 24', 'Route 25', condition=None) + G.add_edge('Route 25', BILLS_HOUSE, condition=None) + G.add_edge('Route 10', 'Power Plant', condition=[SURF]) + G.add_edge('Route 5', 'Underground Path', condition=None) + G.add_edge('Underground Path', 'Route 6', condition=None) + G.add_edge(LAVENDER_TOWN, POKEMON_TOWER, condition=[SILPH_SCOPE]) + G.add_edge(UNDERGROUND_PASSAGE, 'Route 7', condition=None) + G.add_edge(UNDERGROUND_PASSAGE, 'Route 8', condition=None) + + return G \ No newline at end of file diff --git a/routes/__init__.py b/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/routes/pokemon_game_desc.py b/routes/pokemon_game_desc.py new file mode 100644 index 0000000..94e6ca5 --- /dev/null +++ b/routes/pokemon_game_desc.py @@ -0,0 +1,154 @@ +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 = "" + self.towns_and_cities: Set[str] = set() + self.badges: Set[str] = set() + self.items: Set[str] = set() + self.hms: Set[str] = set() + self.starting_town: str + self.end_goal: str + self.flying_badge: str + self.additional_goals: List[str] = [] + self.one_way_routes: List[str] = [] + self.must_visit: Set[str] = set() + self.graph: nx.Graph = nx.Graph() + self.games_covered: List[str] = [] + self.file_name: str = "" + + 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/ui/main_window_controller.py b/ui/main_window_controller.py index d60307c..2425f57 100644 --- a/ui/main_window_controller.py +++ b/ui/main_window_controller.py @@ -4,6 +4,7 @@ from PyQt6.QtWidgets import QMenu from PyQt6.QtGui import QAction import os +from routes.pokemon_game_desc import PokemonGameDesc from ui.workers.calculate_origin_mark_worker import CalculateOriginMarkWorker from ui.workers.gather_baby_form_status import GatherBabyStatusWorker from ui.workers.gather_encounter_locations import GatherEncountersWorker @@ -11,7 +12,9 @@ from ui.workers.gather_home_storage_status_worker import GatherHomeStorageStatus from ui.workers.gather_pokemon_forms_worker import GatherPokemonFormsWorker from ui.workers.gather_evolutions_worker import GatherEvolutions +from ui.workers.generate_PDDLs_worker import GeneratePDDLsWorker from ui.workers.generate_plan_worker import GeneratePlanWorker +from ui.workers.solve_pddl_route import SolvePDDLsWorker from utility.functions import get_display_name, sanitize_filename from db import db @@ -256,3 +259,18 @@ class MainWindowController: with open(output_file, 'w', encoding='utf-8') as f: json.dump(full_plan, f, indent=4, ensure_ascii=False) + def generate_pddls(self): + worker = GeneratePDDLsWorker() + worker.signals.finished.connect(self.on_pddls_generated) + self.thread_pool.start(worker) + + def on_pddls_generated(self, data): + self.view.update_route_list(data) + + def solve_route(self, data: PokemonGameDesc): + worker = SolvePDDLsWorker(data) + worker.signals.finished.connect(self.on_route_solved) + self.thread_pool.start(worker) + + def on_route_solved(self, data): + pass \ No newline at end of file diff --git a/ui/main_window_view.py b/ui/main_window_view.py index 37d196b..8e139d4 100644 --- a/ui/main_window_view.py +++ b/ui/main_window_view.py @@ -1,9 +1,12 @@ +from typing import List from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QLineEdit, QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget, QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox, QSpinBox, QMenu, QTabWidget, QTextEdit) from PyQt6.QtCore import Qt, QSize, QTimer, QMetaObject from PyQt6.QtGui import QPixmap, QFontMetrics, QColor, QAction + +from routes.pokemon_game_desc import PokemonGameDesc from .main_window_controller import MainWindowController from utility.functions import get_display_name @@ -27,6 +30,7 @@ class PokemonUI(QWidget): self.setup_db_operations_tab() self.setup_manage_encounters_tab() self.setup_plan_tab() + self.setup_route_planning_tab() self.save_button = QPushButton("Save Changes") self.save_button.clicked.connect(self.controller.save_changes) @@ -225,9 +229,28 @@ class PokemonUI(QWidget): plan_tab_layout.addStretch(1) - generate_plan__btn = QPushButton("Generate Plan") - generate_plan__btn.clicked.connect(self.controller.generate_plan) - plan_tab_layout.addWidget(generate_plan__btn) + generate_plan_btn = QPushButton("Generate Plan") + generate_plan_btn.clicked.connect(self.controller.generate_plan) + plan_tab_layout.addWidget(generate_plan_btn) + + def setup_route_planning_tab(self): + route_plan_tab = QWidget() + route_plan_tab_layout = QVBoxLayout(route_plan_tab) + self.tab_widget.addTab(route_plan_tab, "Route Generation") + + self.route_combo_box = QComboBox() + route_plan_tab_layout.addWidget(self.route_combo_box) + + solve_route_btn = QPushButton("Solve Selected Route") + solve_route_btn.clicked.connect(self.solve_route) + route_plan_tab_layout.addWidget(solve_route_btn) + + route_plan_tab_layout.addStretch(1) + + generate_pddls_btn = QPushButton("Generate PDDLs") + generate_pddls_btn.clicked.connect(self.controller.generate_pddls) + route_plan_tab_layout.addWidget(generate_pddls_btn) + def update_pokemon_forms(self, data): self.pokemon_list.clear() @@ -365,4 +388,15 @@ class PokemonUI(QWidget): #self.encounter_cache[pfic] = len(encounters) > 0 # After updating the locations tree - #self.update_pokemon_list_highlights() \ No newline at end of file + #self.update_pokemon_list_highlights() + + def update_route_list(self, data: List[PokemonGameDesc]): + self.route_combo_box.clear() + + for item in data: + self.route_combo_box.addItem(item.game_name, item) + + def solve_route(self): + index = self.route_combo_box.currentIndex() + data = self.route_combo_box.itemData(index, role=Qt.ItemDataRole.UserRole) + self.controller.solve_route(data) diff --git a/ui/workers/generate_PDDLs_worker.py b/ui/workers/generate_PDDLs_worker.py new file mode 100644 index 0000000..8814a77 --- /dev/null +++ b/ui/workers/generate_PDDLs_worker.py @@ -0,0 +1,30 @@ +from PyQt6.QtCore import QObject, pyqtSignal, QRunnable + +from routes.Gold_Silver_Route import get_gold_silver_desc +from routes.Red_Blue_Route import get_red_blue_desc +from utility.convert_to_pddl import generate_pddl_domain, generate_pddl_problem + +class GeneratePDDLsWorkerSignals(QObject): + finished = pyqtSignal(list) + +class GeneratePDDLsWorker(QRunnable): + def __init__(self): + super().__init__() + self.signals = GeneratePDDLsWorkerSignals() + + def run(self): + try: + gathered_data = self.generate_PDDLs() + self.signals.finished.emit(gathered_data) + except Exception as e: + print(f"Error gathering Pokémon home storage status: {e}") + + def generate_PDDLs(self): + generate_pddl_domain() + red_blue = get_red_blue_desc() + gold_silver = get_gold_silver_desc() + + generate_pddl_problem(red_blue) + generate_pddl_problem(gold_silver) + + return [red_blue, gold_silver] \ No newline at end of file diff --git a/ui/workers/solve_pddl_route.py b/ui/workers/solve_pddl_route.py new file mode 100644 index 0000000..229bdb8 --- /dev/null +++ b/ui/workers/solve_pddl_route.py @@ -0,0 +1,214 @@ +import json +import re +import sys +from PyQt6.QtCore import QObject, pyqtSignal, QRunnable +import subprocess +import os +from db import db + +from routes.pokemon_game_desc import PokemonGameDesc + +from downward.driver.main import main +from utility.convert_to_pddl import format_name, generate_pddl_problem + +class SolvePDDLsWorkerSignals(QObject): + finished = pyqtSignal(list) + +class SolvePDDLsWorker(QRunnable): + def __init__(self, data: PokemonGameDesc): + super().__init__() + self.signals = SolvePDDLsWorkerSignals() + self.data = data + + def run(self): + try: + gathered_data = self.solve_PDDL(self.data) + self.signals.finished.emit(gathered_data) + except Exception as e: + print(f"Error gathering Pokémon home storage status: {e}") + + def solve_PDDL(self, data: PokemonGameDesc, game_name: str = ""): + if game_name == "": + game_name = "Crystal" + + route, _, _ = data.astar_search() + + locations = set(route) + + print("Unique locations visisted: ", locations) + + output_file = "./temp/plan.json" + plan = {} + with open(output_file, 'r', encoding='utf-8') as f: + plan = json.load(f) + + target = None + for game in plan: + if game["game_name"] == game_name: + target = game + break + + if not target: + return [] + + # gather up all the pokemon needed for say crystal and find all the encounter routes/locations + needed_locations = set() + + game_info = db.get_game_id_by_name(game_name) + + for key in target["pokemon"]: + catch_stats = target["pokemon"][key] + rep = catch_stats["representative"] + rand = db.get_encounters(rep, "random") + static = db.get_encounters(rep, "static") + encounters = [] + encounters.extend(rand) + encounters.extend(static) + + for encounter in encounters: + if encounter["game_id"] != game_info["id"]: + continue + + encounter_data = json.loads(encounter["data"]) + needed_locations.add(encounter_data["location"].replace("*", "")) + + # compare those to all the ones we visit in the generated route + missing_locations = needed_locations - locations + print("missing_locations:", missing_locations) + + node_list = list(data.graph.nodes()) + + # add missing ones to the has_visited list and re-gen the problem, then run it again + nodes_to_visit = [] + for loc in missing_locations: + if loc in node_list: + nodes_to_visit.append(loc) + + print("Additional Nodes to Visit:", nodes_to_visit) + + data.must_visit = data.must_visit | set(nodes_to_visit) + + route, _, _ = data.astar_search() + + ordered_dict = dict.fromkeys(route) + unique_list = list(ordered_dict) + + return unique_list + + def solve_PDDL_old(self, data: PokemonGameDesc, game_name: str = ""): + if game_name == "": + game_name = "Crystal" + + plan_file_name = f'./{game_name.lower()}_sas_plan' + + if os.path.exists(os.path.abspath(plan_file_name)) == False: + self.generate_sas_plan(data) + + locations = set() + pattern = re.compile(r"\((?:move|fly)\s+(\w+)\s+(\w+)\)") + with open(plan_file_name, 'r') as f: + for line in f: + # Find all matches for move and fly actions + matches = pattern.findall(line) + for loc1, loc2 in matches: + # Add both locations to the set + locations.add(loc1) + locations.add(loc2) + + print("Unique locations visisted: ", locations) + + output_file = "./temp/plan.json" + plan = {} + with open(output_file, 'r', encoding='utf-8') as f: + plan = json.load(f) + + target = None + for game in plan: + if game["game_name"] == game_name: + target = game + break + + if not target: + return [] + + # gather up all the pokemon needed for say crystal and find all the encounter routes/locations + needed_locations = set() + + game_info = db.get_game_id_by_name(game_name) + + for key in target["pokemon"]: + catch_stats = target["pokemon"][key] + rep = catch_stats["representative"] + rand = db.get_encounters(rep, "random") + static = db.get_encounters(rep, "static") + encounters = [] + encounters.extend(rand) + encounters.extend(static) + + for encounter in encounters: + if encounter["game_id"] != game_info["id"]: + continue + + encounter_data = json.loads(encounter["data"]) + needed_locations.add(format_name(encounter_data["location"].replace("*", "")).lower()) + + # compare those to all the ones we visit in the generated route + missing_locations = needed_locations - locations + print("missing_locations:", missing_locations) + + node_list = list(data.graph.nodes()) + for i in range(len(node_list)): + node_list[i] = format_name(node_list[i]) + + # add missing ones to the has_visited list and re-gen the problem, then run it again + nodes_to_visit = [] + for loc in missing_locations: + if loc in node_list: + nodes_to_visit.append(loc) + + print("Additional Nodes to Visit:", nodes_to_visit) + + data.has_visited.extend(nodes_to_visit) + + generate_pddl_problem(data) + + self.generate_sas_plan(data) + + return [] + + def generate_sas_plan(self, data: PokemonGameDesc): + print("Starting fast-downward...") + try: + command_translate = [ + 'python', + os.path.abspath('./downward/builds/release/bin/translate/translate.py'), + os.path.abspath('./temp/pokemon_domain.pddl'), + os.path.abspath(f'./temp/{data.file_name}'), + '--sas-file', + 'output.sas' + ] + + command_search = [ + os.path.abspath('./downward/builds/release/bin/downward.exe'), + '--search', + 'astar(hmax())', + '--internal-plan-file', + 'sas_plan' + ] + + if os.path.exists(os.path.abspath('./output.sas')): + os.remove(os.path.abspath('./output.sas')) + + if os.path.exists(os.path.abspath('./sas_plan')): + os.remove(os.path.abspath('./sas_plan')) + + print("Command to be executed:", command_translate) + + ret = subprocess.run(command_translate, check=True) + + with open(f'./output.sas', 'r') as f: + print("Command to be executed:", command_translate) + ret = subprocess.run(command_search, check=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, stdin=f) + except subprocess.CalledProcessError as e: + print(f"An error occurred: {e}") + print("fast-downward compelted") \ No newline at end of file diff --git a/utility/convert_to_pddl.py b/utility/convert_to_pddl.py new file mode 100644 index 0000000..159adec --- /dev/null +++ b/utility/convert_to_pddl.py @@ -0,0 +1,176 @@ + + +from routes.pokemon_game_desc import PokemonGameDesc + + +def format_name(name): + return name.replace(' ', '_').replace('.', '').replace("'", '').lower() + +def generate_pddl_domain(): + # Define the types, predicates, and action as shown above + domain_pddl = """ + (define (domain pokemon) + (:requirements :strips :typing :equality :quantified-preconditions :conditional-effects :negative-preconditions) + (:types + location + condition + ) + (:predicates + (at ?loc - location) + (connected ?from ?to - location) + (has ?cond - condition) + (grants ?loc - location ?cond - condition) + (requires ?from ?to - location ?cond - condition) + (requires_grant ?loc - location ?cond - condition ?req - condition) + (visited ?loc - location) + (is_town_or_city ?loc - location) + ) + (:action move + :parameters (?from ?to - location) + :precondition (and + (at ?from) + (connected ?from ?to) + ; Remove (not (visited ?to)) if re-visiting is allowed + ; Universal preconditions for required conditions + (forall (?cond - condition) + (imply (requires ?from ?to ?cond) (has ?cond)) + ) + ) + :effect (and + (at ?to) + (not (at ?from)) + (visited ?to) + ) + ) + (:action acquire + :parameters (?loc - location ?cond - condition) + :precondition (and + (at ?loc) + (grants ?loc ?cond) + (not (has ?cond)) + ; Universal preconditions for required grants + (forall (?req - condition) + (imply (requires_grant ?loc ?cond ?req) (has ?req)) + ) + ) + :effect (and + (has ?cond) + ) + ) + (:action fly + :parameters (?from ?to - location) + :precondition (and + (at ?from) + (has Fly) + (has Fly_out_of_battle) + (visited ?to) + (is_town_or_city ?to) + (not (at ?to)) ; Prevents flying to the current location + ) + :effect (and + (at ?to) + (not (at ?from)) + ) + ) + ) + """ + with open('./temp/pokemon_domain.pddl', 'w') as f: + f.write(domain_pddl) + +def generate_pddl_problem(desc: PokemonGameDesc): + # Extract objects, init, and goal + locations = set() + conditions = set() + connections = [] + grants = [] + requirements = [] + requires_grant = [] + + # Gather conditions from node grants and edge requirements + for node, attrs in desc.graph.nodes(data=True): + node_formatted = format_name(node) + locations.add(node_formatted) + grants_conditions = attrs.get('grants_conditions', []) + for grant in grants_conditions: + condition = format_name(grant['condition']) + conditions.add(condition) + grants.append((node_formatted, condition)) + required_conditions = grant.get('required_conditions', []) + for req in required_conditions: + req_condition = format_name(req) + conditions.add(req_condition) + requires_grant.append((node_formatted, condition, req_condition)) + + for u, v, attrs in desc.graph.edges(data=True): + u_formatted = format_name(u) + v_formatted = format_name(v) + locations.update([u_formatted, v_formatted]) + # Add both directions for bidirectional movement + if f'{u_formatted} -> {v_formatted}' not in desc.one_way_routes: + connections.append((u_formatted, v_formatted)) + if f'{v_formatted} -> {u_formatted}' not in desc.one_way_routes: + connections.append((v_formatted, u_formatted)) # Add reverse connection + + edge_condition = attrs.get('condition') + if edge_condition: + if isinstance(edge_condition, list): + for cond in edge_condition: + cond_formatted = format_name(cond) + conditions.add(cond_formatted) + requirements.append((u_formatted, v_formatted, cond_formatted)) + requirements.append((v_formatted, u_formatted, cond_formatted)) # Reverse + else: + cond_formatted = format_name(edge_condition) + conditions.add(cond_formatted) + requirements.append((u_formatted, v_formatted, cond_formatted)) + requirements.append((v_formatted, u_formatted, cond_formatted)) # Reverse + + conditions.add(format_name('Fly')) + conditions.add(format_name('Fly out of battle')) + + formatted_towns_and_cities = [format_name(town) for town in desc.towns_and_cities] + + # Prepare the PDDL problem file content + problem_pddl = "(define (problem pokemon_problem)\n" + problem_pddl += " (:domain pokemon)\n" + + # Objects + problem_pddl += " (:objects\n" + problem_pddl += " " + " ".join(sorted(locations)) + " - location\n" + problem_pddl += " " + " ".join(sorted(conditions)) + " - condition\n" + problem_pddl += " )\n" + + # Initial state + problem_pddl += " (:init\n" + problem_pddl += f" (at {format_name(desc.starting_town)})\n" + for u, v in connections: + problem_pddl += f" (connected {u} {v})\n" + for loc, cond in grants: + problem_pddl += f" (grants {loc} {cond})\n" + for u, v, cond in requirements: + problem_pddl += f" (requires {u} {v} {cond})\n" + for loc, cond, req in requires_grant: + problem_pddl += f" (requires_grant {loc} {cond} {req})\n" + for town in formatted_towns_and_cities: + problem_pddl += f" (is_town_or_city {town})\n" + problem_pddl += " )\n" + + # Goal state + badges = desc.badges + additional_conditions = desc.additional_goals + visited = desc.must_visit + problem_pddl += " (:goal\n" + problem_pddl += " (and\n" + problem_pddl += f" (at {format_name(desc.end_goal)})\n" + for badge in badges: + problem_pddl += f" (has {format_name(badge)})\n" + for cond in additional_conditions: + problem_pddl += f" (has {format_name(cond)})\n" + for location in visited: + problem_pddl += f" (visited {format_name(location)})\n" + problem_pddl += " )\n" + problem_pddl += " )\n" + problem_pddl += ")\n" + + with open(f'./temp/{desc.file_name}', 'w') as f: + f.write(problem_pddl)