@ -1,4 +1,6 @@ |
|||
pokemon_cache.db |
|||
pokemon_database.db |
|||
Utilities/venv/ |
|||
venv/ |
|||
venv/ |
|||
DBVisualiser/node_modules/ |
|||
Utilities/__pycache__/ |
|||
@ -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">×</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> |
|||
@ -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 }); |
|||
} |
|||
}); |
|||
}); |
|||
@ -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,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); |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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() |
|||
@ -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 |
|||
@ -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_()) |
|||
@ -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() |
|||
@ -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() |
|||
@ -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() |
|||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.9 KiB |