Dan 1 year ago
parent
commit
75a72ceb1b
  1. 4
      .gitignore
  2. 87
      DBVisualiser/index.html
  3. 200
      DBVisualiser/main.js
  4. 5193
      DBVisualiser/package-lock.json
  5. 19
      DBVisualiser/package.json
  6. 0
      DBVisualiser/pokemon_forms.db
  7. 483
      DBVisualiser/renderer.js
  8. 338
      DBVisualiser/styles.css
  9. 128
      OriginDex.py
  10. 26
      Utilities/DBVisualiser.py
  11. 28
      Utilities/DatabaseBuilder.py
  12. 614
      Utilities/DetermineOriginGame.py
  13. 136
      Utilities/ExtractPokemonData.py
  14. 40
      Utilities/Formet.yaml
  15. 296
      Utilities/NewDBVisualiser.py
  16. 139
      Utilities/Update_evolution_information.py
  17. 75
      Utilities/cache_manager.py
  18. 255
      Utilities/pokemondb_scraper.py
  19. BIN
      images-new/0001-01-000-0.png
  20. BIN
      images-new/0002-01-000-0.png
  21. BIN
      images-new/0003-01-000-1.png
  22. BIN
      images-new/0003-01-000-2.png
  23. BIN
      images-new/0003-01-001-0.png
  24. BIN
      images-new/0003-01-002-0.png
  25. BIN
      images-new/0004-01-000-0.png
  26. BIN
      images-new/0005-01-000-0.png
  27. BIN
      images-new/0006-01-000-0.png
  28. BIN
      images-new/0006-01-001-0.png
  29. BIN
      images-new/0006-01-002-0.png
  30. BIN
      images-new/0006-01-003-0.png
  31. BIN
      images-new/0007-01-000-0.png
  32. BIN
      images-new/0008-01-000-0.png
  33. BIN
      images-new/0009-01-000-0.png
  34. BIN
      images-new/0009-01-001-0.png
  35. BIN
      images-new/0009-01-002-0.png
  36. BIN
      images-new/0010-01-000-0.png
  37. BIN
      images-new/0011-01-000-0.png
  38. BIN
      images-new/0012-01-000-1.png
  39. BIN
      images-new/0012-01-000-2.png
  40. BIN
      images-new/0012-01-001-0.png
  41. BIN
      images-new/0013-01-000-0.png
  42. BIN
      images-new/0014-01-000-0.png
  43. BIN
      images-new/0015-01-000-0.png
  44. BIN
      images-new/0015-01-001-0.png
  45. BIN
      images-new/0016-01-000-0.png
  46. BIN
      images-new/0017-01-000-0.png
  47. BIN
      images-new/0018-01-000-0.png
  48. BIN
      images-new/0018-01-001-0.png
  49. BIN
      images-new/0019-01-000-1.png
  50. BIN
      images-new/0019-01-000-2.png
  51. BIN
      images-new/0019-01-001-0.png
  52. BIN
      images-new/0020-01-000-1.png
  53. BIN
      images-new/0020-01-000-2.png
  54. BIN
      images-new/0020-01-001-0.png
  55. BIN
      images-new/0021-01-000-0.png
  56. BIN
      images-new/0022-01-000-0.png
  57. BIN
      images-new/0023-01-000-0.png
  58. BIN
      images-new/0024-01-000-0.png
  59. BIN
      images-new/0025-01-000-1.png
  60. BIN
      images-new/0025-01-000-2.png
  61. BIN
      images-new/0025-01-001-0.png
  62. BIN
      images-new/0025-01-002-0.png
  63. BIN
      images-new/0025-01-003-0.png
  64. BIN
      images-new/0025-01-004-0.png
  65. BIN
      images-new/0025-01-005-0.png
  66. BIN
      images-new/0025-01-006-0.png
  67. BIN
      images-new/0025-01-007-0.png
  68. BIN
      images-new/0025-01-008-0.png
  69. BIN
      images-new/0025-01-009-0.png
  70. BIN
      images-new/0026-01-000-1.png
  71. BIN
      images-new/0026-01-000-2.png
  72. BIN
      images-new/0026-01-001-0.png
  73. BIN
      images-new/0027-01-000-0.png
  74. BIN
      images-new/0027-01-001-0.png
  75. BIN
      images-new/0028-01-000-0.png
  76. BIN
      images-new/0028-01-001-0.png
  77. BIN
      images-new/0029-01-000-0.png
  78. BIN
      images-new/0030-01-000-0.png
  79. BIN
      images-new/0031-01-000-0.png
  80. BIN
      images-new/0032-01-000-0.png
  81. BIN
      images-new/0033-01-000-0.png
  82. BIN
      images-new/0034-01-000-0.png
  83. BIN
      images-new/0035-01-000-0.png
  84. BIN
      images-new/0036-01-000-0.png
  85. BIN
      images-new/0037-01-000-0.png
  86. BIN
      images-new/0037-01-001-0.png
  87. BIN
      images-new/0038-01-000-0.png
  88. BIN
      images-new/0038-01-001-0.png
  89. BIN
      images-new/0039-01-000-0.png
  90. BIN
      images-new/0040-01-000-0.png
  91. BIN
      images-new/0041-01-000-1.png
  92. BIN
      images-new/0041-01-000-2.png
  93. BIN
      images-new/0042-01-000-1.png
  94. BIN
      images-new/0042-01-000-2.png
  95. BIN
      images-new/0043-01-000-0.png
  96. BIN
      images-new/0044-01-000-1.png
  97. BIN
      images-new/0044-01-000-2.png
  98. BIN
      images-new/0045-01-000-1.png
  99. BIN
      images-new/0045-01-000-2.png
  100. BIN
      images-new/0046-01-000-0.png

4
.gitignore

@ -1,4 +1,6 @@
pokemon_cache.db
pokemon_database.db
Utilities/venv/
venv/
venv/
DBVisualiser/node_modules/
Utilities/__pycache__/

87
DBVisualiser/index.html

@ -0,0 +1,87 @@
<!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

@ -0,0 +1,200 @@
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

@ -0,0 +1,19 @@
{
"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

@ -0,0 +1,483 @@
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

@ -0,0 +1,338 @@
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;
}

128
OriginDex.py

@ -1,30 +1,122 @@
import csv
from flask import Flask, render_template
import sqlite3
from flask import Flask, render_template, jsonify
from collections import defaultdict
import os
app = Flask(__name__)
def load_pokemon_data():
pokemon_list = []
earliest_games = {}
conn = sqlite3.connect('pokemon_database.db')
cursor = conn.cursor()
# 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)
cursor.execute('''
SELECT p.national_dex_number, p.name, pf.form_name, pf.image_path, pf.is_default, p.introduced_in_gen,
g.name AS earliest_game, m.icon_path AS mark_icon
FROM pokemon p
JOIN pokemon_forms pf ON p.national_dex_number = pf.pokemon_id
LEFT JOIN form_encounters fe ON pf.id = fe.form_id
LEFT JOIN games g ON fe.game_id = g.id
LEFT JOIN mark_game_associations mga ON g.id = mga.game_id
LEFT JOIN marks m ON mga.mark_id = m.id
GROUP BY p.national_dex_number, pf.id
ORDER BY p.national_dex_number, pf.is_default DESC, pf.form_name
''')
current_group = []
current_generation = None
current_dex_number = None
pokemon_forms = []
for row in cursor.fetchall():
national_dex_number, name, form_name, image_path, is_default, introduced_in_gen, earliest_game, mark_icon = row
pokemon = {
'ID': national_dex_number,
'Name': name,
'Form': form_name,
'Image': image_path,
'IsDefault': is_default,
'Generation': introduced_in_gen,
'EarliestGame': earliest_game,
'MarkIcon': mark_icon
}
if national_dex_number != current_dex_number:
if pokemon_forms:
for form in pokemon_forms:
current_group.append(form)
if len(current_group) == 30:
pokemon_list.append(current_group)
current_group = []
pokemon_forms = []
current_dex_number = national_dex_number
if is_default:
if current_generation is None or introduced_in_gen != current_generation:
if current_group:
while len(current_group) < 30:
current_group.append(None) # Add empty slots
pokemon_list.append(current_group)
current_group = []
current_generation = introduced_in_gen
pokemon_forms.append(pokemon)
# Add the last set of forms
for form in pokemon_forms:
current_group.append(form)
if len(current_group) == 30:
pokemon_list.append(current_group)
current_group = []
# Add any remaining Pokémon
if current_group:
while len(current_group) < 30:
current_group.append(None) # Add empty slots to the last group
pokemon_list.append(current_group)
# 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
conn.close()
return pokemon_list
@app.route('/')
def index():
pokemon_list, earliest_games = load_pokemon_data()
return render_template('index.html', pokemon_list=pokemon_list, earliest_games=earliest_games)
pokemon_list = load_pokemon_data()
return render_template('index.html', grouped_pokemon=pokemon_list)
@app.route('/pokemon/<int:dex_number>')
def pokemon_details(dex_number):
conn = sqlite3.connect('pokemon_database.db')
cursor = conn.cursor()
cursor.execute('''
SELECT pf.form_name, 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
JOIN pokemon_forms pf ON fe.form_id = pf.id
WHERE pf.pokemon_id = ?
ORDER BY pf.is_default DESC, pf.form_name, g.generation, g.name, l.name
''', (dex_number,))
encounters = [
{'form': form, 'game': game, 'location': location, 'method': method}
for form, game, location, method in cursor.fetchall()
]
conn.close()
return jsonify(encounters)
if __name__ == '__main__':
app.run(debug=True)
extra_files = ['.']
extra_dirs = ['./templates/', './static/']
for extra_dir in extra_dirs:
for dirname, dirs, files in os.walk(extra_dir):
for filename in files:
filename = os.path.join(dirname, filename)
if os.path.isfile(filename):
extra_files.append(filename)
app.run(debug=True, extra_files=extra_files)

26
Utilities/DBVisualiser.py

@ -51,6 +51,11 @@ class PokemonDatabaseApp(QMainWindow):
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)
@ -578,8 +583,27 @@ class PokemonDatabaseApp(QMainWindow):
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())
sys.exit(app.exec())

28
Utilities/DatabaseBuilder.py

@ -4,6 +4,7 @@ import re
def create_connection():
conn = sqlite3.connect('pokemon_database.db')
conn.text_factory = str
return conn
def create_tables(conn):
@ -42,7 +43,8 @@ def create_tables(conn):
cursor.execute('''
CREATE TABLE IF NOT EXISTS pokemon (
national_dex_number INTEGER PRIMARY KEY,
name TEXT NOT NULL
name TEXT NOT NULL,
introduced_in_gen INTEGER
)
''')
@ -109,12 +111,11 @@ 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)
# Capitalize the first letter of the first word
name = name.capitalize()
# Check for cardinal directions at the end
cardinal_directions = ['north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest']
for direction in cardinal_directions:
@ -128,6 +129,9 @@ def tidy_location_name(name):
name = "Route " + name
name = name.replace("Routes", "Route")
# Capitalize the first letter of the first word
name = name.capitalize()
return name
@ -235,7 +239,7 @@ def load_mark_data(conn):
def load_pokemon_data(conn):
cursor = conn.cursor()
with open('pokemon_home_list.csv', 'r') as f:
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:
@ -275,7 +279,7 @@ def load_pokemon_data(conn):
def load_encounter_data(conn):
cursor = conn.cursor()
with open('pokemon_earliest_games.csv', 'r') as f:
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'])
@ -283,7 +287,7 @@ def load_encounter_data(conn):
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:
@ -295,11 +299,11 @@ def load_encounter_data(conn):
base_name = full_name
form_name = "Default"
# Ensure the Pokémon and form exist in the database
# Update the Pokémon entry with introduced_in_gen
cursor.execute('''
INSERT OR IGNORE INTO pokemon (national_dex_number, name)
VALUES (?, ?)
''', (national_dex_number, base_name))
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)

614
Utilities/DetermineOriginGame.py

@ -13,19 +13,7 @@ from fuzzywuzzy import fuzz
from fuzzywuzzy import process
from collections import defaultdict
# 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()
from cache_manager import CacheManager
# List of all main series Pokémon games in chronological order, with special games first in each generation
all_games = [
@ -46,43 +34,17 @@ all_games = [
]
big_pokemon_list = []
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 >= 1:
save_cached_data()
time.sleep(1)
pokemon_index = None
pokemon_index = {}
def create_pokemon_index(pokemon_list):
global pokemon_index
name_index = defaultdict(list)
for pokemon in pokemon_list:
name_index[pokemon.name.lower()].append(pokemon)
return name_index
pokemon_index = name_index
def find_pokemon(name, form=None, threshold=80):
global pokemon_index
name = name.lower()
if name in pokemon_index:
candidates = pokemon_index[name]
@ -129,6 +91,30 @@ def find_pokemon(name, form=None, threshold=80):
return None
def roman_to_int(s):
roman_values = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000
}
total = 0
prev_value = 0
for char in reversed(s):
current_value = roman_values[char]
if current_value >= prev_value:
total += current_value
else:
total -= current_value
prev_value = current_value
return total
class Pokemon:
def __init__(self, name: str, number: int, form: Optional[str] = None):
self.name = name
@ -140,6 +126,7 @@ class Pokemon:
self.encounter_information: Optional[List['EncounterInformation']] = []
self.earliest_game: Optional['EncounterInformation'] = None
self.obtain_method: Optional[str] = None
self.introduced_in_gen = None
def get_earliest_game_and_method(self):
if self.evolution_chain:
@ -245,6 +232,7 @@ class EvolutionStage:
self.pokemon = pokemon
self.method = method
self.next_stage: Optional[EvolutionStage] = None
self.previous_stage: Optional[EvolutionStage] = None # New attribute
self.branches: List[EvolutionStage] = []
self.stage = stage
self.is_baby = self.stage is not None and 'Baby' in self.stage
@ -253,8 +241,6 @@ class EvolutionStage:
self.pokemon_reference = find_pokemon(pokemon, None)
self.form = form
def __str__(self):
return f"{self.pokemon} {self.form if self.form else ''} ({self.method if self.method else 'Base'})"
@ -283,10 +269,12 @@ def parse_evolution_chain(table: Tag, form: Optional[str] = None) -> List[Evolut
# This TD contains Pokemon information
pokemon_name = extract_pokemon_name(td)
stage = extract_stage_form(td)
new_stage = EvolutionStage(pokemon_name, pending_method, stage, form)
evolution_form = extract_evolution_form(td, pokemon_name)
new_stage = EvolutionStage(pokemon_name, pending_method, stage, evolution_form)
pending_method = None
if current_stage:
current_stage.next_stage = new_stage
new_stage.previous_stage = current_stage # Set the back link
current_stage = new_stage
main_chain.append(current_stage)
else:
@ -301,29 +289,37 @@ def parse_evolution_chain(table: Tag, form: Optional[str] = None) -> List[Evolut
if td.find('table'):
pokemon_name = extract_pokemon_name(td)
stage = extract_stage_form(td)
new_stage = EvolutionStage(pokemon_name, branch_method, stage, form)
evolution_form = extract_evolution_form(td, pokemon_name)
new_stage = EvolutionStage(pokemon_name, branch_method, stage, evolution_form)
branch_method = None
if branch_stage:
branch_stage.next_stage = new_stage
new_stage.previous_stage = branch_stage # Set the back link
branch_stage = new_stage
# Find which main chain Pokemon this branches from
for main_stage in main_chain:
if td.get('rowspan') and main_stage.pokemon == pokemon_name:
main_stage.branches.append(branch_stage)
branch_stage.previous_stage = main_stage # Set the back link to the main chain
break
else:
branch_method = extract_evolution_method(td)
return main_chain
def extract_pokemon_name(td: Tag) -> str:
# Extract Pokemon name from the table within the TD
def find_name_tag(td: Tag) -> Optional[Tag]:
table = td.find('table')
name_tag = table.find('a', class_='selflink')
if name_tag:
return name_tag.get_text(strip=True)
return name_tag
name_tag = table.find('a', title=True, class_=lambda x: x != 'image')
return name_tag.get_text(strip=True)
return name_tag
def extract_pokemon_name(td: Tag) -> Optional[str]:
name_tag = find_name_tag(td)
if name_tag:
return name_tag.get_text(strip=True)
return None
def extract_evolution_method(td: Tag) -> str:
# Extract evolution method from the TD
@ -335,11 +331,14 @@ def extract_stage_form(td: Tag) -> Optional[str]:
return stage_tag.get_text(strip=True)
return None
def extract_is_baby(td: Tag) -> bool:
stage_tag = td.find('table').find('small')
if stage_tag:
return 'Baby' in stage_tag.get_text(strip=True)
return False
def extract_evolution_form(td: Tag, name: str) -> Optional[str]:
name_tag = find_name_tag(td)
if name_tag:
name_row = name_tag.parent
small_tags = name_row.find_all('small')
if len(small_tags) > 1:
return small_tags[0].get_text(strip=True)
return None
def read_pokemon_list(filename, limit=50):
pokemon_list = []
@ -358,163 +357,11 @@ def read_pokemon_list(filename, limit=50):
new_pokemon = Pokemon(row['base_name'], row['number'], row['form'])
big_pokemon_list.append(new_pokemon)
return pokemon_list
return big_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_data_bulbapedia(pokemon_name, cache):
cache_key = f"pokemon_{pokemon_name}_bulbapedia"
if cache_key in cache:
return cache[cache_key]
def get_pokemon_data_bulbapedia(pokemon_name, cache: CacheManager):
url = f"https://bulbapedia.bulbagarden.net/wiki/{pokemon_name}_(Pokémon)"
print(f"Fetching Pokémon data for {pokemon_name}: {url}")
response = requests.get(url)
if response.status_code == 200:
data = response.text
update_cache(cache_key, data)
return data
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
return cache.fetch_url(url)
def split_td_contents(td):
groups = []
@ -536,7 +383,7 @@ def split_td_contents(td):
groups[-1].append(copy.copy(item))
return groups
def parse_form_information(html_content):
soup = BeautifulSoup(html_content, 'html.parser')
form_info = soup.find('small')
@ -555,7 +402,7 @@ def parse_form_information(html_content):
return None, None
def get_evolution_data_from_bulbapedia(pokemon_name, form, cache):
def get_evolution_data_from_bulbapedia(pokemon_name, form, cache: CacheManager, gender: Optional[str] = None):
page_data = get_pokemon_data_bulbapedia(pokemon_name, cache)
if not page_data:
return None
@ -605,7 +452,6 @@ def parse_pokemon_subtable(td):
return None, None
def parse_eevee_evolution_chain(table):
tbody = table.find('tbody', recursive=False)
if not tbody:
return []
@ -627,15 +473,55 @@ def parse_eevee_evolution_chain(table):
index = 0
for eeveelution in eeveelutions_row.find_all('td', recursive=False):
pokemon_name, stage = parse_pokemon_subtable(eeveelution)
eeveelutions.append(EvolutionStage(pokemon_name, methods[index], stage, None))
eeveelution_stage = EvolutionStage(pokemon_name, methods[index], stage, None)
eeveelution_stage.previous_stage = eevee_stage # Set the back link to Eevee
eeveelutions.append(eeveelution_stage)
index += 1
eevee_stage.branches.append(eeveelutions)
eevee_stage.branches = eeveelutions # Set the branches directly, not as a nested list
return [eevee_stage]
def get_intro_generation(pokemon_name, form, cache: CacheManager):
page_data = get_pokemon_data_bulbapedia(pokemon_name, cache)
if not page_data:
return None
soup = BeautifulSoup(page_data, 'html.parser')
locations_section = soup.find('span', id='Game_locations')
if not locations_section:
return None
def get_locations_from_bulbapedia(pokemon_name, form, cache):
locations_table = locations_section.find_next('table', class_='roundy')
if not locations_table:
return None
generation_tbody = locations_table.find('tbody', recursive=False)
generation_rows = generation_tbody.find_all('tr', recursive=False)
for generation_row in generation_rows:
random_nested_td = generation_row.find('td', recursive=False)
if not random_nested_td:
continue
random_nested_table = random_nested_td.find('table', recursive=False)
if not random_nested_table:
continue
random_nested_tbody = random_nested_table.find('tbody', recursive=False)
random_nested_rows = random_nested_tbody.find_all('tr', recursive=False)
for nested_row in random_nested_rows:
test_text = None
pattern = r"Generation\s+([IVXLCDM]+)"
match = re.search(pattern, nested_row.get_text(strip=True))
if match:
test_text = match.group(1) # This returns just the Roman numeral
if test_text:
return roman_to_int(test_text.replace("Generation ", "").strip())
return None
def get_locations_from_bulbapedia(pokemon_name, form, cache: CacheManager):
page_data = get_pokemon_data_bulbapedia(pokemon_name, cache)
if not page_data:
return None
@ -669,6 +555,8 @@ def get_locations_from_bulbapedia(pokemon_name, form, cache):
continue
random_nested_tbody = random_nested_table.find('tbody', recursive=False)
random_nested_rows = random_nested_tbody.find_all('tr', recursive=False)
intro_gen = None
for nested_row in random_nested_rows:
if 'Generation' in nested_row.get_text(strip=True):
continue
@ -740,7 +628,7 @@ def get_locations_from_bulbapedia(pokemon_name, form, cache):
sub_form_match = False if not sub_form else fuzz.partial_ratio(form.lower(), sub_form.lower()) >= 80
if main_form_match or sub_form_match:
locations = raw_location.get_text().split(',')
locations = raw_location.get_text().replace('and', ',').replace('#', '').split(',')
for location in locations:
if raw_game not in game_locations:
game_locations[raw_game] = []
@ -762,46 +650,6 @@ def get_locations_from_bulbapedia(pokemon_name, form, cache):
return game_locations
def get_earliest_game(encounter_data, pokemon_name, form):
if not encounter_data:
return "Unknown", "Unknown"
non_catchable_methods = ["trade", "event", "global link", "poké transfer", "time capsule", "unobtainable", "pokémon home"]
game_methods = {}
for game, locations in encounter_data.items():
for location in locations:
method = "Catchable"
for non_catchable in non_catchable_methods:
if non_catchable in location.lower():
method = None
break
if method is None:
continue
if "first partner" in location.lower():
method = "Starter"
elif "received" in location.lower():
method = "Gift"
elif "evolve" in location.lower():
method = "Evolve"
else:
method = "Catchable"
if method:
if game not in game_methods:
game_methods[game.lower()] = method
else:
if method == "Catchable":
game_methods[game.lower()] = method
for game in all_games:
if game.lower() in game_methods:
return game, game_methods[game.lower()]
return "Unknown", "Unknown"
def handle_unown(pokemon, encounter_data):
if not pokemon.name == "Unown":
return
@ -833,17 +681,6 @@ def handle_unown(pokemon, encounter_data):
else:
pokemon.encounter_information = one_form_unown.encounter_information
def handle_deoxys(pokemon, encounter_data):
if not pokemon.name == "Deoxys":
return
normal_form_deoxys = find_pokemon(pokemon.name, None)
if not normal_form_deoxys:
return
if pokemon.form:
pokemon.encounter_information = normal_form_deoxys.encounter_information
list_of_shifting_form_pokemon = [
"Deoxys",
"Burmy",
@ -905,7 +742,7 @@ def get_bad_tea_form(pokemon):
else:
return pokemon.form
def determine_earliest_games(pokemon_list, cache):
def determine_earliest_games(cache: CacheManager):
for pokemon in big_pokemon_list:
print(f"Processing {pokemon}")
form_to_find = pokemon.form
@ -917,6 +754,7 @@ def determine_earliest_games(pokemon_list, cache):
form_to_find = None
if pokemon.name in bad_tea_pokemon:
form_to_find = get_bad_tea_form(pokemon)
pokemon.introduced_in_gen = get_intro_generation(pokemon.name, form_to_find, cache)
encounter_data = get_locations_from_bulbapedia(pokemon.name, form_to_find, cache)
for encounter in encounter_data:
encounter_information = EncounterInformation(encounter, encounter_data[encounter])
@ -930,50 +768,6 @@ def determine_earliest_games(pokemon_list, cache):
pokemon.determine_earliest_game()
print(f"Processed {pokemon}: {pokemon.earliest_game.game} ({pokemon.earliest_game.method})")
#for pokemon in pokemon_list:
# print(f"Processing {pokemon['name']} (#{pokemon['number']})")
# encounter_data = get_locations_from_bulbapedia(pokemon['base_name'], pokemon['form'], cache)
# pokemon['earliest_game'], pokemon['obtain_method'] = get_earliest_game(encounter_data, pokemon['base_name'], pokemon['form'])
# print(f"Processed {pokemon['name']} (#{pokemon['number']}): {pokemon['earliest_game']} ({pokemon['obtain_method']})")
# #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:List[EvolutionStage]):
if not evolution_chain:
return None
@ -986,66 +780,16 @@ def get_base_form(evolution_chain:List[EvolutionStage]):
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):
def adjust_for_evolution(cache: CacheManager):
for pokemon in big_pokemon_list:
evolution_chain = get_evolution_data_from_bulbapedia(pokemon.name, pokemon.form, cache)
pokemon.add_evolution_chain(evolution_chain)
game, method = pokemon.get_earliest_game_and_method()
print(f"Adjusted {pokemon}: {game} ({method})")
return []
pokemon_dict = {f"{pokemon['base_name']}_{pokemon['form']}".lower(): pokemon for pokemon in pokemon_list}
for pokemon in pokemon_list:
evolution_chain = get_evolution_data_from_bulbapedia(pokemon['base_name'], pokemon['form'], cache)
if evolution_chain:
if evolution_chain[0].is_baby:
pokemon['obtain_method'] = 'Breed'
else:
base_form = get_base_form(evolution_chain)
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'
#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'):
def save_to_csv(filename='pokemon_earliest_games.csv'):
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['number', 'name', 'earliest_game', 'obtain_method', 'encounter_locations']
fieldnames = ['number', 'name', 'introduced_in_gen', 'earliest_game', 'obtain_method', 'encounter_locations']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
@ -1057,147 +801,29 @@ def save_to_csv(pokemon_list, filename='pokemon_earliest_games.csv'):
writer.writerow({
'number': pokemon.number,
'name': f"{pokemon.name} ({pokemon.form})",
'introduced_in_gen': pokemon.introduced_in_gen,
'earliest_game': pokemon.earliest_game.game,
'obtain_method': pokemon.earliest_game.method,
'encounter_locations': ' | '.join((str(item) for item in 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
def get_marriland_page(pokemon_name, cache):
url_name = pokemon_name.lower().replace(' ', '-').replace('(', '').replace(')', '')
cache_key = f"marriland_{url_name}"
if cache_key in cache:
return cache[cache_key]
url = f"https://marriland.com/pokedex/{url_name}/"
try:
response = requests.get(url)
response.raise_for_status() # Raise an exception for bad status codes
data = response.text
update_cache(cache_key, data)
return data
except requests.RequestException as e:
print(f"Error accessing the page for {pokemon_name}: {e}")
return None
def is_event_pokemon(pokemon_name, cache):
page_data = get_marriland_page(pokemon_name, cache)
if not page_data:
return False
soup = BeautifulSoup(page_data, 'html.parser')
# Find the "Where to Find" section
location_section = soup.find('div', id='locations')
if not location_section:
print(f"Could not find 'Where to Find' section for {pokemon_name}")
return None
special_section = soup.find('div', class_='location-special')
location_tables = soup.find_all('table', class_='location-table')
event_only = "Only available from events or promotions.".lower()
if len(location_tables) == 0 and special_section and event_only in special_section.get_text(strip=True).lower():
return True
return False
def check_alternative_sources(pokemon, cache):
# This function will check alternative sources for Pokémon with "Unknown" encounter types
species_data = get_species_data(pokemon['base_name'], cache)
if species_data:
# Check if it's a mythical Pokémon
if species_data.get('is_mythical', False):
return "Event", "Event"
# Check if it's a legendary Pokémon
if species_data.get('is_legendary', False):
return pokemon['earliest_game'], "Legendary"
event_status = is_event_pokemon(pokemon['name'], cache)
if event_status:
return "Event", "Event"
#bulb_locations = get_locations_from_bulbapedia(pokemon['base_name'], pokemon['form'], cache)
#if bulb_locations:
# return bulb_locations[0], "Bulbapedia"
# Check generation introduced
#generation = species_data.get('generation', {}).get('name', '')
#if generation:
# gen_number = int(generation.split('-')[1])
# for game in all_games:
# if game != "Unknown" and get_generation(game) == gen_number:
# return game, "First appearance"
return "Unknown", "Unknown"
def handle_unknown_encounters(pokemon_list, cache):
def handle_unknown_encounters(cache):
for pokemon in big_pokemon_list:
if pokemon.earliest_game == None or pokemon.earliest_game.method == None:
print(f"Checking alternative sources for {pokemon.name}")
return
for pokemon in pokemon_list:
if pokemon['earliest_game'] == "Unknown" or pokemon['obtain_method'] == "Unknown":
new_game, new_method = check_alternative_sources(pokemon, cache)
if new_game != "Unknown":
pokemon['earliest_game'] = new_game
pokemon['obtain_method'] = new_method
pokemon['encounter_locations'] = 'N/A'
print(f"Checked alternative sources for {pokemon['name']} (#{pokemon['number']}): {pokemon['earliest_game']} ({pokemon['obtain_method']})")
return pokemon_list
# Update the main function
if __name__ == "__main__":
get_cached_data()
cache = CacheManager()
pokemon_list = read_pokemon_list('pokemon_home_list.csv', limit=3000)
pokemon_index = create_pokemon_index(big_pokemon_list)
create_pokemon_index(big_pokemon_list)
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)
pokemon_list_final = handle_unknown_encounters(pokemon_list_adjusted, cache)
save_to_csv(pokemon_list_final)
determine_earliest_games(cache)
adjust_for_evolution(cache)
handle_unknown_encounters(cache)
save_to_csv()
save_cached_data() # Save any remaining new entries
conn.close() # Close the database connection
cache.close()
print(f"Earliest obtainable games and encounter locations determined for {len(pokemon_list)} Pokémon and saved to pokemon_earliest_games.csv")

136
Utilities/ExtractPokemonData.py

@ -0,0 +1,136 @@
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()

40
Utilities/Formet.yaml

@ -0,0 +1,40 @@
Format: XXXX-YY-ZZZ-G
Where:
XXXX: 4-digit National Dex number (padded with zeros)
YY: 2-digit region code
ZZZ: 3-digit form index
G: 1-digit gender code
Region Codes (YY):
01: Kanto
02: Johto
03: Hoenn
04: Sinnoh
05: Unova
06: Kalos
07: Alola
08: Galar
09: Hisui
10: Paldea
(... expandable for future regions)
Form Index (ZZZ):
000: Default form
001-999: Various forms (Mega, Gigantamax, regional variants, etc.)
Gender Code (G):
0: Genderless or Default
1: Female
2: Male
Examples:
Bulbasaur: 0001-01-000-0
Female Venusaur: 0003-01-000-1
Male Venusaur: 0003-01-000-2
Alolan Rattata (Female): 0019-07-000-1
Mega Charizard X: 0006-01-001-0
Galarian Ponyta: 0077-08-000-0
Hisuian Growlithe: 0058-09-000-0
Pikachu (Female): 0025-01-000-1
Unown A: 0201-02-000-0
Unown B: 0201-02-001-0
Giratina (Origin Forme): 0487-04-001-0
Meowth (Galarian): 0052-08-000-0
Meowth (Alolan): 0052-07-000-0
Meowth (Gigantamax): 0052-01-001-0

296
Utilities/NewDBVisualiser.py

@ -0,0 +1,296 @@
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_())

139
Utilities/Update_evolution_information.py

@ -0,0 +1,139 @@
import sqlite3
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
@dataclass
class EvolutionInfo:
from_pfic: str
to_pfic: str
method: str
def create_evolution_table():
conn = sqlite3.connect('pokemon_forms.db')
cursor = conn.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)
)
''')
conn.commit()
return conn
def insert_evolution_info(conn, evolution_info: EvolutionInfo):
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO evolution_chains
(from_pfic, to_pfic, method)
VALUES (?, ?, ?)
''', (evolution_info.from_pfic, evolution_info.to_pfic, evolution_info.method))
conn.commit()
def strip_pokemon_name(pokemon_name: str, form_name: str) -> str:
"""Remove the Pokémon's name from the form name if present."""
if form_name:
form_name = form_name.replace("Form", "").strip()
form_name = re.sub(f'{re.escape(pokemon_name)}\\s*', '', form_name, flags=re.IGNORECASE).strip()
form_name = form_name.replace(" ", " ")
return form_name
return form_name
def fuzzy_match_form(form1: str, form2: str, threshold: int = 80) -> bool:
"""Perform fuzzy matching between two form names."""
if form1 is None or form2 is None:
return form1 == form2
return fuzz.ratio(form1.lower(), form2.lower()) >= threshold
def get_pokemon_form_by_name(conn, name: str, form: Optional[str] = None, threshold: int = 80, gender: Optional[str] = None) -> Optional[str]:
cursor = conn.cursor()
cursor.execute('SELECT PFIC, name, form_name FROM pokemon_forms WHERE name = ?', (name,))
results = cursor.fetchall()
if not results:
return None
if form is None and gender is None:
if len(results) > 1:
if results[0][2] == None:
return results[0][0]
else:
return get_pokemon_form_by_name(conn, name, "Male", threshold=100, gender=gender)
else:
return results[0][0] # Return the PFIC of the first result if no form is specified
if gender:
gendered_form = get_pokemon_form_by_name(conn, name, gender, threshold=100)
if gendered_form:
return gendered_form
stripped_form = strip_pokemon_name(name, form)
for pfic, pokemon_name, db_form in results:
stripped_db_form = strip_pokemon_name(pokemon_name, db_form)
if fuzzy_match_form(stripped_form, stripped_db_form, threshold):
return pfic
# Some times we get a form for a pokemon that doesn't really have one.
if len(results) > 1 and form != None:
return results[0][0]
return None
def process_evolution_chain(conn, evolution_chain, cache, gender: Optional[str] = None):
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}")
continue
if stage.next_stage:
to_pfic = get_pokemon_form_by_name(conn, stage.next_stage.pokemon, stage.next_stage.form, gender=gender)
if to_pfic:
evolution_info = EvolutionInfo(from_pfic, to_pfic, stage.next_stage.method)
insert_evolution_info(conn, evolution_info)
for branch in stage.branches:
to_pfic = get_pokemon_form_by_name(conn, branch.pokemon, branch.form, gender=gender)
if to_pfic:
evolution_info = EvolutionInfo(from_pfic, to_pfic, branch.method)
insert_evolution_info(conn, evolution_info)
def update_evolution_chains():
cache = CacheManager()
conn = create_evolution_table()
cursor = conn.cursor()
cursor.execute('SELECT DISTINCT name, form_name FROM pokemon_forms')
pokemon_forms = cursor.fetchall()
for name, form in pokemon_forms:
print(f"Processing {name} {form if form else ''}")
if form and name in form:
form = form.replace(name, "").strip()
gender = None
if form and "male" in form.lower():
gender = form
form = None
evolution_chain = get_evolution_data_from_bulbapedia(name, form, cache, gender)
if evolution_chain:
if name == "Tauros": # Bulbapedia has a weird formatting for Tauros.
for stage in evolution_chain:
if stage.form:
stage.form = stage.form.replace("Paldean Form(", "").replace(")", "").strip()
process_evolution_chain(conn, evolution_chain, cache, gender)
conn.close()
if __name__ == "__main__":
update_evolution_chains()

75
Utilities/cache_manager.py

@ -0,0 +1,75 @@
import sqlite3
import json
import time
import requests
from typing import Any, Optional
class CacheManager:
def __init__(self, db_name: str = 'pokemon_cache.db'):
self.conn = sqlite3.connect(db_name)
self.cursor = self.conn.cursor()
self._create_cache_table()
def _create_cache_table(self):
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value TEXT,
timestamp FLOAT
)
''')
self.conn.commit()
def get(self, key: str) -> Optional[Any]:
self.cursor.execute('SELECT value, timestamp FROM cache WHERE key = ?', (key,))
result = self.cursor.fetchone()
if result:
value, timestamp = result
return json.loads(value)
return None
def set(self, key: str, value: Any):
serialized_value = json.dumps(value)
timestamp = time.time()
self.cursor.execute('''
INSERT OR REPLACE INTO cache (key, value, timestamp)
VALUES (?, ?, ?)
''', (key, serialized_value, timestamp))
self.conn.commit()
def fetch_url(self, url: str, force_refresh: bool = False, expiry: int = 86400) -> Optional[str]:
cache_key = f"url_{url}"
if not force_refresh:
cached_data = self.get(cache_key)
if cached_data:
cached_time = cached_data['timestamp']
if time.time() - cached_time < expiry:
return cached_data['content']
print(f"Fetching URL: {url}")
response = requests.get(url)
if response.status_code == 200:
content = response.text
self.set(cache_key, {
'content': content,
'timestamp': time.time()
})
return content
return None
def close(self):
self.conn.close()
# Usage example
if __name__ == "__main__":
cache = CacheManager()
# Example usage
url = "https://example.com"
data = cache.fetch_url(url)
if data:
print("Data fetched successfully")
else:
print("Failed to fetch data")
cache.close()

255
Utilities/pokemondb_scraper.py

@ -0,0 +1,255 @@
import requests
from bs4 import BeautifulSoup
from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
import os
import sqlite3
from cache_manager import CacheManager
@dataclass
class PokemonForm:
id: str # This will be our PFIC
name: str
form_name: Optional[str]
sprite_url: str
national_dex: int
generation: int
def create_pokemon_db():
conn = sqlite3.connect('pokemon_forms.db')
cursor = conn.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
)
''')
conn.commit()
return conn
def create_pokemon_storage_db():
conn = sqlite3.connect('pokemon_forms.db')
cursor = conn.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)
)
''')
conn.commit()
return conn
def initialize_db():
create_pokemon_db()
create_pokemon_storage_db()
def insert_pokemon_form(conn, pokemon_form):
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO pokemon_forms
(PFIC, name, form_name, national_dex, generation)
VALUES (?, ?, ?, ?, ?)
''', (
pokemon_form.id,
pokemon_form.name,
pokemon_form.form_name,
pokemon_form.national_dex,
pokemon_form.generation
))
conn.commit()
def insert_pokemon_storage(conn, pfic: str, storable_in_home: bool):
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO pokemon_storage
(PFIC, storable_in_home)
VALUES (?, ?)
''', (pfic, storable_in_home))
conn.commit()
class PokemonDatabase:
def __init__(self):
self.pokemon: Dict[str, List[PokemonForm]] = {}
def add_pokemon(self, national_dex: int, name: str, region_code: int, form_index: int, gender_code: int, form_name: Optional[str], sprite_url: str):
pokemon_id = format_pokemon_id(national_dex, region_code, form_index, gender_code)
pokemon_form = PokemonForm(id=pokemon_id, name=name, form_name=form_name, sprite_url=sprite_url, national_dex=national_dex, generation=region_code)
if national_dex not in self.pokemon:
self.pokemon[national_dex] = []
self.pokemon[national_dex].append(pokemon_form)
def get_pokemon(self, national_dex: Optional[int] = None, region_code: Optional[int] = None,
form_index: Optional[int] = None, gender_code: Optional[int] = None) -> List[PokemonForm]:
results = []
for dex_forms in self.pokemon.values():
for form in dex_forms:
parts = form.id.split('-')
if (national_dex is None or int(parts[0]) == national_dex) and \
(region_code is None or int(parts[1]) == region_code) and \
(form_index is None or int(parts[2]) == form_index) and \
(gender_code is None or int(parts[3]) == gender_code):
results.append(form)
return results
def get_pokemon_by_id(self, pokemon_id: str) -> Optional[PokemonForm]:
national_dex = int(pokemon_id.split('-')[0])
if national_dex in self.pokemon:
for form in self.pokemon[national_dex]:
if form.id == pokemon_id:
return form
return None
def format_pokemon_id(national_dex: int, region_code: int, form_index: int, gender_code: int) -> str:
return f"{national_dex:04d}-{region_code:02d}-{form_index:03d}-{gender_code}"
def get_pokemon_sprites_page(cache: CacheManager):
url = "https://pokemondb.net/sprites"
return cache.fetch_url(url)
def get_pokemon_sprites_page_data(cache: CacheManager, pokemon_name: str):
url = f"https://pokemondb.net/sprites/{pokemon_name}"
return cache.fetch_url(url)
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 thingy(cache: CacheManager):
db = PokemonDatabase()
pokemon_db_conn = create_pokemon_db()
create_pokemon_storage_db()
page_data = get_pokemon_sprites_page(cache)
if not page_data:
return None
soup = BeautifulSoup(page_data, 'html.parser')
pokemon = soup.find_all('a', class_='infocard')
pokemon_generations = {
1: {"min": 1, "max": 151},
2: {"min": 152, "max": 251},
3: {"min": 252, "max": 386},
4: {"min": 387, "max": 493},
5: {"min": 494, "max": 649},
6: {"min": 650, "max": 721},
7: {"min": 722, "max": 809},
8: {"min": 810, "max": 905},
9: {"min": 906, "max": 1025},
}
national_dex_index = 1
for mon in pokemon:
generation = 1
for gen in pokemon_generations:
if pokemon_generations[gen]["min"] <= national_dex_index <= pokemon_generations[gen]["max"]:
generation = gen
break
pokemon_name = mon.get_text(strip=True)
print(pokemon_name)
pokemon_url_name = pokemon_name.replace("", "-f").replace("", "-m").replace("'", "").replace(".", "").replace('é', 'e').replace(':', '')
pokemon_url_name = pokemon_url_name.replace(" ", "-")
sprites_page_data = get_pokemon_sprites_page_data(cache, pokemon_url_name)
if not sprites_page_data:
return None
sprites_soup = BeautifulSoup(sprites_page_data, 'html.parser')
generation_8_header = sprites_soup.find('h2', string='Generation 8')
if not generation_8_header:
continue
generation_8_table = generation_8_header.find_next('table')
if not generation_8_table:
continue
generation_8_tbody = generation_8_table.find('tbody')
if not generation_8_tbody:
continue
generation_8_rows = generation_8_tbody.find_all('tr')
for row in generation_8_rows:
row_text = row.get_text(strip=True)
if 'Home' in row_text:
sprites = row.find_all('span', class_='sprites-table-card')
if not sprites:
continue
form = 0
for sprite in sprites:
sprite_img = sprite.find('img')
sprite_url = "missing"
if sprite_img:
sprite_url = sprite_img.get('src')
if "shiny" in sprite_url:
continue
form_name = "None"
if sprite.find('small'):
form_name = sprite.find('small').get_text(strip=True)
print(sprite_url, form_name)
if form_name != "None":
form += 1
gender = 0
if form_name == "Female":
form -= 1
gender = 1
elif form_name == "Male":
form -= 1
gender = 2
pokemon_form = PokemonForm(
id=format_pokemon_id(national_dex_index, generation, form, gender),
name=pokemon_name,
form_name=form_name if form_name != "None" else None,
sprite_url=sprite_url,
national_dex=national_dex_index,
generation=generation
)
db.add_pokemon(
national_dex_index,
pokemon_name,
generation,
form,
gender,
form_name if form_name != "None" else None,
sprite_url
)
insert_pokemon_form(pokemon_db_conn, pokemon_form)
storable_in_home = not any(keyword in form_name.lower() for keyword in ['mega', 'gigantamax']) if form_name else True
insert_pokemon_storage(pokemon_db_conn, pokemon_form.id, storable_in_home)
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)}")
if not os.path.exists('images-new'):
os.makedirs('images-new')
for pokemon in db.pokemon.values():
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")
else:
download_image(form.sprite_url, filename)
print(f"Downloaded image for {form.id}")
pokemon_db_conn.close()
if __name__ == "__main__":
cache = CacheManager()
thingy(cache)
cache.close()

BIN
images-new/0001-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0002-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0003-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0003-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0003-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
images-new/0003-01-002-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0004-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images-new/0005-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0006-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0006-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images-new/0006-01-002-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images-new/0006-01-003-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images-new/0007-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images-new/0008-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0009-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0009-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images-new/0009-01-002-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images-new/0010-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0011-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
images-new/0012-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images-new/0012-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images-new/0012-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images-new/0013-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
images-new/0014-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
images-new/0015-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0015-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images-new/0016-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
images-new/0017-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0018-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0018-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0019-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images-new/0019-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0019-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images-new/0020-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0020-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0020-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0021-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0022-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0023-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0024-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0025-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
images-new/0025-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
images-new/0025-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
images-new/0025-01-002-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
images-new/0025-01-003-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images-new/0025-01-004-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
images-new/0025-01-005-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
images-new/0025-01-006-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images-new/0025-01-007-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images-new/0025-01-008-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
images-new/0025-01-009-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0026-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
images-new/0026-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images-new/0026-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0027-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0027-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0028-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0028-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0029-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0030-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0031-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images-new/0032-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0033-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0034-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images-new/0035-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images-new/0036-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0037-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0037-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0038-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0038-01-001-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0039-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0040-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images-new/0041-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0041-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0042-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0042-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images-new/0043-01-000-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images-new/0044-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0044-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0045-01-000-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images-new/0045-01-000-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images-new/0046-01-000-0.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