Browse Source

- Large work on the plan generation.

- Mostly all working, about 7 or so forms not account for, excluding event pokemon
plan_generation
Quildra 1 year ago
parent
commit
a2efb738a8
  1. 3
      .gitignore
  2. 104
      database/db_controller.py
  3. 9
      ui/main_window_controller.py
  4. 12
      ui/main_window_view.py
  5. 367
      ui/workers/generate_plan_worker.py
  6. 24
      utility/data.py

3
.gitignore

@ -139,4 +139,5 @@ dmypy.json
cython_debug/
# diskcache folder
cache/
cache/
temp/

104
database/db_controller.py

@ -504,4 +504,106 @@ class DBController:
# Fetch and print the results
results = self.cursor.fetchall()
return [dict(row) for row in results]
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

9
ui/main_window_controller.py

@ -9,6 +9,7 @@ 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_plan_worker import GeneratePlanWorker
from utility.functions import get_display_name
from db import db
@ -208,3 +209,11 @@ 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!")

12
ui/main_window_view.py

@ -26,6 +26,7 @@ class PokemonUI(QWidget):
self.setup_main_tab()
self.setup_db_operations_tab()
self.setup_manage_encounters_tab()
self.setup_plan_tab()
self.save_button = QPushButton("Save Changes")
self.save_button.clicked.connect(self.controller.save_changes)
@ -217,7 +218,16 @@ 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 update_pokemon_forms(self, data):
self.pokemon_list.clear()

367
ui/workers/generate_plan_worker.py

@ -0,0 +1,367 @@
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, parse_pfic
class GeneratePlanWorkerSignals(QObject):
finished = pyqtSignal(dict)
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 {}
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:
if pokemon["storable_in_home"] == False:
continue
pfic = pokemon["PFIC"]
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)
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']])
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:
output_file = "./temp/"+game["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()
evolution_paths = {}
evolution_predecessors = {}
all_pokemon = needed_pokemon | available_pokemon
for group in exclusive_groups:
for pfic in group:
all_pokemon.add(pfic)
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)
for pfic in needed_pokemon & available_pokemon:
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 = {}
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
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
# 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
for pfic in required_pokemon:
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
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

24
utility/data.py

@ -420,6 +420,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",

Loading…
Cancel
Save