From 6d0a44e885563be1526742ef3d469582ce551c75 Mon Sep 17 00:00:00 2001 From: Quildra Date: Fri, 25 Oct 2024 22:05:30 +0100 Subject: [PATCH] - Progres updates to solve the origin mark calculation --- DBEditor/DBEditor.py | 131 ++++++++++++++++++++++++++++++++++---- DBEditor/db_controller.py | 31 ++++++++- Site/OriginDex.py | 107 ++++++------------------------- pokemon_forms.db | Bin 3121152 -> 3166208 bytes 4 files changed, 168 insertions(+), 101 deletions(-) diff --git a/DBEditor/DBEditor.py b/DBEditor/DBEditor.py index bf0ec90..81c413c 100644 --- a/DBEditor/DBEditor.py +++ b/DBEditor/DBEditor.py @@ -437,6 +437,25 @@ class DBEditor(QMainWindow): self.logger.info("Database reinitialized successfully.") QMessageBox.information(self, 'Database Reinitialized', 'The database has been cleared and reinitialized.') + def determine_origin_mark(self, pfic, target_generation): + encounters = event_system.call_sync('get_encounter_locations', pfic) + if encounters: + generation_encounters = [] + for encounter in encounters: + game_generation = event_system.call_sync('get_game_generation', encounter[0]) + if game_generation == target_generation: + generation_encounters.append(encounter) + if len(generation_encounters) > 0: + form_info = event_system.call_sync('get_pokemon_data', pfic) + mark_id = event_system.call_sync('get_mark_for_game_name', generation_encounters[0][0]) + if mark_id == None: + self.logger.info(f"No mark found for {form_info[0]} {form_info[1]}") + else: + mark_details = event_system.call_sync('get_mark_details', mark_id) + self.logger.info(f"Mark for {form_info[0]} {form_info[1]} is {mark_details[0]} - {mark_details[1]}") + return mark_id + return None + def gather_marks_info(self, data): event_system.emit_sync('clear_log_display') self.logger.info("Starting to gather marks information...") @@ -445,32 +464,118 @@ class DBEditor(QMainWindow): for pfic, name, form_name, national_dex in pokemon_list: pokemon_data = event_system.call_sync('get_pokemon_data', pfic) + + if pokemon_data[4] == False: + continue; + + self.logger.info(f"Determining mark for {name} {form_name if form_name else ''}") + + target_generation = pokemon_data[3] #Rule 1 # 1. If a pokemon form has a previous evolution from within the same generation, # use the mark of the previous evolution. This should be recursive within the same generation. chain = event_system.call_sync('get_evolution_chain', pfic) if chain: - find_me = lambda x: x[0] == pfic - target_index = next((i for i, item in enumerate(chain) if find_me(item)), -1) base_form_in_generation = None + last_pfic = pfic current_pfic = pfic while True: - current_pfic = event_system.call_sync('get_evolution_parent', current_pfic) + current_pfic = event_system.call_sync('get_evolution_parent', last_pfic) if current_pfic == None: - base_form_in_generation = current_pfic + base_form_in_generation = last_pfic break - chain_pokemon_data = event_system.call_sync('get_pokemon_data', current_pfic) - if chain_pokemon_data[3] == pokemon_data[3]: - base_form_in_generation = current_pfic + chain_pokemon_data = event_system.call_sync('get_pokemon_data', current_pfic[0]) + if chain_pokemon_data[3] == target_generation: + base_form_in_generation = current_pfic[0] + else: + base_form_in_generation = last_pfic break + last_pfic = current_pfic[0] - if base_form_in_generation: + if base_form_in_generation and base_form_in_generation != pfic: self.logger.info(f"Base form in generation for {name} {form_name if form_name else ''} is {base_form_in_generation}") - - - - - + mark_id = self.determine_origin_mark(base_form_in_generation, target_generation) + if mark_id != None: + event_system.emit_sync('assign_mark_to_form', (pfic, mark_id)) + continue; + elif base_form_in_generation == pfic: + mark_id = self.determine_origin_mark(pfic, target_generation) + if mark_id != None: + event_system.emit_sync('assign_mark_to_form', (pfic, mark_id)) + continue; + #Rule 2 + # If a pokemon form has no previous evolution from within the same generation, + # look at the encounters of the pokemon form from this generation and use the mark of the earliest + # game you can encounter that form in from that generation + mark_id = self.determine_origin_mark(pfic, target_generation) + if mark_id != None: + event_system.emit_sync('assign_mark_to_form', (pfic, mark_id)) + continue; + + + # this bit doesn't work as we exclude eveolve encounters from our dataset. + # this needs to change so we do include them, but filter them out of all our current queries + # then make a new API that includes them to solve this problem. + # We then find the earliest encounter in the for the base pokemon, that gets uus a game id + # we then use that game id to get the generation and run the determine_origin_mark using that generation + # for its evolution line. + + #Rule 3 + # If there are no encounters for the pokemon form from this generation, + # look to see if a previous evolution has an encounter from this generation, and use the mark of the earliest + # game from this generation that the previous evolution is encounterable in. + if chain: + last_pfic = pfic + mark_identified = False + should_continue = True + while should_continue: + encounters = event_system.call_sync('get_encounter_locations', last_pfic) + if encounters: + earliest_game = 100 + for encounter in encounters: + game_id = event_system.call_sync('get_game_id_for_name', encounter[0]) + if game_id <= earliest_game: + earliest_game = game_id + if earliest_game < 100: + form_info = event_system.call_sync('get_pokemon_data', last_pfic) + mark_id = event_system.call_sync('get_mark_for_game', earliest_game) + if mark_id == None: + self.logger.info(f"No mark found for {form_info[0]} {form_info[1]}") + else: + mark_details = event_system.call_sync('get_mark_details', mark_id) + self.logger.info(f"Mark for {form_info[0]} {form_info[1]} is {mark_details[0]} - {mark_details[1]}") + event_system.emit_sync('assign_mark_to_form', (pfic, mark_id)) + should_continue = False + mark_identified = True + break; + + last_pfic = event_system.call_sync('get_evolution_parent', last_pfic) + if last_pfic == None: + should_continue = False + break + if mark_identified: + continue; + + #Rule 4 + # If there are no encounters for the pokemon form or its evolution line from this generation, + # use the mark of the earliest game of the generation is marked as being introducted in. + encounters = event_system.call_sync('get_encounter_locations', pfic) + if encounters: + earliest_game = 100 + for encounter in encounters: + game_id = event_system.call_sync('get_game_id_for_name', encounter[0]) + if game_id <= earliest_game: + earliest_game = game_id + if earliest_game < 100: + form_info = event_system.call_sync('get_pokemon_data', pfic) + mark_id = event_system.call_sync('get_mark_for_game', earliest_game) + if mark_id == None: + self.logger.info(f"No mark found for {form_info[0]} {form_info[1]}") + else: + mark_details = event_system.call_sync('get_mark_details', mark_id) + self.logger.info(f"Mark for {form_info[0]} {form_info[1]} is {mark_details[0]} - {mark_details[1]}") + event_system.emit_sync('assign_mark_to_form', (pfic, mark_id)) + continue; def qt_message_handler(mode, context, message): print(f"Qt Message: {mode} {context} {message}") diff --git a/DBEditor/db_controller.py b/DBEditor/db_controller.py index 2baa8cc..0bd980e 100644 --- a/DBEditor/db_controller.py +++ b/DBEditor/db_controller.py @@ -35,6 +35,9 @@ class DBController: event_system.add_listener('save_changes', self.save_changes) event_system.add_listener('export_database', self.export_database) event_system.add_listener('assign_mark_to_form', self.assign_mark_to_form) + event_system.add_listener('get_mark_for_game', self.get_mark_for_game) + event_system.add_listener('get_mark_for_game_name', self.get_mark_for_game_name) + event_system.add_listener('get_mark_details', self.get_mark_details) def init_database(self): disk_conn = sqlite3.connect('pokemon_forms.db') @@ -484,6 +487,32 @@ class DBController: def assign_mark_to_form(self, data): pfic, mark_id = data - self.cursor.execute('INSERT INTO form_marks (pfic, mark_id) VALUES (?, ?)', (pfic, mark_id)) + self.cursor.execute(''' + INSERT OR REPLACE INTO form_marks (pfic, mark_id) + VALUES (?, ?) + ''', (pfic, mark_id)) self.conn.commit() + def get_mark_for_game(self, data): + game_id = data + self.cursor.execute('SELECT mark_id FROM mark_game_associations WHERE game_id = ?', (game_id,)) + result = self.cursor.fetchone() + return result[0] if result else None + + def get_mark_for_game_name(self, data): + game_name = data + self.cursor.execute(''' + SELECT m.id + FROM marks m + JOIN mark_game_associations mga ON m.id = mga.mark_id + JOIN games g ON mga.game_id = g.id + WHERE g.name = ? + ''', (game_name,)) + result = self.cursor.fetchone() + return result[0] if result else None + + def get_mark_details(self, data): + mark_id = data + self.cursor.execute('SELECT name, icon_path FROM marks WHERE id = ?', (mark_id,)) + return self.cursor.fetchone() + diff --git a/Site/OriginDex.py b/Site/OriginDex.py index a1f415e..9d6cf77 100644 --- a/Site/OriginDex.py +++ b/Site/OriginDex.py @@ -18,97 +18,13 @@ def load_pokemon_data(): ps.storable_in_home FROM pokemon_forms pf JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC + WHERE ps.storable_in_home = 1 ORDER BY pf.PFIC ''') pokemon_data = cursor.fetchall() - # Function to get the origin mark for a Pokémon - def get_origin_mark(pfic, generation): - # Step 1: Check for previous evolution in the same generation - cursor.execute(''' - WITH RECURSIVE EvolutionChain AS ( - SELECT ec.to_pfic, ec.from_pfic, pf_to.generation as to_gen, pf_from.generation as from_gen - FROM evolution_chains ec - JOIN pokemon_forms pf_to ON ec.to_pfic = pf_to.PFIC - JOIN pokemon_forms pf_from ON ec.from_pfic = pf_from.PFIC - WHERE ec.to_pfic = ? AND pf_to.generation = ? - UNION ALL - SELECT ec.to_pfic, ec.from_pfic, pf_to.generation, pf_from.generation - FROM evolution_chains ec - JOIN EvolutionChain e ON ec.to_pfic = e.from_pfic - JOIN pokemon_forms pf_to ON ec.to_pfic = pf_to.PFIC - JOIN pokemon_forms pf_from ON ec.from_pfic = pf_from.PFIC - WHERE pf_to.generation = ? AND pf_from.generation = ? - ) - SELECT from_pfic - FROM EvolutionChain - WHERE from_pfic IS NOT NULL AND from_gen = ? - ORDER BY from_pfic - LIMIT 1 - ''', (pfic, generation, generation, generation, generation)) - base_pfic = cursor.fetchone() - - if base_pfic: - pfic = base_pfic[0] - - # Step 2: Look for the earliest encounter for this form in the current generation - cursor.execute(''' - SELECT g.name, m.icon_path, m.id - FROM encounters e - JOIN games g ON e.game_id = g.id - LEFT JOIN mark_game_associations mga ON g.id = mga.game_id - LEFT JOIN marks m ON mga.mark_id = m.id - WHERE e.pfic = ? AND g.generation = ? - ORDER BY g.id - LIMIT 1 - ''', (pfic, generation)) - encounter_data = cursor.fetchone() - - if encounter_data: - return encounter_data - - # Step 3: Look for encounters of previous evolutions in the current generation - cursor.execute(''' - WITH RECURSIVE EvolutionChain AS ( - SELECT ec.to_pfic, ec.from_pfic - FROM evolution_chains ec - WHERE ec.to_pfic = ? - UNION ALL - SELECT ec.to_pfic, ec.from_pfic - FROM evolution_chains ec - JOIN EvolutionChain e ON ec.to_pfic = e.from_pfic - ) - SELECT g.name, m.icon_path, m.id - FROM EvolutionChain ec - JOIN encounters e ON ec.from_pfic = e.pfic - JOIN games g ON e.game_id = g.id - LEFT JOIN mark_game_associations mga ON g.id = mga.game_id - LEFT JOIN marks m ON mga.mark_id = m.id - WHERE g.generation = ? - ORDER BY g.id - LIMIT 1 - ''', (pfic, generation)) - evolution_encounter_data = cursor.fetchone() - - if evolution_encounter_data: - return evolution_encounter_data - - # Step 4: Use the mark of the earliest game of the generation - cursor.execute(''' - SELECT g.name, m.icon_path, m.id - FROM games g - LEFT JOIN mark_game_associations mga ON g.id = mga.game_id - LEFT JOIN marks m ON mga.mark_id = m.id - WHERE g.generation = ? - ORDER BY g.id - LIMIT 1 - ''', (generation,)) - generation_data = cursor.fetchone() - - return generation_data if generation_data else (None, None, None) - # Process the data current_group = [] current_generation = None @@ -117,8 +33,26 @@ def load_pokemon_data(): for row in pokemon_data: national_dex, name, form_name, pfic, generation, storable_in_home = row + + mark_id = None + mark_icon = None - earliest_game, mark_icon, mark_id = get_origin_mark(pfic, generation) + cursor.execute(''' + SELECT mark_id + FROM form_marks + WHERE pfic = ? + ''', (pfic,)) + result = cursor.fetchone() + mark_id = result[0] if result else None + + if mark_id: + cursor.execute(''' + SELECT icon_path + FROM marks + WHERE id = ? + ''', (mark_id,)) + result = cursor.fetchone() + mark_icon = result[0] if result else None pokemon = { 'pfic': pfic, @@ -128,7 +62,6 @@ def load_pokemon_data(): 'Image': f"images/pokemon/{pfic}.png", 'IsDefault': form_name is None or form_name == '', 'Generation': generation, - 'EarliestGame': earliest_game, 'StorableInHome': storable_in_home, 'MarkIcon': mark_icon, 'MarkID': mark_id diff --git a/pokemon_forms.db b/pokemon_forms.db index cc7f9479490bf09a1e49c65d6eeb6a075b6e1998..a97bbad9729360a21ea1354b2d7fdab229ae6245 100644 GIT binary patch delta 53145 zcmeI5cf2G;)%T~nrzeMQa?Y7u=$^gz&I~yRiIPM?a#EraTw-(Q20%bSK><+|6;Tio zQ4kSP5djk@Dk3N%@Bku+qJSd4zjLZiRrOpSW%sJjA1{6wR=#_>s?O=^?qAobsy_3M z+r2YC_6_gCW#h{Vg&-{0|1EdXoO$78h4oH4_X~&I^Y48@~v@7{5jETNJ;=@LL?eCGcAk zzoqb78oy=mTNc0N@LL|g74TaTzm@P?8NXHVTNS_6@LL_fHSk*#zqRmN8^3k%TNl4M z_|3&{y_@g%)?fdO70AFPzeO!CL_KxihKCr?8OJ)bQc4=+g zTBo&CtJHk4`B?M5=IzaEo0m1uYM#(MsJTmXVK|3UjI@3j{5cWE9&p9 =-=n@oJ*h8Wuh(9#Jz4v4?T*^@ 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

(#HcTf9vQaugkPXuDQkdx$82@{> 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

K=!;~W|L=7WHvUrM>sDa88 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 E0lS^o$N&HU delta 281 zcmY+Z1x5{ zhYLPVhBx^P@A!ltI|;wGGIQ-nDeXu7&v_q=j7SZizdtAYgZ`v~#e>Lr6^JMEjrRB! zi$H^p6bzV1;{>P3Ad4I<3I`Xc!9^V&F44dhnrNYo4!Y>!8hza0 e7Izrn9>aAkzW-`kf2GA`Gnp3VLGof|j{h%t