25 changed files with 2936 additions and 8390 deletions
@ -0,0 +1,382 @@ |
|||||
|
import sqlite3 |
||||
|
import sys |
||||
|
import os |
||||
|
|
||||
|
from event_system import event_system |
||||
|
|
||||
|
def parse_pfic(pfic): |
||||
|
parts = pfic.split('-') |
||||
|
return tuple(int(part) if part.isdigit() else part for part in parts) |
||||
|
|
||||
|
class DBController: |
||||
|
def __init__(self): |
||||
|
self.conn = sqlite3.connect(':memory:', check_same_thread=False) # Use in-memory database for runtime |
||||
|
self.cursor = self.conn.cursor() |
||||
|
|
||||
|
event_system.add_listener('get_pokemon_data', self.load_pokemon_details) |
||||
|
event_system.add_listener('get_pokemon_list', self.send_pokemon_list) |
||||
|
event_system.add_listener('get_home_storable', self.get_home_storable) |
||||
|
event_system.add_listener('get_evolution_chain', self.get_evolution_chain) |
||||
|
event_system.add_listener('get_evolution_parent', self.get_evolution_parent) |
||||
|
event_system.add_listener('get_encounter_locations', self.get_encounter_locations) |
||||
|
event_system.add_listener('get_game_generation', self.get_game_generation) |
||||
|
event_system.add_listener('get_pokemon_with_encounters', self.get_pokemon_with_encounters) |
||||
|
event_system.add_listener('refresh_in_memory_db', self.refresh_in_memory_db) |
||||
|
event_system.add_listener('clear_encounters_for_pokemon', self.clear_encounters_for_pokemon) |
||||
|
event_system.add_listener('get_game_id_for_name', self.get_game_id_for_name) |
||||
|
|
||||
|
def init_database(self): |
||||
|
disk_conn = sqlite3.connect('pokemon_forms.db') |
||||
|
disk_cursor = disk_conn.cursor() |
||||
|
|
||||
|
# Create tables in the file-based database |
||||
|
self.create_games_table(disk_cursor) |
||||
|
self.create_pokemon_forms_table(disk_cursor) |
||||
|
self.create_pokemon_storage_table(disk_cursor) |
||||
|
self.create_evolution_chains_table(disk_cursor) |
||||
|
self.create_exclusive_encounter_groups_table(disk_cursor) |
||||
|
self.create_encounters_table(disk_cursor) |
||||
|
self.create_mark_table(disk_cursor) |
||||
|
|
||||
|
# Commit changes to the file-based database |
||||
|
disk_conn.commit() |
||||
|
|
||||
|
# Copy the file-based database to the in-memory database |
||||
|
disk_conn.backup(self.conn) |
||||
|
|
||||
|
# Close the file-based database connection |
||||
|
disk_conn.close() |
||||
|
|
||||
|
def create_pokemon_forms_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS pokemon_forms ( |
||||
|
PFIC TEXT PRIMARY KEY, |
||||
|
name TEXT NOT NULL, |
||||
|
form_name TEXT, |
||||
|
national_dex INTEGER NOT NULL, |
||||
|
generation INTEGER NOT NULL, |
||||
|
is_baby_form BOOLEAN |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
def create_pokemon_storage_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS pokemon_storage ( |
||||
|
PFIC TEXT PRIMARY KEY, |
||||
|
storable_in_home BOOLEAN NOT NULL, |
||||
|
FOREIGN KEY (PFIC) REFERENCES pokemon_forms (PFIC) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
def create_evolution_chains_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS evolution_chains ( |
||||
|
from_pfic TEXT, |
||||
|
to_pfic TEXT, |
||||
|
method TEXT, |
||||
|
PRIMARY KEY (from_pfic, to_pfic), |
||||
|
FOREIGN KEY (from_pfic) REFERENCES pokemon_forms (PFIC), |
||||
|
FOREIGN KEY (to_pfic) REFERENCES pokemon_forms (PFIC) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
def create_exclusive_encounter_groups_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS exclusive_encounter_groups ( |
||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
||||
|
group_name TEXT NOT NULL, |
||||
|
description TEXT, |
||||
|
game_id INTEGER, |
||||
|
FOREIGN KEY (game_id) REFERENCES games (id) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
def create_encounter_exclusive_group_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS encounter_exclusive_groups ( |
||||
|
encounter_id INTEGER, |
||||
|
group_id INTEGER, |
||||
|
FOREIGN KEY (encounter_id) REFERENCES encounters (id), |
||||
|
FOREIGN KEY (group_id) REFERENCES exclusive_encounter_groups (id), |
||||
|
PRIMARY KEY (encounter_id, group_id) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
def create_encounters_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS encounters ( |
||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
||||
|
pfic TEXT, |
||||
|
game_id INTEGER, |
||||
|
location TEXT, |
||||
|
day TEXT, |
||||
|
time TEXT, |
||||
|
dual_slot TEXT, |
||||
|
static_encounter BOOLEAN, |
||||
|
static_encounter_count INTEGER, |
||||
|
extra_text TEXT, |
||||
|
stars TEXT, |
||||
|
fishing BOOLEAN, |
||||
|
rods TEXT, |
||||
|
starter BOOLEAN, |
||||
|
FOREIGN KEY (pfic) REFERENCES pokemon_forms (PFIC), |
||||
|
FOREIGN KEY (game_id) REFERENCES games (id) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
def create_mark_table(self, cursor): |
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS marks ( |
||||
|
id INTEGER PRIMARY KEY, |
||||
|
name TEXT NOT NULL UNIQUE, |
||||
|
icon_path TEXT NOT NULL |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS mark_game_associations ( |
||||
|
mark_id INTEGER, |
||||
|
game_id INTEGER, |
||||
|
FOREIGN KEY (mark_id) REFERENCES marks (id), |
||||
|
FOREIGN KEY (game_id) REFERENCES games (id), |
||||
|
PRIMARY KEY (mark_id, game_id) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
marks = [ |
||||
|
("Game Boy", "images/marks/GB_icon_HOME.png", ["Red", "Blue", "Yellow", "Gold", "Silver", "Crystal"]), |
||||
|
("Kalos", "images/marks/Blue_pentagon_HOME.png", ["X", "Y", "Omega Ruby", "Alpha Sapphire"]), |
||||
|
("Alola", "images/marks/Black_clover_HOME.png", ["Sun", "Moon", "Ultra Sun", "Ultra Moon"]), |
||||
|
("Let's Go", "images/marks/Let's_Go_icon_HOME.png", ["Let's Go Pikachu", "Let's Go Eevee"]), |
||||
|
("Galar", "images/marks/Galar_symbol_HOME.png", ["Sword", "Shield"]), |
||||
|
("Sinnoh", "images/marks/BDSP_icon_HOME.png", ["Brilliant Diamond", "Shining Pearl"]), |
||||
|
("Hisui", "images/marks/Arceus_mark_HOME.png", ["Legends Arceus"]), |
||||
|
("Paldea", "images/marks/Paldea_icon_HOME.png", ["Scarlet", "Violet"]), |
||||
|
] |
||||
|
|
||||
|
# Check if marks already exist |
||||
|
cursor.execute('SELECT COUNT(*) FROM marks') |
||||
|
marks_count = cursor.fetchone()[0] |
||||
|
|
||||
|
if marks_count == 0: |
||||
|
for mark in marks: |
||||
|
cursor.execute(''' |
||||
|
INSERT OR IGNORE INTO marks (name, icon_path) |
||||
|
VALUES (?, ?) |
||||
|
''', (mark[0], mark[1])) |
||||
|
|
||||
|
mark_id = cursor.lastrowid |
||||
|
|
||||
|
for game_name in mark[2]: |
||||
|
cursor.execute(''' |
||||
|
INSERT OR IGNORE INTO mark_game_associations (mark_id, game_id) |
||||
|
SELECT ?, id FROM games WHERE name = ? |
||||
|
''', (mark_id, game_name)) |
||||
|
|
||||
|
def create_games_table(self, cursor): |
||||
|
|
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS games ( |
||||
|
id INTEGER PRIMARY KEY, |
||||
|
name TEXT NOT NULL, |
||||
|
generation INTEGER NOT NULL |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS alternate_game_names ( |
||||
|
id INTEGER PRIMARY KEY, |
||||
|
game_id INTEGER NOT NULL, |
||||
|
alternate_name TEXT NOT NULL, |
||||
|
FOREIGN KEY (game_id) REFERENCES games (id), |
||||
|
UNIQUE (alternate_name COLLATE NOCASE) |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
games = [ |
||||
|
("Red", 1, ["Red Version"]), |
||||
|
("Blue", 1, ["Blue Version"]), |
||||
|
("Yellow", 1, ["Yellow Version"]), |
||||
|
("Gold", 2, ["Gold Version"]), |
||||
|
("Silver", 2, ["Silver Version"]), |
||||
|
("Crystal", 2, ["Crystal Version"]), |
||||
|
("Ruby", 3, ["Ruby Version"]), |
||||
|
("Sapphire", 3, ["Sapphire Version"]), |
||||
|
("Emerald", 3, ["Emerald Version"]), |
||||
|
("FireRed", 3, ["Fire Red", "Fire-Red"]), |
||||
|
("LeafGreen", 3, ["Leaf Green", "Leaf-Green"]), |
||||
|
("Diamond", 4, ["Diamond Version"]), |
||||
|
("Pearl", 4, ["Pearl Version"]), |
||||
|
("Platinum", 4, ["Platinum Version"]), |
||||
|
("HeartGold", 4, ["Heart Gold", "Heart-Gold"]), |
||||
|
("SoulSilver", 4, ["Soul Silver", "Soul-Silver"]), |
||||
|
("Black", 5, ["Black Version"]), |
||||
|
("White", 5, ["White Version"]), |
||||
|
("Black 2", 5, ["Black Version 2", "Black-2"]), |
||||
|
("White 2", 5, ["White Version 2", "White-2"]), |
||||
|
("X", 6, ["X Version"]), |
||||
|
("Y", 6, ["Y Version"]), |
||||
|
("Omega Ruby", 6, ["Omega Ruby Version", "Omega-Ruby"]), |
||||
|
("Alpha Sapphire", 6, ["Alpha Sapphire Version", "Alpha-Sapphire"]), |
||||
|
("Sun", 7, ["Sun Version"]), |
||||
|
("Moon", 7, ["Moon Version"]), |
||||
|
("Ultra Sun", 7, ["Ultra Sun Version", "Ultra-Sun"]), |
||||
|
("Ultra Moon", 7, ["Ultra Moon Version", "Ultra-Moon"]), |
||||
|
("Let's Go Pikachu", 7, ["Let's Go, Pikachu!", "Lets Go Pikachu"]), |
||||
|
("Let's Go Eevee", 7, ["Let's Go, Eevee!", "Lets Go Eevee"]), |
||||
|
("Sword", 8, ["Sword Version"]), |
||||
|
("Shield", 8, ["Shield Version"]), |
||||
|
("Expansion Pass", 8, ["Expansion Pass (Sword)", "Expansion Pass (Shield)"]), |
||||
|
("Brilliant Diamond", 8, ["Brilliant Diamond Version", "Brilliant-Diamond"]), |
||||
|
("Shining Pearl", 8, ["Shining Pearl Version", "Shining-Pearl"]), |
||||
|
("Legends Arceus", 8, ["Legends: Arceus", "Legends-Arceus"]), |
||||
|
("Scarlet", 9, ["Scarlet Version"]), |
||||
|
("Violet", 9, ["Violet Version"]), |
||||
|
("The Teal Mask", 9, ["The Teal Mask Version", "The Teal Mask (Scarlet)", "The Teal Mask (Violet)"]), |
||||
|
("The Hidden Treasure of Area Zero", 9, ["The Hidden Treasure of Area Zero Version", "The Hidden Treasure of Area Zero (Scarlet)", "The Hidden Treasure of Area Zero (Violet)"]), |
||||
|
|
||||
|
("Pokémon Home", 98, ["Pokémon HOME"]), |
||||
|
("Pokémon Go", 99, ["Pokémon GO"]), |
||||
|
] |
||||
|
|
||||
|
for game in games: |
||||
|
cursor.execute(''' |
||||
|
INSERT OR IGNORE INTO games (name, generation) |
||||
|
VALUES (?, ?) |
||||
|
''', (game[0], game[1])) |
||||
|
|
||||
|
game_id = cursor.lastrowid |
||||
|
|
||||
|
# Insert alternate names |
||||
|
for alt_name in game[2]: |
||||
|
cursor.execute(''' |
||||
|
INSERT OR IGNORE INTO alternate_game_names (game_id, alternate_name) |
||||
|
VALUES (?, ?) |
||||
|
''', (game_id, alt_name)) |
||||
|
|
||||
|
def load_pokemon_details(self, pfic): |
||||
|
self.cursor.execute(''' |
||||
|
SELECT pf.name, pf.form_name, pf.national_dex, pf.generation, ps.storable_in_home, pf.is_baby_form |
||||
|
FROM pokemon_forms pf |
||||
|
LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC |
||||
|
WHERE pf.PFIC = ? |
||||
|
''', (pfic,)) |
||||
|
|
||||
|
return self.cursor.fetchone() |
||||
|
|
||||
|
def send_pokemon_list(self, data): |
||||
|
# Fetch pokemon list from database |
||||
|
self.cursor.execute(''' |
||||
|
SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex |
||||
|
FROM pokemon_forms pf |
||||
|
''') |
||||
|
pokemon_data = self.cursor.fetchall() |
||||
|
|
||||
|
# Sort the pokemon_data based on PFIC |
||||
|
pokemon_data.sort(key=lambda x: parse_pfic(x[0])) |
||||
|
return pokemon_data |
||||
|
|
||||
|
def get_home_storable(self, pfic): |
||||
|
self.cursor.execute('SELECT storable_in_home FROM pokemon_storage WHERE PFIC = ?', (pfic,)) |
||||
|
result = self.cursor.fetchone() |
||||
|
home_storable = result[0] if result else False |
||||
|
return home_storable |
||||
|
|
||||
|
def get_evolution_chain(self, pfic): |
||||
|
def follow_chain(start_pfic, visited=None): |
||||
|
if visited is None: |
||||
|
visited = set() |
||||
|
|
||||
|
chain = [] |
||||
|
stack = [(start_pfic, None)] |
||||
|
|
||||
|
while stack: |
||||
|
current_pfic, method = stack.pop() |
||||
|
if current_pfic in visited: |
||||
|
continue |
||||
|
|
||||
|
visited.add(current_pfic) |
||||
|
self.cursor.execute('SELECT name, form_name FROM pokemon_forms WHERE PFIC = ?', (current_pfic,)) |
||||
|
name, form_name = self.cursor.fetchone() |
||||
|
|
||||
|
chain.append((current_pfic, name, form_name, method)) |
||||
|
|
||||
|
# Get previous evolutions |
||||
|
self.cursor.execute('SELECT from_pfic, method FROM evolution_chains WHERE to_pfic = ?', (current_pfic,)) |
||||
|
prev_evolutions = self.cursor.fetchall() |
||||
|
for prev_pfic, prev_method in prev_evolutions: |
||||
|
if prev_pfic not in visited: |
||||
|
stack.append((prev_pfic, prev_method)) |
||||
|
|
||||
|
# Get next evolutions |
||||
|
self.cursor.execute('SELECT to_pfic, method FROM evolution_chains WHERE from_pfic = ?', (current_pfic,)) |
||||
|
next_evolutions = self.cursor.fetchall() |
||||
|
for next_pfic, next_method in next_evolutions: |
||||
|
if next_pfic not in visited: |
||||
|
stack.append((next_pfic, next_method)) |
||||
|
|
||||
|
return chain |
||||
|
|
||||
|
return follow_chain(pfic) |
||||
|
|
||||
|
def get_evolution_parent(self, pfic): |
||||
|
self.cursor.execute('SELECT from_pfic FROM evolution_chains WHERE to_pfic = ?', (pfic,)) |
||||
|
return self.cursor.fetchone() |
||||
|
|
||||
|
def get_encounter_locations(self, pfic): |
||||
|
self.cursor.execute(''' |
||||
|
SELECT g.name, e.location, e.day, e.time, e.dual_slot, e.static_encounter_count, e.static_encounter, e.extra_text, e.stars, e.rods, e.fishing |
||||
|
FROM encounters e |
||||
|
JOIN games g ON e.game_id = g.id |
||||
|
WHERE e.pfic = ? |
||||
|
ORDER BY g.name, e.location |
||||
|
''', (pfic,)) |
||||
|
return self.cursor.fetchall() |
||||
|
|
||||
|
def get_game_generation(self, game): |
||||
|
self.cursor.execute(''' |
||||
|
SELECT g.generation |
||||
|
FROM games g |
||||
|
LEFT JOIN alternate_game_names agn ON g.id = agn.game_id |
||||
|
WHERE g.name = ? OR agn.alternate_name = ? |
||||
|
LIMIT 1 |
||||
|
''', (game, game)) |
||||
|
result = self.cursor.fetchone() |
||||
|
return result[0] if result else None |
||||
|
|
||||
|
def get_game_id_for_name(self, game): |
||||
|
# First, try to find the game in the main games table |
||||
|
self.cursor.execute(''' |
||||
|
SELECT id |
||||
|
FROM games |
||||
|
WHERE name = ? |
||||
|
''', (game,)) |
||||
|
result = self.cursor.fetchone() |
||||
|
|
||||
|
if result: |
||||
|
return result[0] |
||||
|
|
||||
|
# If not found, check the alternate_game_names table |
||||
|
self.cursor.execute(''' |
||||
|
SELECT game_id |
||||
|
FROM alternate_game_names |
||||
|
WHERE alternate_name = ? |
||||
|
''', (game,)) |
||||
|
result = self.cursor.fetchone() |
||||
|
|
||||
|
return result[0] if result else None |
||||
|
|
||||
|
def get_pokemon_with_encounters(self, data): |
||||
|
self.cursor.execute(''' |
||||
|
SELECT DISTINCT pfic |
||||
|
FROM encounters |
||||
|
''') |
||||
|
pokemon_with_encounters = set(row[0] for row in self.cursor.fetchall()) |
||||
|
return pokemon_with_encounters |
||||
|
|
||||
|
def refresh_in_memory_db(self, temp_conn): |
||||
|
temp_conn.backup(self.conn) |
||||
|
|
||||
|
def clear_encounters_for_pokemon(self, pfic): |
||||
|
self.cursor.execute('DELETE FROM encounters WHERE pfic = ?', (pfic,)) |
||||
|
self.conn.commit() |
||||
@ -0,0 +1,269 @@ |
|||||
|
import sqlite3 |
||||
|
from collections import defaultdict |
||||
|
|
||||
|
class OriginDexPlanner: |
||||
|
def __init__(self, db_path): |
||||
|
self.conn = sqlite3.connect(db_path) |
||||
|
self.conn.row_factory = sqlite3.Row |
||||
|
|
||||
|
def get_game_data(self): |
||||
|
"""Fetch game and pokemon information from the database.""" |
||||
|
cursor = self.conn.cursor() |
||||
|
cursor.execute("SELECT * FROM games") |
||||
|
games = cursor.fetchall() |
||||
|
|
||||
|
cursor.execute("SELECT * FROM pokemon_forms WHERE name IN ('Elekid', 'Electabuzz', 'Electivire')") |
||||
|
pokemon_forms = cursor.fetchall() |
||||
|
|
||||
|
cursor.execute("SELECT * FROM encounters") |
||||
|
encounters = cursor.fetchall() |
||||
|
|
||||
|
exclusive_encounters = [] |
||||
|
cursor.execute("SELECT * FROM evolution_chains WHERE from_pfic IN (SELECT PFIC FROM pokemon_forms WHERE name IN ('Elekid', 'Electabuzz', 'Electivire'))") |
||||
|
evolutions = cursor.fetchall() |
||||
|
|
||||
|
return games, pokemon_forms, encounters, exclusive_encounters, evolutions |
||||
|
|
||||
|
def build_pokemon_data(self): |
||||
|
"""Construct data dictionaries to store relationships between the Pokemon and encounters.""" |
||||
|
games, pokemon_forms, encounters, exclusive_encounters, evolutions = self.get_game_data() |
||||
|
|
||||
|
pokemon_by_gen = defaultdict(list) |
||||
|
game_by_gen = defaultdict(list) |
||||
|
encounter_data = defaultdict(list) |
||||
|
|
||||
|
for game in games: |
||||
|
game_by_gen[game['generation']].append(game) |
||||
|
|
||||
|
for pokemon in pokemon_forms: |
||||
|
pokemon_by_gen[pokemon['generation']].append(pokemon) |
||||
|
|
||||
|
# Build a dict of encounters, mapping game_id to Pokemon encountered in it |
||||
|
for encounter in encounters: |
||||
|
encounter_data[encounter['game_id']].append(encounter) |
||||
|
|
||||
|
evolution_map = defaultdict(list) |
||||
|
for evolution in evolutions: |
||||
|
from_pfic = evolution['from_pfic'] |
||||
|
to_pfic = evolution['to_pfic'] |
||||
|
# Lookup generation from pokemon_forms table |
||||
|
cursor = self.conn.cursor() |
||||
|
cursor.execute("SELECT generation FROM pokemon_forms WHERE PFIC = ?", (to_pfic,)) |
||||
|
to_generation = cursor.fetchone()['generation'] |
||||
|
evolution_map[from_pfic].append((to_pfic, to_generation)) |
||||
|
|
||||
|
exclusive_group_map = defaultdict(list) |
||||
|
for exclusive_encounter in exclusive_encounters: |
||||
|
exclusive_group_map[exclusive_encounter['group_id']].append(exclusive_encounter['encounter_id']) |
||||
|
|
||||
|
return pokemon_by_gen, game_by_gen, encounter_data, evolution_map, exclusive_group_map |
||||
|
|
||||
|
def generate_plan(self, generations_to_group_list=None): |
||||
|
"""Generate a game plan to catch all Pokemon in their origin generation.""" |
||||
|
if generations_to_group_list is None: |
||||
|
generations_to_group_list = [] |
||||
|
|
||||
|
pokemon_by_gen, game_by_gen, encounter_data, evolution_map, exclusive_group_map = self.build_pokemon_data() |
||||
|
|
||||
|
plan = [] |
||||
|
pokemon_to_catch = defaultdict(list) |
||||
|
caught_pokemon = set() |
||||
|
pokemon_names = {} |
||||
|
|
||||
|
# Handle multiple sets of grouped generations |
||||
|
grouped_generations_sets = [set(generations) for generations in generations_to_group_list] |
||||
|
for grouped_generations in grouped_generations_sets: |
||||
|
grouped_games = [] |
||||
|
grouped_pokemon_list = [] |
||||
|
for gen in grouped_generations: |
||||
|
if gen in game_by_gen: |
||||
|
grouped_games.extend(game_by_gen[gen]) |
||||
|
grouped_pokemon_list.extend(pokemon_by_gen[gen]) |
||||
|
|
||||
|
if grouped_games: |
||||
|
plan.extend(self.generate_plan_for_generation(grouped_games, grouped_pokemon_list, encounter_data, evolution_map, exclusive_group_map, pokemon_names, caught_pokemon, game_by_gen)) |
||||
|
|
||||
|
# Loop through each generation, generating a plan for each game |
||||
|
for gen in sorted(game_by_gen.keys()): |
||||
|
# Skip generations that have been grouped |
||||
|
if any(gen in grouped_generations for grouped_generations in grouped_generations_sets): |
||||
|
continue |
||||
|
games = game_by_gen[gen] |
||||
|
pokemon_list = pokemon_by_gen[gen] |
||||
|
|
||||
|
plan.extend(self.generate_plan_for_generation(games, pokemon_list, encounter_data, evolution_map, exclusive_group_map, pokemon_names, caught_pokemon, game_by_gen)) |
||||
|
|
||||
|
return plan |
||||
|
|
||||
|
def generate_plan_for_generation(self, games, pokemon_list, encounter_data, evolution_map, exclusive_group_map, pokemon_names, caught_pokemon, game_by_gen): |
||||
|
"""Generate a game plan for a specific generation or group of generations.""" |
||||
|
plan = [] |
||||
|
pokemon_to_catch = defaultdict(list) |
||||
|
pokemon_to_breed = defaultdict(list) |
||||
|
catchable_pokemon = set() |
||||
|
|
||||
|
# Create a set of baby Pokémon PFICs |
||||
|
baby_pokemon = set(pokemon['PFIC'] for pokemon in pokemon_list if pokemon['is_baby_form']) |
||||
|
|
||||
|
# Identify unique evolution chains and the starting point |
||||
|
for pokemon in pokemon_list: |
||||
|
pfic = pokemon['PFIC'] |
||||
|
pokemon_names[pfic] = f'{pokemon["name"]} ({pokemon["form_name"] if pokemon["form_name"] is not None else ""})' |
||||
|
if pfic not in evolution_map: # Not evolved from anything |
||||
|
catchable_pokemon.add(pfic) |
||||
|
else: |
||||
|
catchable_pokemon.add(pfic) |
||||
|
# Add all evolution stages to catchable list if they belong to the current or previous generations |
||||
|
current = pfic |
||||
|
current_generation = pokemon['generation'] |
||||
|
while current in evolution_map: |
||||
|
next_pfic, next_generation = evolution_map[current][0] |
||||
|
if any(p['PFIC'] == next_pfic for p in pokemon_list): |
||||
|
if next_generation <= current_generation: |
||||
|
catchable_pokemon.add(next_pfic) |
||||
|
current = next_pfic |
||||
|
|
||||
|
# Determine the best game to catch the most Pokemon |
||||
|
best_game = None |
||||
|
max_catchable = 0 |
||||
|
game_catchable_pokemon = defaultdict(set) |
||||
|
for game in games: |
||||
|
game_id = game['id'] |
||||
|
encounters = encounter_data[game_id] |
||||
|
|
||||
|
# Track how many unique Pokemon are available in this game |
||||
|
for encounter in encounters: |
||||
|
pfic = encounter['pfic'] |
||||
|
if pfic in catchable_pokemon: |
||||
|
game_catchable_pokemon[game_id].add(pfic) |
||||
|
|
||||
|
# Update the best game if this game has more catchable Pokemon |
||||
|
if len(game_catchable_pokemon[game_id]) > max_catchable: |
||||
|
max_catchable = len(game_catchable_pokemon[game_id]) |
||||
|
best_game = game |
||||
|
|
||||
|
# Use the best game to catch as many unique Pokemon as possible |
||||
|
remaining_games = [game for game in games if game != best_game] |
||||
|
if best_game: |
||||
|
game_id = best_game['id'] |
||||
|
game_name = best_game['name'] |
||||
|
encounters = encounter_data[game_id] |
||||
|
|
||||
|
for encounter in encounters: |
||||
|
pfic = encounter['pfic'] |
||||
|
if pfic in catchable_pokemon and pfic not in caught_pokemon: |
||||
|
evolution_chain = self.get_full_evolution_chain(pfic, evolution_map) |
||||
|
base_pokemon = evolution_chain[0] |
||||
|
if base_pokemon not in caught_pokemon: |
||||
|
# Calculate the number of Pokemon needed for the full evolution chain |
||||
|
count = len(evolution_chain) |
||||
|
if encounter['static_encounter']: |
||||
|
count = min(count, encounter['static_encounter_count'] or 1) |
||||
|
|
||||
|
if base_pokemon in baby_pokemon: |
||||
|
# For baby Pokémon, catch the first non-baby evolution and breed for the baby |
||||
|
non_baby_pokemon = next((p for p in evolution_chain if p not in baby_pokemon), None) |
||||
|
if non_baby_pokemon: |
||||
|
pokemon_to_catch[game_name].append({'pfic': non_baby_pokemon, 'count': 1}) |
||||
|
pokemon_to_breed[game_name].append({'pfic': base_pokemon, 'count': 1}) |
||||
|
else: |
||||
|
pokemon_to_catch[game_name].append({'pfic': base_pokemon, 'count': count}) |
||||
|
|
||||
|
caught_pokemon.update(evolution_chain) |
||||
|
|
||||
|
# Account for exclusive encounters |
||||
|
exclusive_group_ids = [group['group_id'] for group in exclusive_group_map if group['encounter_id'] == encounter['id']] |
||||
|
if exclusive_group_ids: |
||||
|
other_options = exclusive_group_map[exclusive_group_ids[0]] |
||||
|
if len(other_options) > 1: |
||||
|
# Pick one of the options, mark the rest to catch in another game |
||||
|
catchable_pokemon.remove(pfic) |
||||
|
else: |
||||
|
continue |
||||
|
|
||||
|
# Use remaining games to catch any remaining Pokemon |
||||
|
for game in remaining_games: |
||||
|
game_id = game['id'] |
||||
|
game_name = game['name'] |
||||
|
encounters = encounter_data[game_id] |
||||
|
|
||||
|
for encounter in encounters: |
||||
|
pfic = encounter['pfic'] |
||||
|
if pfic in catchable_pokemon and pfic not in caught_pokemon: |
||||
|
evolution_chain = self.get_full_evolution_chain(pfic, evolution_map) |
||||
|
base_pokemon = evolution_chain[0] |
||||
|
if base_pokemon not in caught_pokemon: |
||||
|
# Calculate the number of Pokemon needed for the full evolution chain |
||||
|
count = len(evolution_chain) |
||||
|
if encounter['static_encounter']: |
||||
|
count = min(count, encounter['static_encounter_count'] or 1) |
||||
|
|
||||
|
if base_pokemon in baby_pokemon: |
||||
|
# For baby Pokémon, catch the first non-baby evolution and breed for the baby |
||||
|
non_baby_pokemon = next((p for p in evolution_chain if p not in baby_pokemon), None) |
||||
|
if non_baby_pokemon: |
||||
|
pokemon_to_catch[game_name].append({'pfic': non_baby_pokemon, 'count': 1}) |
||||
|
pokemon_to_breed[game_name].append({'pfic': base_pokemon, 'count': 1}) |
||||
|
else: |
||||
|
pokemon_to_catch[game_name].append({'pfic': base_pokemon, 'count': count}) |
||||
|
|
||||
|
caught_pokemon.update(evolution_chain) |
||||
|
|
||||
|
# Handle new evolutions or forms added in later generations |
||||
|
for pfic in list(caught_pokemon): |
||||
|
if pfic in evolution_map: |
||||
|
for new_evolution, new_generation in evolution_map[pfic]: |
||||
|
# Check if the new evolution is from a later generation |
||||
|
full_evolution_chain = self.get_full_evolution_chain(pfic, evolution_map) |
||||
|
current_generation = min((pokemon['generation'] for pokemon in pokemon_list if pokemon['PFIC'] in full_evolution_chain), default=None) |
||||
|
if new_generation and current_generation and new_generation > current_generation: |
||||
|
# Add the baby/base form to be caught in the new generation if required |
||||
|
base_pokemon = full_evolution_chain[0] |
||||
|
new_gen_games = game_by_gen[new_generation] |
||||
|
for game in new_gen_games: |
||||
|
game_id = game['id'] |
||||
|
game_name = game['name'] |
||||
|
encounters = encounter_data[game_id] |
||||
|
for encounter in encounters: |
||||
|
if encounter['pfic'] == base_pokemon and base_pokemon not in caught_pokemon: |
||||
|
if base_pokemon in baby_pokemon: |
||||
|
non_baby_pokemon = next((p for p in full_evolution_chain if p not in baby_pokemon), None) |
||||
|
if non_baby_pokemon: |
||||
|
pokemon_to_catch[game_name].append({'pfic': non_baby_pokemon, 'count': 1}) |
||||
|
pokemon_to_breed[game_name].append({'pfic': base_pokemon, 'count': 1}) |
||||
|
else: |
||||
|
pokemon_to_catch[game_name].append({'pfic': base_pokemon, 'count': 1}) |
||||
|
caught_pokemon.add(base_pokemon) |
||||
|
break |
||||
|
|
||||
|
# Generate the plan for this generation or group of generations |
||||
|
for game_name in set(list(pokemon_to_catch.keys()) + list(pokemon_to_breed.keys())): |
||||
|
plan.append(f"Play {game_name}:") |
||||
|
if game_name in pokemon_to_catch: |
||||
|
plan.append(f" Catch {len(pokemon_to_catch[game_name])} unique Pokemon:") |
||||
|
for pokemon in pokemon_to_catch[game_name]: |
||||
|
plan.append(f" - {pokemon_names[pokemon['pfic']]}: {pokemon['count']} times") |
||||
|
|
||||
|
if game_name in pokemon_to_breed: |
||||
|
plan.append(f" Breed {len(pokemon_to_breed[game_name])} Pokemon:") |
||||
|
for pokemon in pokemon_to_breed[game_name]: |
||||
|
plan.append(f" - {pokemon_names[pokemon['pfic']]}: {pokemon['count']} times") |
||||
|
|
||||
|
return plan |
||||
|
|
||||
|
def get_full_evolution_chain(self, pfic, evolution_map): |
||||
|
"""Get the full evolution chain for a given PFIC.""" |
||||
|
chain = [pfic] |
||||
|
while pfic in evolution_map: |
||||
|
pfic = evolution_map[pfic][0][0] |
||||
|
chain.append(pfic) |
||||
|
return chain |
||||
|
|
||||
|
def display_plan(self, generations_to_group_list=None): |
||||
|
plan = self.generate_plan(generations_to_group_list) |
||||
|
for step in plan: |
||||
|
print(step) |
||||
|
|
||||
|
# Example usage |
||||
|
planner = OriginDexPlanner('pokemon_forms.db') |
||||
|
planner.display_plan(generations_to_group_list=[[1, 2], [3, 4, 5, 6]]) |
||||
@ -0,0 +1,262 @@ |
|||||
|
import sqlite3 |
||||
|
import os |
||||
|
from collections import defaultdict |
||||
|
|
||||
|
class EfficiencyOriginDexPlanner: |
||||
|
def __init__(self, db_path): |
||||
|
self.conn = sqlite3.connect(db_path) |
||||
|
self.conn.row_factory = sqlite3.Row |
||||
|
|
||||
|
def get_all_data(self): |
||||
|
cursor = self.conn.cursor() |
||||
|
cursor.execute("SELECT * FROM games ORDER BY generation, id") |
||||
|
games = cursor.fetchall() |
||||
|
|
||||
|
cursor.execute("SELECT * FROM pokemon_forms ORDER BY generation, national_dex") |
||||
|
pokemon_forms = cursor.fetchall() |
||||
|
|
||||
|
cursor.execute("SELECT * FROM encounters") |
||||
|
encounters = cursor.fetchall() |
||||
|
|
||||
|
cursor.execute("SELECT * FROM evolution_chains") |
||||
|
evolutions = cursor.fetchall() |
||||
|
|
||||
|
return games, pokemon_forms, encounters, evolutions |
||||
|
|
||||
|
def build_data_structures(self): |
||||
|
games, pokemon_forms, encounters, evolutions = self.get_all_data() |
||||
|
|
||||
|
pokemon_by_gen = defaultdict(list) |
||||
|
game_by_gen = defaultdict(list) |
||||
|
encounter_data = defaultdict(list) |
||||
|
evolution_map = defaultdict(list) |
||||
|
|
||||
|
for game in games: |
||||
|
game_by_gen[game['generation']].append(game) |
||||
|
|
||||
|
for pokemon in pokemon_forms: |
||||
|
pokemon_by_gen[pokemon['generation']].append(pokemon) |
||||
|
|
||||
|
for encounter in encounters: |
||||
|
encounter_data[encounter['game_id']].append(encounter) |
||||
|
|
||||
|
for evolution in evolutions: |
||||
|
evolution_map[evolution['from_pfic']].append(evolution['to_pfic']) |
||||
|
|
||||
|
return pokemon_by_gen, game_by_gen, encounter_data, evolution_map |
||||
|
|
||||
|
def generate_efficient_plan(self, generation_groups): |
||||
|
pokemon_by_gen, game_by_gen, encounter_data, evolution_map = self.build_data_structures() |
||||
|
plan = [] |
||||
|
caught_pokemon = set() |
||||
|
|
||||
|
for group in generation_groups: |
||||
|
group_plan = self.plan_for_group(group, pokemon_by_gen, game_by_gen, encounter_data, evolution_map, caught_pokemon) |
||||
|
plan.extend(group_plan) |
||||
|
|
||||
|
return plan |
||||
|
|
||||
|
def plan_for_group(self, generations, pokemon_by_gen, game_by_gen, encounter_data, evolution_map, caught_pokemon): |
||||
|
group_plan = [] |
||||
|
needed_pokemon = set() |
||||
|
games_in_group = [] |
||||
|
|
||||
|
for gen in generations: |
||||
|
needed_pokemon.update(pokemon['PFIC'] for pokemon in pokemon_by_gen[gen]) |
||||
|
games_in_group.extend(game_by_gen[gen]) |
||||
|
|
||||
|
# Check for new evolutions of already caught Pokémon |
||||
|
new_evolutions = set() |
||||
|
for pfic in caught_pokemon: |
||||
|
if pfic in evolution_map: |
||||
|
for evolved_pfic in evolution_map[pfic]: |
||||
|
if self.get_pokemon_generation(evolved_pfic) in generations and evolved_pfic not in caught_pokemon: |
||||
|
new_evolutions.add(pfic) |
||||
|
needed_pokemon.add(evolved_pfic) |
||||
|
print(f"New evolution: {self.get_pokemon_name(pfic)} into {self.get_pokemon_name(evolved_pfic)}") |
||||
|
|
||||
|
games_in_group.sort(key=lambda g: len([e for e in encounter_data[g['id']] if e['pfic'] in needed_pokemon]), reverse=True) |
||||
|
|
||||
|
for game in games_in_group: |
||||
|
game_plan = self.plan_for_game(game, needed_pokemon, encounter_data, evolution_map, caught_pokemon, set(generations), new_evolutions) |
||||
|
if game_plan: |
||||
|
group_plan.extend(game_plan) |
||||
|
|
||||
|
return group_plan |
||||
|
|
||||
|
def plan_for_game(self, game, needed_pokemon, encounter_data, evolution_map, caught_pokemon, target_generations, new_evolutions): |
||||
|
game_plan = [] |
||||
|
to_catch = defaultdict(int) |
||||
|
to_evolve = defaultdict(list) |
||||
|
to_breed = defaultdict(list) |
||||
|
planned_evolutions = defaultdict(int) |
||||
|
processed_chains = set() |
||||
|
|
||||
|
# First, handle new evolutions for already caught Pokémon |
||||
|
for pfic in new_evolutions: |
||||
|
evolution_chains, visit_counts, baby_forms = self.get_evolution_chains(pfic, evolution_map, target_generations) |
||||
|
|
||||
|
new_evolutions_count = sum(1 for chain in evolution_chains for pokemon in chain if pokemon not in caught_pokemon) |
||||
|
|
||||
|
if new_evolutions_count > 0: |
||||
|
if any(encounter['pfic'] == pfic for encounter in encounter_data[game['id']]): |
||||
|
to_catch[pfic] += new_evolutions_count - planned_evolutions[pfic] |
||||
|
planned_evolutions[pfic] += new_evolutions_count |
||||
|
|
||||
|
for chain in evolution_chains: |
||||
|
if len(chain) > 1: |
||||
|
for i in range(len(chain) - 1): |
||||
|
from_pfic, to_pfic = chain[i], chain[i+1] |
||||
|
if to_pfic not in caught_pokemon: |
||||
|
to_evolve[from_pfic].append(to_pfic) |
||||
|
caught_pokemon.add(to_pfic) |
||||
|
needed_pokemon.discard(to_pfic) |
||||
|
|
||||
|
# Then proceed with the regular planning |
||||
|
for encounter in encounter_data[game['id']]: |
||||
|
if encounter['pfic'] in needed_pokemon and encounter['pfic'] not in caught_pokemon: |
||||
|
evolution_chains, visit_counts, baby_forms = self.get_evolution_chains(encounter['pfic'], evolution_map, target_generations) |
||||
|
base_form = evolution_chains[0][0] |
||||
|
|
||||
|
if base_form not in caught_pokemon: |
||||
|
chain_key = tuple(sorted(set(pokemon for chain in evolution_chains for pokemon in chain))) |
||||
|
if chain_key not in processed_chains: |
||||
|
processed_chains.add(chain_key) |
||||
|
|
||||
|
num_to_catch = max(0, visit_counts[base_form] - planned_evolutions[base_form]) |
||||
|
if num_to_catch > 0: |
||||
|
to_catch[encounter['pfic']] += num_to_catch |
||||
|
planned_evolutions[base_form] += num_to_catch |
||||
|
|
||||
|
for chain in evolution_chains: |
||||
|
if len(chain) > 1: |
||||
|
for i in range(len(chain) - 1): |
||||
|
from_pfic, to_pfic = chain[i], chain[i+1] |
||||
|
if from_pfic not in baby_forms: |
||||
|
if not self.is_final_evolution(to_pfic, evolution_map) or (self.is_final_evolution(to_pfic, evolution_map) and not self.is_in_to_evolve(to_pfic, to_evolve)): |
||||
|
to_evolve[from_pfic].append(to_pfic) |
||||
|
else: |
||||
|
to_catch[encounter['pfic']] += -1 |
||||
|
planned_evolutions[base_form] += -1 |
||||
|
|
||||
|
if from_pfic in baby_forms and from_pfic not in encounter_data[game['id']]: |
||||
|
if to_pfic not in to_breed or from_pfic not in to_breed[to_pfic]: |
||||
|
to_breed[to_pfic].append(from_pfic) |
||||
|
|
||||
|
caught_pokemon.update(pfic for chain in evolution_chains for pfic in chain) |
||||
|
needed_pokemon.difference_update(pfic for chain in evolution_chains for pfic in chain) |
||||
|
|
||||
|
if to_catch or to_evolve or to_breed: |
||||
|
game_plan.append(f"Play {game['name']}:") |
||||
|
if to_catch: |
||||
|
game_plan.append(" Catch:") |
||||
|
for pfic, count in to_catch.items(): |
||||
|
game_plan.append(f" - {self.get_pokemon_name(pfic)}: {count} time(s)") |
||||
|
if to_evolve: |
||||
|
game_plan.append(" Evolve:") |
||||
|
for from_pfic, to_pfics in to_evolve.items(): |
||||
|
for to_pfic in to_pfics: |
||||
|
game_plan.append(f" - {self.get_pokemon_name(from_pfic)} into {self.get_pokemon_name(to_pfic)}") |
||||
|
if to_breed: |
||||
|
game_plan.append(" Breed:") |
||||
|
for parent_pfic, baby_pfics in to_breed.items(): |
||||
|
for baby_pfic in baby_pfics: |
||||
|
game_plan.append(f" - {self.get_pokemon_name(parent_pfic)} to get {self.get_pokemon_name(baby_pfic)}") |
||||
|
|
||||
|
return game_plan |
||||
|
|
||||
|
def plan_evolutions(self, evolution_chains, evolution_map, caught_pokemon): |
||||
|
chains, visit_counts = self.get_evolution_chains(evolution_chains[0][0], evolution_map) |
||||
|
evolution_plan = [] |
||||
|
needed_counts = defaultdict(int) |
||||
|
|
||||
|
# Count how many of each Pokémon we need based on visit counts |
||||
|
for pfic, count in visit_counts.items(): |
||||
|
if pfic not in caught_pokemon: |
||||
|
needed_counts[pfic] = count |
||||
|
|
||||
|
# Plan evolutions |
||||
|
for chain in chains: |
||||
|
for i in range(len(chain) - 1): |
||||
|
from_pfic, to_pfic = chain[i], chain[i+1] |
||||
|
if needed_counts[to_pfic] > 0: |
||||
|
evolution_plan.append((from_pfic, to_pfic)) |
||||
|
needed_counts[to_pfic] -= 1 |
||||
|
needed_counts[from_pfic] -= 1 |
||||
|
|
||||
|
return evolution_plan, needed_counts |
||||
|
|
||||
|
def get_evolution_chains(self, pfic, evolution_map, target_generations): |
||||
|
visited = defaultdict(int) |
||||
|
unique_paths = set() |
||||
|
baby_forms = set() |
||||
|
|
||||
|
def dfs(current_pfic, path): |
||||
|
path_tuple = tuple(path) |
||||
|
if path_tuple in unique_paths: |
||||
|
return |
||||
|
|
||||
|
unique_paths.add(path_tuple) |
||||
|
for pfic in path: |
||||
|
visited[pfic] += 1 |
||||
|
if self.is_baby_pokemon(pfic): |
||||
|
baby_forms.add(pfic) |
||||
|
|
||||
|
if current_pfic in evolution_map: |
||||
|
for next_pfic in evolution_map[current_pfic]: |
||||
|
if self.get_pokemon_generation(next_pfic) in target_generations: |
||||
|
dfs(next_pfic, path + [next_pfic]) |
||||
|
|
||||
|
dfs(pfic, [pfic]) |
||||
|
chains = [list(path) for path in unique_paths] |
||||
|
|
||||
|
# Adjust visit counts for baby Pokémon |
||||
|
for baby in baby_forms: |
||||
|
visited[baby] = 1 |
||||
|
|
||||
|
return chains, visited, baby_forms |
||||
|
|
||||
|
def get_pokemon_name(self, pfic): |
||||
|
cursor = self.conn.cursor() |
||||
|
cursor.execute("SELECT name, form_name FROM pokemon_forms WHERE PFIC = ?", (pfic,)) |
||||
|
result = cursor.fetchone() |
||||
|
if result['form_name']: |
||||
|
return f"{result['name']} ({result['form_name']})" |
||||
|
return result['name'] |
||||
|
|
||||
|
def display_plan(self, generation_groups, output_file='efficiency_plan.txt'): |
||||
|
plan = self.generate_efficient_plan(generation_groups) |
||||
|
|
||||
|
# Print to console |
||||
|
for step in plan: |
||||
|
print(step) |
||||
|
|
||||
|
# Write to file |
||||
|
with open(output_file, 'w', encoding='utf-8') as f: |
||||
|
for step in plan: |
||||
|
f.write(f"{step}\n") |
||||
|
|
||||
|
print(f"\nPlan has been written to {os.path.abspath(output_file)}") |
||||
|
|
||||
|
def is_baby_pokemon(self, pfic): |
||||
|
cursor = self.conn.cursor() |
||||
|
cursor.execute("SELECT is_baby_form FROM pokemon_forms WHERE PFIC = ?", (pfic,)) |
||||
|
result = cursor.fetchone() |
||||
|
return result['is_baby_form'] if result else False |
||||
|
|
||||
|
def get_pokemon_generation(self, pfic): |
||||
|
cursor = self.conn.cursor() |
||||
|
cursor.execute("SELECT generation FROM pokemon_forms WHERE PFIC = ?", (pfic,)) |
||||
|
result = cursor.fetchone() |
||||
|
return result['generation'] if result else None |
||||
|
|
||||
|
def is_final_evolution(self, pfic, evolution_map): |
||||
|
return pfic not in evolution_map |
||||
|
|
||||
|
def is_in_to_evolve(self, pfic, to_evolve): |
||||
|
return any(pfic in to_pfics for to_pfics in to_evolve.values()) |
||||
|
|
||||
|
# Example usage |
||||
|
planner = EfficiencyOriginDexPlanner('pokemon_forms.db') |
||||
|
planner.display_plan([[1, 2], [3, 4, 5, 6], [7], [8], [9]]) |
||||
|
|
||||
@ -1,87 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html> |
|
||||
<head> |
|
||||
<title>Pokémon Database Visualizer</title> |
|
||||
<link rel="stylesheet" href="styles.css"> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div id="app"> |
|
||||
<div id="tabs"> |
|
||||
<button class="tab-button active" data-tab="forms">Pokémon Forms</button> |
|
||||
<button class="tab-button" data-tab="evolutions">Evolution Chains</button> |
|
||||
</div> |
|
||||
|
|
||||
<div id="forms-tab" class="tab-content active"> |
|
||||
<div class="forms-container"> |
|
||||
<div id="pokemon-forms-list"> |
|
||||
<input type="text" id="forms-filter" placeholder="Filter Pokémon..."> |
|
||||
<ul id="forms-list-items"></ul> |
|
||||
</div> |
|
||||
<div id="pokemon-details"> |
|
||||
<h2>Pokémon Details</h2> |
|
||||
<div id="details-content"> |
|
||||
<div id="pokemon-basic-info"> |
|
||||
<img id="pokemon-image" src="" alt="Pokémon Image"> |
|
||||
<div id="pokemon-info"> |
|
||||
<h3 id="pokemon-name"></h3> |
|
||||
<p id="pokemon-pfic"></p> |
|
||||
<p id="pokemon-national-dex"></p> |
|
||||
<p id="pokemon-generation"></p> |
|
||||
<div id="pokemon-storable"> |
|
||||
<label for="storable-checkbox">Storable in Home:</label> |
|
||||
<input type="checkbox" id="storable-checkbox"> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div id="pokemon-evolution-chain"> |
|
||||
<h4>Evolution Chain</h4> |
|
||||
<div id="details-evolution-chain-content"> |
|
||||
<table id="evolution-table"> |
|
||||
<!-- The table will be populated dynamically --> |
|
||||
</table> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
|
|
||||
|
|
||||
<div id="evolutions-tab" class="tab-content"> |
|
||||
<div class="evolution-container"> |
|
||||
<div id="pokemon-list"> |
|
||||
<input type="text" id="pokemon-filter" placeholder="Filter Pokémon..."> |
|
||||
<ul id="pokemon-list-items"></ul> |
|
||||
</div> |
|
||||
<div id="evolution-chain-container"> |
|
||||
<div id="evolution-chain"></div> |
|
||||
<div id="evolution-controls"> |
|
||||
<button id="add-stage">Add Stage</button> |
|
||||
<button id="save-evolution-changes">Save Changes</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<script src="renderer.js"></script> |
|
||||
|
|
||||
<!-- Add this just before the closing </body> tag --> |
|
||||
<div id="edit-pokemon-modal" class="modal"> |
|
||||
<div class="modal-content"> |
|
||||
<span class="close">×</span> |
|
||||
<h2>Edit Pokémon</h2> |
|
||||
<form id="edit-pokemon-form"> |
|
||||
<label for="pokemon-select">Select Pokémon:</label> |
|
||||
<select id="pokemon-select" required> |
|
||||
<!-- Options will be populated dynamically --> |
|
||||
</select> |
|
||||
<label for="evolution-method">Evolution Method:</label> |
|
||||
<input type="text" id="evolution-method" required> |
|
||||
<button type="submit">Save Changes</button> |
|
||||
</form> |
|
||||
</div> |
|
||||
</div> |
|
||||
</body> |
|
||||
</html> |
|
||||
@ -1,200 +0,0 @@ |
|||||
const { app, BrowserWindow, ipcMain } = require('electron'); |
|
||||
const path = require('path'); |
|
||||
const sqlite3 = require('sqlite3').verbose(); |
|
||||
|
|
||||
app.commandLine.appendSwitch('remote-debugging-port', '9222'); |
|
||||
|
|
||||
let mainWindow; |
|
||||
let db; |
|
||||
|
|
||||
function createWindow() { |
|
||||
mainWindow = new BrowserWindow({ |
|
||||
width: 1200, |
|
||||
height: 800, |
|
||||
webPreferences: { |
|
||||
nodeIntegration: true, |
|
||||
contextIsolation: false |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
mainWindow.loadFile('index.html'); |
|
||||
|
|
||||
db = new sqlite3.Database('../pokemon_forms.db', (err) => { |
|
||||
if (err) { |
|
||||
console.error('Database opening error: ', err); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
app.whenReady().then(createWindow); |
|
||||
|
|
||||
app.on('window-all-closed', () => { |
|
||||
if (process.platform !== 'darwin') { |
|
||||
app.quit(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
app.on('activate', () => { |
|
||||
if (BrowserWindow.getAllWindows().length === 0) { |
|
||||
createWindow(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('load-forms-data', (event) => { |
|
||||
db.all(` |
|
||||
SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex, pf.generation, ps.storable_in_home |
|
||||
FROM pokemon_forms pf |
|
||||
LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC |
|
||||
`, (err, rows) => {
|
|
||||
if (err) { |
|
||||
console.error('Error fetching data: ', err); |
|
||||
event.reply('forms-data-response', { error: err.message }); |
|
||||
} else { |
|
||||
event.reply('forms-data-response', { data: rows }); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('search-evolution', (event, searchTerm) => { |
|
||||
db.all(` |
|
||||
SELECT DISTINCT name, PFIC |
|
||||
FROM pokemon_forms |
|
||||
WHERE LOWER(name) LIKE ? |
|
||||
`, [`%${searchTerm.toLowerCase()}%`], (err, rows) => {
|
|
||||
if (err) { |
|
||||
console.error('Error searching evolution: ', err); |
|
||||
event.reply('evolution-search-response', { error: err.message }); |
|
||||
} else { |
|
||||
event.reply('evolution-search-response', { data: rows }); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('get-evolution-chain', (event, pfic) => { |
|
||||
function getEvolutions(currentPfic) { |
|
||||
return new Promise((resolve, reject) => { |
|
||||
db.all(` |
|
||||
SELECT ec.to_pfic as pfic, pf.name, pf.form_name, ec.method, ec.from_pfic |
|
||||
FROM evolution_chains ec |
|
||||
JOIN pokemon_forms pf ON ec.to_pfic = pf.PFIC |
|
||||
WHERE ec.from_pfic = ? |
|
||||
`, [currentPfic], (err, rows) => {
|
|
||||
if (err) { |
|
||||
reject(err); |
|
||||
} else { |
|
||||
resolve(rows); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
async function buildChain(pfic) { |
|
||||
const pokemon = await new Promise((resolve, reject) => { |
|
||||
db.get(`SELECT PFIC as pfic, name, form_name FROM pokemon_forms WHERE PFIC = ?`, [pfic], (err, row) => { |
|
||||
if (err) reject(err); |
|
||||
else resolve(row); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
if (!pokemon) return null; |
|
||||
|
|
||||
const evolutions = await getEvolutions(pfic); |
|
||||
pokemon.evolutions = await Promise.all(evolutions.map(evo => buildChain(evo.pfic))); |
|
||||
|
|
||||
return pokemon; |
|
||||
} |
|
||||
|
|
||||
buildChain(pfic) |
|
||||
.then(chain => { |
|
||||
event.reply('evolution-chain-response', { data: chain }); |
|
||||
}) |
|
||||
.catch(err => { |
|
||||
console.error('Error building evolution chain:', err); |
|
||||
event.reply('evolution-chain-response', { error: err.message }); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('edit-pokemon', (event, data) => { |
|
||||
// Implement the logic to update the Pokémon in the database
|
|
||||
console.log('Editing Pokémon:', data); |
|
||||
// Update the database and send a response back to the renderer
|
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('remove-pokemon', (event, data) => { |
|
||||
// Implement the logic to remove the Pokémon from the evolution chain in the database
|
|
||||
console.log('Removing Pokémon:', data); |
|
||||
// Update the database and send a response back to the renderer
|
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('add-stage', (event, data) => { |
|
||||
// Implement the logic to add a new stage to the evolution chain in the database
|
|
||||
console.log('Adding new stage:', data); |
|
||||
// Update the database and send a response back to the renderer
|
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('save-evolution-changes', (event, data) => { |
|
||||
// Implement the logic to save all changes to the evolution chain in the database
|
|
||||
console.log('Saving evolution changes:', data); |
|
||||
|
|
||||
// Here you would update the database with the new evolution chain data
|
|
||||
// This is a placeholder implementation
|
|
||||
setTimeout(() => { |
|
||||
event.reply('save-evolution-changes-response', { success: true }); |
|
||||
}, 1000); |
|
||||
|
|
||||
// If there's an error, you would reply with:
|
|
||||
// event.reply('save-evolution-changes-response', { error: 'Error message' });
|
|
||||
}); |
|
||||
|
|
||||
// Add more IPC handlers for other database operations
|
|
||||
|
|
||||
// Add this IPC handler
|
|
||||
ipcMain.on('load-all-pokemon', (event) => { |
|
||||
db.all(` |
|
||||
SELECT PFIC, name, form_name |
|
||||
FROM pokemon_forms |
|
||||
ORDER BY |
|
||||
CAST(SUBSTR(PFIC, 1, 4) AS INTEGER), -- National Dex number |
|
||||
CAST(SUBSTR(PFIC, 6, 2) AS INTEGER), -- Region code |
|
||||
CAST(SUBSTR(PFIC, 9, 3) AS INTEGER), -- Form index |
|
||||
CAST(SUBSTR(PFIC, 13, 1) AS INTEGER) -- Gender code |
|
||||
`, (err, rows) => {
|
|
||||
if (err) { |
|
||||
console.error('Error fetching all Pokémon:', err); |
|
||||
event.reply('all-pokemon-response', { error: err.message }); |
|
||||
} else { |
|
||||
event.reply('all-pokemon-response', { data: rows }); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('get-pokemon-details', (event, pfic) => { |
|
||||
db.get(` |
|
||||
SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex, pf.generation, ps.storable_in_home |
|
||||
FROM pokemon_forms pf |
|
||||
LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC |
|
||||
WHERE pf.PFIC = ? |
|
||||
`, [pfic], (err, row) => {
|
|
||||
if (err) { |
|
||||
console.error('Error fetching Pokémon details:', err); |
|
||||
event.reply('pokemon-details-response', { error: err.message }); |
|
||||
} else { |
|
||||
event.reply('pokemon-details-response', { data: row }); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
ipcMain.on('update-storable-in-home', (event, data) => { |
|
||||
db.run(` |
|
||||
UPDATE pokemon_storage |
|
||||
SET storable_in_home = ? |
|
||||
WHERE PFIC = ? |
|
||||
`, [data.storable ? 1 : 0, data.pfic], (err) => {
|
|
||||
if (err) { |
|
||||
console.error('Error updating storable in home:', err); |
|
||||
event.reply('update-storable-in-home-response', { error: err.message }); |
|
||||
} else { |
|
||||
event.reply('update-storable-in-home-response', { success: true }); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
File diff suppressed because it is too large
@ -1,19 +0,0 @@ |
|||||
{ |
|
||||
"name": "dbvisualiser", |
|
||||
"version": "1.0.0", |
|
||||
"main": "main.js", |
|
||||
"scripts": { |
|
||||
"test": "echo \"Error: no test specified\" && exit 1", |
|
||||
"start": "electron . --inspect=5858" |
|
||||
}, |
|
||||
"keywords": [], |
|
||||
"author": "", |
|
||||
"license": "ISC", |
|
||||
"description": "", |
|
||||
"dependencies": { |
|
||||
"electron": "^32.1.2", |
|
||||
"electron-builder": "^25.1.7", |
|
||||
"sqlite3": "^5.1.7" |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,483 +0,0 @@ |
|||||
const { ipcRenderer } = require('electron'); |
|
||||
|
|
||||
// Add these variables at the top of the file
|
|
||||
let currentEditingStageIndex, currentEditingPokemonIndex; |
|
||||
let allPokemon = []; // This will store all Pokémon for the select dropdown
|
|
||||
let currentEvolutionChain = null; // Add this line
|
|
||||
let allPokemonForms = []; |
|
||||
|
|
||||
document.addEventListener('DOMContentLoaded', () => { |
|
||||
loadFormsData(); |
|
||||
setupTabButtons(); |
|
||||
setupSearchButtons(); |
|
||||
setupSaveChangesButton(); |
|
||||
|
|
||||
const modal = document.getElementById('edit-pokemon-modal'); |
|
||||
const closeBtn = modal.querySelector('.close'); |
|
||||
const form = document.getElementById('edit-pokemon-form'); |
|
||||
|
|
||||
closeBtn.onclick = () => { |
|
||||
modal.style.display = 'none'; |
|
||||
}; |
|
||||
|
|
||||
window.onclick = (event) => { |
|
||||
if (event.target === modal) { |
|
||||
modal.style.display = 'none'; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
form.onsubmit = (e) => { |
|
||||
e.preventDefault(); |
|
||||
const newPfic = document.getElementById('pokemon-select').value; |
|
||||
const newMethod = document.getElementById('evolution-method').value; |
|
||||
updatePokemonInChain(currentEditingStageIndex, currentEditingPokemonIndex, newPfic, newMethod); |
|
||||
modal.style.display = 'none'; |
|
||||
}; |
|
||||
|
|
||||
loadAllPokemon(); |
|
||||
setupPokemonFilter(); |
|
||||
}); |
|
||||
|
|
||||
function setupTabButtons() { |
|
||||
const tabButtons = document.querySelectorAll('.tab-button'); |
|
||||
tabButtons.forEach(button => { |
|
||||
button.addEventListener('click', () => { |
|
||||
const tabName = button.getAttribute('data-tab'); |
|
||||
activateTab(tabName); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
function activateTab(tabName) { |
|
||||
document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); |
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); |
|
||||
|
|
||||
document.querySelector(`.tab-button[data-tab="${tabName}"]`).classList.add('active'); |
|
||||
document.getElementById(`${tabName}-tab`).classList.add('active'); |
|
||||
} |
|
||||
|
|
||||
function setupSearchButtons() { |
|
||||
//document.getElementById('forms-search-button').addEventListener('click', searchForms);
|
|
||||
//document.getElementById('evolution-search-button').addEventListener('click', searchEvolution);
|
|
||||
} |
|
||||
|
|
||||
function setupSaveChangesButton() { |
|
||||
//document.getElementById('save-changes').addEventListener('click', saveChanges);
|
|
||||
} |
|
||||
|
|
||||
function loadFormsData() { |
|
||||
ipcRenderer.send('load-all-pokemon'); |
|
||||
} |
|
||||
|
|
||||
ipcRenderer.on('all-pokemon-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error loading all Pokémon:', response.error); |
|
||||
} else { |
|
||||
allPokemonForms = response.data; |
|
||||
populateFormsList(); |
|
||||
setupFormsFilter(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
function populateFormsList() { |
|
||||
const listElement = document.getElementById('forms-list-items'); |
|
||||
listElement.innerHTML = ''; |
|
||||
allPokemonForms.forEach(pokemon => { |
|
||||
const li = document.createElement('li'); |
|
||||
li.textContent = `${pokemon.name} ${pokemon.form_name ? `(${pokemon.form_name})` : ''}`; |
|
||||
li.addEventListener('click', () => showPokemonDetails(pokemon.PFIC)); |
|
||||
listElement.appendChild(li); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
function setupFormsFilter() { |
|
||||
const filterInput = document.getElementById('forms-filter'); |
|
||||
filterInput.addEventListener('input', () => { |
|
||||
const filterValue = filterInput.value.toLowerCase(); |
|
||||
const listItems = document.querySelectorAll('#forms-list-items li'); |
|
||||
listItems.forEach(item => { |
|
||||
const text = item.textContent.toLowerCase(); |
|
||||
item.style.display = text.includes(filterValue) ? '' : 'none'; |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
function showPokemonDetails(pfic) { |
|
||||
ipcRenderer.send('get-pokemon-details', pfic); |
|
||||
} |
|
||||
|
|
||||
ipcRenderer.on('pokemon-details-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error fetching Pokémon details:', response.error); |
|
||||
} else { |
|
||||
displayPokemonDetails(response.data); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
function displayPokemonDetails(pokemon) { |
|
||||
const detailsContent = document.getElementById('details-content'); |
|
||||
const pokemonImage = document.getElementById('pokemon-image'); |
|
||||
const pokemonName = document.getElementById('pokemon-name'); |
|
||||
const pokemonPfic = document.getElementById('pokemon-pfic'); |
|
||||
const pokemonNationalDex = document.getElementById('pokemon-national-dex'); |
|
||||
const pokemonGeneration = document.getElementById('pokemon-generation'); |
|
||||
const storableCheckbox = document.getElementById('storable-checkbox'); |
|
||||
const evolutionChainContent = document.getElementById('details-evolution-chain-content'); |
|
||||
|
|
||||
pokemonImage.src = `../images-new/${pokemon.PFIC}.png`; |
|
||||
pokemonImage.onerror = () => { pokemonImage.src = 'placeholder.png'; }; |
|
||||
|
|
||||
pokemonName.textContent = `${pokemon.name} ${pokemon.form_name ? `(${pokemon.form_name})` : ''}`; |
|
||||
pokemonPfic.textContent = `PFIC: ${pokemon.PFIC}`; |
|
||||
pokemonNationalDex.textContent = `National Dex: ${pokemon.national_dex.toString().padStart(4, '0')}`; |
|
||||
pokemonGeneration.textContent = `Generation: ${pokemon.generation}`; |
|
||||
|
|
||||
storableCheckbox.checked = pokemon.storable_in_home; |
|
||||
storableCheckbox.addEventListener('change', () => updateStorableInHome(pokemon.PFIC, storableCheckbox.checked)); |
|
||||
|
|
||||
// Load and display evolution chain
|
|
||||
loadEvolutionChain(pokemon.PFIC); |
|
||||
} |
|
||||
|
|
||||
function updateStorableInHome(pfic, storable) { |
|
||||
ipcRenderer.send('update-storable-in-home', { pfic, storable }); |
|
||||
} |
|
||||
|
|
||||
function loadEvolutionChain(pfic) { |
|
||||
ipcRenderer.send('get-evolution-chain', pfic); |
|
||||
} |
|
||||
|
|
||||
ipcRenderer.on('evolution-chain-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error fetching evolution chain:', response.error); |
|
||||
} else { |
|
||||
displayEvolutionChain(response.data); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
function displayEvolutionChain(chain) { |
|
||||
const table = document.getElementById('evolution-table'); |
|
||||
table.innerHTML = ''; |
|
||||
|
|
||||
const stages = splitIntoStages(chain); |
|
||||
const maxForms = Math.max(...stages.map(stage => stage.length)); |
|
||||
const rowCount = maxForms % 2 === 0 ? maxForms + 1 : maxForms; |
|
||||
const middleRow = Math.floor(rowCount / 2) |
|
||||
|
|
||||
for (let i = 0; i < rowCount; i++) { |
|
||||
const row = table.insertRow(); |
|
||||
for (let j = 0; j < stages.length; j++) { |
|
||||
const cell = row.insertCell(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
stages.forEach((stage, stageIndex) => { |
|
||||
if (stage.length == 1) |
|
||||
{ |
|
||||
const pokemon = stage[0]; |
|
||||
table.rows[middleRow].cells[stageIndex].appendChild(createPokemonElement(pokemon)); |
|
||||
} else { |
|
||||
let start = middleRow - Math.floor(stage.length / 2) |
|
||||
|
|
||||
stage.forEach((pokemon, index) => { |
|
||||
let rowIndex = start + index; |
|
||||
|
|
||||
// If the number of elements is even, skip the middle row
|
|
||||
if (stage.length % 2 === 0 && rowIndex >= middleRow) { |
|
||||
rowIndex++; |
|
||||
} |
|
||||
|
|
||||
table.rows[rowIndex].cells[stageIndex].appendChild(createPokemonElement(pokemon)); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
function createPokemonElement(pokemon) { |
|
||||
const element = document.createElement('div'); |
|
||||
element.className = 'pokemon-card'; |
|
||||
|
|
||||
const img = document.createElement('img'); |
|
||||
img.src = `../images-new/${pokemon.pfic}.png`; |
|
||||
img.alt = pokemon.name; |
|
||||
img.onerror = () => { img.src = 'placeholder.png'; }; |
|
||||
|
|
||||
const name = document.createElement('div'); |
|
||||
name.className = 'pokemon-name'; |
|
||||
name.textContent = pokemon.name; |
|
||||
|
|
||||
const form = document.createElement('div'); |
|
||||
form.className = 'pokemon-form'; |
|
||||
form.textContent = pokemon.form_name || ''; |
|
||||
|
|
||||
element.appendChild(img); |
|
||||
element.appendChild(name); |
|
||||
element.appendChild(form); |
|
||||
|
|
||||
return element; |
|
||||
} |
|
||||
|
|
||||
function createEvolutionArrow() { |
|
||||
const arrow = document.createElement('div'); |
|
||||
arrow.className = 'evolution-arrow'; |
|
||||
arrow.textContent = '→'; |
|
||||
return arrow; |
|
||||
} |
|
||||
|
|
||||
function createBranchElement(evolutions) { |
|
||||
const branchElement = document.createElement('div'); |
|
||||
branchElement.className = 'evolution-branch'; |
|
||||
const arrowElement = document.createElement('span'); |
|
||||
arrowElement.className = 'evolution-arrow'; |
|
||||
arrowElement.textContent = '→'; |
|
||||
branchElement.appendChild(arrowElement); |
|
||||
const methodElement = document.createElement('span'); |
|
||||
methodElement.className = 'evolution-method'; |
|
||||
methodElement.textContent = evolutions[0].method || ''; |
|
||||
branchElement.appendChild(methodElement); |
|
||||
return branchElement; |
|
||||
} |
|
||||
|
|
||||
function searchForms() { |
|
||||
const searchTerm = document.getElementById('forms-search').value.toLowerCase(); |
|
||||
const rows = document.querySelectorAll('#forms-table tbody tr'); |
|
||||
|
|
||||
rows.forEach(row => { |
|
||||
const text = row.textContent.toLowerCase(); |
|
||||
row.style.display = text.includes(searchTerm) ? '' : 'none'; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
function searchEvolution() { |
|
||||
const searchTerm = document.getElementById('evolution-search').value; |
|
||||
ipcRenderer.send('search-evolution', searchTerm); |
|
||||
} |
|
||||
|
|
||||
ipcRenderer.on('evolution-search-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error searching evolution:', response.error); |
|
||||
} else if (response.data.length > 0) { |
|
||||
const pfic = response.data[0].PFIC; |
|
||||
ipcRenderer.send('get-evolution-chain', pfic); |
|
||||
} else { |
|
||||
document.getElementById('evolution-chain').innerHTML = 'No Pokémon found.'; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
ipcRenderer.on('evolution-chain-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error fetching evolution chain:', response.error); |
|
||||
} else { |
|
||||
currentEvolutionChain = response.data; // Add this line
|
|
||||
displayEvolutionChain(currentEvolutionChain); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
function createPokemonElement(pokemon, stageIndex, pokemonIndex) { |
|
||||
const element = document.createElement('div'); |
|
||||
element.className = 'pokemon-card'; |
|
||||
|
|
||||
const img = document.createElement('img'); |
|
||||
img.src = `../images-new/${pokemon.pfic}.png`; |
|
||||
img.alt = pokemon.name; |
|
||||
img.onerror = () => { img.src = 'placeholder.png'; }; |
|
||||
|
|
||||
const name = document.createElement('div'); |
|
||||
name.className = 'pokemon-name'; |
|
||||
name.textContent = pokemon.name; |
|
||||
|
|
||||
const form = document.createElement('div'); |
|
||||
form.className = 'pokemon-form'; |
|
||||
form.textContent = pokemon.form_name || ''; |
|
||||
|
|
||||
const editButton = document.createElement('button'); |
|
||||
editButton.textContent = 'Edit'; |
|
||||
editButton.className = 'edit-pokemon'; |
|
||||
editButton.addEventListener('click', () => editPokemon(stageIndex, pokemonIndex)); |
|
||||
|
|
||||
const editButtons = document.createElement('div'); |
|
||||
editButtons.className = 'edit-buttons'; |
|
||||
editButtons.appendChild(editButton); |
|
||||
|
|
||||
element.appendChild(img); |
|
||||
element.appendChild(name); |
|
||||
element.appendChild(form); |
|
||||
element.appendChild(editButtons); |
|
||||
|
|
||||
return element; |
|
||||
} |
|
||||
|
|
||||
function setupEvolutionControls() { |
|
||||
document.getElementById('add-stage').addEventListener('click', addStage); |
|
||||
document.getElementById('save-evolution-changes').addEventListener('click', saveEvolutionChanges); |
|
||||
} |
|
||||
|
|
||||
function editPokemon(stageIndex, pokemonIndex) { |
|
||||
console.log('Editing Pokemon:', stageIndex, pokemonIndex); |
|
||||
if (!currentEvolutionChain) { |
|
||||
console.error('No evolution chain loaded'); |
|
||||
return; |
|
||||
} |
|
||||
currentEditingStageIndex = stageIndex; |
|
||||
currentEditingPokemonIndex = pokemonIndex; |
|
||||
|
|
||||
const modal = document.getElementById('edit-pokemon-modal'); |
|
||||
console.log('Modal element:', modal); |
|
||||
const pokemonSelect = document.getElementById('pokemon-select'); |
|
||||
const evolutionMethod = document.getElementById('evolution-method'); |
|
||||
|
|
||||
// Set current values
|
|
||||
const currentPokemon = getCurrentPokemon(stageIndex, pokemonIndex); |
|
||||
console.log('Current Pokemon:', currentPokemon); |
|
||||
if (currentPokemon) { |
|
||||
pokemonSelect.value = currentPokemon.pfic; |
|
||||
evolutionMethod.value = currentPokemon.method || ''; |
|
||||
|
|
||||
modal.style.display = 'block'; |
|
||||
console.log('Modal display set to block'); |
|
||||
} else { |
|
||||
console.error('Could not find the current Pokémon'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function removePokemon(stageIndex, pokemonIndex) { |
|
||||
// Implement remove functionality
|
|
||||
console.log(`Removing Pokémon at stage ${stageIndex}, index ${pokemonIndex}`); |
|
||||
// Remove the Pokémon from the DOM and update the data structure
|
|
||||
} |
|
||||
|
|
||||
function addStage() { |
|
||||
// Implement add stage functionality
|
|
||||
console.log('Adding new stage'); |
|
||||
// You can open a modal or inline form to add a new stage
|
|
||||
} |
|
||||
|
|
||||
function saveEvolutionChanges() { |
|
||||
console.log('Saving evolution changes'); |
|
||||
ipcRenderer.send('save-evolution-changes', currentEvolutionChain); |
|
||||
} |
|
||||
|
|
||||
function splitIntoStages(chain) { |
|
||||
const stages = []; |
|
||||
let currentStage = [chain]; |
|
||||
|
|
||||
while (currentStage.length > 0) { |
|
||||
stages.push(currentStage); |
|
||||
const nextStage = []; |
|
||||
currentStage.forEach(pokemon => { |
|
||||
nextStage.push(...pokemon.evolutions); |
|
||||
}); |
|
||||
currentStage = nextStage; |
|
||||
} |
|
||||
|
|
||||
return stages; |
|
||||
} |
|
||||
|
|
||||
function saveChanges() { |
|
||||
// Implement the logic to save changes
|
|
||||
// This will involve collecting the data from the forms table
|
|
||||
// and sending it back to the main process to update the database
|
|
||||
} |
|
||||
|
|
||||
// Add this function to load all Pokémon
|
|
||||
function loadAllPokemon() { |
|
||||
ipcRenderer.send('load-all-pokemon'); |
|
||||
} |
|
||||
|
|
||||
// Add this event listener
|
|
||||
ipcRenderer.on('all-pokemon-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error loading all Pokémon:', response.error); |
|
||||
} else { |
|
||||
allPokemon = response.data; |
|
||||
populatePokemonSelect(); |
|
||||
populatePokemonList(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// Add this function to populate the Pokémon select dropdown
|
|
||||
function populatePokemonSelect() { |
|
||||
const select = document.getElementById('pokemon-select'); |
|
||||
select.innerHTML = ''; |
|
||||
allPokemon.forEach(pokemon => { |
|
||||
const option = document.createElement('option'); |
|
||||
option.value = pokemon.PFIC; |
|
||||
option.textContent = `${pokemon.PFIC} - ${pokemon.name} ${pokemon.form_name ? `(${pokemon.form_name})` : ''}`; |
|
||||
select.appendChild(option); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// Add this function to get the current Pokémon being edited
|
|
||||
function getCurrentPokemon(stageIndex, pokemonIndex) { |
|
||||
if (!currentEvolutionChain) { |
|
||||
console.error('No evolution chain loaded'); |
|
||||
return null; |
|
||||
} |
|
||||
const stages = splitIntoStages(currentEvolutionChain); |
|
||||
if (stageIndex < 0 || stageIndex >= stages.length || pokemonIndex < 0 || pokemonIndex >= stages[stageIndex].length) { |
|
||||
console.error('Invalid stage or pokemon index'); |
|
||||
return null; |
|
||||
} |
|
||||
return stages[stageIndex][pokemonIndex]; |
|
||||
} |
|
||||
|
|
||||
// Add this function to update the Pokémon in the chain
|
|
||||
function updatePokemonInChain(stageIndex, pokemonIndex, newPfic, newMethod) { |
|
||||
const stages = splitIntoStages(currentEvolutionChain); |
|
||||
const pokemon = stages[stageIndex][pokemonIndex]; |
|
||||
|
|
||||
// Update the Pokémon data
|
|
||||
pokemon.pfic = newPfic; |
|
||||
pokemon.name = allPokemon.find(p => p.PFIC === newPfic).name; |
|
||||
pokemon.form_name = allPokemon.find(p => p.PFIC === newPfic).form_name; |
|
||||
|
|
||||
// Update the evolution method if it's not the first stage
|
|
||||
if (stageIndex > 0) { |
|
||||
const previousStagePokemon = stages[stageIndex - 1].find(p => p.evolutions.includes(pokemon)); |
|
||||
const evolutionIndex = previousStagePokemon.evolutions.indexOf(pokemon); |
|
||||
previousStagePokemon.evolutions[evolutionIndex].method = newMethod; |
|
||||
} |
|
||||
|
|
||||
// Redisplay the evolution chain
|
|
||||
displayEvolutionChain(currentEvolutionChain); |
|
||||
} |
|
||||
|
|
||||
// Add this event listener for the save response
|
|
||||
ipcRenderer.on('save-evolution-changes-response', (event, response) => { |
|
||||
if (response.error) { |
|
||||
console.error('Error saving evolution changes:', response.error); |
|
||||
alert('Failed to save changes. Please try again.'); |
|
||||
} else { |
|
||||
alert('Changes saved successfully!'); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// Add this function to populate the Pokémon list
|
|
||||
function populatePokemonList() { |
|
||||
const listElement = document.getElementById('pokemon-list-items'); |
|
||||
listElement.innerHTML = ''; |
|
||||
allPokemon.forEach(pokemon => { |
|
||||
const li = document.createElement('li'); |
|
||||
li.textContent = `${pokemon.name} ${pokemon.form_name ? `(${pokemon.form_name})` : ''}`; |
|
||||
li.addEventListener('click', () => loadEvolutionChain(pokemon.PFIC)); |
|
||||
listElement.appendChild(li); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// Add this function to set up the Pokémon filter
|
|
||||
function setupPokemonFilter() { |
|
||||
const filterInput = document.getElementById('pokemon-filter'); |
|
||||
filterInput.addEventListener('input', () => { |
|
||||
const filterValue = filterInput.value.toLowerCase(); |
|
||||
const listItems = document.querySelectorAll('#pokemon-list-items li'); |
|
||||
listItems.forEach(item => { |
|
||||
const text = item.textContent.toLowerCase(); |
|
||||
item.style.display = text.includes(filterValue) ? '' : 'none'; |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// Modify the loadEvolutionChain function
|
|
||||
function loadEvolutionChain(pfic) { |
|
||||
ipcRenderer.send('get-evolution-chain', pfic); |
|
||||
} |
|
||||
@ -1,338 +0,0 @@ |
|||||
body { |
|
||||
font-family: Arial, sans-serif; |
|
||||
margin: 0; |
|
||||
padding: 20px; |
|
||||
} |
|
||||
|
|
||||
#tabs { |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
|
|
||||
.tab-button { |
|
||||
padding: 10px 20px; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.tab-button.active { |
|
||||
background-color: #ddd; |
|
||||
} |
|
||||
|
|
||||
.tab-content { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
.tab-content.active { |
|
||||
display: block; |
|
||||
} |
|
||||
|
|
||||
.search-bar { |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
|
|
||||
table { |
|
||||
width: 100%; |
|
||||
border-collapse: collapse; |
|
||||
} |
|
||||
|
|
||||
th, td { |
|
||||
border: 1px solid #ddd; |
|
||||
padding: 8px; |
|
||||
text-align: left; |
|
||||
} |
|
||||
|
|
||||
th { |
|
||||
background-color: #f2f2f2; |
|
||||
} |
|
||||
|
|
||||
#evolution-chain { |
|
||||
display: flex; |
|
||||
overflow-x: auto; |
|
||||
padding: 20px; |
|
||||
align-items: flex-start; |
|
||||
} |
|
||||
|
|
||||
.evolution-stage { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
margin-right: 40px; |
|
||||
} |
|
||||
|
|
||||
.pokemon-card { |
|
||||
background-color: #f9f9f9; |
|
||||
border: 1px solid #ddd; |
|
||||
border-radius: 10px; |
|
||||
padding: 10px; |
|
||||
text-align: center; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
justify-content: space-between; |
|
||||
cursor: pointer; |
|
||||
width: 120px; /* Fixed width */ |
|
||||
height: 100px; /* Fixed height */ |
|
||||
} |
|
||||
|
|
||||
.pokemon-card img { |
|
||||
width: 64px; |
|
||||
height: 64px; |
|
||||
object-fit: contain; |
|
||||
} |
|
||||
|
|
||||
.pokemon-name { |
|
||||
font-weight: bold; |
|
||||
margin-top: 5px; |
|
||||
} |
|
||||
|
|
||||
.pokemon-form { |
|
||||
font-size: 0.8em; |
|
||||
color: #666; |
|
||||
} |
|
||||
|
|
||||
.evolution-branch { |
|
||||
position: absolute; |
|
||||
top: 50%; |
|
||||
left: 100%; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
.evolution-arrow { |
|
||||
font-size: 24px; |
|
||||
color: #666; |
|
||||
margin: 0 10px; |
|
||||
} |
|
||||
|
|
||||
.evolution-method { |
|
||||
font-size: 0.8em; |
|
||||
color: #666; |
|
||||
max-width: 100px; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
.pokemon-card .edit-buttons { |
|
||||
display: none; |
|
||||
position: absolute; |
|
||||
top: 5px; |
|
||||
right: 5px; |
|
||||
} |
|
||||
|
|
||||
.pokemon-card:hover .edit-buttons { |
|
||||
display: block; |
|
||||
} |
|
||||
|
|
||||
.edit-buttons button { |
|
||||
margin-left: 5px; |
|
||||
padding: 2px 5px; |
|
||||
font-size: 0.8em; |
|
||||
} |
|
||||
|
|
||||
#evolution-controls { |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
|
|
||||
#evolution-controls button { |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
|
|
||||
/* Add these styles at the end of the file */ |
|
||||
.modal { |
|
||||
display: none; |
|
||||
position: fixed; |
|
||||
z-index: 1; |
|
||||
left: 0; |
|
||||
top: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
overflow: auto; |
|
||||
background-color: rgba(0,0,0,0.4); |
|
||||
} |
|
||||
|
|
||||
.modal-content { |
|
||||
background-color: #fefefe; |
|
||||
margin: 15% auto; |
|
||||
padding: 20px; |
|
||||
border: 1px solid #888; |
|
||||
width: 80%; |
|
||||
max-width: 500px; |
|
||||
} |
|
||||
|
|
||||
.close { |
|
||||
color: #aaa; |
|
||||
float: right; |
|
||||
font-size: 28px; |
|
||||
font-weight: bold; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.close:hover, |
|
||||
.close:focus { |
|
||||
color: black; |
|
||||
text-decoration: none; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
#edit-pokemon-form { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
} |
|
||||
|
|
||||
#edit-pokemon-form label, |
|
||||
#edit-pokemon-form select, |
|
||||
#edit-pokemon-form input, |
|
||||
#edit-pokemon-form button { |
|
||||
margin-top: 10px; |
|
||||
} |
|
||||
|
|
||||
.evolution-container { |
|
||||
display: flex; |
|
||||
height: calc(100vh - 100px); /* Adjust based on your layout */ |
|
||||
} |
|
||||
|
|
||||
#pokemon-list { |
|
||||
width: 250px; |
|
||||
border-right: 1px solid #ccc; |
|
||||
overflow-y: auto; |
|
||||
padding: 10px; |
|
||||
} |
|
||||
|
|
||||
#pokemon-filter { |
|
||||
width: 100%; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
#pokemon-list-items { |
|
||||
list-style-type: none; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
#pokemon-list-items li { |
|
||||
cursor: pointer; |
|
||||
padding: 5px; |
|
||||
} |
|
||||
|
|
||||
#pokemon-list-items li:hover { |
|
||||
background-color: #f0f0f0; |
|
||||
} |
|
||||
|
|
||||
#evolution-chain-container { |
|
||||
flex-grow: 1; |
|
||||
padding: 20px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.forms-container { |
|
||||
display: flex; |
|
||||
height: calc(100vh - 100px); /* Adjust based on your layout */ |
|
||||
} |
|
||||
|
|
||||
#pokemon-forms-list { |
|
||||
width: 250px; |
|
||||
border-right: 1px solid #ccc; |
|
||||
overflow-y: auto; |
|
||||
padding: 10px; |
|
||||
} |
|
||||
|
|
||||
#forms-filter { |
|
||||
width: 100%; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
#forms-list-items { |
|
||||
list-style-type: none; |
|
||||
padding: 0; |
|
||||
} |
|
||||
|
|
||||
#forms-list-items li { |
|
||||
cursor: pointer; |
|
||||
padding: 5px; |
|
||||
} |
|
||||
|
|
||||
#forms-list-items li:hover { |
|
||||
background-color: #f0f0f0; |
|
||||
} |
|
||||
|
|
||||
#pokemon-details { |
|
||||
flex-grow: 1; |
|
||||
padding: 20px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
#details-content { |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
|
|
||||
#pokemon-basic-info { |
|
||||
display: flex; |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
|
|
||||
#pokemon-image { |
|
||||
width: 200px; |
|
||||
height: 200px; |
|
||||
object-fit: contain; |
|
||||
margin-right: 20px; |
|
||||
} |
|
||||
|
|
||||
#pokemon-info { |
|
||||
flex-grow: 1; |
|
||||
} |
|
||||
|
|
||||
#pokemon-evolution-chain { |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
|
|
||||
#details-evolution-chain-content { |
|
||||
overflow-x: auto; |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
|
|
||||
#evolution-table { |
|
||||
width: auto; |
|
||||
border-collapse: collapse; |
|
||||
border-spacing: 0px; |
|
||||
} |
|
||||
|
|
||||
#evolution-table td { |
|
||||
vertical-align: middle; |
|
||||
text-align: center; |
|
||||
padding: 0%; |
|
||||
border-color: transparent; |
|
||||
} |
|
||||
|
|
||||
#details-evolution-chain-content .evolution-stage { |
|
||||
display: inline-flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
margin-right: 20px; |
|
||||
} |
|
||||
|
|
||||
#details-evolution-chain-content .pokemon-card { |
|
||||
text-align: center; |
|
||||
margin: 0 10px; |
|
||||
position: relative; |
|
||||
border: 1px solid #ddd; |
|
||||
padding: 10px; |
|
||||
border-radius: 5px; |
|
||||
display: inline-block; |
|
||||
} |
|
||||
|
|
||||
#details-evolution-chain-content .pokemon-card img { |
|
||||
width: 64px; |
|
||||
height: 64px; |
|
||||
object-fit: contain; |
|
||||
} |
|
||||
|
|
||||
#details-evolution-chain-content .evolution-branch { |
|
||||
display: inline-flex; |
|
||||
flex-direction: row; |
|
||||
align-items: center; |
|
||||
margin: 0 10px; |
|
||||
} |
|
||||
|
|
||||
#details-evolution-chain-content .evolution-arrow, |
|
||||
#details-evolution-chain-content .evolution-method { |
|
||||
margin: 0 5px; |
|
||||
} |
|
||||
@ -1,401 +0,0 @@ |
|||||
import sqlite3 |
|
||||
import csv |
|
||||
import re |
|
||||
|
|
||||
def create_connection(): |
|
||||
conn = sqlite3.connect('pokemon_database.db') |
|
||||
conn.text_factory = str |
|
||||
return conn |
|
||||
|
|
||||
def create_tables(conn): |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
# Create games table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS games ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
name TEXT NOT NULL, |
|
||||
generation INTEGER NOT NULL |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create marks table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS marks ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
name TEXT NOT NULL, |
|
||||
icon_path TEXT NOT NULL |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create mark_game_associations table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS mark_game_associations ( |
|
||||
mark_id INTEGER, |
|
||||
game_id INTEGER, |
|
||||
FOREIGN KEY (mark_id) REFERENCES marks (id), |
|
||||
FOREIGN KEY (game_id) REFERENCES games (id), |
|
||||
PRIMARY KEY (mark_id, game_id) |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create pokemon table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS pokemon ( |
|
||||
national_dex_number INTEGER PRIMARY KEY, |
|
||||
name TEXT NOT NULL, |
|
||||
introduced_in_gen INTEGER |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create pokemon_forms table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS pokemon_forms ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
pokemon_id INTEGER NOT NULL, |
|
||||
form_name TEXT NOT NULL, |
|
||||
is_default BOOLEAN NOT NULL, |
|
||||
image_path TEXT NOT NULL, |
|
||||
FOREIGN KEY (pokemon_id) REFERENCES pokemon (national_dex_number), |
|
||||
UNIQUE (pokemon_id, form_name) |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create encounter_methods table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS encounter_methods ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
name TEXT UNIQUE NOT NULL |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create locations table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS locations ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
name TEXT UNIQUE NOT NULL, |
|
||||
description TEXT |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create form_encounters table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS form_encounters ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
form_id INTEGER NOT NULL, |
|
||||
game_id INTEGER NOT NULL, |
|
||||
location_id INTEGER NOT NULL, |
|
||||
encounter_method_id INTEGER NOT NULL, |
|
||||
FOREIGN KEY (form_id) REFERENCES pokemon_forms (id), |
|
||||
FOREIGN KEY (game_id) REFERENCES games (id), |
|
||||
FOREIGN KEY (location_id) REFERENCES locations (id), |
|
||||
FOREIGN KEY (encounter_method_id) REFERENCES encounter_methods (id), |
|
||||
UNIQUE (form_id, game_id, location_id, encounter_method_id) |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
# Create alternate_game_names table |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS alternate_game_names ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
game_id INTEGER NOT NULL, |
|
||||
alternate_name TEXT NOT NULL, |
|
||||
FOREIGN KEY (game_id) REFERENCES games (id), |
|
||||
UNIQUE (alternate_name COLLATE NOCASE) |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
conn.commit() |
|
||||
|
|
||||
def tidy_location_name(name): |
|
||||
# Replace '-' with spaces |
|
||||
name = name.replace('-', ' ') |
|
||||
|
|
||||
name = name.replace('#', '') |
|
||||
|
|
||||
# Remove 'area' from the end if present |
|
||||
name = re.sub(r'\sarea$', '', name, flags=re.IGNORECASE) |
|
||||
|
|
||||
# Check for cardinal directions at the end |
|
||||
cardinal_directions = ['north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest'] |
|
||||
for direction in cardinal_directions: |
|
||||
if name.lower().endswith(f' {direction}'): |
|
||||
# Remove the direction from the end and add it in brackets |
|
||||
name = name[:-len(direction)].strip() |
|
||||
name += f' ({direction.capitalize()})' |
|
||||
break |
|
||||
|
|
||||
if name.isdigit(): |
|
||||
name = "Route " + name |
|
||||
|
|
||||
name = name.replace("Routes", "Route") |
|
||||
|
|
||||
# Capitalize the first letter of the first word |
|
||||
name = name.capitalize() |
|
||||
|
|
||||
return name |
|
||||
|
|
||||
def generate_location_description(name): |
|
||||
# Generate a simple description based on the name |
|
||||
description = f"A location in the Pokémon world known as {name}." |
|
||||
return description |
|
||||
|
|
||||
def load_game_data(conn): |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
games = [ |
|
||||
("Red", 1, ["Red Version"]), |
|
||||
("Blue", 1, ["Blue Version"]), |
|
||||
("Yellow", 1, ["Yellow Version"]), |
|
||||
("Gold", 2, ["Gold Version"]), |
|
||||
("Silver", 2, ["Silver Version"]), |
|
||||
("Crystal", 2, ["Crystal Version"]), |
|
||||
("Ruby", 3, ["Ruby Version"]), |
|
||||
("Sapphire", 3, ["Sapphire Version"]), |
|
||||
("Emerald", 3, ["Emerald Version"]), |
|
||||
("FireRed", 3, ["Fire Red", "Fire-Red"]), |
|
||||
("LeafGreen", 3, ["Leaf Green", "Leaf-Green"]), |
|
||||
("Diamond", 4, ["Diamond Version"]), |
|
||||
("Pearl", 4, ["Pearl Version"]), |
|
||||
("Platinum", 4, ["Platinum Version"]), |
|
||||
("HeartGold", 4, ["Heart Gold", "Heart-Gold"]), |
|
||||
("SoulSilver", 4, ["Soul Silver", "Soul-Silver"]), |
|
||||
("Black", 5, ["Black Version"]), |
|
||||
("White", 5, ["White Version"]), |
|
||||
("Black 2", 5, ["Black Version 2", "Black-2"]), |
|
||||
("White 2", 5, ["White Version 2", "White-2"]), |
|
||||
("X", 6, ["X Version"]), |
|
||||
("Y", 6, ["Y Version"]), |
|
||||
("Omega Ruby", 6, ["Omega Ruby Version", "Omega-Ruby"]), |
|
||||
("Alpha Sapphire", 6, ["Alpha Sapphire Version", "Alpha-Sapphire"]), |
|
||||
("Sun", 7, ["Sun Version"]), |
|
||||
("Moon", 7, ["Moon Version"]), |
|
||||
("Ultra Sun", 7, ["Ultra Sun Version", "Ultra-Sun"]), |
|
||||
("Ultra Moon", 7, ["Ultra Moon Version", "Ultra-Moon"]), |
|
||||
("Let's Go Pikachu", 7, ["Let's Go, Pikachu!", "Lets Go Pikachu"]), |
|
||||
("Let's Go Eevee", 7, ["Let's Go, Eevee!", "Lets Go Eevee"]), |
|
||||
("Sword", 8, ["Sword Version"]), |
|
||||
("Shield", 8, ["Shield Version"]), |
|
||||
("Expansion Pass", 8, ["Expansion Pass (Sword)", "Expansion Pass (Shield)"]), |
|
||||
("Brilliant Diamond", 8, ["Brilliant Diamond Version", "Brilliant-Diamond"]), |
|
||||
("Shining Pearl", 8, ["Shining Pearl Version", "Shining-Pearl"]), |
|
||||
("Legends Arceus", 8, ["Legends: Arceus", "Legends-Arceus"]), |
|
||||
("Scarlet", 9, ["Scarlet Version"]), |
|
||||
("Violet", 9, ["Violet Version"]), |
|
||||
("The Teal Mask", 9, ["The Teal Mask Version", "The Teal Mask (Scarlet)", "The Teal Mask (Violet)"]), |
|
||||
("The Hidden Treasure of Area Zero", 9, ["The Hidden Treasure of Area Zero Version", "The Hidden Treasure of Area Zero (Scarlet)", "The Hidden Treasure of Area Zero (Violet)"]), |
|
||||
|
|
||||
("Pokémon Home", 98, ["Pokémon HOME"]), |
|
||||
("Pokémon Go", 99, ["Pokémon GO"]), |
|
||||
] |
|
||||
|
|
||||
for game in games: |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO games (name, generation) |
|
||||
VALUES (?, ?) |
|
||||
''', (game[0], game[1])) |
|
||||
|
|
||||
game_id = cursor.lastrowid |
|
||||
|
|
||||
# Insert alternate names |
|
||||
for alt_name in game[2]: |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO alternate_game_names (game_id, alternate_name) |
|
||||
VALUES (?, ?) |
|
||||
''', (game_id, alt_name)) |
|
||||
|
|
||||
conn.commit() |
|
||||
|
|
||||
def load_mark_data(conn): |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
marks = [ |
|
||||
("Game Boy", "images/marks/GB_icon_HOME.png", ["Red", "Blue", "Yellow", "Gold", "Silver", "Crystal", "Ruby", "Sapphire", "Emerald", "FireRed", "LeafGreen"]), |
|
||||
("Kalos", "images/marks/Blue_pentagon_HOME.png", ["X", "Y", "Omega Ruby", "Alpha Sapphire"]), |
|
||||
("Alola", "images/marks/Black_clover_HOME.png", ["Sun", "Moon", "Ultra Sun", "Ultra Moon"]), |
|
||||
("Let's Go", "images/marks/Let's_Go_icon_HOME.png", ["Let's Go Pikachu", "Let's Go Eevee"]), |
|
||||
("Galar", "images/marks/Galar_symbol_HOME.png", ["Sword", "Shield"]), |
|
||||
("Sinnoh", "images/marks/BDSP_icon_HOME.png", ["Brilliant Diamond", "Shining Pearl"]), |
|
||||
("Hisui", "images/marks/Arceus_mark_HOME.png", ["Legends Arceus"]), |
|
||||
("Paldea", "images/marks/Paldea_icon_HOME.png", ["Scarlet", "Violet"]), |
|
||||
] |
|
||||
|
|
||||
for mark in marks: |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO marks (name, icon_path) |
|
||||
VALUES (?, ?) |
|
||||
''', (mark[0], mark[1])) |
|
||||
|
|
||||
mark_id = cursor.lastrowid |
|
||||
|
|
||||
for game_name in mark[2]: |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO mark_game_associations (mark_id, game_id) |
|
||||
SELECT ?, id FROM games WHERE name = ? |
|
||||
''', (mark_id, game_name)) |
|
||||
|
|
||||
conn.commit() |
|
||||
|
|
||||
def load_pokemon_data(conn): |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
with open('pokemon_home_list.csv', 'r', encoding='utf-8') as f: |
|
||||
reader = csv.reader(f) |
|
||||
next(reader) # Skip header row if it exists |
|
||||
for row in reader: |
|
||||
national_dex_number = int(row[0]) |
|
||||
full_name = row[1] |
|
||||
|
|
||||
# Extract the base name and form name |
|
||||
match = re.match(r'([^(]+)(?:\s*\(([^)]+)\))?', full_name) |
|
||||
if match: |
|
||||
base_name = match.group(1).strip() |
|
||||
form_name = match.group(2).strip() if match.group(2) else "Default" |
|
||||
else: |
|
||||
base_name = full_name |
|
||||
form_name = "Default" |
|
||||
|
|
||||
# Insert or update the pokemon entry |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO pokemon (national_dex_number, name) |
|
||||
VALUES (?, ?) |
|
||||
''', (national_dex_number, base_name)) |
|
||||
|
|
||||
# Create the image path |
|
||||
padded_dex = f"{national_dex_number:04d}" |
|
||||
if form_name == "Default": |
|
||||
image_path = f"images/pokemon/{padded_dex}_{base_name}.png" |
|
||||
else: |
|
||||
image_path = f"images/pokemon/{padded_dex}_{base_name}_({form_name}).png".replace(" ", "_") |
|
||||
|
|
||||
# Insert the form entry |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
''', (national_dex_number, form_name, form_name == "Default", image_path)) |
|
||||
|
|
||||
conn.commit() |
|
||||
|
|
||||
def load_encounter_data(conn): |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
with open('pokemon_earliest_games.csv', 'r', encoding='utf-8') as f: |
|
||||
reader = csv.DictReader(f) |
|
||||
for row in reader: |
|
||||
national_dex_number = int(row['number']) |
|
||||
full_name = row['name'] |
|
||||
earliest_game = row['earliest_game'] |
|
||||
obtain_method = row['obtain_method'] |
|
||||
encounter_locations = row['encounter_locations'] |
|
||||
introduced_in_gen = row['introduced_in_gen'] |
|
||||
# Extract the base name and form name |
|
||||
match = re.match(r'([^(]+)(?:\s*\(([^)]+)\))?', full_name) |
|
||||
if match: |
|
||||
base_name = match.group(1).strip() |
|
||||
form_name = match.group(2).strip() if match.group(2) else "Default" |
|
||||
if form_name == "None": |
|
||||
form_name = "Default" |
|
||||
else: |
|
||||
base_name = full_name |
|
||||
form_name = "Default" |
|
||||
|
|
||||
# Update the Pokémon entry with introduced_in_gen |
|
||||
cursor.execute(''' |
|
||||
INSERT OR REPLACE INTO pokemon (national_dex_number, name, introduced_in_gen) |
|
||||
VALUES (?, ?, ?) |
|
||||
''', (national_dex_number, base_name, introduced_in_gen)) |
|
||||
|
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
''', (national_dex_number, form_name, form_name == "Default", f"images/pokemon/{national_dex_number:04d}_{base_name}.png")) |
|
||||
|
|
||||
# Skip encounter data if it's unknown or N/A |
|
||||
if earliest_game == "Unknown" or obtain_method == "Unknown": |
|
||||
continue |
|
||||
|
|
||||
# Get the form_id |
|
||||
cursor.execute(''' |
|
||||
SELECT id FROM pokemon_forms |
|
||||
WHERE pokemon_id = ? AND form_name = ? |
|
||||
''', (national_dex_number, form_name)) |
|
||||
form_id = cursor.fetchone()[0] |
|
||||
|
|
||||
# Get the game_id (now case-insensitive and including alternate names) |
|
||||
cursor.execute(''' |
|
||||
SELECT g.id FROM games g |
|
||||
LEFT JOIN alternate_game_names agn ON g.id = agn.game_id |
|
||||
WHERE g.name = ? COLLATE NOCASE OR agn.alternate_name = ? COLLATE NOCASE |
|
||||
''', (earliest_game, earliest_game)) |
|
||||
result = cursor.fetchone() |
|
||||
if result: |
|
||||
game_id = result[0] |
|
||||
else: |
|
||||
print(f"Warning: Game '{earliest_game}' not found for {full_name}") |
|
||||
continue |
|
||||
|
|
||||
# Handle gift Pokémon |
|
||||
if obtain_method.lower() == "gift" and (encounter_locations == "N/A" or not encounter_locations): |
|
||||
# Insert or get the "Gift" location |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO locations (name, description) |
|
||||
VALUES (?, ?) |
|
||||
''', ("Gift", "Pokémon received as a gift")) |
|
||||
cursor.execute('SELECT id FROM locations WHERE name = ?', ("Gift",)) |
|
||||
location_id = cursor.fetchone()[0] |
|
||||
|
|
||||
# Insert or get the "gift" encounter method |
|
||||
cursor.execute('INSERT OR IGNORE INTO encounter_methods (name) VALUES (?)', ("gift",)) |
|
||||
cursor.execute('SELECT id FROM encounter_methods WHERE name = ?', ("gift",)) |
|
||||
method_id = cursor.fetchone()[0] |
|
||||
|
|
||||
# Insert form_encounter for gift Pokémon |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO form_encounters |
|
||||
(form_id, game_id, location_id, encounter_method_id) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
''', (form_id, game_id, location_id, method_id)) |
|
||||
elif encounter_locations != "N/A" and encounter_locations: |
|
||||
# Process each encounter location |
|
||||
for location_info in encounter_locations.split('|'): |
|
||||
location = location_info.strip() |
|
||||
|
|
||||
location = tidy_location_name(location) |
|
||||
|
|
||||
# Tidy up the location name and generate a description |
|
||||
description = tidy_location_name(location) |
|
||||
|
|
||||
# Insert or get location_id |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO locations (name, description) |
|
||||
VALUES (?, ?) |
|
||||
''', (location, description)) |
|
||||
cursor.execute('SELECT id FROM locations WHERE name = ?', (location,)) |
|
||||
location_id = cursor.fetchone()[0] |
|
||||
|
|
||||
# Insert or get encounter_method_id |
|
||||
cursor.execute('INSERT OR IGNORE INTO encounter_methods (name) VALUES (?)', (obtain_method,)) |
|
||||
cursor.execute('SELECT id FROM encounter_methods WHERE name = ?', (obtain_method,)) |
|
||||
method_id = cursor.fetchone()[0] |
|
||||
|
|
||||
# Insert form_encounter |
|
||||
cursor.execute(''' |
|
||||
INSERT OR IGNORE INTO form_encounters |
|
||||
(form_id, game_id, location_id, encounter_method_id) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
''', (form_id, game_id, location_id, method_id)) |
|
||||
|
|
||||
conn.commit() |
|
||||
|
|
||||
def main(): |
|
||||
conn = create_connection() |
|
||||
create_tables(conn) |
|
||||
load_game_data(conn) |
|
||||
load_mark_data(conn) |
|
||||
load_pokemon_data(conn) |
|
||||
load_encounter_data(conn) |
|
||||
conn.close() |
|
||||
print("All data has been successfully added to the database.") |
|
||||
|
|
||||
if __name__ == "__main__": |
|
||||
main() |
|
||||
@ -1,136 +0,0 @@ |
|||||
import sqlite3 |
|
||||
import csv |
|
||||
from typing import List, Dict, Optional |
|
||||
from bs4 import BeautifulSoup |
|
||||
import requests |
|
||||
import re |
|
||||
from fuzzywuzzy import fuzz |
|
||||
|
|
||||
# Import necessary functions from DetermineOriginGame.py |
|
||||
from DetermineOriginGame import ( |
|
||||
create_pokemon_index, |
|
||||
get_intro_generation, |
|
||||
get_locations_from_bulbapedia, |
|
||||
get_evolution_data_from_bulbapedia, |
|
||||
split_td_contents, |
|
||||
parse_form_information, |
|
||||
get_cached_data, |
|
||||
all_games, |
|
||||
pokemon_index, |
|
||||
cache, |
|
||||
read_pokemon_list |
|
||||
) |
|
||||
|
|
||||
class Pokemon: |
|
||||
def __init__(self, number: int, name: str, form: Optional[str] = None): |
|
||||
self.number = number |
|
||||
self.name = name |
|
||||
self.form = form |
|
||||
self.introduced_in_gen: Optional[int] = None |
|
||||
self.encounters: Dict[str, List[str]] = {} |
|
||||
self.evolution_chain: List[Dict] = [] |
|
||||
self.stage: Optional[str] = None |
|
||||
|
|
||||
def create_database(): |
|
||||
conn = sqlite3.connect('unprocessed_pokemon_database.db') |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
# Create tables |
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS pokemon ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
national_dex_number INTEGER, |
|
||||
name TEXT, |
|
||||
form TEXT, |
|
||||
introduced_in_gen INTEGER |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS encounters ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
pokemon_id INTEGER, |
|
||||
game TEXT, |
|
||||
location TEXT, |
|
||||
FOREIGN KEY (pokemon_id) REFERENCES pokemon (id) |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
cursor.execute(''' |
|
||||
CREATE TABLE IF NOT EXISTS evolution_chain ( |
|
||||
id INTEGER PRIMARY KEY, |
|
||||
pokemon_id INTEGER, |
|
||||
stage INTEGER, |
|
||||
evolves_from TEXT, |
|
||||
evolution_method TEXT, |
|
||||
FOREIGN KEY (pokemon_id) REFERENCES pokemon (id) |
|
||||
) |
|
||||
''') |
|
||||
|
|
||||
conn.commit() |
|
||||
return conn |
|
||||
|
|
||||
def extract_pokemon_data(pokemon_list: List[Pokemon], conn: sqlite3.Connection): |
|
||||
cursor = conn.cursor() |
|
||||
|
|
||||
for pokemon in pokemon_list: |
|
||||
print(f"Processing {pokemon.name} ({pokemon.form})") |
|
||||
|
|
||||
# Get introduction generation |
|
||||
pokemon.introduced_in_gen = get_intro_generation(pokemon.name, pokemon.form, cache) |
|
||||
|
|
||||
# Get encounter data |
|
||||
encounter_data = get_locations_from_bulbapedia(pokemon.name, pokemon.form, cache) |
|
||||
for game, locations in encounter_data.items(): |
|
||||
pokemon.encounters[game] = locations |
|
||||
|
|
||||
# Get evolution data |
|
||||
pokemon.evolution_chain = get_evolution_data_from_bulbapedia(pokemon.name, pokemon.form, cache) |
|
||||
|
|
||||
# Insert data into database |
|
||||
cursor.execute(''' |
|
||||
INSERT INTO pokemon (national_dex_number, name, form, introduced_in_gen) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
''', (pokemon.number, pokemon.name, pokemon.form, pokemon.introduced_in_gen)) |
|
||||
pokemon_id = cursor.lastrowid |
|
||||
|
|
||||
for game, locations in pokemon.encounters.items(): |
|
||||
for location in locations: |
|
||||
cursor.execute(''' |
|
||||
INSERT INTO encounters (pokemon_id, game, location) |
|
||||
VALUES (?, ?, ?) |
|
||||
''', (pokemon_id, game, location)) |
|
||||
|
|
||||
if pokemon.evolution_chain: |
|
||||
for i, stage in enumerate(pokemon.evolution_chain): |
|
||||
previous_stage = None |
|
||||
if stage.previous_stage: |
|
||||
previous_stage = stage.previous_stage.pokemon |
|
||||
cursor.execute(''' |
|
||||
INSERT INTO evolution_chain (pokemon_id, stage, evolves_from, evolution_method) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
''', (pokemon_id, i, previous_stage, stage.method)) |
|
||||
|
|
||||
conn.commit() |
|
||||
|
|
||||
def read_and_convert_pokemon_list(filename: str) -> List[Pokemon]: |
|
||||
pokemon_list = read_pokemon_list(filename, 3000) |
|
||||
local_list = [] |
|
||||
for entry in pokemon_list: |
|
||||
number = entry.number |
|
||||
name = entry.name |
|
||||
form = entry.form |
|
||||
local_list.append(Pokemon(number, name, form)) |
|
||||
return local_list |
|
||||
|
|
||||
def main(): |
|
||||
get_cached_data() |
|
||||
conn = create_database() |
|
||||
pokemon_list = read_and_convert_pokemon_list('pokemon_home_list.csv') |
|
||||
create_pokemon_index(pokemon_list) |
|
||||
extract_pokemon_data(pokemon_list, conn) |
|
||||
conn.close() |
|
||||
print("Data extraction complete and stored in the database.") |
|
||||
|
|
||||
if __name__ == "__main__": |
|
||||
main() |
|
||||
@ -1,609 +0,0 @@ |
|||||
import sys |
|
||||
import sqlite3 |
|
||||
import json |
|
||||
import os |
|
||||
from datetime import datetime |
|
||||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QListWidget, QLabel, |
|
||||
QVBoxLayout, QHBoxLayout, QWidget, QPushButton, |
|
||||
QTableWidget, QTableWidgetItem, QHeaderView, |
|
||||
QLineEdit, QCheckBox, QFormLayout, QMessageBox, |
|
||||
QDialog, QComboBox, QFileDialog, QTabWidget) |
|
||||
from PyQt6.QtGui import QPixmap |
|
||||
from PyQt6.QtCore import Qt |
|
||||
|
|
||||
class PokemonDatabaseApp(QMainWindow): |
|
||||
def __init__(self): |
|
||||
super().__init__() |
|
||||
self.setWindowTitle("Pokémon Database Viewer") |
|
||||
self.setGeometry(100, 100, 1200, 600) |
|
||||
|
|
||||
# Create an in-memory database |
|
||||
self.conn = sqlite3.connect(':memory:') |
|
||||
self.cursor = self.conn.cursor() |
|
||||
|
|
||||
# Load the database from disk into memory |
|
||||
self.load_database() |
|
||||
|
|
||||
# Apply all existing patches |
|
||||
self.apply_all_patches() |
|
||||
|
|
||||
self.current_pokemon_id = None |
|
||||
self.current_pokemon_name = None |
|
||||
self.current_form_id = None |
|
||||
self.all_pokemon = [] |
|
||||
self.patches = {} |
|
||||
self.init_ui() |
|
||||
self.load_locations_list() |
|
||||
|
|
||||
def init_ui(self): |
|
||||
main_layout = QVBoxLayout() |
|
||||
|
|
||||
# Create tab widget |
|
||||
self.tab_widget = QTabWidget() |
|
||||
|
|
||||
# Create and add Pokemon tab |
|
||||
pokemon_tab = self.create_pokemon_tab() |
|
||||
self.tab_widget.addTab(pokemon_tab, "Pokémon") |
|
||||
|
|
||||
# Create and add Locations tab |
|
||||
locations_tab = self.create_locations_tab() |
|
||||
self.tab_widget.addTab(locations_tab, "Locations") |
|
||||
|
|
||||
main_layout.addWidget(self.tab_widget) |
|
||||
|
|
||||
# Add export button |
|
||||
self.export_button = QPushButton("Export Production Database") |
|
||||
self.export_button.clicked.connect(self.export_production_database) |
|
||||
main_layout.addWidget(self.export_button) |
|
||||
|
|
||||
container = QWidget() |
|
||||
container.setLayout(main_layout) |
|
||||
self.setCentralWidget(container) |
|
||||
|
|
||||
self.load_pokemon_list() |
|
||||
|
|
||||
def create_pokemon_tab(self): |
|
||||
pokemon_tab = QWidget() |
|
||||
tab_layout = QHBoxLayout() |
|
||||
|
|
||||
# Pokémon list section |
|
||||
pokemon_list_layout = QVBoxLayout() |
|
||||
|
|
||||
# Search box |
|
||||
self.search_box = QLineEdit() |
|
||||
self.search_box.setPlaceholderText("Search Pokémon...") |
|
||||
self.search_box.textChanged.connect(self.filter_pokemon_list) |
|
||||
pokemon_list_layout.addWidget(self.search_box) |
|
||||
|
|
||||
# Pokémon list |
|
||||
self.pokemon_list = QListWidget() |
|
||||
self.pokemon_list.itemClicked.connect(self.load_pokemon_data) |
|
||||
pokemon_list_layout.addWidget(self.pokemon_list) |
|
||||
|
|
||||
tab_layout.addLayout(pokemon_list_layout, 1) |
|
||||
|
|
||||
# Pokémon details |
|
||||
details_layout = QVBoxLayout() |
|
||||
|
|
||||
image_and_form_layout = QHBoxLayout() |
|
||||
|
|
||||
# Image |
|
||||
self.image_label = QLabel() |
|
||||
self.image_label.setFixedSize(200, 200) |
|
||||
self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) |
|
||||
image_and_form_layout.addWidget(self.image_label) |
|
||||
|
|
||||
# Form details |
|
||||
form_details_layout = QFormLayout() |
|
||||
self.form_name_edit = QLineEdit() |
|
||||
self.is_default_checkbox = QCheckBox() |
|
||||
self.image_path_edit = QLineEdit() |
|
||||
form_details_layout.addRow("Form Name:", self.form_name_edit) |
|
||||
form_details_layout.addRow("Default Form:", self.is_default_checkbox) |
|
||||
form_details_layout.addRow("Image Path:", self.image_path_edit) |
|
||||
|
|
||||
# Save button |
|
||||
self.save_button = QPushButton("Save Form Changes") |
|
||||
self.save_button.clicked.connect(self.save_form_changes) |
|
||||
form_details_layout.addRow(self.save_button) |
|
||||
|
|
||||
image_and_form_layout.addLayout(form_details_layout) |
|
||||
details_layout.addLayout(image_and_form_layout) |
|
||||
|
|
||||
# Forms list and add new form button |
|
||||
forms_layout = QHBoxLayout() |
|
||||
self.forms_list = QListWidget() |
|
||||
self.forms_list.itemClicked.connect(self.load_form_data) |
|
||||
forms_layout.addWidget(self.forms_list) |
|
||||
|
|
||||
add_form_button = QPushButton("Add New Form") |
|
||||
add_form_button.clicked.connect(self.add_new_form) |
|
||||
details_layout.addWidget(add_form_button) |
|
||||
|
|
||||
details_layout.addWidget(QLabel("Forms:")) |
|
||||
details_layout.addLayout(forms_layout) |
|
||||
|
|
||||
# Encounters table and add new encounter button |
|
||||
self.encounters_table = QTableWidget() |
|
||||
self.encounters_table.setColumnCount(3) |
|
||||
self.encounters_table.setHorizontalHeaderLabels(['Game', 'Location', 'Method']) |
|
||||
self.encounters_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) |
|
||||
details_layout.addWidget(QLabel("Encounters:")) |
|
||||
details_layout.addWidget(self.encounters_table) |
|
||||
|
|
||||
add_encounter_button = QPushButton("Add New Encounter") |
|
||||
add_encounter_button.clicked.connect(self.add_new_encounter) |
|
||||
details_layout.addWidget(add_encounter_button) |
|
||||
|
|
||||
tab_layout.addLayout(details_layout, 2) |
|
||||
|
|
||||
pokemon_tab.setLayout(tab_layout) |
|
||||
return pokemon_tab |
|
||||
|
|
||||
def create_locations_tab(self): |
|
||||
locations_tab = QWidget() |
|
||||
tab_layout = QHBoxLayout() |
|
||||
|
|
||||
# Locations list |
|
||||
locations_list_layout = QVBoxLayout() |
|
||||
|
|
||||
# Search box for locations |
|
||||
self.locations_search_box = QLineEdit() |
|
||||
self.locations_search_box.setPlaceholderText("Search Locations...") |
|
||||
self.locations_search_box.textChanged.connect(self.filter_locations_list) |
|
||||
locations_list_layout.addWidget(self.locations_search_box) |
|
||||
|
|
||||
# Locations list |
|
||||
self.locations_list = QListWidget() |
|
||||
self.locations_list.itemClicked.connect(self.load_location_data) |
|
||||
locations_list_layout.addWidget(self.locations_list) |
|
||||
|
|
||||
# Add new location button |
|
||||
add_location_button = QPushButton("Add New Location") |
|
||||
add_location_button.clicked.connect(self.add_new_location) |
|
||||
locations_list_layout.addWidget(add_location_button) |
|
||||
|
|
||||
tab_layout.addLayout(locations_list_layout, 1) |
|
||||
|
|
||||
# Location details |
|
||||
location_details_layout = QFormLayout() |
|
||||
|
|
||||
self.location_name_edit = QLineEdit() |
|
||||
self.location_description_edit = QLineEdit() |
|
||||
|
|
||||
location_details_layout.addRow("Name:", self.location_name_edit) |
|
||||
location_details_layout.addRow("description:", self.location_description_edit) |
|
||||
|
|
||||
# Save location changes button |
|
||||
save_location_button = QPushButton("Save Location Changes") |
|
||||
save_location_button.clicked.connect(self.save_location_changes) |
|
||||
location_details_layout.addRow(save_location_button) |
|
||||
|
|
||||
tab_layout.addLayout(location_details_layout, 2) |
|
||||
|
|
||||
locations_tab.setLayout(tab_layout) |
|
||||
return locations_tab |
|
||||
|
|
||||
def load_database(self): |
|
||||
disk_conn = sqlite3.connect('pokemon_database.db') |
|
||||
disk_conn.backup(self.conn) |
|
||||
disk_conn.close() |
|
||||
|
|
||||
def apply_all_patches(self): |
|
||||
patches_dir = "patches" |
|
||||
if not os.path.exists(patches_dir): |
|
||||
return |
|
||||
|
|
||||
patch_files = sorted([f for f in os.listdir(patches_dir) if f.endswith('.json')]) |
|
||||
for patch_file in patch_files: |
|
||||
with open(os.path.join(patches_dir, patch_file), 'r') as f: |
|
||||
patches = json.load(f) |
|
||||
self.apply_patches(patches) |
|
||||
|
|
||||
def apply_patches(self, patches): |
|
||||
try: |
|
||||
for timestamp, patch in patches.items(): |
|
||||
if patch["type"] == "form_update": |
|
||||
self.cursor.execute(""" |
|
||||
UPDATE pokemon_forms |
|
||||
SET form_name = ?, is_default = ?, image_path = ? |
|
||||
WHERE id = ? |
|
||||
""", (patch["form_name"], patch["is_default"], patch["image_path"], patch["form_id"])) |
|
||||
elif patch["type"] == "new_form": |
|
||||
self.cursor.execute(""" |
|
||||
INSERT INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
""", (patch["pokemon_id"], patch["form_name"], patch["is_default"], patch["image_path"])) |
|
||||
elif patch["type"] == "new_encounter": |
|
||||
self.cursor.execute(""" |
|
||||
INSERT INTO form_encounters (form_id, game_id, location_id, encounter_method_id) |
|
||||
VALUES (?, |
|
||||
(SELECT id FROM games WHERE name = ?), |
|
||||
(SELECT id FROM locations WHERE name = ?), |
|
||||
(SELECT id FROM encounter_methods WHERE name = ?)) |
|
||||
""", (patch["form_id"], patch["game"], patch["location"], patch["method"])) |
|
||||
elif patch["type"] == "location_update": |
|
||||
self.cursor.execute(""" |
|
||||
UPDATE locations |
|
||||
SET name = ?, description = ? |
|
||||
WHERE name = ? |
|
||||
""", (patch["new_name"], patch["description"], patch["old_name"])) |
|
||||
elif patch["type"] == "new_location": |
|
||||
self.cursor.execute(""" |
|
||||
INSERT INTO locations (name, description) |
|
||||
VALUES (?, ?) |
|
||||
""", (patch["name"], patch["description"])) |
|
||||
|
|
||||
self.conn.commit() |
|
||||
except sqlite3.Error as e: |
|
||||
print(f"An error occurred while applying patches: {e}") |
|
||||
|
|
||||
def load_pokemon_list(self): |
|
||||
self.cursor.execute("SELECT national_dex_number, name FROM pokemon ORDER BY national_dex_number") |
|
||||
self.all_pokemon = [f"{row[0]:03d} - {row[1]}" for row in self.cursor.fetchall()] |
|
||||
self.pokemon_list.addItems(self.all_pokemon) |
|
||||
|
|
||||
def filter_pokemon_list(self): |
|
||||
search_text = self.search_box.text().lower() |
|
||||
self.pokemon_list.clear() |
|
||||
for pokemon in self.all_pokemon: |
|
||||
if search_text in pokemon.lower(): |
|
||||
self.pokemon_list.addItem(pokemon) |
|
||||
|
|
||||
def load_pokemon_data(self, item): |
|
||||
self.current_pokemon_id = int(item.text().split('-')[0]) |
|
||||
self.current_pokemon_name = item.text().split('-')[1] |
|
||||
# Load forms |
|
||||
self.forms_list.clear() |
|
||||
self.cursor.execute(""" |
|
||||
SELECT form_name FROM pokemon_forms |
|
||||
WHERE pokemon_id = ? |
|
||||
ORDER BY is_default DESC, form_name |
|
||||
""", (self.current_pokemon_id,)) |
|
||||
for row in self.cursor.fetchall(): |
|
||||
self.forms_list.addItem(row[0]) |
|
||||
|
|
||||
# Load default form data |
|
||||
self.forms_list.setCurrentRow(0) |
|
||||
self.load_form_data(self.forms_list.item(0)) |
|
||||
|
|
||||
def load_form_data(self, item): |
|
||||
if not item: |
|
||||
return |
|
||||
|
|
||||
form_name = item.text() |
|
||||
|
|
||||
# Load form data |
|
||||
self.cursor.execute(""" |
|
||||
SELECT id, form_name, is_default, image_path FROM pokemon_forms |
|
||||
WHERE pokemon_id = ? AND form_name = ? |
|
||||
""", (self.current_pokemon_id, form_name)) |
|
||||
form_data = self.cursor.fetchone() |
|
||||
if form_data: |
|
||||
self.current_form_id, form_name, is_default, image_path = form_data |
|
||||
|
|
||||
# Update form details |
|
||||
self.form_name_edit.setText(form_name) |
|
||||
self.is_default_checkbox.setChecked(bool(is_default)) |
|
||||
self.image_path_edit.setText(image_path) |
|
||||
|
|
||||
# Load image |
|
||||
pixmap = QPixmap(image_path) |
|
||||
self.image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio)) |
|
||||
|
|
||||
# Load encounters |
|
||||
self.encounters_table.setRowCount(0) |
|
||||
self.cursor.execute(""" |
|
||||
SELECT g.name, l.name, em.name |
|
||||
FROM form_encounters fe |
|
||||
JOIN games g ON fe.game_id = g.id |
|
||||
JOIN locations l ON fe.location_id = l.id |
|
||||
JOIN encounter_methods em ON fe.encounter_method_id = em.id |
|
||||
WHERE fe.form_id = ? |
|
||||
ORDER BY g.name, l.name |
|
||||
""", (self.current_form_id,)) |
|
||||
for row in self.cursor.fetchall(): |
|
||||
current_row = self.encounters_table.rowCount() |
|
||||
self.encounters_table.insertRow(current_row) |
|
||||
for col, value in enumerate(row): |
|
||||
self.encounters_table.setItem(current_row, col, QTableWidgetItem(str(value))) |
|
||||
|
|
||||
def save_form_changes(self): |
|
||||
if not self.current_form_id: |
|
||||
return |
|
||||
|
|
||||
new_form_name = self.form_name_edit.text() |
|
||||
new_is_default = self.is_default_checkbox.isChecked() |
|
||||
new_image_path = self.image_path_edit.text() |
|
||||
|
|
||||
# Add changes to patches |
|
||||
patch = { |
|
||||
"type": "form_update", |
|
||||
"form_id": self.current_form_id, |
|
||||
"form_name": new_form_name, |
|
||||
"is_default": new_is_default, |
|
||||
"image_path": new_image_path |
|
||||
} |
|
||||
self.add_to_patches(patch) |
|
||||
|
|
||||
try: |
|
||||
self.cursor.execute(""" |
|
||||
UPDATE pokemon_forms |
|
||||
SET form_name = ?, is_default = ?, image_path = ? |
|
||||
WHERE id = ? |
|
||||
""", (new_form_name, new_is_default, new_image_path, self.current_form_id)) |
|
||||
self.conn.commit() |
|
||||
QMessageBox.information(self, "Success", "Form data updated successfully!") |
|
||||
|
|
||||
# Refresh the forms list and current form data |
|
||||
self.load_pokemon_data(self.pokemon_list.currentItem()) |
|
||||
except sqlite3.Error as e: |
|
||||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|
||||
|
|
||||
def add_new_form(self): |
|
||||
if not self.current_pokemon_id: |
|
||||
QMessageBox.warning(self, "Error", "Please select a Pokémon first.") |
|
||||
return |
|
||||
|
|
||||
dialog = QDialog(self) |
|
||||
dialog.setWindowTitle("Add New Form") |
|
||||
layout = QFormLayout(dialog) |
|
||||
|
|
||||
form_name_edit = QLineEdit() |
|
||||
layout.addRow("Form Name:", form_name_edit) |
|
||||
|
|
||||
buttons = QHBoxLayout() |
|
||||
save_button = QPushButton("Save") |
|
||||
save_button.clicked.connect(dialog.accept) |
|
||||
cancel_button = QPushButton("Cancel") |
|
||||
cancel_button.clicked.connect(dialog.reject) |
|
||||
buttons.addWidget(save_button) |
|
||||
buttons.addWidget(cancel_button) |
|
||||
layout.addRow(buttons) |
|
||||
|
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted: |
|
||||
form_name = form_name_edit.text() |
|
||||
|
|
||||
try: |
|
||||
self.cursor.execute(""" |
|
||||
INSERT INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) |
|
||||
VALUES (?, ?, ?, ?) |
|
||||
""", (self.current_pokemon_id, form_name, False, f"images/pokemon/{self.current_pokemon_id:04d}_{self.current_pokemon_name}_({form_name}).png".replace(" ", "_"))) |
|
||||
self.conn.commit() |
|
||||
new_form_id = self.cursor.lastrowid |
|
||||
|
|
||||
# Add new form to patches |
|
||||
patch = { |
|
||||
"type": "new_form", |
|
||||
"form_id": new_form_id, |
|
||||
"pokemon_id": self.current_pokemon_id, |
|
||||
"form_name": form_name, |
|
||||
"is_default": False, |
|
||||
"image_path": f"images/pokemon/{self.current_pokemon_id:04d}_{self.current_pokemon_name}_({form_name}).png".replace(" ", "_") |
|
||||
} |
|
||||
self.add_to_patches(patch) |
|
||||
|
|
||||
QMessageBox.information(self, "Success", "New form added successfully!") |
|
||||
self.load_pokemon_data(self.pokemon_list.currentItem()) |
|
||||
except sqlite3.Error as e: |
|
||||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|
||||
|
|
||||
def add_new_encounter(self): |
|
||||
if not self.current_form_id: |
|
||||
QMessageBox.warning(self, "Error", "Please select a form first.") |
|
||||
return |
|
||||
|
|
||||
dialog = QDialog(self) |
|
||||
dialog.setWindowTitle("Add New Encounter") |
|
||||
layout = QFormLayout(dialog) |
|
||||
|
|
||||
game_combo = QComboBox() |
|
||||
self.cursor.execute("SELECT name FROM games ORDER BY name") |
|
||||
games = [row[0] for row in self.cursor.fetchall()] |
|
||||
game_combo.addItems(games) |
|
||||
layout.addRow("Game:", game_combo) |
|
||||
|
|
||||
location_combo = QComboBox() |
|
||||
self.cursor.execute("SELECT name FROM locations ORDER BY name") |
|
||||
locations = [row[0] for row in self.cursor.fetchall()] |
|
||||
location_combo.addItems(locations) |
|
||||
layout.addRow("Location:", location_combo) |
|
||||
|
|
||||
method_combo = QComboBox() |
|
||||
self.cursor.execute("SELECT name FROM encounter_methods ORDER BY name") |
|
||||
methods = [row[0] for row in self.cursor.fetchall()] |
|
||||
method_combo.addItems(methods) |
|
||||
layout.addRow("Method:", method_combo) |
|
||||
|
|
||||
buttons = QHBoxLayout() |
|
||||
save_button = QPushButton("Save") |
|
||||
save_button.clicked.connect(dialog.accept) |
|
||||
cancel_button = QPushButton("Cancel") |
|
||||
cancel_button.clicked.connect(dialog.reject) |
|
||||
buttons.addWidget(save_button) |
|
||||
buttons.addWidget(cancel_button) |
|
||||
layout.addRow(buttons) |
|
||||
|
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted: |
|
||||
game = game_combo.currentText() |
|
||||
location = location_combo.currentText() |
|
||||
method = method_combo.currentText() |
|
||||
|
|
||||
try: |
|
||||
self.cursor.execute(""" |
|
||||
INSERT INTO form_encounters (form_id, game_id, location_id, encounter_method_id) |
|
||||
VALUES (?, |
|
||||
(SELECT id FROM games WHERE name = ?), |
|
||||
(SELECT id FROM locations WHERE name = ?), |
|
||||
(SELECT id FROM encounter_methods WHERE name = ?)) |
|
||||
""", (self.current_form_id, game, location, method)) |
|
||||
self.conn.commit() |
|
||||
new_encounter_id = self.cursor.lastrowid |
|
||||
|
|
||||
# Add new encounter to patches |
|
||||
patch = { |
|
||||
"type": "new_encounter", |
|
||||
"encounter_id": new_encounter_id, |
|
||||
"form_id": self.current_form_id, |
|
||||
"game": game, |
|
||||
"location": location, |
|
||||
"method": method |
|
||||
} |
|
||||
self.add_to_patches(patch) |
|
||||
|
|
||||
QMessageBox.information(self, "Success", "New encounter added successfully!") |
|
||||
self.load_form_data(self.forms_list.currentItem()) |
|
||||
except sqlite3.Error as e: |
|
||||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|
||||
|
|
||||
def add_to_patches(self, patch): |
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
||||
self.patches[f"{timestamp}_{len(self.patches)}"] = patch |
|
||||
self.auto_save_patches() |
|
||||
|
|
||||
def auto_save_patches(self): |
|
||||
patches_dir = "patches" |
|
||||
if not os.path.exists(patches_dir): |
|
||||
os.makedirs(patches_dir) |
|
||||
|
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
||||
file_name = f"{patches_dir}/patch_{timestamp}.json" |
|
||||
|
|
||||
with open(file_name, 'w') as f: |
|
||||
json.dump(self.patches, f, indent=2) |
|
||||
|
|
||||
print(f"Patches auto-saved to {file_name}") |
|
||||
|
|
||||
def closeEvent(self, event): |
|
||||
reply = QMessageBox.question(self, 'Save Changes', |
|
||||
"Do you want to save changes to the disk database?", |
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, |
|
||||
QMessageBox.StandardButton.No) |
|
||||
|
|
||||
if reply == QMessageBox.StandardButton.Yes: |
|
||||
# Save changes to disk |
|
||||
disk_conn = sqlite3.connect('pokemon_database.db') |
|
||||
self.conn.backup(disk_conn) |
|
||||
disk_conn.close() |
|
||||
QMessageBox.information(self, "Success", "Changes saved to disk database.") |
|
||||
|
|
||||
self.conn.close() |
|
||||
event.accept() |
|
||||
|
|
||||
def filter_locations_list(self): |
|
||||
search_text = self.locations_search_box.text().lower() |
|
||||
for i in range(self.locations_list.count()): |
|
||||
item = self.locations_list.item(i) |
|
||||
if search_text in item.text().lower(): |
|
||||
item.setHidden(False) |
|
||||
else: |
|
||||
item.setHidden(True) |
|
||||
|
|
||||
def load_location_data(self, item): |
|
||||
location_name = item.text() |
|
||||
self.cursor.execute("SELECT name, description FROM locations WHERE name = ?", (location_name,)) |
|
||||
location_data = self.cursor.fetchone() |
|
||||
if location_data: |
|
||||
self.location_name_edit.setText(location_data[0]) |
|
||||
self.location_description_edit.setText(location_data[1]) |
|
||||
|
|
||||
def save_location_changes(self): |
|
||||
old_name = self.locations_list.currentItem().text() |
|
||||
new_name = self.location_name_edit.text() |
|
||||
new_description = self.location_description_edit.text() |
|
||||
|
|
||||
try: |
|
||||
self.cursor.execute(""" |
|
||||
UPDATE locations |
|
||||
SET name = ?, description = ? |
|
||||
WHERE name = ? |
|
||||
""", (new_name, new_description, old_name)) |
|
||||
self.conn.commit() |
|
||||
|
|
||||
# Add changes to patches |
|
||||
patch = { |
|
||||
"type": "location_update", |
|
||||
"old_name": old_name, |
|
||||
"new_name": new_name, |
|
||||
"description": new_description |
|
||||
} |
|
||||
self.add_to_patches(patch) |
|
||||
|
|
||||
QMessageBox.information(self, "Success", "Location data updated successfully!") |
|
||||
self.load_locations_list() |
|
||||
except sqlite3.Error as e: |
|
||||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|
||||
|
|
||||
def add_new_location(self): |
|
||||
dialog = QDialog(self) |
|
||||
dialog.setWindowTitle("Add New Location") |
|
||||
layout = QFormLayout(dialog) |
|
||||
|
|
||||
name_edit = QLineEdit() |
|
||||
description_edit = QLineEdit() |
|
||||
layout.addRow("Name:", name_edit) |
|
||||
layout.addRow("description:", description_edit) |
|
||||
|
|
||||
buttons = QHBoxLayout() |
|
||||
save_button = QPushButton("Save") |
|
||||
save_button.clicked.connect(dialog.accept) |
|
||||
cancel_button = QPushButton("Cancel") |
|
||||
cancel_button.clicked.connect(dialog.reject) |
|
||||
buttons.addWidget(save_button) |
|
||||
buttons.addWidget(cancel_button) |
|
||||
layout.addRow(buttons) |
|
||||
|
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted: |
|
||||
name = name_edit.text() |
|
||||
description = description_edit.text() |
|
||||
|
|
||||
try: |
|
||||
self.cursor.execute(""" |
|
||||
INSERT INTO locations (name, description) |
|
||||
VALUES (?, ?) |
|
||||
""", (name, description)) |
|
||||
self.conn.commit() |
|
||||
|
|
||||
# Add new location to patches |
|
||||
patch = { |
|
||||
"type": "new_location", |
|
||||
"name": name, |
|
||||
"description": description |
|
||||
} |
|
||||
self.add_to_patches(patch) |
|
||||
|
|
||||
QMessageBox.information(self, "Success", "New location added successfully!") |
|
||||
self.load_locations_list() |
|
||||
except sqlite3.Error as e: |
|
||||
QMessageBox.warning(self, "Error", f"An error occurred: {e}") |
|
||||
|
|
||||
def load_locations_list(self): |
|
||||
self.locations_list.clear() |
|
||||
self.cursor.execute("SELECT name FROM locations ORDER BY name") |
|
||||
locations = [row[0] for row in self.cursor.fetchall()] |
|
||||
self.locations_list.addItems(locations) |
|
||||
|
|
||||
def export_production_database(self): |
|
||||
try: |
|
||||
# Create a new connection for the production database |
|
||||
production_db_path = QFileDialog.getSaveFileName(self, "Save Production Database", "", "SQLite Database (*.db)")[0] |
|
||||
if not production_db_path: |
|
||||
return # User cancelled the file dialog |
|
||||
|
|
||||
production_conn = sqlite3.connect(production_db_path) |
|
||||
|
|
||||
# Copy the current in-memory database to the production database |
|
||||
self.conn.backup(production_conn) |
|
||||
|
|
||||
# Close the production database connection |
|
||||
production_conn.close() |
|
||||
|
|
||||
QMessageBox.information(self, "Success", f"Production database exported successfully to {production_db_path}") |
|
||||
except sqlite3.Error as e: |
|
||||
QMessageBox.warning(self, "Error", f"An error occurred while exporting the production database: {e}") |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
app = QApplication(sys.argv) |
|
||||
window = PokemonDatabaseApp() |
|
||||
window.show() |
|
||||
sys.exit(app.exec()) |
|
||||
@ -1,296 +0,0 @@ |
|||||
import sys |
|
||||
import os |
|
||||
import sqlite3 |
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QPushButton, QVBoxLayout, |
|
||||
QHBoxLayout, QWidget, QLineEdit, QLabel, QMessageBox, QTabWidget, QScrollArea, QFrame, QGridLayout) |
|
||||
from PyQt5.QtCore import Qt |
|
||||
from PyQt5.QtGui import QPixmap |
|
||||
|
|
||||
class PokemonEvolutionWidget(QWidget): |
|
||||
def __init__(self, conn, pfic): |
|
||||
super().__init__() |
|
||||
self.conn = conn |
|
||||
self.pfic = pfic |
|
||||
self.layout = QVBoxLayout() |
|
||||
self.setLayout(self.layout) |
|
||||
self.stage_width = 200 # Fixed width for each evolution stage |
|
||||
self.stage_height = 250 # Fixed height for each evolution stage |
|
||||
self.build_evolution_chain() |
|
||||
|
|
||||
def build_evolution_chain(self): |
|
||||
chain = self.get_full_evolution_chain(self.pfic) |
|
||||
self.display_evolution_chain(chain) |
|
||||
|
|
||||
def get_full_evolution_chain(self, pfic): |
|
||||
cursor = self.conn.cursor() |
|
||||
chain = [] |
|
||||
visited = set() |
|
||||
|
|
||||
def build_chain(current_pfic): |
|
||||
if current_pfic in visited: |
|
||||
return None |
|
||||
visited.add(current_pfic) |
|
||||
|
|
||||
cursor.execute('SELECT name, form_name FROM pokemon_forms WHERE PFIC = ?', (current_pfic,)) |
|
||||
pokemon = cursor.fetchone() |
|
||||
if not pokemon: |
|
||||
return None |
|
||||
|
|
||||
name, form_name = pokemon |
|
||||
node = {"pfic": current_pfic, "name": name, "form_name": form_name, "evolutions": []} |
|
||||
|
|
||||
cursor.execute(''' |
|
||||
SELECT ec.to_pfic, pf.name, pf.form_name, ec.method |
|
||||
FROM evolution_chains ec |
|
||||
JOIN pokemon_forms pf ON ec.to_pfic = pf.PFIC |
|
||||
WHERE ec.from_pfic = ? |
|
||||
''', (current_pfic,)) |
|
||||
evolutions = cursor.fetchall() |
|
||||
|
|
||||
for evo_pfic, evo_name, evo_form, method in evolutions: |
|
||||
evo_node = build_chain(evo_pfic) |
|
||||
if evo_node: |
|
||||
node["evolutions"].append({"node": evo_node, "method": method}) |
|
||||
|
|
||||
return node |
|
||||
|
|
||||
chain = build_chain(pfic) |
|
||||
return chain |
|
||||
|
|
||||
def display_evolution_chain(self, chain): |
|
||||
if not chain: |
|
||||
return |
|
||||
|
|
||||
main_layout = QHBoxLayout() |
|
||||
self.layout.addLayout(main_layout) |
|
||||
|
|
||||
stages = self.split_into_stages(chain) |
|
||||
for stage_index, stage in enumerate(stages): |
|
||||
stage_widget = QWidget() |
|
||||
stage_layout = QGridLayout() |
|
||||
stage_widget.setLayout(stage_layout) |
|
||||
stage_widget.setFixedSize(self.stage_width, self.stage_height * len(stage)) |
|
||||
|
|
||||
for row, pokemon in enumerate(stage): |
|
||||
pokemon_widget = self.create_pokemon_widget(pokemon["pfic"], pokemon["name"], pokemon["form_name"]) |
|
||||
stage_layout.addWidget(pokemon_widget, row, 0, Qt.AlignCenter) |
|
||||
|
|
||||
if stage_index < len(stages) - 1 and pokemon.get("method"): |
|
||||
arrow_label = QLabel("→") |
|
||||
arrow_label.setStyleSheet("font-size: 24px;") |
|
||||
stage_layout.addWidget(arrow_label, row, 1, Qt.AlignCenter) |
|
||||
|
|
||||
method_label = QLabel(pokemon["method"]) |
|
||||
method_label.setWordWrap(True) |
|
||||
method_label.setFixedWidth(80) |
|
||||
stage_layout.addWidget(method_label, row, 2, Qt.AlignCenter) |
|
||||
|
|
||||
main_layout.addWidget(stage_widget) |
|
||||
|
|
||||
def split_into_stages(self, chain): |
|
||||
stages = [] |
|
||||
current_stage = [{"pfic": chain["pfic"], "name": chain["name"], "form_name": chain["form_name"]}] |
|
||||
stages.append(current_stage) |
|
||||
|
|
||||
while current_stage: |
|
||||
next_stage = [] |
|
||||
for pokemon in current_stage: |
|
||||
evolutions = self.get_evolutions(pokemon["pfic"]) |
|
||||
for evolution in evolutions: |
|
||||
next_stage.append({ |
|
||||
"pfic": evolution["to_pfic"], |
|
||||
"name": evolution["name"], |
|
||||
"form_name": evolution["form_name"], |
|
||||
"method": evolution["method"] |
|
||||
}) |
|
||||
if next_stage: |
|
||||
stages.append(next_stage) |
|
||||
current_stage = next_stage |
|
||||
|
|
||||
return stages |
|
||||
|
|
||||
def get_evolutions(self, pfic): |
|
||||
cursor = self.conn.cursor() |
|
||||
cursor.execute(''' |
|
||||
SELECT ec.to_pfic, pf.name, pf.form_name, ec.method |
|
||||
FROM evolution_chains ec |
|
||||
JOIN pokemon_forms pf ON ec.to_pfic = pf.PFIC |
|
||||
WHERE ec.from_pfic = ? |
|
||||
''', (pfic,)) |
|
||||
evolutions = cursor.fetchall() |
|
||||
return [{"to_pfic": to_pfic, "name": name, "form_name": form_name, "method": method} for to_pfic, name, form_name, method in evolutions] |
|
||||
|
|
||||
def create_pokemon_widget(self, pfic, name, form_name): |
|
||||
widget = QWidget() |
|
||||
layout = QVBoxLayout() |
|
||||
widget.setLayout(layout) |
|
||||
|
|
||||
image_path = f"images-new/{pfic}.png" |
|
||||
if os.path.exists(image_path): |
|
||||
pixmap = QPixmap(image_path) |
|
||||
image_label = QLabel() |
|
||||
image_label.setPixmap(pixmap.scaled(96, 96, Qt.KeepAspectRatio, Qt.SmoothTransformation)) |
|
||||
layout.addWidget(image_label, alignment=Qt.AlignCenter) |
|
||||
|
|
||||
name_label = QLabel(name) |
|
||||
name_label.setAlignment(Qt.AlignCenter) |
|
||||
layout.addWidget(name_label) |
|
||||
|
|
||||
if form_name: |
|
||||
form_label = QLabel(form_name) |
|
||||
form_label.setAlignment(Qt.AlignCenter) |
|
||||
layout.addWidget(form_label) |
|
||||
|
|
||||
return widget |
|
||||
|
|
||||
class DatabaseVisualizer(QMainWindow): |
|
||||
def __init__(self): |
|
||||
super().__init__() |
|
||||
self.setWindowTitle("Pokémon Database Visualizer") |
|
||||
self.setGeometry(100, 100, 1200, 800) |
|
||||
|
|
||||
self.conn = sqlite3.connect('pokemon_forms.db') |
|
||||
self.cursor = self.conn.cursor() |
|
||||
|
|
||||
self.central_widget = QWidget() |
|
||||
self.setCentralWidget(self.central_widget) |
|
||||
|
|
||||
self.layout = QVBoxLayout() |
|
||||
self.central_widget.setLayout(self.layout) |
|
||||
|
|
||||
self.tab_widget = QTabWidget() |
|
||||
self.layout.addWidget(self.tab_widget) |
|
||||
|
|
||||
self.forms_tab = QWidget() |
|
||||
self.evolutions_tab = QWidget() |
|
||||
self.tab_widget.addTab(self.forms_tab, "Pokémon Forms") |
|
||||
self.tab_widget.addTab(self.evolutions_tab, "Evolution Chains") |
|
||||
|
|
||||
self.setup_forms_tab() |
|
||||
self.setup_evolutions_tab() |
|
||||
|
|
||||
def setup_forms_tab(self): |
|
||||
layout = QVBoxLayout() |
|
||||
self.forms_tab.setLayout(layout) |
|
||||
|
|
||||
self.search_layout = QHBoxLayout() |
|
||||
self.search_label = QLabel("Search:") |
|
||||
self.search_input = QLineEdit() |
|
||||
self.search_button = QPushButton("Search") |
|
||||
self.search_button.clicked.connect(self.search_pokemon) |
|
||||
self.search_layout.addWidget(self.search_label) |
|
||||
self.search_layout.addWidget(self.search_input) |
|
||||
self.search_layout.addWidget(self.search_button) |
|
||||
layout.addLayout(self.search_layout) |
|
||||
|
|
||||
self.table = QTableWidget() |
|
||||
layout.addWidget(self.table) |
|
||||
|
|
||||
self.save_button = QPushButton("Save Changes") |
|
||||
self.save_button.clicked.connect(self.save_changes) |
|
||||
layout.addWidget(self.save_button) |
|
||||
|
|
||||
self.load_forms_data() |
|
||||
|
|
||||
def setup_evolutions_tab(self): |
|
||||
layout = QVBoxLayout() |
|
||||
self.evolutions_tab.setLayout(layout) |
|
||||
|
|
||||
self.evolution_search_layout = QHBoxLayout() |
|
||||
self.evolution_search_label = QLabel("Search Pokémon:") |
|
||||
self.evolution_search_input = QLineEdit() |
|
||||
self.evolution_search_button = QPushButton("Search") |
|
||||
self.evolution_search_button.clicked.connect(self.search_evolution) |
|
||||
self.evolution_search_layout.addWidget(self.evolution_search_label) |
|
||||
self.evolution_search_layout.addWidget(self.evolution_search_input) |
|
||||
self.evolution_search_layout.addWidget(self.evolution_search_button) |
|
||||
layout.addLayout(self.evolution_search_layout) |
|
||||
|
|
||||
self.evolution_scroll_area = QScrollArea() |
|
||||
self.evolution_scroll_area.setWidgetResizable(True) |
|
||||
layout.addWidget(self.evolution_scroll_area) |
|
||||
|
|
||||
def load_forms_data(self): |
|
||||
self.cursor.execute(''' |
|
||||
SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex, pf.generation, ps.storable_in_home |
|
||||
FROM pokemon_forms pf |
|
||||
LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC |
|
||||
''') |
|
||||
data = self.cursor.fetchall() |
|
||||
|
|
||||
self.table.setColumnCount(6) |
|
||||
self.table.setHorizontalHeaderLabels(["PFIC", "Name", "Form Name", "National Dex", "Generation", "Storable in Home"]) |
|
||||
self.table.setRowCount(len(data)) |
|
||||
|
|
||||
for row, record in enumerate(data): |
|
||||
for col, value in enumerate(record): |
|
||||
item = QTableWidgetItem(str(value)) |
|
||||
if col == 0: # PFIC column |
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsEditable) # Make PFIC non-editable |
|
||||
self.table.setItem(row, col, item) |
|
||||
|
|
||||
self.table.resizeColumnsToContents() |
|
||||
|
|
||||
def search_pokemon(self): |
|
||||
search_term = self.search_input.text().lower() |
|
||||
for row in range(self.table.rowCount()): |
|
||||
match = False |
|
||||
for col in range(self.table.columnCount()): |
|
||||
item = self.table.item(row, col) |
|
||||
if item and search_term in item.text().lower(): |
|
||||
match = True |
|
||||
break |
|
||||
self.table.setRowHidden(row, not match) |
|
||||
|
|
||||
def save_changes(self): |
|
||||
try: |
|
||||
for row in range(self.table.rowCount()): |
|
||||
pfic = self.table.item(row, 0).text() |
|
||||
name = self.table.item(row, 1).text() |
|
||||
form_name = self.table.item(row, 2).text() or None |
|
||||
national_dex = int(self.table.item(row, 3).text()) |
|
||||
generation = int(self.table.item(row, 4).text()) |
|
||||
storable_in_home = self.table.item(row, 5).text().lower() == 'true' |
|
||||
|
|
||||
self.cursor.execute(''' |
|
||||
UPDATE pokemon_forms |
|
||||
SET name = ?, form_name = ?, national_dex = ?, generation = ? |
|
||||
WHERE PFIC = ? |
|
||||
''', (name, form_name, national_dex, generation, pfic)) |
|
||||
|
|
||||
self.cursor.execute(''' |
|
||||
INSERT OR REPLACE INTO pokemon_storage (PFIC, storable_in_home) |
|
||||
VALUES (?, ?) |
|
||||
''', (pfic, storable_in_home)) |
|
||||
|
|
||||
self.conn.commit() |
|
||||
QMessageBox.information(self, "Success", "Changes saved successfully!") |
|
||||
except Exception as e: |
|
||||
QMessageBox.critical(self, "Error", f"An error occurred while saving changes: {str(e)}") |
|
||||
|
|
||||
def search_evolution(self): |
|
||||
search_term = self.evolution_search_input.text().lower() |
|
||||
|
|
||||
self.cursor.execute(''' |
|
||||
SELECT DISTINCT name, PFIC |
|
||||
FROM pokemon_forms |
|
||||
WHERE LOWER(name) LIKE ? |
|
||||
''', (f'%{search_term}%',)) |
|
||||
|
|
||||
matching_pokemon = self.cursor.fetchall() |
|
||||
|
|
||||
if matching_pokemon: |
|
||||
pokemon_name, pfic = matching_pokemon[0] |
|
||||
evolution_widget = PokemonEvolutionWidget(self.conn, pfic) |
|
||||
self.evolution_scroll_area.setWidget(evolution_widget) |
|
||||
else: |
|
||||
QMessageBox.information(self, "No Results", "No Pokémon found matching the search term.") |
|
||||
|
|
||||
def closeEvent(self, event): |
|
||||
self.conn.close() |
|
||||
|
|
||||
if __name__ == "__main__": |
|
||||
app = QApplication(sys.argv) |
|
||||
window = DatabaseVisualizer() |
|
||||
window.show() |
|
||||
sys.exit(app.exec_()) |
|
||||
File diff suppressed because it is too large
Loading…
Reference in new issue