Compare commits

...

15 Commits

  1. 1
      .gitignore
  2. 106
      database/db_controller.py
  3. 458
      routes/Gold_Silver_Route.py
  4. 239
      routes/Red_Blue_Route.py
  5. 0
      routes/__init__.py
  6. 38
      routes/game_world.py
  7. 20
      routes/pokemon_game_desc.py
  8. 37
      routes/requirements.py
  9. 184
      routes/route_planner.py
  10. 86
      ui/main_window_controller.py
  11. 50
      ui/main_window_view.py
  12. 39
      ui/workers/gather_baby_form_status.py
  13. 32
      ui/workers/gather_encounter_locations.py
  14. 113
      ui/workers/gather_evolutions_worker.py
  15. 7
      ui/workers/gather_pokemon_forms_worker.py
  16. 30
      ui/workers/generate_PDDLs_worker.py
  17. 432
      ui/workers/generate_plan_worker.py
  18. 81
      ui/workers/route_solve_worker.py
  19. 212
      ui/workers/solve_pddl_route.py
  20. 176
      utility/convert_to_pddl.py
  21. 111
      utility/data.py
  22. 7
      utility/functions.py
  23. 2
      utility/pokemon_word_ninja.py

1
.gitignore

@ -140,3 +140,4 @@ cython_debug/
# diskcache folder
cache/
temp/

106
database/db_controller.py

@ -195,6 +195,10 @@ class DBController:
self.update_pokemon_field(pfic, "storable_in_home", status)
pass
def update_baby_status(self, pfic, status):
self.update_pokemon_field(pfic, "is_baby_form", status)
pass
def update_mark(self, pfic, data):
self.update_pokemon_field(pfic, "mark", data)
pass
@ -505,3 +509,105 @@ class DBController:
# Fetch and print the results
results = self.cursor.fetchall()
return [dict(row) for row in results]
def get_all_encounters(self, type=None):
query = '''
SELECT * FROM encounters
'''
if type:
query += f"WHERE type = '{type}'"
self.cursor.execute(query)
# Fetch and print the results
results = self.cursor.fetchall()
return [dict(row) for row in results]
def get_pokemon_by_generation(self, generation):
self.cursor.execute(f"SELECT * FROM pokemon_forms WHERE JSON_EXTRACT(data, '$.generation') = {generation}")
results = self.cursor.fetchall()
processed_data = []
pre_processed_data = [dict(row) for row in results]
for row in pre_processed_data:
data = {}
data["PFIC"] = row["PFIC"]
if row["data"] and row["data"] != "":
data.update(json.loads(row["data"]))
processed_data.append(data)
return processed_data
def find_most_distant_predecessors(self, node):
"""
Finds the most distant predecessor(s) of a given node in a DAG.
Parameters:
G (networkx.DiGraph): The directed graph.
node (hashable): The node from which to find the most distant predecessors.
Returns:
tuple: A tuple containing:
- A list of the most distant predecessor node(s).
- The distance (number of edges) to the most distant predecessor(s).
"""
if node not in self.graph:
raise ValueError(f"The node {node} is not in the graph.")
# Reverse the graph to make predecessors into successors
G_rev = self.graph.reverse(copy=False)
# Compute longest path lengths from the node in the reversed graph
dist = {}
# Perform topological sort on the reversed graph
topo_order = list(nx.topological_sort(G_rev))
# Find nodes reachable from the given node in the reversed graph
reachable_nodes = set(nx.descendants(G_rev, node))
reachable_nodes.add(node)
# Filter the topological order to include only reachable nodes
topo_order = [n for n in topo_order if n in reachable_nodes]
# Initialize distances with negative infinity for all nodes
for n in reachable_nodes:
dist[n] = float('-inf')
dist[node] = 0 # Distance to the starting node is zero
# Dynamic programming to compute longest path lengths
for u in topo_order:
for v in G_rev.successors(u):
if dist[v] < dist[u] + 1:
dist[v] = dist[u] + 1
if not dist:
return [], 0 # No predecessors found
# Find the maximum distance
max_distance = max(dist.values())
# Identify the node(s) with the maximum distance
most_distant_predecessors = [n for n, d in dist.items() if d == max_distance]
return most_distant_predecessors, max_distance
def get_pokemon_home_list(self):
self.cursor.execute('''
SELECT *
FROM pokemon_forms
WHERE JSON_EXTRACT(data, '$.storable_in_home') == true
GROUP BY json_extract(data, '$.sprite_url')
ORDER BY PFIC
''')
results = self.cursor.fetchall()
processed_data = []
pre_processed_data = [dict(row) for row in results]
for row in pre_processed_data:
data = {}
data["pfic"] = row["PFIC"]
if row["data"] and row["data"] != "":
data.update(json.loads(row["data"]))
processed_data.append(data)
return processed_data

458
routes/Gold_Silver_Route.py

@ -0,0 +1,458 @@
from types import MethodType
import networkx as nx
from networkx import Graph
from routes.game_world import RouteStage
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 = "Crystal"
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]
#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")
stage: RouteStage = RouteStage(
"Johto",
get_johto_route(),
NEW_BARK_TOWN,
INDIGO_PLATEAU,
[ZEPHYR_BADGE, HIVE_BADGE, PLAIN_BADGE, FOG_BADGE, STORM_BADGE, MINERAL_BADGE, GLACIER_BADGE, RISING_BADGE],
set(),
[NEW_BARK_TOWN, CHERRYGROVE_CITY, VIOLET_CITY, AZALEA_TOWN, GOLDENROD_CITY, ECRUTEAK_CITY, OLIVINE_CITY, CIANWOOD_CITY, MAHOGANY_TOWN, BLACKTHORN_CITY]
)
desc.stages.append(stage)
kanto_stage: RouteStage = RouteStage(
"Kanto",
nx.compose(get_johto_route(), get_kanto_route()),
NEW_BARK_TOWN,
MT_SILVER,
[BOULDER_BADGE, CASCADE_BADGE, THUNDER_BADGE, RAINBOW_BADGE, MARSH_BADGE, SOUL_BADGE, VOLCANO_BADGE, EARTH_BADGE],
set(),
[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.stages.append(kanto_stage)
return desc
def get_johto_desc():
desc: PokemonGameDesc = PokemonGameDesc()
#desc.graph = get_gold_silver_route()
desc.game_name = "Gold_Silver_Johto"
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]
#desc.badges = [ZEPHYR_BADGE, HIVE_BADGE, PLAIN_BADGE, FOG_BADGE, STORM_BADGE, MINERAL_BADGE, GLACIER_BADGE, RISING_BADGE]
desc.hms = [CUT, SURF, FLASH, STRENGTH, FLY, WATERFALL, WHIRLPOOL]
desc.additional_goals = []
#desc.starting_town = NEW_BARK_TOWN
#desc.end_goal = INDIGO_PLATEAU
desc.must_visit = set()
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_johto_route():
G = nx.Graph()
for i in range(29,46):
G.add_node(f'Route {i}', node_type='route')
G.add_node(f'Route 26', node_type='route')
G.add_node(f'Route 27', node_type='route')
G.add_node(f'Route 23', 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(SAFARI_ZONE, node_type='location')
G.add_node(TOHJO_FALLS, node_type='location')
G.add_node(LEAGUE_RECEPTION_GATE, 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(VICTORY_ROAD, INDIGO_PLATEAU, condition=None)
#G.add_edge(SS_AQUA, VERMILION_CITY, 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': []}
]
return G
def get_kanto_route():
G = nx.Graph()
for i in range(1,23):
G.add_node(f'Route {i}', node_type='route')
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(POWER_PLANT, node_type='location')
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[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
def get_gold_silver_route() -> Graph:
a = get_johto_route()
b = get_kanto_route()
return a

239
routes/Red_Blue_Route.py

@ -0,0 +1,239 @@
import networkx as nx
from networkx import Graph
from routes.game_world import RouteStage
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"
desc.towns_and_cities = [PALLET_TOWN, VIRIDIAN_CITY, PEWTER_CITY, CERULEAN_CITY, SAFFRON_CITY, CELADON_CITY, VERMILLION_CITY, FUCHSIA_CITY, CINNABAR_ISLAND]
desc.hms = [CUT, SURF, FLASH, STRENGTH, FLY]
desc.file_name = "red_blue_yellow_problem.pddl"
desc.games_covered = ["Red", "Blue", "Yellow"]
stage: RouteStage = RouteStage(
"Kanto",
get_red_blue_route(),
PALLET_TOWN,
CERULEAN_CAVE,
[BOULDER_BADGE, CASCADE_BADGE, THUNDER_BADGE, RAINBOW_BADGE, MARSH_BADGE, SOUL_BADGE, VOLCANO_BADGE, EARTH_BADGE],
set(),
[PALLET_TOWN, VIRIDIAN_CITY, PEWTER_CITY, CERULEAN_CITY, SAFFRON_CITY, CELADON_CITY, VERMILLION_CITY, FUCHSIA_CITY, CINNABAR_ISLAND]
)
desc.stages.append(stage)
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

0
routes/__init__.py

38
routes/game_world.py

@ -0,0 +1,38 @@
from typing import List, Set
import networkx as nx
class GameWorld:
def __init__(self, graph: nx.Graph, start: str, end: str, goals: Set[str], must_visit: Set[str], initial_conditions: frozenset, towns_and_cities: Set[str]):
self.graph = graph
self.start = start
self.end = end
self.goals = goals
self.must_visit = must_visit
self.initial_conditions = initial_conditions
self.towns_and_cities = towns_and_cities
class RouteStage:
def __init__(self, name: str, graph: nx.Graph, start: str, end: str, goals: Set[str], must_visit: Set[str], towns_and_cities: Set[str]):
self.name: str = name
self.graph: nx.Graph = graph # A function or class that returns a subgraph for this stage
self.start: str = start
self.end: str = end
self.goals: Set[str] = goals
self.must_visit: Set[str] = must_visit
self.towns_and_cities: Set[str] = towns_and_cities
def create_world(self, previous_conditions=None):
# Combine initial_conditions with previous_conditions if needed
# previous_conditions are those acquired from the last stage.
#final_goals = self.goals.union(previous_conditions.get('badges', set())) if previous_conditions else self.goals
# Construct a GameWorld for this stage
world = GameWorld(
graph=self.graph,
start=self.start,
end=self.end,
goals=self.goals,
must_visit=self.must_visit,
initial_conditions=previous_conditions.get('conditions', frozenset()) if previous_conditions else frozenset(),
towns_and_cities = self.towns_and_cities
)
return world

20
routes/pokemon_game_desc.py

@ -0,0 +1,20 @@
from typing import List, Set
import networkx as nx
from routes.game_world import RouteStage
class PokemonGameDesc:
def __init__(self):
self.game_name: str = ""
self.towns_and_cities: Set[str] = set()
self.items: Set[str] = set()
self.hms: Set[str] = set()
self.flying_badge: str
self.additional_goals: List[str] = []
self.one_way_routes: List[str] = []
self.must_visit: Set[str] = set()
self.games_covered: List[str] = []
self.file_name: str = ""
self.stages: List[RouteStage] = []
__all__ = ["PokemonGameDesc"]

37
routes/requirements.py

@ -0,0 +1,37 @@
import json
from typing import Set
def determine_must_visit_locations(plan_json: dict, db_conn, game_name: str) -> Set[str]:
# Extract needed Pokemon, find their encounter locations via db, return that set.
# No route logic here, just data derivation.
target = None
for game in plan_json:
if game["game_name"] == game_name:
target = game
break
if not target:
return set()
# gather up all the pokemon needed for say crystal and find all the encounter routes/locations
needed_locations = set()
game_info = db_conn.get_game_id_by_name(game_name)
for key in target["pokemon"]:
catch_stats = target["pokemon"][key]
rep = catch_stats["representative"]
rand = db_conn.get_encounters(rep, "random")
static = db_conn.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("*", ""))
return needed_locations

184
routes/route_planner.py

@ -0,0 +1,184 @@
from typing import List, Set
from routes.game_world import GameWorld
import heapq
import networkx as nx
class RoutePlan:
def __init__(self, path: List[str], cost: int, conditions: Set[str]):
self.path = path
self.cost = cost
self.conditions = conditions
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
class RoutePlanner:
def __init__(self, world: GameWorld):
self.world: GameWorld = world
def heuristic(self, 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 heuristic2(self, state, goal_conditions, end_goal, required_nodes, distances):
remaining_conditions = goal_conditions - state.conditions
remaining_nodes = required_nodes - state.visited_required_nodes
# Find the shortest distance from current_state.location to any required node + eventually to the goal
# As a simple first step: take the minimum distance from the current node to any required node or the goal.
node_candidates = list(remaining_nodes) + [end_goal]
min_dist = float('inf')
for candidate in node_candidates:
d = distances.get((state.location, candidate), float('inf'))
if d < min_dist:
min_dist = d
# If no must-visit nodes remain, just consider distance to the goal
if not remaining_nodes:
min_dist = distances.get((state.location, end_goal), float('inf'))
# Combine with remaining conditions count as before
return len(remaining_conditions) + len(remaining_nodes) + (min_dist if min_dist != float('inf') else 0)
def is_goal_state(self, state, goal_location, goals, required_nodes):
return (
state.location == goal_location and
goals.issubset(state.conditions) and
required_nodes.issubset(state.visited_required_nodes)
)
def compute_shortest_path(self, graph, key_nodes):
distances = {} # distances[(u,v)] = shortest distance from u to v ignoring conditions
for node in key_nodes:
dist_from_node = nx.single_source_shortest_path_length(graph, node)
for other in key_nodes:
distances[(node, other)] = dist_from_node.get(other, float('inf'))
return distances
def astar_search(self) -> RoutePlan:
from collections import deque
self.goals = set(self.world.goals)
key_nodes = [self.world.start, self.world.end] + list(self.world.towns_and_cities)
if len(self.world.must_visit) > 0:
key_nodes += list(self.world.must_visit)
distances = self.compute_shortest_path(self.world.graph, key_nodes)
# Priority queue for open states
open_list = []
heapq.heappush(open_list, (0, State(
location=self.world.start,
conditions=self.world.initial_conditions, # Start with no conditions
cost=0,
path=[self.world.start],
visited_required_nodes=frozenset([self.world.start]) if self.world.start in self.world.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 self.is_goal_state(current_state, self.world.end, self.goals, self.world.must_visit):
return RoutePlan(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.world.graph.neighbors(current_state.location):
edge_data = self.world.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.world.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.world.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 + self.heuristic(new_state, self.goals, self.world.must_visit)
estimated_total_cost = new_state.cost + self.heuristic2(new_state, self.goals, self.world.end, self.world.must_visit, distances)
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.world.towns_and_cities:
for fly_target in self.world.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.world.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.world.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 + self.heuristic(fly_state, self.goals, self.world.must_visit)
estimated_total_cost = fly_state.cost + self.heuristic2(fly_state, self.goals, self.world.end, self.world.must_visit, distances)
heapq.heappush(open_list, (estimated_total_cost, fly_state))
return None # No path found

86
ui/main_window_controller.py

@ -1,15 +1,22 @@
import json
from PyQt6.QtCore import Qt, QTimer, QThreadPool
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
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 utility.functions import get_display_name
from ui.workers.generate_PDDLs_worker import GeneratePDDLsWorker
from ui.workers.generate_plan_worker import GeneratePlanWorker
from ui.workers.route_solve_worker import SolveRouteWorker
from ui.workers.solve_pddl_route import SolvePDDLsWorker
from utility.functions import get_display_name, sanitize_filename
from db import db
class MainWindowController:
@ -133,14 +140,15 @@ class MainWindowController:
print("Works Done!")
db.update_evolution_graph(data)
def adjust_gender_relevancy(self):
list = db.get_gender_specific_evolutions()
second_list = db.get_gender_relevant_pokemon()
print(list)
print(second_list)
db.propagate_gender_relevance(list)
db.propagate_gender_relevance(second_list)
pass
def gather_baby_status(self):
worker = GatherBabyStatusWorker()
worker.signals.finished.connect(self.on_baby_status_gathered)
self.thread_pool.start(worker)
def on_baby_status_gathered(self, data):
print("Works Done!")
for pfic in data:
db.update_baby_status(pfic, True)
def reinitialize_database(self):
pass
@ -208,3 +216,63 @@ class MainWindowController:
def load_encounter_locations(self, pfic):
encounters = db.get_encounters(pfic)
self.view.update_encounter_list(encounters, pfic)
def generate_plan(self):
worker = GeneratePlanWorker()
worker.signals.finished.connect(self.on_plan_generated)
self.thread_pool.start(worker)
def on_plan_generated(self, data):
print("Plans Done!")
full_plan = []
family_map = {}
for game_plan in data:
obj = {}
obj["game_name"] = game_plan["game_name"]
family_map = {}
for key in game_plan["pokemon_to_catch"]:
pokemon_to_catch = game_plan["pokemon_to_catch"][key]
family_key = key[:-2]
if family_key not in family_map:
family_map[family_key] = {}
family_map[family_key]["representative"] = key
family_map[family_key]["catch_count"] = 0
family_map[family_key]["evolve_to"] = []
family_map[family_key]["breed_for"] = []
catch_count = family_map[family_key]["catch_count"]
for catch_key in pokemon_to_catch:
family_map[family_key][catch_key] = pokemon_to_catch[catch_key]
catch_count += pokemon_to_catch[catch_key]
family_map[family_key]["catch_count"] = catch_count
catch_details = game_plan["other_map"][key]
family_map[family_key]["evolve_to"].extend(catch_details["EvolveTo"])
family_map[family_key]["breed_for"].extend(catch_details["BreedFor"])
pass
obj["pokemon"] = family_map
full_plan.append(obj)
output_file = "./temp/plan.json"
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 = SolveRouteWorker(data)
worker.signals.finished.connect(self.on_route_solved)
self.thread_pool.start(worker)
def on_route_solved(self, data):
pass

50
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
@ -26,6 +29,8 @@ class PokemonUI(QWidget):
self.setup_main_tab()
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)
@ -153,8 +158,8 @@ class PokemonUI(QWidget):
gather_evolutions_btn.clicked.connect(self.controller.gather_evolution_info)
db_tab_layout.addWidget(gather_evolutions_btn)
gather_evolutions_btn = QPushButton("Adjust Gender Relevant Information")
gather_evolutions_btn.clicked.connect(self.controller.adjust_gender_relevancy)
gather_evolutions_btn = QPushButton("Gather Baby Status")
gather_evolutions_btn.clicked.connect(self.controller.gather_baby_status)
db_tab_layout.addWidget(gather_evolutions_btn)
gather_encounters_btn = QPushButton("Gather Encounter Information")
@ -217,7 +222,35 @@ class PokemonUI(QWidget):
self.manage_encounters_tab.addLayout(left_layout, 1)
self.manage_encounters_tab.addLayout(right_layout, 2)
#self.load_exclusive_sets()
def setup_plan_tab(self):
plan_tab = QWidget()
plan_tab_layout = QVBoxLayout(plan_tab)
self.tab_widget.addTab(plan_tab, "Plan Generation")
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)
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()
@ -356,3 +389,14 @@ class PokemonUI(QWidget):
# After updating the locations tree
#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)

39
ui/workers/gather_baby_form_status.py

@ -0,0 +1,39 @@
from PyQt6.QtCore import QObject, pyqtSignal, QRunnable
import json
from cache import cache
from db import db
from ui.workers.gather_evolutions_worker import GatherEvolutions
from utility.data import exclusive_choice_pokemon
from utility.functions import get_display_name, get_form_name, get_shiftable_forms, parse_pfic
class GatherBabyStatusWorkerSignals(QObject):
finished = pyqtSignal(list)
class GatherBabyStatusWorker(GatherEvolutions):
def __init__(self):
super().__init__()
self.signals = GatherBabyStatusWorkerSignals()
def run(self):
try:
gathered_data = self.gather_baby_status(False)
self.signals.finished.emit(gathered_data)
except Exception as e:
print(f"Error gathering Pokémon home storage status: {e}")
def gather_baby_status(self, force_refresh = False):
all_pokemon_forms = db.get_pokemon_home_list()
babys = []
for pokemon_form in all_pokemon_forms:
evolution_tree = self.gather_evolution_tree(pokemon_form, force_refresh)
if not evolution_tree:
continue
if evolution_tree["pokemon"] != pokemon_form["name"]:
continue
if evolution_tree["is_baby"]:
babys.append(pokemon_form["pfic"])
return babys

32
ui/workers/gather_encounter_locations.py

@ -395,6 +395,36 @@ class GatherEncountersWorker(QRunnable):
if form.lower() in ["spring form", "summer form", "autumn form", "winter form"] and main_form == None:
return True
if form.lower() in ["plant cloak", "sandy cloak", "trash cloak"] and main_form == None:
return True
if form.lower() in ["fan", "frost", "heat", "mow", "wash"] and main_form == None:
return True
if form.lower() in [
"meadow pattern",
"archipelago pattern",
"continental pattern",
"elegant pattern",
"garden pattern",
"high plains pattern",
"icy snow pattern",
"jungle pattern",
"marine pattern",
"meadow pattern",
"modern pattern",
"monsoon pattern",
"ocean pattern",
"polar pattern",
"river pattern",
"sandstorm pattern",
"savanna pattern",
"sun pattern",
"tundra pattern",
"fancy pattern"
]:
return True
if form and main_form is None:
return False
@ -415,7 +445,7 @@ class GatherEncountersWorker(QRunnable):
def extract_routes(self, s):
# Find all route numbers, including those after "and" or separated by commas
route_pattern = r'Routes?\s+((?:\d+(?:,?\s+(?:and\s+)?)?)+)'
route_pattern = r'Routes?\s?((?:\d+(?:,?\s+(?:and\s+)?)?)+)'
route_match = re.search(route_pattern, s, re.IGNORECASE)
if route_match:

113
ui/workers/gather_evolutions_worker.py

@ -9,7 +9,7 @@ from db import db
import re
from utility.functions import get_form_name, get_display_name, parse_pfic
from utility.data import non_evolution_forms, alcremie_forms
from utility.data import non_evolution_forms, alcremie_forms, vivillon_patterns, default_pokemon_forms
class GatherEvolutionsWorkerSignals(QObject):
finished = pyqtSignal(dict)
@ -34,12 +34,34 @@ class GatherEvolutions(QRunnable):
evolutions = {}
for pokemon_form in all_pokemon_forms:
evolution_tree = self.gather_evolution_tree(pokemon_form, force_refresh)
if not evolution_tree:
continue
gender = None
search_form = get_form_name(pokemon_form)
if search_form and "male" in search_form.lower():
if "female" in search_form.lower():
gender = "Female"
else:
gender = "Male"
cacheable_container = {}
self.traverse_and_store(evolution_tree, cacheable_container, gender)
evolutions = evolutions | cacheable_container
print(self.evolution_methods)
return evolutions
def gather_evolution_tree(self, pokemon_form, force_refresh = False):
print(f"Processing {get_display_name(pokemon_form)}'s evolutions")
pokemon_name = pokemon_form["name"]
form = get_form_name(pokemon_form)
if pokemon_form["form_name"] and any(s in pokemon_form["form_name"] for s in non_evolution_forms):
continue
return None
cache_record_name = f"chain_{pokemon_name}_{form}"
if force_refresh:
@ -47,22 +69,16 @@ class GatherEvolutions(QRunnable):
cached_entry = cache.get(cache_record_name)
if cached_entry != None:
evolutions = evolutions | cached_entry
continue
#form = get_form_name(pokemon_form, not pokemon_form["gender_relevant"])
return cached_entry
search_form = form
if search_form and pokemon_name in search_form:
search_form = search_form.replace(pokemon_name, "").strip()
gender = None
if search_form and "male" in search_form.lower():
if "female" in search_form.lower():
search_form = search_form.replace("Female", "").replace("female", "").strip()
gender = "Female"
else:
search_form = search_form.replace("Male", "").replace("male", "").strip()
gender = "Male"
if search_form == "":
search_form = None
@ -75,12 +91,12 @@ class GatherEvolutions(QRunnable):
url = f"https://bulbapedia.bulbagarden.net/wiki/{pokemon_name}_(Pokémon)"
page_data = cache.fetch_url(url)
if not page_data:
continue
return None
soup = BeautifulSoup(page_data, 'html.parser')
evolution_section = soup.find('span', id='Evolution_data')
if not evolution_section:
continue
return None
evolution_table = None
evolution_table = evolution_section.parent.find_next('table')
@ -94,13 +110,13 @@ class GatherEvolutions(QRunnable):
if tag.name == 'h3':
break
if not evolution_table:
continue
return None
evolution_tree = None
if pokemon_name == "Eevee":
evolution_tree = self.parse_eevee_evolution_chain(evolution_table, pokemon_form)
else:
evolution_tree = self.parse_evolution_chain(evolution_table, pokemon_form)
evolution_tree = self.parse_evolution_chain(evolution_table, pokemon_form, force_refresh)
if evolution_tree["pokemon"] == "Milcery":
evolution_tree["evolves_to"] = []
@ -115,6 +131,20 @@ class GatherEvolutions(QRunnable):
}
evolution_tree["evolves_to"].append(node)
if evolution_tree["pokemon"] == "Scatterbug":
spewpa_node = evolution_tree["evolves_to"][0]
spewpa_node["evolves_to"] = []
for form in vivillon_patterns:
node = {
"pokemon": "Vivillon",
"form": form,
"requirement": None,
"method": "Level 12 →",
"evolves_to": [],
"stage": 2
}
spewpa_node["evolves_to"].append(node)
if evolution_tree["pokemon"] == "Flabébé":
def fix_form(node, new_form):
node["form"] = new_form
@ -128,15 +158,12 @@ class GatherEvolutions(QRunnable):
flower_form = flower_form.replace("Male", "").replace("male", "").strip()
fix_form(evolution_tree, flower_form)
cacheable_container = {}
if evolution_tree:
self.traverse_and_store(evolution_tree, cacheable_container, gender)
if evolution_tree["pokemon"] == "Pichu":
evolution_tree["is_baby"] = True
cache.set(cache_record_name, cacheable_container)
evolutions = evolutions | cacheable_container
cache.set(cache_record_name, evolution_tree)
print(self.evolution_methods)
return evolutions
return evolution_tree
def traverse_and_store(self, node, evolutions, gender):
"""Helper function to traverse evolution tree and store evolutions."""
@ -180,8 +207,11 @@ class GatherEvolutions(QRunnable):
evolution_form = self.extract_evolution_form(td, pokemon_name)
stage = self.extract_stage_form(td).replace("Evolution", "").replace("evolution", "").strip()
numberical_stage = -1
is_baby = False
if stage == "Unevolved" or stage == "Baby form":
numberical_stage = 0
if stage == "Baby form":
is_baby = True
elif stage == "Castoff":
numberical_stage = 1
else:
@ -192,7 +222,8 @@ class GatherEvolutions(QRunnable):
"requirement": None,
"method": None,
"evolves_to": [],
"stage": numberical_stage
"stage": numberical_stage,
"is_baby": is_baby
}
# Parse main evolution chain
@ -315,7 +346,8 @@ class GatherEvolutions(QRunnable):
"pokemon": pokemon_name,
"form": None,
"method": None,
"evolves_to": []
"evolves_to": [],
"is_baby": False
}
rows = tbody.find_all('tr', recursive=False)
@ -383,6 +415,9 @@ class GatherEvolutions(QRunnable):
if not results:
return None
if form is None and name in default_pokemon_forms:
form = default_pokemon_forms[name]
if gender:
gender_filtered_results = []
for entry in results:
@ -410,14 +445,24 @@ class GatherEvolutions(QRunnable):
stripped_form = self.strip_pokemon_name(name, form)
best_match = None
best_score = -1
for entry in results:
stripped_db_form = self.strip_pokemon_name(entry["name"], entry["form_name"])
if self.fuzzy_match_form(stripped_form, stripped_db_form, threshold):
return entry["pfic"]
stripped_db_form_genderless = self.strip_gender_from_form(stripped_db_form)
match_score = self.fuzzy_match_form(stripped_form, stripped_db_form)
genderless_score = self.fuzzy_match_form(stripped_form, stripped_db_form_genderless)
if match_score > best_score:
best_match = entry["pfic"]
best_score = match_score
stripped_db_form = self.strip_gender_from_form(stripped_db_form)
if self.fuzzy_match_form(stripped_form, stripped_db_form, threshold):
return entry["pfic"]
if genderless_score > best_score:
best_match = entry["pfic"]
best_score = genderless_score
return best_match if best_score >= threshold else None
# Some times we get a form for a pokemon that doesn't really have one.
#if len(results) > 1 and form != None and gender and threshold != 100:
@ -427,7 +472,7 @@ class GatherEvolutions(QRunnable):
def strip_pokemon_name(self, pokemon_name: str, form_name: str) -> str:
if form_name:
form_name = form_name.replace("Form", "").strip()
form_name = form_name.replace("Forme", "").replace("Form", "").strip()
form_name = re.sub(f'{re.escape(pokemon_name)}\\s*', '', form_name, flags=re.IGNORECASE).strip()
form_name = form_name.replace(" ", " ")
return form_name
@ -439,7 +484,11 @@ class GatherEvolutions(QRunnable):
form_name = form_name.replace("Male", "").replace("male", "").strip()
return form_name
def fuzzy_match_form(self, form1: str, form2: str, threshold: int = 80) -> bool:
if form1 is None or form2 is None:
return form1 == form2
return fuzz.ratio(form1.lower(), form2.lower()) >= threshold
def fuzzy_match_form(self, form1: str, form2: str) -> bool:
if form1 is None:
form1 = ""
if form2 is None:
form2 = ""
return fuzz.ratio(form1.lower(), form2.lower())

7
ui/workers/gather_pokemon_forms_worker.py

@ -187,6 +187,13 @@ class GatherPokemonFormsWorker(QRunnable):
if not record_female_form and not record_male_form:
record_genderless_form = True
if pokemon_name == "Basculin" and form_name.lower() == "white-striped form":
generation = 8
elif pokemon_name == "Burmy":
gender_relevant = True
if pokemon_name == "Ursaluna" and form_name.lower() == "blood moon":
generation = 9
if gender_relevant or record_genderless_form:
pokemon_form = {
"pfic":format_pokemon_id(national_dex_number, generation, form_index, gender),

30
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]

432
ui/workers/generate_plan_worker.py

@ -0,0 +1,432 @@
from PyQt6.QtCore import QObject, pyqtSignal, QRunnable
import json
from cache import cache
from db import db
from utility.data import exclusive_choice_pokemon
from utility.functions import get_shiftable_forms, get_shiftable_forms_bidirectional, parse_pfic, sanitize_filename
class GeneratePlanWorkerSignals(QObject):
finished = pyqtSignal(list)
class GeneratePlanWorker(QRunnable):
def __init__(self):
super().__init__()
self.signals = GeneratePlanWorkerSignals()
self.caught_pokemon = {}
self.group_plan = []
self.game_plan = []
def run(self):
try:
gathered_data = self.generate_plan()
self.signals.finished.emit(gathered_data)
except Exception as e:
print(f"Error gathering Pokémon home storage status: {e}")
def generate_plan(self):
generational_groups = [1, 2], [3, 4, 5], [6], [7], [8], [9]
for group in generational_groups:
group_plan = self.plan_for_group(group)
self.group_plan.append(group_plan)
for group in self.group_plan:
for game in group:
game_plan = self.plan_for_game(game, group[game])
self.game_plan.append(game_plan)
storable_in_home = db.get_pokemon_home_list()
total_accounted_for = 0
for entry in self.game_plan:
total_accounted_for += len(entry["pokemon_map"])
total_needed = len(storable_in_home)
for home_pokemon in storable_in_home:
found = False
for game in self.game_plan:
if home_pokemon["pfic"] in game["pokemon_map"]:
found = True
break
if not found:
print(home_pokemon)
return self.game_plan
def plan_for_group(self, group):
group_plan = []
needed_pokemon = set()
games_in_group = []
game_pokemon = {}
for generation in group:
games_in_group.extend(db.get_games_by_generation(generation))
pokemon_in_generation = db.get_pokemon_by_generation(generation)
for pokemon in pokemon_in_generation:
pfic = pokemon["PFIC"]
if pokemon["storable_in_home"] == False:
continue
testing = [
"0140-01-000-1", "0138-01-000-1", "0140-01-000-2", "0138-01-000-2",
"0141-01-000-1", "0139-01-000-1", "0141-01-000-2", "0139-01-000-2",
]
#if pfic not in testing:
# continue
if pokemon["gender_relevant"] or pokemon["PFIC"][-1] == '0' or not any(pokemon["PFIC"][:-1] == s[:-1] for s in needed_pokemon):
needed_pokemon.add(pfic)
random = db.get_all_encounters("random")
static = db.get_all_encounters("static")
starter = db.get_all_encounters("starter")
encounters = []
encounters.extend(random)
encounters.extend(static)
encounters.extend(starter)
"""
def thingy(current_pfic):
evolution_chains = db.get_full_evolution_paths(current_pfic)
if evolution_chains and (len(evolution_chains["predecessors"]) > 0 or len(evolution_chains["successors"]) > 0):
for chains in evolution_chains["predecessors"]:
for evolved_pfic, method in chains:
if evolved_pfic in needed_pokemon:
game_pokemon[game['id']].add(evolved_pfic)
for shiftable_form in get_shiftable_forms_bidirectional(evolved_pfic):
game_pokemon[game['id']].add(shiftable_form["to_pfic"])
game_pokemon[game['id']].add(shiftable_form["from_pfic"])
for chains in evolution_chains["successors"]:
for evolved_pfic, method in chains:
if evolved_pfic in needed_pokemon:
game_pokemon[game['id']].add(evolved_pfic)
for shiftable_form in get_shiftable_forms_bidirectional(evolved_pfic):
game_pokemon[game['id']].add(shiftable_form["to_pfic"])
game_pokemon[game['id']].add(shiftable_form["from_pfic"])
for game in games_in_group:
game_pokemon[game['id']] = set()
for encounter in encounters:
current_pfic = encounter['PFIC']
if game["id"] == encounter["game_id"]:
game_pokemon[game['id']].add(current_pfic)
for shiftable_form in get_shiftable_forms_bidirectional(current_pfic):
game_pokemon[game['id']].add(shiftable_form["to_pfic"])
game_pokemon[game['id']].add(shiftable_form["from_pfic"])
thingy(shiftable_form["from_pfic"])
thingy(pfic)
"""
for game in games_in_group:
game_pokemon[game['id']] = set()
for encounter in encounters:
if game["id"] == encounter["game_id"]:
current_pfic = encounter['PFIC']
game_pokemon[game['id']].add(current_pfic)
evolution_chains = db.get_full_evolution_paths(current_pfic)
if evolution_chains and (len(evolution_chains["predecessors"]) > 0 or len(evolution_chains["successors"]) > 0):
for chains in evolution_chains["predecessors"]:
for evolved_pfic, method in chains:
if evolved_pfic in needed_pokemon:
game_pokemon[game['id']].add(evolved_pfic)
for chains in evolution_chains["successors"]:
for evolved_pfic, method in chains:
if evolved_pfic in needed_pokemon:
game_pokemon[game['id']].add(evolved_pfic)
selected_games = []
catch_in_game = {}
remaining_pokemon = needed_pokemon.copy()
available_games = games_in_group.copy()
while remaining_pokemon:
cache = {}
for game in available_games:
cache[game['id']] = self.get_covered_pokemon(game, remaining_pokemon, game_pokemon[game['id']])
pokemon_covered = {}
if cache:
best_game = max(available_games, key=lambda g: len(cache[g["id"]]))
pokemon_covered = cache[best_game['id']]
if not pokemon_covered:
print("No more Pokémon can be covered. Breaking loop.")
break
selected_games.append(best_game)
catch_in_game[best_game["id"]] = pokemon_covered
remaining_pokemon -= pokemon_covered
available_games.remove(best_game)
for game in selected_games[:-1]:
if game['id'] in cache and all(pokemon in cache[best_game['id']]
for pokemon in cache[game['id']] ):
selected_games.remove(game)
print(f"Removed {game['name']} as it's covered by {best_game['name']}")
for game in selected_games:
file_name = sanitize_filename(game["name"])
output_file = "./temp/"+file_name+".json"
with open(output_file, 'w', encoding='utf-8') as f:
temp = []
for pfic in catch_in_game[game["id"]]:
deets = db.get_pokemon_details(pfic, ["pfic", "name", "form_name"])
temp.append(deets)
temp.sort(key=lambda x: parse_pfic(x["pfic"]))
json.dump(temp, f, indent=4, ensure_ascii=False)
pass
return catch_in_game
def get_covered_pokemon(self, game, needed_pokemon, available_pokemon):
exclusive_groups = self.get_exclusive_groups(game['id'])
pokemon_covered = set()
search_value = "0025-01-000-1"
if search_value in needed_pokemon:
pass
if search_value in available_pokemon:
pass
evolution_paths = {}
evolution_predecessors = {}
temp_all_pokemon = needed_pokemon | available_pokemon
for group in exclusive_groups:
for pfic in group:
temp_all_pokemon.add(pfic)
all_pokemon = list(temp_all_pokemon)
all_pokemon.sort(key=lambda x: parse_pfic(x))
for pfic in all_pokemon:
chain = db.get_full_evolution_paths(pfic)
evolution_paths[pfic] = chain
predecessors = set([pfic])
if chain and chain.get("predecessors"):
for entry in chain["predecessors"]:
for evolved_pfic, _ in entry:
predecessors.add(evolved_pfic)
evolution_predecessors[pfic] = predecessors
exclusive_group_predecessors = []
for group in exclusive_groups:
group_pokemon = set()
for pfic in group:
group_pokemon.update(evolution_predecessors.get(pfic, set()))
exclusive_group_predecessors.append(group_pokemon)
def record_pokemon(pfic, container, needed):
container.add(pfic)
evolution_chains = evolution_paths.get(pfic)
if evolution_chains and (evolution_chains.get("predecessors") or evolution_chains.get("successors")):
for chains in evolution_chains.get("predecessors", []):
for evolved_pfic, _ in chains:
if evolved_pfic in needed:
container.add(evolved_pfic)
for chains in evolution_chains.get("successors", []):
for evolved_pfic, _ in chains:
if evolved_pfic in needed:
container.add(evolved_pfic)
pokemon_still_needed = list(needed_pokemon & available_pokemon)
pokemon_still_needed.sort(key=lambda x: parse_pfic(x))
for pfic in pokemon_still_needed:
if pfic in pokemon_covered:
continue
previous_evolutions = evolution_predecessors.get(pfic, set())
is_exclusive = False
for group_pokemon in exclusive_group_predecessors:
if previous_evolutions & group_pokemon:
is_exclusive = True
if not pokemon_covered & group_pokemon:
record_pokemon(pfic, pokemon_covered, needed_pokemon)
break
if not is_exclusive:
record_pokemon(pfic, pokemon_covered, needed_pokemon)
return pokemon_covered
def get_exclusive_groups(self, game_id):
for data in exclusive_choice_pokemon:
if data["game_id"] == game_id:
return data["choices"]
return []
def plan_for_game(self, game_id, required_pokemon):
game = db.get_game_by_id(game_id)
game_plan = {
"game_name": game['name'],
"pokemon_to_catch": {},
"pokemon_to_breed": {},
"pokemon_map": {}
}
print(f'Processing {game['name']}')
random = db.get_all_encounters("random")
static = db.get_all_encounters("static")
starter = db.get_all_encounters("starter")
encounters = []
encounters.extend(random)
encounters.extend(static)
encounters.extend(starter)
encounters_in_game = {}
for encounter in encounters:
if game["id"] == encounter["game_id"]:
encounters_in_game[encounter["PFIC"]] = encounter
pokemon_to_catch = {}
pokemon_to_breed = {}
pokemon_map = {}
other_map = {}
def record_catch(pfic, gender, to_get):
if pfic not in encounters_in_game:
pass
if pfic not in pokemon_to_catch:
pokemon_to_catch[pfic] = {}
data = pokemon_to_catch[pfic]
if gender not in data:
data[gender] = 1
else:
data[gender] += 1
if to_get not in pokemon_map:
pokemon_map[to_get] = {}
pokemon_map[to_get]["ByEvolving"] = pfic
if pfic not in other_map:
other_map[pfic] = {}
other_map[pfic]["EvolveTo"] = []
other_map[pfic]["BreedFor"] = []
other_map[pfic]["EvolveTo"].append(to_get)
def record_breed(pfic, gender, to_get):
if pfic not in pokemon_to_breed:
pokemon_to_breed[pfic] = {}
data = pokemon_to_breed[pfic]
if gender not in data:
data[gender] = 1
else:
data[gender] += 1
if to_get not in pokemon_map:
pokemon_map[to_get] = {}
pokemon_map[to_get]["ByBreeding"] = pfic
if pfic not in other_map:
other_map[pfic] = {}
other_map[pfic]["EvolveTo"] = []
other_map[pfic]["BreedFor"] = []
other_map[pfic]["BreedFor"].append(to_get)
# TODO: Move this to a last pass
#if pfic not in pokemon_to_catch:
# record_catch(pfic, gender)
def get_gender_string(value):
value_map = {"0": "Any", "1": "Male", "2": "Female"}
return value_map.get(str(value), "Unknown")
missing_count = 0
required_pokemon_list = list(required_pokemon)
required_pokemon_list.sort(key=lambda x: parse_pfic(x))
for pfic in required_pokemon_list:
pokemon_data = db.get_pokemon_details(pfic)
evolution_chain = db.get_full_evolution_paths(pfic)
if evolution_chain and not any(evolution_chain["predecessors"]):
if pokemon_data["is_baby_form"]:
recorded = False
evolutions = db.get_evolution_graph(pfic)
for evolution in evolutions:
if evolution in encounters_in_game:
bucket = get_gender_string(0)
record_breed(evolution, bucket, pfic)
recorded = True
if not recorded:
if pfic in encounters_in_game:
bucket = get_gender_string(pfic[-1])
record_catch(pfic, bucket, pfic)
else:
if pokemon_data["gender_relevant"]:
bucket = get_gender_string(pfic[-1])
record_catch(pfic, bucket, pfic)
else:
shiftable_forms = get_shiftable_forms(pfic)
if len(shiftable_forms) > 0:
shiftable_pfic = shiftable_forms[0]["to_pfic"]
record_catch(shiftable_pfic, "Any", pfic)
else:
record_catch(pfic, "Any", pfic)
elif evolution_chain:
bucket = get_gender_string(0)
if pokemon_data["gender_relevant"]:
bucket = get_gender_string(pfic[-1])
first_form = db.find_most_distant_predecessors(pfic)
if first_form:
first_form_pfic = first_form[0][0]
first_form_data = db.get_pokemon_details(first_form_pfic)
if first_form_data["is_baby_form"] == False:
shiftable_forms = get_shiftable_forms(first_form_pfic)
if len(shiftable_forms) > 0:
shiftable_pfic = shiftable_forms[0]["to_pfic"]
record_catch(shiftable_pfic, bucket, pfic)
else:
record_catch(first_form_pfic, bucket, pfic)
else:
recorded = False
evolutions = db.get_evolution_graph(first_form_pfic)
for evolution in evolutions:
if evolution in encounters_in_game:
record_catch(evolution, bucket, pfic)
recorded = True
if not recorded:
if first_form_pfic in encounters_in_game:
bucket = get_gender_string(pfic[-1])
record_catch(first_form_pfic, bucket, pfic)
total_catch = 0
for sub_dict in pokemon_to_catch.values():
total_catch += sum(sub_dict.values())
total_breed = 0
for sub_dict in pokemon_to_breed.values():
total_breed += sum(sub_dict.values())
all = total_catch + total_breed + missing_count
sorted_keys = sorted(pokemon_to_catch.keys())
# Create a new dictionary with sorted keys
sorted_dict = {key: pokemon_to_catch[key] for key in sorted_keys}
game_plan["pokemon_to_catch"] = pokemon_to_catch
game_plan["pokemon_to_breed"] = pokemon_to_breed
game_plan["pokemon_map"] = pokemon_map
game_plan["other_map"] = other_map
for required in required_pokemon:
if required not in pokemon_map:
pokemon_data = db.get_pokemon_details(required)
print(pokemon_data["name"])
return game_plan

81
ui/workers/route_solve_worker.py

@ -0,0 +1,81 @@
import json
from PyQt6.QtCore import QObject, pyqtSignal, QRunnable
from db import db
from routes.game_world import GameWorld
from routes.pokemon_game_desc import PokemonGameDesc
from routes.requirements import determine_must_visit_locations
from routes.route_planner import RoutePlanner
class SolveRouteWorkerSignals(QObject):
finished = pyqtSignal(list)
class SolveRouteWorker(QRunnable):
def __init__(self, initial_data: PokemonGameDesc):
super().__init__()
self.signals = SolveRouteWorkerSignals()
self.initial_data = initial_data
def run(self):
try:
stage_results = []
current_conditions = {
'badges': set(),
'conditions': frozenset()
}
for i, stage in enumerate(self.initial_data.stages):
if i == 0:
world = stage.create_world()
else:
prev_result = stage_results[-1]
current_conditions['conditions'] = prev_result.conditions
#current_conditions['badges'] = prev_result.conditions.intersection(all_badges_set) # if badges are a subset of conditions
stage.start = prev_result.path[-1] # end of last path is start of this stage
world = stage.create_world(previous_conditions=current_conditions)
# First route calculation
planner = RoutePlanner(world)
initial_plan = planner.astar_search()
# Load plan.json and determine if more visits are needed
with open("./temp/plan.json", 'r', encoding='utf-8') as f:
plan_json = json.load(f)
needed_locations = determine_must_visit_locations(plan_json, db, self.initial_data.game_name)
missing_locations = needed_locations - set(initial_plan.path)
for prev_stage in stage_results:
missing_locations = missing_locations - set(prev_stage.path)
# compare those to all the ones we visit in the generated route
node_list = list(world.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)
result = None
if nodes_to_visit:
stage.must_visit = stage.must_visit.union(nodes_to_visit)
re_visisted = stage.create_world()
planner = RoutePlanner(re_visisted)
distances = planner.compute_shortest_path(re_visisted.graph, stage.must_visit)
result = planner.astar_search()
else:
result = initial_plan
if result:
print(result.path)
stage_results.append(result)
self.signals.finished.emit(stage_results)
except Exception as e:
print(f"Error: {e}")

212
ui/workers/solve_pddl_route.py

@ -0,0 +1,212 @@
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 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[0:2])
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")

176
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)

111
utility/data.py

@ -323,6 +323,61 @@ main_line_games = [
scarlet, violet,
]
default_pokemon_forms = {
"Deoxys":"Normal Forme",
"Zacian ":"Hero of Many Battles",
"Zamazenta ":"Hero of Many Battles",
"Giratina": "Altered Forme",
"Shaymin": "Land Forme",
"Keldeo": "Ordinary Form",
"Meloetta": "Aria Forme",
"Furfrou": "Normal Form",
"Aegislash": "Shield Forme",
"Xerneas": "Neutral Mode",
"Hoopa": "Hoopa Confined",
"Wishiwashi": "Solo Form",
"Silvally": "Type: Normal",
"Minior": "Red Core",
"Mimikyu": "Disguised Form",
"Eiscue": "Ice Face",
"Morpeko": "Full Belly Mode",
"Palafin": "Zero Form",
"Tatsugiri": "Curly Form",
"Koraidon": "Apex Build",
"Miraidon": "Ultimate Mode",
"Ogerpon": "Teal Mask",
"Terapagos": "Normal Form",
"Burmy": "Plant Cloak",
"Wormadam": "Plant Cloak",
"Cherrim": "Overcast Form",
"Shellos": "West Sea",
"Gastrodon": "West Sea",
"Arceus": "Normal",
"Basculin": "Red-Striped Form",
"Deerling": "Spring Form",
"Sawsbuck": "Spring Form",
"Tornadus": "Incarnate Forme",
"Thundurus": "Incarnate Forme",
"Landorus": "Incarnate Forme",
"Enamorus": "Incarnate Forme",
"Flabébé": "Red Flower",
"Floette": "Red Flower",
"Florges": "Red Flower",
"Zygarde": "50% Forme",
"Oricorio": "Baile Style",
"Lycanroc": "Midday Form",
"Toxtricity": "Amped Form",
"Alcremie": "Vanilla Cream Strawberry Sweet",
"Urshifu": "Single Strike Style",
"Squawkabilly": "Green Plumage",
"Dudunsparce": "Two-Segment Form",
#"Poltchageist": "Counterfeit Form",
#"Sinistcha": "Unremarkable Form",
#"Sinistea": "Antique Form",
#"Polteageist": "Phony Form",
"Gimmighoul": "Chest Form"
}
# If a pokemon is in this form then its generally* not refered to as a form
# *I say generally as some do and some don't
default_forms = [
@ -354,10 +409,10 @@ default_forms = [
"Overcast Form",
"West Sea",
"Normal",
"Red Striped Form",
"Red-Striped Form",
"Spring Form",
"Incarnate Forme",
"Meadow Pattern",
"Red Flower",
"Average Size",
"50% Forme",
@ -396,7 +451,12 @@ POKEMON_PROPER_NOUNS = {
"Pa'u",
"Sensu",
"Debutante",
"Douse"
"Douse",
"Red-Striped",
"Blue-Striped",
"White-Striped",
"Ash-Greninja",
"Pom-Pom"
}
POKEMON_PROPER_NOUNS = POKEMON_PROPER_NOUNS | set(regions)
@ -420,6 +480,30 @@ shiftable_forms = [
{"from_pfic":"0492-04-002-0", "to_pfic":"0492-04-001-0"}
]
exclusive_choice_pokemon = [
{
"game_id": 1,
"choices":[
["0106-01-000-1", "0107-01-000-1"], #hitmonlee, hitmonchan
["0140-01-000-1", "0138-01-000-1", "0140-01-000-2", "0138-01-000-2"] #Omanyte, Kabuto
]
},
{
"game_id": 2,
"choices":[
["0106-01-000-1", "0107-01-000-1"], #hitmonlee, hitmonchan
["0140-01-000-1", "0138-01-000-1", "0140-01-000-2", "0138-01-000-2"] #Omanyte, Kabuto
]
},
{
"game_id": 3,
"choices":[
["0106-01-000-1", "0107-01-000-1"], #hitmonlee, hitmonchan
["0140-01-000-1", "0138-01-000-1", "0140-01-000-2", "0138-01-000-2"] #Omanyte, Kabuto
]
}
]
alcremie_forms = [
"Caramel Swirl Berry Sweet",
"Caramel Swirl Clover Sweet",
@ -485,3 +569,26 @@ alcremie_forms = [
"Salted Cream Star Sweet",
"Salted Cream Strawberry Sweet",
]
vivillon_patterns =[
"Meadow Pattern",
"Archipelago Pattern",
"Continental Pattern",
"Elegant Pattern",
"Garden Pattern",
"High plains Pattern",
"Icy snow Pattern",
"Jungle Pattern",
"Marine Pattern",
"Meadow Pattern",
"Modern Pattern",
"Monsoon Pattern",
"Ocean Pattern",
"Polar Pattern",
"River Pattern",
"Sandstorm Pattern",
"Savanna Pattern",
"Sun Pattern",
"Tundra Pattern",
"Fancy Pattern"
]

7
utility/functions.py

@ -137,3 +137,10 @@ def get_shiftable_forms(pfic):
if pfic == form_pair["from_pfic"]:
forms.append(form_pair)
return forms
def get_shiftable_forms_bidirectional(pfic):
forms = []
for form_pair in shiftable_forms:
if pfic == form_pair["from_pfic"] or pfic == form_pair["to_pfic"]:
forms.append(form_pair)
return forms

2
utility/pokemon_word_ninja.py

@ -31,7 +31,7 @@ class PokemonWordNinja:
def split(self, text: str) -> str:
working_text = text
working_text = working_text.replace("-", " ")
#working_text = working_text.replace("-", " ")
# First handle exact custom words to preserve capitalization
for word in self.custom_words:

Loading…
Cancel
Save