Browse Source

Big changes including a "working" planner

master
Quildra 1 year ago
parent
commit
167124591e
  1. 5
      .gitignore
  2. 673
      DBEditor/DBEditor.py
  3. 382
      DBEditor/db_controller.py
  4. 29
      DBEditor/event_system.py
  5. 269
      DBEditor/plan.py
  6. 262
      DBEditor/plan2.py
  7. 282
      DBEditor/pokemon_db_ui.py
  8. 87
      DBVisualiser/index.html
  9. 200
      DBVisualiser/main.js
  10. 5193
      DBVisualiser/package-lock.json
  11. 19
      DBVisualiser/package.json
  12. 0
      DBVisualiser/pokemon_forms.db
  13. 483
      DBVisualiser/renderer.js
  14. 338
      DBVisualiser/styles.css
  15. 401
      DataGatherers/DatabaseBuilder.py
  16. 3
      DataGatherers/DefaultForms.json
  17. 7
      DataGatherers/DetermineOriginGame.py
  18. 136
      DataGatherers/ExtractPokemonData.py
  19. 24
      DataGatherers/Update_evolution_information.py
  20. 27
      DataGatherers/pokemondb_scraper.py
  21. 76
      DataGatherers/update_location_information.py
  22. 65
      DataGatherers/update_storable_in_home.py
  23. 609
      Utilities/DBVisualiser.py
  24. 296
      Utilities/NewDBVisualiser.py
  25. 1460
      efficiency_plan.txt

5
.gitignore

@ -3,4 +3,7 @@ pokemon_database.db
Utilities/venv/
venv/
DBVisualiser/node_modules/
__pycache__/
__pycache__/
ui_feedback.log
logs/qt_log.log
logs/default.log

673
DBEditor/DBEditor.py

@ -3,294 +3,134 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QH
QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget,
QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox, QSpinBox, QMenu, QTabWidget,
QTextEdit)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtCore import Qt, QSize, qInstallMessageHandler, QThread, pyqtSignal
from PyQt6.QtGui import QPixmap, QFontMetrics, QColor, QAction
import sqlite3
import json
import os
import traceback
import time
import pdb
import debugpy
# Add the parent directory to the Python path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Now try to import
from db_controller import DBController
from pokemon_db_ui import PokemonUI
from DataGatherers.update_location_information import process_pokemon_for_location_data
from DataGatherers.cache_manager import CacheManager
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)
import logging
class UILogHandler(logging.Handler):
def __init__(self):
super().__init__()
def emit(self, record):
log_entry = self.format(record) # Format the log message
event_system.emit('update_log_display', log_entry)
class PokemonFormGatherer(QThread):
progress_signal = pyqtSignal(str)
finished_signal = pyqtSignal()
def run(self):
cache = CacheManager()
debugpy.debug_this_thread()
from DataGatherers.pokemondb_scraper import retrieve_all_pokemon_forms
retrieve_all_pokemon_forms(cache, progress_callback=self.progress_signal.emit)
self.finished_signal.emit()
class PokemonHomeStatusUpdater(QThread):
progress_signal = pyqtSignal(str)
finished_signal = pyqtSignal()
def run(self):
cache = CacheManager()
debugpy.debug_this_thread()
from DataGatherers.update_storable_in_home import update_storable_in_home
update_storable_in_home(cache, progress_callback=self.progress_signal.emit)
self.finished_signal.emit()
class PokemonEvolutionUpdater(QThread):
progress_signal = pyqtSignal(str)
finished_signal = pyqtSignal()
def run(self):
time.sleep(0.1)
#pdb.set_trace()
debugpy.debug_this_thread()
cache = CacheManager()
from DataGatherers.Update_evolution_information import update_evolution_chains
update_evolution_chains(cache, progress_callback=self.progress_signal.emit)
self.finished_signal.emit()
class PokemonEncounterUpdater(QThread):
progress_signal = pyqtSignal(str)
finished_signal = pyqtSignal()
def run(self):
time.sleep(0.1)
debugpy.debug_this_thread()
cache = CacheManager()
from DataGatherers.update_location_information import update_location_information
update_location_information(cache, progress_callback=self.progress_signal.emit)
self.finished_signal.emit()
class DBEditor(QMainWindow):
def __init__(self):
super().__init__()
self.setup_logging()
self.setWindowTitle("Pokémon Database Editor")
self.setGeometry(100, 100, 1000, 600)
event_system.add_listener('get_pokemon_data', self.load_pokemon_details)
event_system.add_listener('save_changes', self.save_changes)
event_system.add_listener('add_new_encounter', self.add_new_encounter)
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('refresh_pokemon_encounters', self.refresh_pokemon_encounters)
event_system.add_listener('gather_pokemon_forms', self.gather_pokemon_forms)
event_system.add_listener('gather_home_storage_info', self.gather_home_storage_info)
event_system.add_listener('gather_evolution_info', self.gather_evolution_info)
event_system.add_listener('gather_encounter_info', self.gather_encounter_info)
event_system.add_listener('reinitialize_database', self.reinitialize_database)
self.conn = sqlite3.connect(':memory:', check_same_thread=False) # Use in-memory database for runtime
self.cursor = self.conn.cursor()
self.init_database()
self.patches = self.load_and_apply_patches()
#self.patches = self.load_and_apply_patches()
self.patches = {}
self.encounter_cache = {} # Add this line
self.init_ui()
def init_database(self):
# Create or open the file-based database
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()
# Create tables in the in-memory database (in case they weren't copied)
self.create_pokemon_forms_table(self.cursor)
self.create_pokemon_storage_table(self.cursor)
self.create_evolution_chains_table(self.cursor)
self.create_exclusive_encounter_groups_table(self.cursor)
self.create_encounters_table(self.cursor)
# Commit changes to the in-memory database
self.conn.commit()
self.db_controller = DBController()
self.db_controller.init_database()
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
)
''')
def create_encounters_table(self, cursor):
# First, check if the table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='encounters'")
table_exists = cursor.fetchone() is not None
if not table_exists:
# If the table doesn't exist, create it with all columns
cursor.execute('''
CREATE TABLE encounters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pfic TEXT,
game TEXT,
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,
exclusive_group_id INTEGER,
FOREIGN KEY (pfic) REFERENCES pokemon_forms (PFIC),
FOREIGN KEY (exclusive_group_id) REFERENCES exclusive_encounter_groups (id)
)
''')
else:
# If the table exists, check if the exclusive_group_id column exists
cursor.execute("PRAGMA table_info(encounters)")
columns = [column[1] for column in cursor.fetchall()]
if 'exclusive_group_id' not in columns:
# If the column doesn't exist, add it
cursor.execute('''
ALTER TABLE encounters
ADD COLUMN exclusive_group_id INTEGER
REFERENCES exclusive_encounter_groups (id)
''')
if 'starter' not in columns:
# If the column doesn't exist, add it
cursor.execute('''
ALTER TABLE encounters
ADD COLUMN starter BOOLEAN
''')
def create_mark_table(self, cursor):
cursor.execute('''
CREATE TABLE IF NOT EXISTS marks (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
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", "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))
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 setup_logging(self):
# Create the logger with the name 'ui_feedback'
self.logger = logging.getLogger('ui_feedback')
self.logger.setLevel(logging.DEBUG) # Set the logging level
# Create handlers
console_handler = logging.StreamHandler() # Log to the console
file_handler = logging.FileHandler('ui_feedback.log') # Log to a file
ui_handler = UILogHandler()
# Create formatters and add them to handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
ui_handler.setFormatter(formatter)
# Add the handler to the logger
self.logger.addHandler(ui_handler)
# Add handlers to the logger
self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler)
def load_and_apply_patches(self):
try:
@ -333,16 +173,6 @@ class DBEditor(QMainWindow):
with open('patches.json', 'w') as f:
json.dump(self.patches, f)
def load_pokemon_details(self, pfic):
self.cursor.execute('''
SELECT 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,))
return self.cursor.fetchone()
def save_changes(self, data):
# Your existing code to save changes
pass
@ -351,18 +181,6 @@ class DBEditor(QMainWindow):
# Your existing code to add a new encounter
pass
def send_pokemon_list(self, callback):
# 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]))
callback(pokemon_data)
def init_ui(self):
# Create a central widget for the main window
central_widget = QWidget()
@ -373,12 +191,6 @@ class DBEditor(QMainWindow):
self.pokemon_ui = PokemonUI(self)
main_layout.addWidget(self.pokemon_ui)
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 update_home_storable(self):
if hasattr(self, 'current_pfic'):
storable_in_home = self.home_checkbox.isChecked()
@ -440,131 +252,10 @@ class DBEditor(QMainWindow):
# Refresh the evolution chain display
# self.load_evolution_chain(self.current_pfic)
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 closeEvent(self, event):
self.conn.close()
event.accept()
def load_encounter_locations(self, pfic):
self.locations_tree.clear()
self.cursor.execute('''
SELECT game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing
FROM encounters
WHERE pfic = ?
ORDER BY game, location
''', (pfic,))
encounters = self.cursor.fetchall()
game_items = {}
for encounter in encounters:
game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing = encounter
if game not in game_items:
game_item = QTreeWidgetItem([game])
game_items[game] = game_item
# Use generation for sorting, default to 0 if not found
game_item.setData(0, Qt.ItemDataRole.UserRole, self.game_generations.get(game, 0))
location_item = QTreeWidgetItem([location])
details = []
if day:
details.append(f"Day: {day}")
if time:
details.append(f"Time: {time}")
if dual_slot:
details.append(f"Dual Slot: {dual_slot}")
if static_encounter:
details.append(f"Static Encounter (Count: {static_encounter_count})")
if extra_text:
details.append(f"Extra: {extra_text}")
if stars:
details.append(f"Stars: {stars}")
if fishing:
details.append(f"Fishing")
if rods:
details.append(f"Rods: {rods}")
location_item.setText(1, ", ".join(details))
game_items[game].addChild(location_item)
# Sort game items by generation and add them to the tree
sorted_game_items = sorted(game_items.values(), key=lambda x: x.data(0, Qt.ItemDataRole.UserRole))
self.locations_tree.addTopLevelItems(sorted_game_items)
self.locations_tree.expandAll()
# Update the cache for this Pokémon
self.encounter_cache[pfic] = len(encounters) > 0
# After updating the locations tree
self.update_pokemon_list_highlights()
# Add this as a class attribute in the DBEditor class
game_generations = {
"Red": 1, "Blue": 1, "Yellow": 1,
"Gold": 2, "Silver": 2, "Crystal": 2,
"Ruby": 3, "Sapphire": 3, "Emerald": 3, "FireRed": 3, "LeafGreen": 3,
"Diamond": 4, "Pearl": 4, "Platinum": 4, "HeartGold": 4, "SoulSilver": 4,
"Black": 5, "White": 5, "Black 2": 5, "White 2": 5,
"X": 6, "Y": 6, "Omega Ruby": 6, "Alpha Sapphire": 6,
"Sun": 7, "Moon": 7, "Ultra Sun": 7, "Ultra Moon": 7,
"Sword": 8, "Shield": 8, "Brilliant Diamond": 8, "Shining Pearl": 8, "Expansion Pass": 8,
"Legends: Arceus": 8,
"Scarlet": 9, "Violet": 9, "The Teal Mask": 9, "The Hidden Treasure of Area Zero": 9, "The Hidden Treasure of Area Zero (Scarlet)": 9, "The Hidden Treasure of Area Zero (Violet)": 9, "The Teal Mask (Scarlet)": 9, "The Teal Mask (Violet)": 9,
"Pokémon Go": 0, "Pokémon Home": 0
}
def update_encounter_cache(self):
self.cursor.execute('''
SELECT DISTINCT pfic
FROM encounters
''')
pokemon_with_encounters = set(row[0] for row in self.cursor.fetchall())
#for i in range(self.pokemon_list.count()):
# item = self.pokemon_list.item(i)
# pfic = item.data(Qt.ItemDataRole.UserRole)
# self.encounter_cache[pfic] = pfic in pokemon_with_encounters
event.accept()
def check_pokemon_has_encounters(self, pfic):
return self.encounter_cache.get(pfic, False)
@ -606,16 +297,24 @@ class DBEditor(QMainWindow):
name_edit = QLineEdit()
description_edit = QLineEdit()
game_combo = QComboBox()
layout.addRow("Group Name:", name_edit)
layout.addRow("Description:", description_edit)
layout.addRow("Game:", game_combo)
# Populate game combo box
self.cursor.execute('SELECT id, name FROM games ORDER BY name')
for game_id, game_name in self.cursor.fetchall():
game_combo.addItem(game_name, game_id)
if item:
group_id = item.data(Qt.ItemDataRole.UserRole)
self.cursor.execute('SELECT group_name, description FROM exclusive_encounter_groups WHERE id = ?', (group_id,))
group_name, description = self.cursor.fetchone()
self.cursor.execute('SELECT group_name, description, game_id FROM exclusive_encounter_groups WHERE id = ?', (group_id,))
group_name, description, game_id = self.cursor.fetchone()
name_edit.setText(group_name)
description_edit.setText(description)
game_combo.setCurrentIndex(game_combo.findData(game_id))
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(dialog.accept)
@ -625,13 +324,14 @@ class DBEditor(QMainWindow):
if dialog.exec() == QDialog.DialogCode.Accepted:
name = name_edit.text()
description = description_edit.text()
game_id = game_combo.currentData()
if item:
self.cursor.execute('UPDATE exclusive_encounter_groups SET group_name = ?, description = ? WHERE id = ?', (name, description, group_id))
item.setText(f"{name} - {description}")
self.cursor.execute('UPDATE exclusive_encounter_groups SET group_name = ?, description = ?, game_id = ? WHERE id = ?', (name, description, game_id, group_id))
item.setText(f"{name} - {description} ({game_combo.currentText()})")
else:
self.cursor.execute('INSERT INTO exclusive_encounter_groups (group_name, description) VALUES (?, ?)', (name, description))
self.cursor.execute('INSERT INTO exclusive_encounter_groups (group_name, description, game_id) VALUES (?, ?, ?)', (name, description, game_id))
group_id = self.cursor.lastrowid
new_item = QListWidgetItem(f"{name} - {description}")
new_item = QListWidgetItem(f"{name} - {description} ({game_combo.currentText()})")
new_item.setData(Qt.ItemDataRole.UserRole, group_id)
group_list.addItem(new_item)
self.conn.commit()
@ -646,35 +346,15 @@ class DBEditor(QMainWindow):
self.conn.commit()
group_list.takeItem(group_list.row(item))
def show_pokemon_context_menu(self, position):
item = self.pokemon_list.itemAt(position)
if item is not None:
context_menu = QMenu(self)
refresh_action = QAction("Refresh Encounters", self)
refresh_action.triggered.connect(lambda: self.refresh_pokemon_encounters(item))
context_menu.addAction(refresh_action)
context_menu.exec(self.pokemon_list.viewport().mapToGlobal(position))
def refresh_pokemon_encounters(self, item):
pfic = item.data(Qt.ItemDataRole.UserRole)
self.cursor.execute('''
SELECT name, form_name, national_dex
FROM pokemon_forms
WHERE PFIC = ?
''', (pfic,))
pokemon_data = self.cursor.fetchone()
def refresh_pokemon_encounters(self, pfic):
pokemon_data = event_system.call_sync('get_pokemon_data', pfic)
if pokemon_data:
name, form, national_dex = pokemon_data
# Import the necessary function and classes
#from DataGatherers.update_location_information import process_pokemon_for_location_data
import json
name, form_name, national_dex, generation, storable_in_home, is_baby_form = pokemon_data
# Create a temporary connection for this operation
temp_conn = sqlite3.connect('pokemon_forms.db')
# Load default forms
try:
with open('./DataGatherers/DefaultForms.json', 'r') as f:
default_forms = json.load(f)
@ -684,73 +364,98 @@ class DBEditor(QMainWindow):
# Create a cache manager instance
cache = CacheManager()
# Delete existing encounters for this Pokémon
self.cursor.execute('DELETE FROM encounters WHERE pfic = ?', (pfic,))
self.conn.commit()
event_system.emit_sync('clear_encounters_for_pokemon', pfic)
# Process the Pokémon data
process_pokemon_for_location_data(pfic, name, form, national_dex, default_forms, cache, temp_conn)
process_pokemon_for_location_data(pfic, name, form_name, national_dex, default_forms, cache, temp_conn)
temp_conn.backup(self.conn)
event_system.emit_sync('refresh_in_memory_db', temp_conn)
# Close the temporary connection
temp_conn.close()
# Refresh the encounter locations in the UI
self.load_encounter_locations(pfic)
# Update the encounter cache and highlights
self.update_encounter_cache()
self.update_pokemon_list_highlights()
print(f"Refreshed encounters for {name} {form if form else ''}")
def gather_pokemon_forms(self):
self.progress_text.clear() # Clear previous progress text
self.progress_text.append("Starting to gather Pokémon forms...")
# Implement the logic to gather Pokémon forms
# Update progress as you go, for example:
self.progress_text.append("Processed 100 Pokémon...")
self.progress_text.append("Processed 200 Pokémon...")
# ...
self.progress_text.append("Finished gathering Pokémon forms.")
def gather_home_storage_info(self):
self.progress_text.clear()
self.progress_text.append("Starting to gather Home storage information...")
# Implement the logic
# ...
def gather_evolution_info(self):
self.progress_text.clear()
self.progress_text.append("Starting to gather evolution information...")
# Implement the logic
# ...
def gather_encounter_info(self):
self.progress_text.clear()
self.progress_text.append("Starting to gather encounter information...")
# Implement the logic
# ...
def reinitialize_database(self):
event_system.emit_sync('refresh_pokemon_details_panel', pfic)
print(f"Refreshed encounters for {name} {form_name if form_name else ''}")
def gather_pokemon_forms(self, data):
event_system.emit_sync('clear_log_display')
self.logger.info("Starting to gather Pokémon forms...")
self.form_gatherer = PokemonFormGatherer()
self.form_gatherer.progress_signal.connect(self.update_progress)
self.form_gatherer.finished_signal.connect(self.gathering_finished)
self.form_gatherer.start()
def update_progress(self, message):
self.logger.info(message)
def gathering_finished(self):
self.logger.info("Finished gathering Pokémon forms.")
def gather_home_storage_info(self, data):
event_system.emit_sync('clear_log_display')
self.logger.info("Starting to gather Home storage information...")
self.home_storage_updater = PokemonHomeStatusUpdater()
self.home_storage_updater.progress_signal.connect(self.update_progress)
self.home_storage_updater.finished_signal.connect(self.updating_home_storage_finished)
self.home_storage_updater.start()
def updating_home_storage_finished(self):
self.logger.info("Finished updating Home storage information.")
def gather_evolution_info(self, data):
event_system.call_sync('clear_log_display')
self.logger.info("Starting to gather evolution information...")
self.evolution_updater = PokemonEvolutionUpdater()
self.evolution_updater.progress_signal.connect(self.update_progress)
self.evolution_updater.finished_signal.connect(self.updating_evolution_info_finished)
self.evolution_updater.start()
def updating_evolution_info_finished(self):
self.logger.info("Finished updating evolution information.")
def gather_encounter_info(self, data):
event_system.call_sync('clear_log_display')
self.logger.info("Starting to gather encounter information...")
self.encounter_updater = PokemonEncounterUpdater()
self.encounter_updater.progress_signal.connect(self.update_progress)
self.encounter_updater.finished_signal.connect(self.updating_encounter_info_finished)
self.encounter_updater.start()
def updating_encounter_info_finished(self):
self.logger.info("Finished updating encounter information.")
def reinitialize_database(self, data):
reply = QMessageBox.question(self, 'Confirm Action',
'Are you sure you want to clear and reinitialize the database? This action cannot be undone.',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
self.progress_text.clear()
self.progress_text.append("Starting database reinitialization...")
self.logger.info("Starting database reinitialization...")
# Implement the logic to clear and reinitialize the database
self.conn.close()
os.remove('pokemon_forms.db') # Remove the existing database file
self.conn = sqlite3.connect('pokemon_forms.db')
self.cursor = self.conn.cursor()
self.init_database() # Reinitialize the database structure
self.progress_text.append("Database reinitialized successfully.")
self.logger.info("Database reinitialized successfully.")
QMessageBox.information(self, 'Database Reinitialized', 'The database has been cleared and reinitialized.')
def qt_message_handler(mode, context, message):
print(f"Qt Message: {mode} {context} {message}")
def exception_hook(exctype, value, tb):
print(''.join(traceback.format_exception(exctype, value, tb)))
sys.exit(1)
sys.excepthook = exception_hook
if __name__ == '__main__':
app = QApplication(sys.argv)
qInstallMessageHandler(qt_message_handler)
editor = DBEditor()
editor.show()
sys.exit(app.exec())
sys.exit(app.exec())

382
DBEditor/db_controller.py

@ -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()

29
DBEditor/event_system.py

@ -20,35 +20,28 @@ class EventSystem:
with self.lock:
self.listeners[event_type].remove(callback)
def emit(self, event_type, data=None, callback=None):
self.event_queue.put((event_type, data, callback))
def emit(self, event_type, data=None):
self.event_queue.put((event_type, data))
def emit_sync(self, event_type, data=None):
results = []
with self.lock:
for listener in self.listeners[event_type]:
result = listener(data)
results.append(result)
return results
#with self.lock:
for listener in self.listeners[event_type]:
listener(data)
def call_sync(self, event_type, data=None):
with self.lock:
if event_type in self.listeners and self.listeners[event_type]:
# Call only the first registered listener
return self.listeners[event_type][0](data)
#with self.lock:
if event_type in self.listeners and self.listeners[event_type]:
# Call only the first registered listener
return self.listeners[event_type][0](data)
return None
def _process_events(self):
while not self._stop_event.is_set():
try:
event_type, data, callback = self.event_queue.get(timeout=0.1)
results = []
event_type, data = self.event_queue.get(timeout=0.1)
with self.lock:
for listener in self.listeners[event_type]:
result = listener(data)
results.append(result)
if callback:
callback(results)
listener(data)
except Empty:
pass

269
DBEditor/plan.py

@ -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]])

262
DBEditor/plan2.py

@ -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]])

282
DBEditor/pokemon_db_ui.py

@ -5,14 +5,18 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QH
QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget,
QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox, QSpinBox, QMenu, QTabWidget,
QTextEdit)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtCore import Qt, QSize, QTimer, QMetaObject
from PyQt6.QtGui import QPixmap, QFontMetrics, QColor, QAction
from collections import deque
from event_system import event_system
import logging
class EvolutionEditDialog(QDialog):
def __init__(self, parent=None, from_pfic=None, to_pfic=None, method=None):
super().__init__(parent)
self.setWindowTitle("Edit Evolution")
self.setModal(True)
@ -59,8 +63,34 @@ class EvolutionEditDialog(QDialog):
class PokemonUI(QWidget): # Change from QMainWindow to QWidget
def __init__(self, parent=None):
super().__init__(parent)
self.log_queue = deque() # Queue to store log messages
self.log_timer = QTimer(self)
self.log_timer.timeout.connect(self.process_log_queue)
self.log_timer.start(100)
event_system.add_listener('refresh_pokemon_details_panel', self.refresh)
event_system.add_listener('update_log_display', self.queue_log_message)
event_system.add_listener('clear_log_display', self.clear_log_display)
self.logger = logging.getLogger('ui_feedback')
self.encounter_cache = {}
self.evolution_load_timer = QTimer()
self.evolution_load_timer.setSingleShot(True)
self.evolution_load_timer.timeout.connect(self._load_evolution_chain)
self.current_evolution_pfic = None
self.init_ui()
self.request_pokemon_list()
self.logger.info("Initializing PokemonUI")
def queue_log_message(self, message):
self.log_queue.append(message)
def process_log_queue(self):
while self.log_queue:
message = self.log_queue.popleft()
self.update_log_display(message)
def init_ui(self):
main_layout = QVBoxLayout(self) # Use self directly instead of a central widget
@ -86,7 +116,7 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
self.pokemon_list = QListWidget()
self.pokemon_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
#self.pokemon_list.customContextMenuRequested.connect(self.show_pokemon_context_menu)
self.pokemon_list.customContextMenuRequested.connect(self.show_pokemon_context_menu)
self.pokemon_list.currentItemChanged.connect(self.on_pokemon_selected)
left_layout.addWidget(self.pokemon_list)
@ -103,9 +133,10 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
main_tab_layout.addLayout(left_layout, 1)
# Right side: Edit panel
right_layout = QHBoxLayout()
right_layout = QVBoxLayout()
# Left side of right panel: Text information
info_layout = QHBoxLayout()
text_layout = QVBoxLayout()
self.edit_form = QFormLayout()
self.name_label = QLabel()
@ -124,54 +155,59 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
text_layout.addLayout(self.edit_form)
# Right side of right panel: Image
image_layout = QVBoxLayout()
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.image_label.setFixedSize(150, 150)
image_layout.addWidget(self.image_label)
image_layout.addStretch(1)
info_layout.addLayout(text_layout)
info_layout.addLayout(image_layout)
second_half_layout = QVBoxLayout()
# Evolution chain tree
self.evolution_tree = QTreeWidget()
self.evolution_tree.setHeaderLabels(["Pokémon", "Evolution Method"])
self.evolution_tree.setColumnWidth(0, 200)
text_layout.addWidget(self.evolution_tree)
second_half_layout.addWidget(self.evolution_tree)
# Add Locations tree
self.locations_tree = QTreeWidget()
self.locations_tree.setHeaderLabels(["Game/Location", "Details"])
self.locations_tree.setColumnWidth(0, 200)
self.locations_tree.itemDoubleClicked.connect(self.edit_encounter)
text_layout.addWidget(QLabel("Locations:"))
text_layout.addWidget(self.locations_tree)
second_half_layout.addWidget(QLabel("Locations:"))
second_half_layout.addWidget(self.locations_tree)
# Add New Encounter button
self.add_encounter_button = QPushButton("Add New Encounter")
self.add_encounter_button.clicked.connect(self.add_new_encounter)
text_layout.addWidget(self.add_encounter_button)
second_half_layout.addWidget(self.add_encounter_button)
# Move buttons to the bottom
text_layout.addStretch(1)
self.save_button = QPushButton("Save Changes")
self.save_button.clicked.connect(self.save_changes)
text_layout.addWidget(self.save_button)
self.export_button = QPushButton("Export Database")
self.export_button.clicked.connect(self.export_database)
text_layout.addWidget(self.export_button)
second_half_layout.addStretch(1)
# Add New Evolution button
self.add_evolution_button = QPushButton("Add New Evolution")
self.add_evolution_button.clicked.connect(self.add_new_evolution)
text_layout.addWidget(self.add_evolution_button)
second_half_layout.addWidget(self.add_evolution_button)
# Right side of right panel: Image
image_layout = QVBoxLayout()
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.image_label.setFixedSize(200, 200)
image_layout.addWidget(self.image_label)
image_layout.addStretch(1)
right_layout.addLayout(text_layout)
right_layout.addLayout(image_layout)
right_layout.addLayout(info_layout)
right_layout.addLayout(second_half_layout)
main_tab_layout.addLayout(right_layout, 1)
self.save_button = QPushButton("Save Changes")
self.save_button.clicked.connect(self.save_changes)
main_layout.addWidget(self.save_button)
self.export_button = QPushButton("Export Database")
self.export_button.clicked.connect(self.export_database)
main_layout.addWidget(self.export_button)
# Database Operations tab
db_tab = QWidget()
db_tab_layout = QVBoxLayout(db_tab)
@ -179,19 +215,19 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
# Add buttons to the Database Operations tab
gather_forms_btn = QPushButton("Gather Pokémon Forms")
#gather_forms_btn.clicked.connect(self.gather_pokemon_forms)
gather_forms_btn.clicked.connect(self.gather_pokemon_forms)
db_tab_layout.addWidget(gather_forms_btn)
gather_home_btn = QPushButton("Gather Home Storage Info")
#gather_home_btn.clicked.connect(self.gather_home_storage_info)
gather_home_btn.clicked.connect(self.gather_home_storage_info)
db_tab_layout.addWidget(gather_home_btn)
gather_evolutions_btn = QPushButton("Gather Evolution Information")
#gather_evolutions_btn.clicked.connect(self.gather_evolution_info)
gather_evolutions_btn.clicked.connect(self.gather_evolution_info)
db_tab_layout.addWidget(gather_evolutions_btn)
gather_encounters_btn = QPushButton("Gather Encounter Information")
#gather_encounters_btn.clicked.connect(self.gather_encounter_info)
gather_encounters_btn.clicked.connect(self.gather_encounter_info)
db_tab_layout.addWidget(gather_encounters_btn)
# Add QTextEdit for progress reporting
@ -203,28 +239,40 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
db_tab_layout.addStretch(1) # Add stretch to separate the last button
reinit_db_btn = QPushButton("Clear and Reinitialize Database")
#reinit_db_btn.clicked.connect(self.reinitialize_database)
reinit_db_btn.clicked.connect(self.reinitialize_database)
db_tab_layout.addWidget(reinit_db_btn)
encounter_tab = QWidget()
encounter_tab_layout = QVBoxLayout(encounter_tab)
self.tab_widget.addTab(encounter_tab, "Manage Encounters")
def on_pokemon_selected(self, item):
pfic = item.data(Qt.ItemDataRole.UserRole)
pokemon_data = event_system.emit_sync('get_pokemon_data', data=pfic)
self.refresh_pokemon_details_panel(pfic)
def refresh(self, pfic):
self.refresh_pokemon_details_panel(pfic)
self.update_encounter_cache()
self.update_pokemon_list_highlights()
def refresh_pokemon_details_panel(self, pfic):
pokemon_data = event_system.call_sync('get_pokemon_data', data=pfic)
if pokemon_data:
name, form_name, national_dex, generation, storable_in_home = pokemon_data[0]
name, form_name, national_dex, generation, storable_in_home, is_baby_form = pokemon_data
self.name_label.setText(name)
self.form_name_label.setText(form_name if form_name else "")
self.national_dex_label.setText(str(national_dex))
self.generation_label.setText(str(generation))
self.home_checkbox.setChecked(bool(storable_in_home))
#self.home_checkbox.stateChanged.connect(self.update_home_storable)
self.is_baby_form_checkbox.setChecked(bool(False))
self.is_baby_form_checkbox.setChecked(bool(is_baby_form))
# Load and display the image
image_path = f"images-new/{pfic}.png"
if os.path.exists(image_path):
pixmap = QPixmap(image_path)
self.image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
self.image_label.setPixmap(pixmap.scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
else:
self.image_label.setText("Image not found")
@ -232,15 +280,33 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
self.load_evolution_chain(pfic)
# Load and display encounter locations
#self.load_encounter_locations(pfic)
self.load_encounter_locations(pfic)
self.current_pfic = pfic
self.add_evolution_button.setEnabled(True) # Enable the button when a Pokémon is selected
def load_evolution_chain(self, pfic):
self.evolution_tree.clear()
self.current_evolution_pfic = pfic
if self.evolution_load_timer.isActive():
self.evolution_load_timer.stop()
self.evolution_load_timer.start(50) # 50ms delay
def _load_evolution_chain(self):
if not hasattr(self, 'evolution_tree') or self.evolution_tree is None:
print("Evolution tree widget not initialized")
return
pfic = self.current_evolution_pfic
if pfic is None:
return
try:
self.evolution_tree.clear()
except RuntimeError:
print("Error clearing evolution tree. Widget might have been deleted.")
return
evolution_chain = event_system.emit_sync('get_evolution_chain', data=pfic)[0]
evolution_chain = event_system.call_sync('get_evolution_chain', data=pfic)
# Create a dictionary to store tree items
tree_items = {}
@ -261,7 +327,7 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
item = tree_items[current_pfic]
# Find the parent of this item
parent_pfic = event_system.emit_sync('get_evolution_parent', data=current_pfic)[0]
parent_pfic = event_system.call_sync('get_evolution_parent', data=current_pfic)
if parent_pfic:
parent_item = tree_items.get(parent_pfic[0])
@ -282,6 +348,58 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
# Connect double-click signal
#self.evolution_tree.itemDoubleClicked.connect(self.edit_evolution)
def load_encounter_locations(self, pfic):
self.locations_tree.clear()
encounters = event_system.call_sync('get_encounter_locations', data=pfic)
game_items = {}
for encounter in encounters:
game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing = encounter
if game not in game_items:
#print(f'finding generation for {game}')
game_item = QTreeWidgetItem([game])
game_items[game] = game_item
generation = event_system.call_sync('get_game_generation', data=game)
# Use generation for sorting, default to 0 if not found
game_item.setData(0, Qt.ItemDataRole.UserRole, generation)
#print(f'generation for {game} is {generation}')
location_item = QTreeWidgetItem([location])
details = []
if day:
details.append(f"Day: {day}")
if time:
details.append(f"Time: {time}")
if dual_slot:
details.append(f"Dual Slot: {dual_slot}")
if static_encounter:
details.append(f"Static Encounter (Count: {static_encounter_count})")
if extra_text:
details.append(f"Extra: {extra_text}")
if stars:
details.append(f"Stars: {stars}")
if fishing:
details.append(f"Fishing")
if rods:
details.append(f"Rods: {rods}")
location_item.setText(1, ", ".join(details))
game_items[game].addChild(location_item)
# Sort game items by generation and add them to the tree
sorted_game_items = sorted(game_items.values(), key=lambda x: x.data(0, Qt.ItemDataRole.UserRole))
self.locations_tree.addTopLevelItems(sorted_game_items)
self.locations_tree.expandAll()
# Update the cache for this Pokémon
self.encounter_cache[pfic] = len(encounters) > 0
# After updating the locations tree
self.update_pokemon_list_highlights()
def on_save_button_clicked(self):
# Gather data from UI elements
data = {
@ -292,7 +410,8 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
event_system.emit('save_changes', data)
def request_pokemon_list(self):
event_system.emit('get_pokemon_list', self.update_pokemon_list)
pokemon_data = event_system.call_sync('get_pokemon_list')
self.update_pokemon_list(pokemon_data)
def update_pokemon_list(self, pokemon_data):
self.pokemon_list.clear()
@ -305,7 +424,7 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
item.setData(Qt.ItemDataRole.UserRole, pfic)
self.pokemon_list.addItem(item)
#self.update_encounter_cache()
self.update_encounter_cache()
self.update_pokemon_list_highlights()
self.adjust_list_width()
self.filter_pokemon_list()
@ -345,6 +464,14 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
def toggle_highlight_mode(self):
self.update_pokemon_list_highlights()
def update_encounter_cache(self):
pokemon_with_encounters = event_system.call_sync('get_pokemon_with_encounters')
for i in range(self.pokemon_list.count()):
item = self.pokemon_list.item(i)
pfic = item.data(Qt.ItemDataRole.UserRole)
self.encounter_cache[pfic] = pfic in pokemon_with_encounters
def update_pokemon_list_highlights(self):
highlight_mode = self.highlight_no_encounters.isChecked()
for i in range(self.pokemon_list.count()):
@ -396,24 +523,20 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
layout.addRow("Fishing:", fishing_check)
layout.addRow("Rods:", rods_edit)
layout.addRow("Exclusive Group:", QLabel())
exclusive_group_combo = QComboBox()
exclusive_group_combo.addItem("None", None)
self.cursor.execute('SELECT id, group_name FROM exclusive_encounter_groups')
for group_id, group_name in self.cursor.fetchall():
exclusive_group_combo.addItem(group_name, group_id)
layout.addRow("Exclusive Group:", exclusive_group_combo)
layout.addRow(exclusive_group_combo)
# Fetch current values
self.cursor.execute('''
SELECT day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods, exclusive_group_id
SELECT day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods
FROM encounters
WHERE pfic = ? AND game = ? AND location = ?
''', (self.current_pfic, game, location))
current_values = self.cursor.fetchone()
if current_values:
day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods, exclusive_group_id = current_values
day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods = current_values
day_edit.setText(day or "")
time_edit.setText(time or "")
dual_slot_edit.setText(dual_slot or "")
@ -453,11 +576,11 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
UPDATE encounters
SET game = ?, location = ?, day = ?, time = ?, dual_slot = ?,
static_encounter = ?, static_encounter_count = ?, extra_text = ?,
stars = ?, fishing = ?, rods = ?, exclusive_group_id = ?
stars = ?, fishing = ?, rods = ?
WHERE pfic = ? AND game = ? AND location = ?
''', (new_game, new_location, new_day, new_time, new_dual_slot,
new_static_encounter, new_static_encounter_count, new_extra_text,
new_stars, new_fishing, new_rods, new_exclusive_group_id,
new_stars, new_fishing, new_rods,
self.current_pfic, game, location))
self.conn.commit()
@ -524,14 +647,15 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
stars = stars_edit.text() or None
fishing = fishing_check.isChecked()
rods = rods_edit.text() or None
exclusive_group_id = exclusive_group_combo.currentData()
game_id = event_system.call_sync('get_game_id_for_name', game)
# Insert new encounter into the database
self.cursor.execute('''
INSERT INTO encounters
(pfic, game, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods, exclusive_group_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (self.current_pfic, game, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods, exclusive_group_id))
(pfic, game_id, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (self.current_pfic, game_id, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods))
self.conn.commit()
# Update the cache
@ -582,4 +706,48 @@ class PokemonUI(QWidget): # Change from QMainWindow to QWidget
self.save_patches()
# Refresh the evolution chain display
self.load_evolution_chain(self.current_pfic)
self.load_evolution_chain(self.current_pfic)
def show_pokemon_context_menu(self, position):
item = self.pokemon_list.itemAt(position)
if item is not None:
context_menu = QMenu(self)
refresh_action = QAction("Refresh Encounters", self)
refresh_action.triggered.connect(lambda: self.refresh_pokemon_encounters(item))
context_menu.addAction(refresh_action)
context_menu.exec(self.pokemon_list.viewport().mapToGlobal(position))
def refresh_pokemon_encounters(self, item):
pfic = item.data(Qt.ItemDataRole.UserRole)
event_system.emit_sync('refresh_pokemon_encounters', pfic)
def update_log_display(self, message):
if self.progress_text is not None:
self.progress_text.append(message)
#QMetaObject.invokeMethod(self.progress_text, "append", Qt.ConnectionType.QueuedConnection, message)
# Scroll to the bottom
scrollbar = self.progress_text.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def clear_log_display(self, data):
if self.progress_text is not None:
self.progress_text.clear()
# Reset scroll to top
scrollbar = self.progress_text.verticalScrollBar()
scrollbar.setValue(scrollbar.minimum())
def gather_pokemon_forms(self):
event_system.emit_sync('gather_pokemon_forms')
def gather_home_storage_info(self):
event_system.emit_sync('gather_home_storage_info')
def gather_evolution_info(self):
event_system.emit_sync('gather_evolution_info')
def reinitialize_database(self):
event_system.emit_sync('reinitialize_database')
def gather_encounter_info(self):
event_system.emit_sync('gather_encounter_info')

87
DBVisualiser/index.html

@ -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">&times;</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>

200
DBVisualiser/main.js

@ -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 });
}
});
});

5193
DBVisualiser/package-lock.json

File diff suppressed because it is too large

19
DBVisualiser/package.json

@ -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"
}
}

0
DBVisualiser/pokemon_forms.db

483
DBVisualiser/renderer.js

@ -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);
}

338
DBVisualiser/styles.css

@ -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;
}

401
DataGatherers/DatabaseBuilder.py

@ -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()

3
DataGatherers/DefaultForms.json

@ -48,5 +48,6 @@
"Unremarkable Form",
"Antique Form",
"Phony Form",
"Masterpiece Form"
"Masterpiece Form",
"Chest Form"
]

7
DataGatherers/DetermineOriginGame.py

@ -18,7 +18,6 @@ import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from DataGatherers.cache_manager import CacheManager
# List of all main series Pokémon games in chronological order, with special games first in each generation
@ -439,6 +438,10 @@ def parse_form_information(html_content):
headings = soup.find_all('b')
if len(headings) > 0:
for heading in headings:
if heading.parent.name == 'sup':
continue
if "form" not in heading.get_text(strip=True).lower():
continue
main_form = heading.get_text(strip=True)
info = {
"main_form": main_form,
@ -721,7 +724,7 @@ def get_locations_from_bulbapedia(pokemon_name, form, cache: CacheManager, defau
games_string = entries[0].find('a').get('title')
for game in all_games:
if game in games_string:
record_location_info(game, game_locations, None, "Event")
record_location_info(game, game_locations, "Event", "Event")
return game_locations

136
DataGatherers/ExtractPokemonData.py

@ -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()

24
DataGatherers/Update_evolution_information.py

@ -3,8 +3,17 @@ from typing import List, Optional
from dataclasses import dataclass
from fuzzywuzzy import fuzz
import re
from cache_manager import CacheManager
from DetermineOriginGame import get_evolution_data_from_bulbapedia
import sys
import os
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from DataGatherers.cache_manager import CacheManager
from DataGatherers.DetermineOriginGame import get_evolution_data_from_bulbapedia
logger = logging.getLogger('ui_feedback')
@dataclass
class EvolutionInfo:
@ -91,7 +100,7 @@ def process_evolution_chain(conn, evolution_chain, cache, gender: Optional[str]
for stage in evolution_chain:
from_pfic = get_pokemon_form_by_name(conn, stage.pokemon, stage.form, gender=gender)
if not from_pfic:
print(f"Warning: Could not find PFIC for {stage.pokemon} {stage.form}")
logger.warning(f"Could not find PFIC for {stage.pokemon} {stage.form}")
continue
if stage.next_stage:
@ -121,8 +130,7 @@ def update_pokemon_baby_status(conn, from_pfic, is_baby_form):
''', (is_baby_form, from_pfic))
conn.commit()
def update_evolution_chains():
cache = CacheManager()
def update_evolution_chains(cache, progress_callback=None):
conn = create_evolution_table()
cursor = conn.cursor()
@ -130,7 +138,8 @@ def update_evolution_chains():
pokemon_forms = cursor.fetchall()
for name, form in pokemon_forms:
print(f"Processing {name} {form if form else ''}")
if progress_callback:
progress_callback(f"Processing {name} {form if form else ''}")
if form and name in form:
form = form.replace(name, "").strip()
@ -151,4 +160,5 @@ def update_evolution_chains():
conn.close()
if __name__ == "__main__":
update_evolution_chains()
cache = CacheManager()
update_evolution_chains(cache)

27
DataGatherers/pokemondb_scraper.py

@ -4,7 +4,14 @@ from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
import os
import sqlite3
from cache_manager import CacheManager
import sys
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from DataGatherers.cache_manager import CacheManager
logger = logging.getLogger('ui_feedback')
@dataclass
class PokemonForm:
@ -121,7 +128,7 @@ def download_image(url, filename):
with open(filename, 'wb') as f:
f.write(response.content)
def thingy(cache: CacheManager):
def retrieve_all_pokemon_forms(cache: CacheManager, progress_callback=None):
db = PokemonDatabase()
pokemon_db_conn = create_pokemon_db()
create_pokemon_storage_db()
@ -155,7 +162,9 @@ def thingy(cache: CacheManager):
break
pokemon_name = mon.get_text(strip=True)
print(pokemon_name)
logger.info(pokemon_name)
if progress_callback:
progress_callback(f"Processing {pokemon_name}")
pokemon_url_name = pokemon_name.replace("", "-f").replace("", "-m").replace("'", "").replace(".", "").replace('é', 'e').replace(':', '')
pokemon_url_name = pokemon_url_name.replace(" ", "-")
@ -197,7 +206,7 @@ def thingy(cache: CacheManager):
form_name = "None"
if sprite.find('small'):
form_name = sprite.find('small').get_text(strip=True)
print(sprite_url, form_name)
logger.info(f'{sprite_url}, {form_name}')
if form_name != "None":
form += 1
gender = 0
@ -232,8 +241,8 @@ def thingy(cache: CacheManager):
national_dex_index += 1
print(f"Total Pokémon forms: {sum(len(forms) for forms in db.pokemon.values())}")
print(f"Pokémon with multiple forms: {sum(1 for forms in db.pokemon.values() if len(forms) > 1)}")
logger.info(f"Total Pokémon forms: {sum(len(forms) for forms in db.pokemon.values())}")
logger.info(f"Pokémon with multiple forms: {sum(1 for forms in db.pokemon.values() if len(forms) > 1)}")
if not os.path.exists('images-new'):
os.makedirs('images-new')
@ -242,14 +251,14 @@ def thingy(cache: CacheManager):
for form in pokemon:
filename = f"images-new/{form.id}.png"
if os.path.exists(filename):
print(f"Image for {form.id} already exists, skipping download")
logger.info(f"Image for {form.id} already exists, skipping download")
else:
download_image(form.sprite_url, filename)
print(f"Downloaded image for {form.id}")
logger.info(f"Downloaded image for {form.id}")
pokemon_db_conn.close()
if __name__ == "__main__":
cache = CacheManager()
thingy(cache)
retrieve_all_pokemon_forms(cache)
cache.close()

76
DataGatherers/update_location_information.py

@ -7,11 +7,15 @@ import json
import sqlite3
from DataGatherers.cache_manager import CacheManager
from DataGatherers.DetermineOriginGame import get_locations_from_bulbapedia
from event_system import event_system
from bs4 import BeautifulSoup, Tag
import re
import time
import unicodedata
import logging
logger = logging.getLogger('ui_feedback')
def create_encounters_table():
conn = sqlite3.connect('pokemon_forms.db')
cursor = conn.cursor()
@ -101,11 +105,11 @@ def extract_bracketed_text(string, timeout=1):
results.append(string[start_index + 1:i])
start_index = -1
else:
print(f"Warning: Unmatched closing parenthesis at position {i}")
logger.warning(f"Warning: Unmatched closing parenthesis at position {i}")
# Handle any remaining unclosed brackets
if stack:
print(f"Warning: {len(stack)} unmatched opening parentheses")
logger.warning(f"Warning: {len(stack)} unmatched opening parentheses")
for unmatched_start in stack:
results.append(string[unmatched_start + 1:])
@ -230,15 +234,17 @@ def save_encounter(conn, pfic, game, location, days, times, dual_slot, static_en
stars_str = ','.join(sorted(stars)) if stars else None
rods_str = ','.join(sorted(rods)) if rods else None
game_id = event_system.call_sync('get_game_id_for_name', game)
insert_query = '''
INSERT INTO encounters
(pfic, game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing, starter)
(pfic, game_id, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing, starter)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
criteria = {
"pfic": pfic,
"game": game,
"game_id": game_id,
"location": location,
"day": None,
"time": None,
@ -263,11 +269,11 @@ def save_encounter(conn, pfic, game, location, days, times, dual_slot, static_en
encounter = cursor.fetchone()
if encounter == None or encounter[0] == 0:
cursor.execute(insert_query, (pfic, game, location, day, None, dual_slot, static_encounter_count,
cursor.execute(insert_query, (pfic, game_id, location, day, None, dual_slot, static_encounter_count,
static_encounter, extra_text_str, stars_str, rods_str, fishing, starter))
print(f"New encounter added for {pfic} in {game} at {location} on {day}")
logger.info(f"New encounter added for {pfic} in {game} at {location} on {day}")
else:
print(f"Identical encounter already exists for {pfic} in {game} at {location} on {day}")
logger.info(f"Identical encounter already exists for {pfic} in {game} at {location} on {day}")
elif len(times) > 0:
for time in times:
@ -280,11 +286,11 @@ def save_encounter(conn, pfic, game, location, days, times, dual_slot, static_en
encounter = cursor.fetchone()
if encounter == None or encounter[0] == 0:
cursor.execute(insert_query, (pfic, game, location, None, time, dual_slot, static_encounter_count,
cursor.execute(insert_query, (pfic, game_id, location, None, time, dual_slot, static_encounter_count,
static_encounter, extra_text_str, stars_str, rods_str, fishing, starter))
print(f"New encounter added for {pfic} in {game} at {location} at {time}")
logger.info(f"New encounter added for {pfic} in {game} at {location} at {time}")
else:
print(f"Identical encounter already exists for {pfic} in {game} at {location} at {time}")
logger.info(f"Identical encounter already exists for {pfic} in {game} at {location} at {time}")
else:
criteria["day"] = None
@ -296,11 +302,11 @@ def save_encounter(conn, pfic, game, location, days, times, dual_slot, static_en
encounter = cursor.fetchone()
if encounter == None or encounter[0] == 0:
cursor.execute(insert_query, (pfic, game, location, None, None, dual_slot, static_encounter_count,
cursor.execute(insert_query, (pfic, game_id, location, None, None, dual_slot, static_encounter_count,
static_encounter, extra_text_str, stars_str, rods_str, fishing, starter))
print(f"New encounter added for {pfic} in {game} at {location}")
logger.info(f"New encounter added for {pfic} in {game} at {location}")
else:
print(f"Identical encounter already exists for {pfic} in {game} at {location}")
logger.info(f"Identical encounter already exists for {pfic} in {game} at {location}")
conn.commit()
@ -320,7 +326,7 @@ def compare_forms(a, b):
return False
def process_pokemon_for_location_data(pfic, name, form, national_dex, default_forms, cache, conn):
print(f"Processing {name} {form if form else ''}")
logger.info(f"Processing {name} {form if form else ''}")
if form and name in form:
form = form.replace(name, "").strip()
@ -348,7 +354,24 @@ def process_pokemon_for_location_data(pfic, name, form, national_dex, default_fo
search_form = form
encounters_to_ignore = ["trade", "time capsule", "unobtainable", "evolve", "tradeversion", "poké transfer", "friend safari", "unavailable", "pokémon home"]
encounters_to_ignore = [
"trade",
"time capsule",
"unobtainable",
"evolve",
"tradeversion",
"poké transfer",
"friend safari",
"unavailable",
"pokémon home",
"union circle",
"pokémon bank",
"pal park",
"transfer from dream radar",
"global link event",
"pokémon channel",
"pokémon colosseum bonus disc"
]
encounter_data = get_locations_from_bulbapedia(name, search_form, cache, default_forms)
if encounter_data == None:
@ -375,17 +398,17 @@ def process_pokemon_for_location_data(pfic, name, form, national_dex, default_fo
continue
if print_encounter:
print(f"Found in {encounter}:")
logger.info(f"Found in {encounter}:")
print_encounter = False
remaining, details = extract_additional_information(location["tag"])
routes, remaining = extract_routes(remaining)
print(f"Routes: {routes}")
print(f"Remaining: {remaining.strip()}")
print(f"Details: {details}")
logger.info(f"Routes: {routes}")
logger.info(f"Remaining: {remaining.strip()}")
logger.info(f"Details: {details}")
if len(details["times"]) > 0:
print("Stupid Data")
logger.info("Stupid Data")
for route in routes:
route_name = f"Route {route}"
@ -399,9 +422,7 @@ def process_pokemon_for_location_data(pfic, name, form, national_dex, default_fo
save_encounter(conn, pfic, encounter, remaining_location.strip(), details["days"], details["times"], details["dual_slot"], details["static_encounter"], details["static_encounter_count"], details["extra_text"], details["stars"], details["Rods"], details["Fishing"], details["starter"] )
if __name__ == "__main__":
cache = CacheManager()
def update_location_information(cache, progress_callback=None):
conn = create_encounters_table()
cursor = conn.cursor()
cursor.execute('''
@ -418,6 +439,13 @@ if __name__ == "__main__":
default_forms = []
for pfic, name, form, national_dex in pokemon_forms:
if progress_callback:
progress_callback(f"Processing {name} {form if form else ''}")
process_pokemon_for_location_data(pfic, name, form, national_dex, default_forms, cache, conn)
conn.close()
conn.close()
if __name__ == "__main__":
cache = CacheManager()
update_location_information(cache)

65
DataGatherers/update_storable_in_home.py

@ -1,8 +1,17 @@
import json
import sqlite3
from cache_manager import CacheManager
from bs4 import BeautifulSoup, Tag
import sys
import os
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from DataGatherers.cache_manager import CacheManager
logger = logging.getLogger('ui_feedback')
def create_pokemon_storage_db():
conn = sqlite3.connect('pokemon_forms.db')
cursor = conn.cursor()
@ -69,7 +78,7 @@ def scrape_all_regions(cache):
url = f"{base_url}{region}pokemon.shtml"
region_pokemon = scrape_serebii_region_pokemon(url, cache)
all_pokemon.extend(region_pokemon)
print(f"Scraped {len(region_pokemon)} Pokémon from {region.capitalize()} region")
logger.info(f"Scraped {len(region_pokemon)} Pokémon from {region.capitalize()} region")
return all_pokemon
@ -93,11 +102,11 @@ def extract_bracketed_text(string):
results.append(string[start_index + 1:i])
start_index = -1
else:
print(f"Warning: Unmatched closing parenthesis at position {i}")
logger.warning(f"Warning: Unmatched closing parenthesis at position {i}")
# Handle any remaining unclosed brackets
if stack:
print(f"Warning: {len(stack)} unmatched opening parentheses")
logger.warning(f"Warning: {len(stack)} unmatched opening parentheses")
for unmatched_start in stack:
results.append(string[unmatched_start + 1:])
@ -118,9 +127,7 @@ def compare_forms(a, b):
return False
if __name__ == "__main__":
cache = CacheManager()
def update_storable_in_home(cache, progress_callback=None):
conn = create_pokemon_storage_db()
cursor = conn.cursor()
cursor.execute('''
@ -139,9 +146,12 @@ if __name__ == "__main__":
default_forms = []
for pfic, name, form, national_dex in pokemon_forms:
print(f"Processing {name} {form if form else ''}")
if progress_callback:
progress_callback(f"Processing {name} {form if form else ''}")
storable_in_home = False
default_form = form
if form and name in form:
form = form.replace(name, "").strip()
@ -150,9 +160,6 @@ if __name__ == "__main__":
if form and ("male" in form.lower() or "female" in form.lower()):
form = None
if form and form in default_forms:
form = None
if name == "Unown" and (form != "!" and form != "?"):
form = None
@ -164,23 +171,31 @@ if __name__ == "__main__":
pokemon = get_objects_by_number(all_depositable_pokemon, f"{national_dex:04d}")
for p in pokemon:
if form:
parts = p['name'].split(" ")
if len(parts) > 1 and parts[0] == form:
storable_in_home = True
brackets = extract_bracketed_text(p['name'])
if brackets:
for bracket in brackets:
if name in bracket:
bracket = bracket.replace(name, "").strip()
if compare_forms(form, bracket):
storable_in_home = True
break
if storable_in_home == False and form and form in default_forms:
form = None
if form == None and name.lower() in p['name'].lower():
storable_in_home = True
break
parts = p['name'].split(" ")
if len(parts) > 1 and parts[0] == form:
storable_in_home = True
brackets = extract_bracketed_text(p['name'])
if brackets:
for bracket in brackets:
if name in bracket:
bracket = bracket.replace(name, "").strip()
if compare_forms(form, bracket):
storable_in_home = True
break
print(f"{name} {form if form else ''} is storable in home: {storable_in_home}")
logger.info(f"{name} {form if form else ''} is storable in home: {storable_in_home}")
insert_pokemon_storage(conn, pfic, storable_in_home)
if __name__ == "__main__":
cache = CacheManager()
update_storable_in_home(cache)

609
Utilities/DBVisualiser.py

@ -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())

296
Utilities/NewDBVisualiser.py

@ -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_())

1460
efficiency_plan.txt

File diff suppressed because it is too large
Loading…
Cancel
Save