Browse Source

- First pass at a working copy

main
Quildra 1 year ago
commit
b308789ec8
  1. 3
      .gitignore
  2. 30
      OriginDex.py
  3. 585
      Utilities/DBVisualiser.py
  4. 354
      Utilities/DatabaseBuilder.py
  5. 386
      Utilities/DetermineOriginGame.py
  6. 111
      Utilities/ScrapSerebii.py
  7. BIN
      Utilities/requirements.txt
  8. BIN
      images/Marks/Arceus_mark_HOME.png
  9. BIN
      images/Marks/BDSP_icon_HOME.png
  10. BIN
      images/Marks/Black_clover_HOME.png
  11. BIN
      images/Marks/Blue_pentagon_HOME.png
  12. BIN
      images/Marks/GB_icon_HOME.png
  13. BIN
      images/Marks/GO_icon_HOME.png
  14. BIN
      images/Marks/Galar_symbol_HOME.png
  15. BIN
      images/Marks/Let's_Go_icon_HOME.png
  16. BIN
      images/Marks/Paldea_icon_HOME.png
  17. BIN
      images/Pokemon/0001_Bulbasaur.png
  18. BIN
      images/Pokemon/0002_Ivysaur.png
  19. BIN
      images/Pokemon/0003_Venusaur.png
  20. BIN
      images/Pokemon/0004_Charmander.png
  21. BIN
      images/Pokemon/0005_Charmeleon.png
  22. BIN
      images/Pokemon/0006_Charizard.png
  23. BIN
      images/Pokemon/0007_Squirtle.png
  24. BIN
      images/Pokemon/0008_Wartortle.png
  25. BIN
      images/Pokemon/0009_Blastoise.png
  26. BIN
      images/Pokemon/0010_Caterpie.png
  27. BIN
      images/Pokemon/0011_Metapod.png
  28. BIN
      images/Pokemon/0012_Butterfree.png
  29. BIN
      images/Pokemon/0013_Weedle.png
  30. BIN
      images/Pokemon/0014_Kakuna.png
  31. BIN
      images/Pokemon/0015_Beedrill.png
  32. BIN
      images/Pokemon/0016_Pidgey.png
  33. BIN
      images/Pokemon/0017_Pidgeotto.png
  34. BIN
      images/Pokemon/0018_Pidgeot.png
  35. BIN
      images/Pokemon/0019_Rattata.png
  36. BIN
      images/Pokemon/0019_Rattata_(Alolan_Form).png
  37. BIN
      images/Pokemon/0020_Raticate.png
  38. BIN
      images/Pokemon/0020_Raticate_(Alolan_Form).png
  39. BIN
      images/Pokemon/0021_Spearow.png
  40. BIN
      images/Pokemon/0022_Fearow.png
  41. BIN
      images/Pokemon/0023_Ekans.png
  42. BIN
      images/Pokemon/0024_Arbok.png
  43. BIN
      images/Pokemon/0025_Pikachu.png
  44. BIN
      images/Pokemon/0025_Pikachu_(Alola_Cap).png
  45. BIN
      images/Pokemon/0025_Pikachu_(Hoenn_Cap).png
  46. BIN
      images/Pokemon/0025_Pikachu_(Kalos_Cap).png
  47. BIN
      images/Pokemon/0025_Pikachu_(Original_Cap).png
  48. BIN
      images/Pokemon/0025_Pikachu_(Partner_Cap).png
  49. BIN
      images/Pokemon/0025_Pikachu_(Sinnoh_Cap).png
  50. BIN
      images/Pokemon/0025_Pikachu_(Unova_Cap).png
  51. BIN
      images/Pokemon/0025_Pikachu_(World_Cap).png
  52. BIN
      images/Pokemon/0026_Raichu.png
  53. BIN
      images/Pokemon/0026_Raichu_(Alolan_Form).png
  54. BIN
      images/Pokemon/0027_Sandshrew.png
  55. BIN
      images/Pokemon/0027_Sandshrew_(Alolan_Form).png
  56. BIN
      images/Pokemon/0028_Sandslash.png
  57. BIN
      images/Pokemon/0028_Sandslash_(Alolan_Form).png
  58. BIN
      images/Pokemon/0029_Nidoran♀.png
  59. BIN
      images/Pokemon/0030_Nidorina.png
  60. BIN
      images/Pokemon/0031_Nidoqueen.png
  61. BIN
      images/Pokemon/0032_Nidoran♂.png
  62. BIN
      images/Pokemon/0033_Nidorino.png
  63. BIN
      images/Pokemon/0034_Nidoking.png
  64. BIN
      images/Pokemon/0035_Clefairy.png
  65. BIN
      images/Pokemon/0036_Clefable.png
  66. BIN
      images/Pokemon/0037_Vulpix.png
  67. BIN
      images/Pokemon/0037_Vulpix_(Alolan_Form).png
  68. BIN
      images/Pokemon/0038_Ninetales.png
  69. BIN
      images/Pokemon/0038_Ninetales_(Alolan_Form).png
  70. BIN
      images/Pokemon/0039_Jigglypuff.png
  71. BIN
      images/Pokemon/0040_Wigglytuff.png
  72. BIN
      images/Pokemon/0041_Zubat.png
  73. BIN
      images/Pokemon/0042_Golbat.png
  74. BIN
      images/Pokemon/0043_Oddish.png
  75. BIN
      images/Pokemon/0044_Gloom.png
  76. BIN
      images/Pokemon/0045_Vileplume.png
  77. BIN
      images/Pokemon/0046_Paras.png
  78. BIN
      images/Pokemon/0047_Parasect.png
  79. BIN
      images/Pokemon/0048_Venonat.png
  80. BIN
      images/Pokemon/0049_Venomoth.png
  81. BIN
      images/Pokemon/0050_Diglett.png
  82. BIN
      images/Pokemon/0050_Diglett_(Alolan_Form).png
  83. BIN
      images/Pokemon/0051_Dugtrio.png
  84. BIN
      images/Pokemon/0051_Dugtrio_(Alolan_Form).png
  85. BIN
      images/Pokemon/0052_Meowth.png
  86. BIN
      images/Pokemon/0052_Meowth_(Alolan_Form).png
  87. BIN
      images/Pokemon/0052_Meowth_(Galarian_Form).png
  88. BIN
      images/Pokemon/0053_Persian.png
  89. BIN
      images/Pokemon/0053_Persian_(Alolan_Form).png
  90. BIN
      images/Pokemon/0054_Psyduck.png
  91. BIN
      images/Pokemon/0055_Golduck.png
  92. BIN
      images/Pokemon/0056_Mankey.png
  93. BIN
      images/Pokemon/0057_Primeape.png
  94. BIN
      images/Pokemon/0058_Growlithe.png
  95. BIN
      images/Pokemon/0058_Growlithe_(Hisuian_Form).png
  96. BIN
      images/Pokemon/0059_Arcanine.png
  97. BIN
      images/Pokemon/0059_Arcanine_(Hisuian_Form).png
  98. BIN
      images/Pokemon/0060_Poliwag.png
  99. BIN
      images/Pokemon/0061_Poliwhirl.png
  100. BIN
      images/Pokemon/0062_Poliwrath.png

3
.gitignore

@ -0,0 +1,3 @@
pokemon_cache.db
pokemon_database.db
Utilities/venv/

30
OriginDex.py

@ -0,0 +1,30 @@
import csv
from flask import Flask, render_template
app = Flask(__name__)
def load_pokemon_data():
pokemon_list = []
earliest_games = {}
# Load Pokemon Home list
with open('pokemon_home_list.csv', 'r') as file:
reader = csv.DictReader(file)
for row in reader:
pokemon_list.append(row)
# Load earliest games data
with open('pokemon_earliest_games.csv', 'r') as file:
reader = csv.DictReader(file)
for row in reader:
earliest_games[row['Pokemon']] = row['Earliest Game']
return pokemon_list, earliest_games
@app.route('/')
def index():
pokemon_list, earliest_games = load_pokemon_data()
return render_template('index.html', pokemon_list=pokemon_list, earliest_games=earliest_games)
if __name__ == '__main__':
app.run(debug=True)

585
Utilities/DBVisualiser.py

@ -0,0 +1,585 @@
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)
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)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = PokemonDatabaseApp()
window.show()
sys.exit(app.exec())

354
Utilities/DatabaseBuilder.py

@ -0,0 +1,354 @@
import sqlite3
import csv
import re
def create_connection():
conn = sqlite3.connect('pokemon_database.db')
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
)
''')
# 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 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"]),
("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"]),
]
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') 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') 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']
# 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"
# Ensure the Pokémon and form exist in the database
cursor.execute('''
INSERT OR IGNORE INTO pokemon (national_dex_number, name)
VALUES (?, ?)
''', (national_dex_number, base_name))
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, method = location_info.strip().rsplit(' ', 1)
method = method.strip('()')
# Insert or get location_id
cursor.execute('''
INSERT OR IGNORE INTO locations (name, description)
VALUES (?, ?)
''', (location, None))
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 (?)', (method,))
cursor.execute('SELECT id FROM encounter_methods WHERE name = ?', (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()

386
Utilities/DetermineOriginGame.py

@ -0,0 +1,386 @@
import csv
import requests
import time
import json
import os
import re
import sqlite3
# Initialize the database connection
conn = sqlite3.connect('pokemon_cache.db')
cursor = conn.cursor()
# Create the cache table if it doesn't exist
cursor.execute('''
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value TEXT
)
''')
conn.commit()
# List of all main series Pokémon games in chronological order, with special games first in each generation
all_games = [
"Yellow", "Red", "Blue",
"Crystal", "Gold", "Silver",
"Emerald", "FireRed", "LeafGreen", "Ruby", "Sapphire",
"Platinum", "HeartGold", "SoulSilver", "Diamond", "Pearl",
"Black-2", "White-2", "Black", "White",
"X", "Y", "Omega-Ruby", "Alpha-Sapphire",
"Ultra-Sun", "Ultra-Moon", "Sun", "Moon",
"Sword", "Shield",
"Brilliant-Diamond", "Shining-Pearl",
"Legends-Arceus",
"Scarlet", "Violet",
"Unknown"
]
cache = {}
new_entries_count = 0
def get_cached_data():
global cache
cursor.execute("SELECT key, value FROM cache")
for key, value in cursor.fetchall():
cache[key] = json.loads(value)
def save_cached_data():
global cache, new_entries_count
if new_entries_count > 0:
for key, value in cache.items():
cursor.execute("INSERT OR REPLACE INTO cache (key, value) VALUES (?, ?)",
(key, json.dumps(value)))
conn.commit()
new_entries_count = 0
def update_cache(key, value):
global cache, new_entries_count
if key not in cache:
cache[key] = value
new_entries_count += 1
if new_entries_count >= 10:
save_cached_data()
time.sleep(1)
def read_pokemon_list(filename, limit=50):
pokemon_list = []
with open(filename, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for i, row in enumerate(reader):
if i >= limit:
break
# Split the name into base name and form
match = re.match(r'(.*?)\s*(\(.*\))?$', row['name'])
base_name, form = match.groups() if match else (row['name'], None)
row['base_name'] = base_name.strip()
row['form'] = form.strip('() ') if form else None
pokemon_list.append(row)
return pokemon_list
def sanitize_name_and_form(name, form):
adjusted_form = None
if form:
adjusted_form = form.lower()
#Some stupid special cases
if name.lower() == 'tauros':
if adjusted_form == 'paldean form':
adjusted_form = 'paldea combat breed'
elif 'blaze' in adjusted_form:
adjusted_form = 'paldea blaze breed'
elif 'aqua' in adjusted_form:
adjusted_form = 'paldea aqua breed'
replacements = {'forme': '',
'form': '',
'alolan': 'alola',
'galarian': 'galar',
'hisuian': 'hisui',
'paldean': 'paldea',
'size': '',
'10%': '10 power construct',
'hoopa': '',
'style': '',
'core': '',
'color': '',
'blood moon': 'bloodmoon'};
for old, new in replacements.items():
adjusted_form = adjusted_form.replace(old, new).strip()
missing_forms = ['burmy',
'shellos',
'gastrodon',
'wormadam',
'unown',
"deerling",
"sawsbuck",
"vivillon",
"flabébé",
"floette",
"florges",
"furfrou",
"sinistea",
"polteageist",
"alcremie",
"poltchageist",
"sinistcha"]
if name.lower() in missing_forms:
adjusted_form = None
if name.lower() == 'wormadam':
adjusted_form = adjusted_form.replace('cloak', '').strip()
if name.lower() == 'rotom':
adjusted_form = adjusted_form.replace('rotom', '').strip()
if name.lower() == 'darmanitan':
adjusted_form = adjusted_form + ' standard'
else:
default_forms = {'deoxys': 'normal',
'wormadam': 'plant',
'giratina': 'origin',
'tornadus': 'incarnate',
'shaymin': 'land',
'basculin': 'red-striped',
'darmanitan': 'standard',
'thundurus': 'incarnate',
'landorus': 'incarnate',
'enamorus': 'incarnate',
'keldeo': 'ordinary',
'meloetta': 'aria',
'meowstic': 'male',
'aegislash': 'shield',
'pumpkaboo': 'average',
'gourgeist': 'average',
'minior': 'red-meteor',
'zygarde': '50 power construct',
'oricorio': 'baile',
'lycanroc': 'midday',
'wishiwashi': 'solo',
'mimikyu': 'disguised',
'cramorant': 'gulping',
'toxtricity': 'low-key',
'eiscue': 'ice',
'indeedee': 'male',
'urshifu': 'single-strike',
'morpeko': 'full belly',
'oinkologne': 'male',
'maushold': 'family of three',
'squawkabilly': 'green plumage',
'palafin': 'zero',
'tatsugiri': 'curly',
'dudunsparce': 'two segment',
'basculegion': 'male'}
if name.lower() in default_forms:
adjusted_form = default_forms[name.lower()]
if adjusted_form:
api_name = f"{name.lower()}-{adjusted_form}"
else:
api_name = name.lower()
api_name = api_name.replace(' ', '-').replace("'", "").replace(".", "").replace('é', 'e').replace(':', '')
#more special cases
if api_name == 'oinkologne-male':
api_name = '916'
return api_name
def get_pokemon_data(pokemon_name, form, cache):
cache_key = f"pokemon_{pokemon_name}_{form}" if form else f"pokemon_{pokemon_name}"
if cache_key in cache:
return cache[cache_key]
api_name = sanitize_name_and_form(pokemon_name, form)
url = f"https://pokeapi.co/api/v2/pokemon/{api_name}"
print(f"Fetching Pokémon data for {pokemon_name}: {url}")
response = requests.get(url)
if response.status_code == 200:
data = response.json()
update_cache(cache_key, data)
return data
return None
def get_pokemon_encounter_data(pokemon_name, form, cache):
cache_key = f"pokemon_encounter_{pokemon_name}_{form}" if form else f"pokemon_encounter_{pokemon_name}"
if cache_key in cache:
return cache[cache_key]
api_name = sanitize_name_and_form(pokemon_name, form)
url = f"https://pokeapi.co/api/v2/pokemon/{api_name}/encounters"
print(f"Fetching encounter data for {pokemon_name}: {url}")
response = requests.get(url)
if response.status_code == 200:
data = response.json()
update_cache(cache_key, data)
return data
else:
return None
def get_earliest_game(encounter_data):
if not encounter_data:
return "Unknown", "Unknown"
game_methods = {}
for location_area in encounter_data:
for version_detail in location_area['version_details']:
game = version_detail['version']['name']
is_gift = any(method['method']['name'] == 'gift' for method in version_detail['encounter_details'])
if game not in game_methods:
game_methods[game] = "Gift" if is_gift else "Catchable"
elif game_methods[game] == "Gift" and not is_gift:
game_methods[game] = "Catchable"
for game in all_games:
if game.lower() in game_methods:
return game, game_methods[game.lower()]
return "Unknown", "Unknown"
def determine_earliest_games(pokemon_list, cache):
for pokemon in pokemon_list:
pokemon_data = get_pokemon_data(pokemon['base_name'], pokemon['form'], cache)
encounter_data = get_pokemon_encounter_data(pokemon['base_name'], pokemon['form'], cache)
pokemon['earliest_game'], pokemon['obtain_method'] = get_earliest_game(encounter_data)
print(f"Processed {pokemon['name']} (#{pokemon['number']}): {pokemon['earliest_game']} ({pokemon['obtain_method']})")
return pokemon_list
def get_species_data(pokemon_name, cache):
cache_key = f"species_{pokemon_name}"
if cache_key in cache:
return cache[cache_key]
api_name = sanitize_name_and_form(pokemon_name, None)
url = f"https://pokeapi.co/api/v2/pokemon-species/{api_name}/"
print(f"Fetching species data for {pokemon_name}: {url}")
response = requests.get(url)
if response.status_code == 200:
data = response.json()
update_cache(cache_key, data)
return data
return None
def get_evolution_chain(pokemon_name, cache):
species_data = get_species_data(pokemon_name, cache)
if not species_data:
return None
cache_key = f"evolution_{species_data['evolution_chain']['url']}"
if cache_key in cache:
return cache[cache_key]
evolution_response = requests.get(species_data['evolution_chain']['url'])
if evolution_response.status_code == 200:
evolution_data = evolution_response.json()
update_cache(cache_key, evolution_data)
return evolution_data
return None
def get_base_form(evolution_chain, cache):
if not evolution_chain or 'chain' not in evolution_chain:
return None
current = evolution_chain['chain']
while current:
species_name = current['species']['name']
species_data = get_species_data(species_name, cache)
if species_data and not species_data.get('is_baby', False):
return species_name
if not current['evolves_to']:
return species_name
current = current['evolves_to'][0]
return None
def adjust_for_evolution(pokemon_list, cache):
pokemon_dict = {f"{pokemon['base_name']}_{pokemon['form']}".lower(): pokemon for pokemon in pokemon_list}
for pokemon in pokemon_list:
species_data = get_species_data(pokemon['base_name'], cache)
evolution_chain = get_evolution_chain(pokemon['base_name'], cache)
base_form = get_base_form(evolution_chain, cache)
# Check if the Pokémon is a baby
if species_data and species_data.get('is_baby', False):
pokemon['obtain_method'] = 'Breed'
elif base_form:
base_key = f"{base_form}_{pokemon['form']}".lower()
if base_key in pokemon_dict:
base_pokemon = pokemon_dict[base_key]
if all_games.index(base_pokemon['earliest_game']) <= all_games.index(pokemon['earliest_game']) and base_pokemon['number'] != pokemon['number']:
pokemon['earliest_game'] = base_pokemon['earliest_game']
pokemon['obtain_method'] = 'Evolve'
print(f"Adjusted {pokemon['name']} (#{pokemon['number']}): {pokemon['earliest_game']} ({pokemon['obtain_method']})")
return pokemon_list
def save_to_csv(pokemon_list, filename='pokemon_earliest_games.csv'):
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['number', 'name', 'earliest_game', 'obtain_method', 'encounter_locations']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for pokemon in pokemon_list:
writer.writerow({
'number': pokemon['number'],
'name': pokemon['name'],
'earliest_game': pokemon['earliest_game'],
'obtain_method': pokemon['obtain_method'],
'encounter_locations': pokemon['encounter_locations']
})
def parse_encounter_locations(encounter_data, game):
locations = []
for location_area in encounter_data:
for version_detail in location_area['version_details']:
if version_detail['version']['name'] == game.lower():
location_name = location_area['location_area']['name']
for encounter_detail in version_detail['encounter_details']:
method = encounter_detail['method']['name']
condition = encounter_detail.get('condition', 'Any')
time = ', '.join(encounter_detail.get('time', ['Any']))
encounter_info = f"{location_name} ({method}"
if condition != 'Any':
encounter_info += f", {condition}"
if time != 'Any':
encounter_info += f", {time}"
encounter_info += ")"
if encounter_info not in locations:
locations.append(encounter_info)
return locations
def add_encounter_locations(pokemon_list, cache):
for pokemon in pokemon_list:
if pokemon['obtain_method'] == 'Catchable':
encounter_data = get_pokemon_encounter_data(pokemon['base_name'], pokemon['form'], cache)
locations = parse_encounter_locations(encounter_data, pokemon['earliest_game'])
pokemon['encounter_locations'] = ' | '.join(locations) if locations else 'Unknown'
else:
pokemon['encounter_locations'] = 'N/A'
print(f"Added encounter locations for {pokemon['name']} (#{pokemon['number']}) in {pokemon['earliest_game']}")
return pokemon_list
# Update the main function
if __name__ == "__main__":
get_cached_data()
pokemon_list = read_pokemon_list('pokemon_home_list.csv', limit=3000)
pokemon_list_with_games = determine_earliest_games(pokemon_list, cache)
pokemon_list_adjusted = adjust_for_evolution(pokemon_list_with_games, cache)
pokemon_list_with_locations = add_encounter_locations(pokemon_list_adjusted, cache)
save_to_csv(pokemon_list_with_locations)
save_cached_data() # Save any remaining new entries
conn.close() # Close the database connection
print(f"Earliest obtainable games and encounter locations determined for {len(pokemon_list)} Pokémon and saved to pokemon_earliest_games.csv")

111
Utilities/ScrapSerebii.py

@ -0,0 +1,111 @@
import requests
from bs4 import BeautifulSoup
import csv
import os
import time
import re
def scrape_serebii_region_pokemon(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
pokemon_list = []
# Find the main table containing Pokémon data
table = soup.find('table', class_='dextable')
if table:
rows = table.find_all('tr')[2:] # Skip the header row and the game intro row
for row in rows:
cells = row.find_all('td')
if len(cells) <= 5: # Ensure we have enough cells to check depositability. if only 5 then its not depositable in any game.
continue
number = cells[0].text.strip().lstrip('#')
name = cells[2].text.strip()
# Get the image URL
img_url = cells[1].find('img')['src']
full_img_url = f"https://www.serebii.net{img_url}"
pokemon_list.append({
'number': number,
'name': name,
'image_url': full_img_url
})
return pokemon_list
def download_image(url, filename):
response = requests.get(url)
if response.status_code == 200:
with open(filename, 'wb') as f:
f.write(response.content)
def sanitize_filename(filename):
# Define a dictionary of symbol replacements
symbol_replacements = {
'?': 'questionmark',
'*': 'asterisk',
':': 'colon',
'/': 'slash',
'\\': 'backslash',
'|': 'pipe',
'<': 'lessthan',
'>': 'greaterthan',
'"': 'quote',
' ': '_'
}
# Replace symbols with their word equivalents
for symbol, word in symbol_replacements.items():
filename = filename.replace(symbol, word)
# Remove any remaining invalid characters
return re.sub(r'[<>:"/\\|?*]', '', filename)
def scrape_all_regions():
base_url = "https://www.serebii.net/pokemonhome/"
regions = ["kanto", "johto", "hoenn", "sinnoh", "unova", "kalos", "alola", "galar", "paldea", "hisui", "unknown"]
all_pokemon = []
for region in regions:
url = f"{base_url}{region}pokemon.shtml"
region_pokemon = scrape_serebii_region_pokemon(url)
all_pokemon.extend(region_pokemon)
print(f"Scraped {len(region_pokemon)} Pokémon from {region.capitalize()} region")
time.sleep(1) # Be nice to the server
return all_pokemon
def save_to_csv(pokemon_list, filename='pokemon_home_list.csv'):
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['number', 'name']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for pokemon in pokemon_list:
writer.writerow({k: pokemon[k] for k in fieldnames})
if __name__ == "__main__":
all_pokemon = scrape_all_regions()
save_to_csv(all_pokemon)
print(f"Scraped a total of {len(all_pokemon)} Pokémon and saved to pokemon_home_list.csv")
# Create 'images' directory if it doesn't exist
if not os.path.exists('images'):
os.makedirs('images')
# Download images
for pokemon in all_pokemon:
sanitized_name = sanitize_filename(pokemon['name'])
filename = f"images/{pokemon['number']}_{sanitized_name}.png"
if os.path.exists(filename):
print(f"Image for {pokemon['name']} already exists, skipping download")
else:
download_image(pokemon['image_url'], filename)
print(f"Downloaded image for {pokemon['name']}")
time.sleep(0.5) # Be nice to the server
print("All images downloaded successfully.")

BIN
Utilities/requirements.txt

Binary file not shown.

BIN
images/Marks/Arceus_mark_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
images/Marks/BDSP_icon_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

BIN
images/Marks/Black_clover_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

BIN
images/Marks/Blue_pentagon_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

BIN
images/Marks/GB_icon_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

BIN
images/Marks/GO_icon_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/Marks/Galar_symbol_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/Marks/Let's_Go_icon_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

BIN
images/Marks/Paldea_icon_HOME.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/Pokemon/0001_Bulbasaur.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0002_Ivysaur.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0003_Venusaur.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0004_Charmander.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0005_Charmeleon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0006_Charizard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0007_Squirtle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/Pokemon/0008_Wartortle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0009_Blastoise.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0010_Caterpie.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0011_Metapod.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
images/Pokemon/0012_Butterfree.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/Pokemon/0013_Weedle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
images/Pokemon/0014_Kakuna.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
images/Pokemon/0015_Beedrill.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0016_Pidgey.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
images/Pokemon/0017_Pidgeotto.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0018_Pidgeot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0019_Rattata.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0019_Rattata_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images/Pokemon/0020_Raticate.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0020_Raticate_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0021_Spearow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0022_Fearow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0023_Ekans.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0024_Arbok.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0025_Pikachu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
images/Pokemon/0025_Pikachu_(Alola_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
images/Pokemon/0025_Pikachu_(Hoenn_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
images/Pokemon/0025_Pikachu_(Kalos_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
images/Pokemon/0025_Pikachu_(Original_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
images/Pokemon/0025_Pikachu_(Partner_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images/Pokemon/0025_Pikachu_(Sinnoh_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
images/Pokemon/0025_Pikachu_(Unova_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
images/Pokemon/0025_Pikachu_(World_Cap).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
images/Pokemon/0026_Raichu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0026_Raichu_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0027_Sandshrew.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0027_Sandshrew_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0028_Sandslash.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0028_Sandslash_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0029_Nidoran♀.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0030_Nidorina.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0031_Nidoqueen.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/Pokemon/0032_Nidoran♂.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0033_Nidorino.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0034_Nidoking.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/Pokemon/0035_Clefairy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0036_Clefable.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0037_Vulpix.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0037_Vulpix_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0038_Ninetales.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0038_Ninetales_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0039_Jigglypuff.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0040_Wigglytuff.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0041_Zubat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0042_Golbat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0043_Oddish.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0044_Gloom.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0045_Vileplume.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0046_Paras.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0047_Parasect.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0048_Venonat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0049_Venomoth.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
images/Pokemon/0050_Diglett.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
images/Pokemon/0050_Diglett_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
images/Pokemon/0051_Dugtrio.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0051_Dugtrio_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0052_Meowth.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0052_Meowth_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
images/Pokemon/0052_Meowth_(Galarian_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/Pokemon/0053_Persian.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0053_Persian_(Alolan_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0054_Psyduck.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/Pokemon/0055_Golduck.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0056_Mankey.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
images/Pokemon/0057_Primeape.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0058_Growlithe.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0058_Growlithe_(Hisuian_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0059_Arcanine.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/Pokemon/0059_Arcanine_(Hisuian_Form).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/Pokemon/0060_Poliwag.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/Pokemon/0061_Poliwhirl.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/Pokemon/0062_Poliwrath.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save