This project simulates how Pokémon battles go, with limitations:
**Whether a move is physical or special is not taken into account, therefore the highest stat will be used for the move and for defending against it.
All of the battle logic and flow is contained within front-end/src/pages/BattlePage.jsx
Here's a quick rundown of what the logic does:
Let me show you a step-by-step rundown of what happens when the user selects an attack for their Pokémon to use.
handleAttack
// Called when the user clicks a move button on the front-end
const handleAttack = (userMove) => {
// Sets state wherein the user cannot mass click a move/button to bypass the intended turn based nature of the game
setUserHasAttacked(true)
if (!userHasAttacked) {
setEvents([]);
const userPoke = currentPokemon;
const cynPoke = cynthiaPokemon;
const userSpeed = userPoke.pokemon.speed;
const cynSpeed = cynPoke.pokemon.speed;
// Stop if the user's Pokémon has fainted, preventing the player from attacking when their Pokemon is KO'd
if (currentPokemon.pokemon.hp <= 0) {
return;
}
// Stop if the opponent's Pokémon's HP < 0, prompt a win if opponent has no more available Pokemon
if (cynthiaPokemon.pokemon.hp <= 0) {
const nextPokemon = selectNextCynthiaPokemon()
if (nextPokemon) {
setCynthiaPokemon(nextPokemon);
} else {
alert("Cynthia has no more Pokémon left! You win!")
return;
}
}
// If not stopped above, call the performBattle function to proceed with the game flow
if (userSpeed > cynSpeed) {
// User's Pokémon is faster and attacks first
performBattle(userPoke, cynPoke, userMove, selectRandomMove(cynPoke))
} else {
// Cynthia's Pokémon is faster and attacks first
performBattle(cynPoke, userPoke, selectRandomMove(cynPoke), userMove)
}
}
};
performBattle
const performBattle = async (firstAttacker, secondAttacker, firstMove, secondMove) => {
// Calls executeAttack and wait for it to complete
const newHPAfterFirstAttack = await executeAttack(firstAttacker, secondAttacker, firstMove);
// Use a delay to simulate asynchronous attack timing
setTimeout(async () => {
// Check if the second attacker is still able to fight before counterattacking
if (newHPAfterFirstAttack > 0) {
// Execute the second attack
await executeAttack(secondAttacker, firstAttacker, secondMove);
} else {
// Handle the scenario where the first attacker's move was enough to make the second attacker faint
const nextPokemon = selectNextCynthiaPokemon();
if (!nextPokemon) {
alert("Cynthia has no more Pokémon left! You win!");
}
}
// Turn has been played out, reset this state to false to allow user to pick another attack
setUserHasAttacked(false)
}, 7000);
};
executeAttack
const executeAttack = async (attacker, defender, move) => {
// Grabs the higher attack stat, either attack or special attack
const attackStat = attacker.pokemon.attack > attacker.pokemon.special_attack ? attacker.pokemon.attack : attacker.pokemon.special_attack;
// Grabs the higher defense stat, either attack or special attack
const defenseStat = defender.pokemon.defense > defender.pokemon.special_defense ? defender.pokemon.defense : defender.pokemon.special_defense;
// Grabs the power of the move chosen
const power = move.learnable_move.power;
// Generates a multiplier based on type relationship i.e. super effective
const typeEffectiveness = getTypeEffectiveness(move.learnable_move.move_type, defender.pokemon.types);
// STAB = Same Type Attribute Bonus, if the pokemon's type is the same as the move chosen, grants a bonus multiplier
const stab = attacker.pokemon.types.some(type => type.name === move.learnable_move.move_type.name) ? 1.5 : 1;
// Setting the defender's HP and making sure it isn't the attacker's
const defenderCurrentHP = (attacker === currentPokemon) ? cynthiaTeamHP[defender.pokemon.pokedex_number] : ourTeamHP[defender.pokemon.pokedex_number];
// Damage calculation formula for Pokemon grabbed from Bulbapedia
const damage = Math.floor(((2 * 50 / 5 + 2) * power * (attackStat / defenseStat) / 50 + 2) * stab * typeEffectiveness);
// Reflect new HP value after all damage calculation has been done
const newHP = Math.max(defenderCurrentHP - damage, 0);
// Setting defender's appropriate HP value after being attacked
const updateHP = (attacker === currentPokemon) ? setCynthiaTeamHP : setOurTeamHP;
updateHP(prevHP => ({ ...prevHP, [defender.pokemon.pokedex_number]: newHP }));
// Preparing commentary message depending on the effectiveness of the move used
let effectivenessMessage = '';
if (typeEffectiveness > 1) {
effectivenessMessage = "It's super effective!";
} else if (typeEffectiveness < 1 && typeEffectiveness > 0) {
effectivenessMessage = "It's not very effective...";
} else if (typeEffectiveness === 0) {
effectivenessMessage = "It doesn't affect the target..."
}
// Generating commentary message using events from above
await createBattleEvent(attacker, move, damage, effectivenessMessage);
if (newHP <= 0) {
setEvents(prevEvents => [...prevEvents, `${capitalizeAndFormat(defender.pokemon.name)} has fainted!`]);
if (attacker === currentPokemon) {
// If Cynthia's Pokémon faints, select the next Pokémon
const nextPokemon = selectNextCynthiaPokemon();
if (nextPokemon) {
// setCynthiaPokemon(nextPokemon);
} else {
alert("Cynthia has no more Pokémon left! You win!");
}
} else {
return
}
}
return newHP
}
At the moment, I've only implemented rudimentary AI, specifically choosing moves and Pokemon at random.
selectRandomMove
const selectRandomMove = (pokemon) => {
const randomIndex = Math.floor(Math.random() * pokemon.chosen_moves.length)
return pokemon.chosen_moves[randomIndex];
};
selectNextCynthiaPokemon
const selectNextCynthiaPokemon = () => {
// Filter out any Pokémon that has no HP left
const availablePokemon = cynthiaTeam.filter(p => cynthiaTeamHP[p.pokemon.pokedex_number] > 0);
if (availablePokemon.length > 0) {
// Randomly select one of the available Pokémon
return availablePokemon[Math.floor(Math.random() * availablePokemon.length)];
} else {
// If no Pokémon are left, Cynthia has been defeated
alert("Cynthia has no more Pokémon left! You win!");
return null; // Indicate game over
}
};
enemySwitchCounterattack
// Just like the games, the opponent is allowed to prompt a counterattack after you switch Pokemon mid-battle when none of two Pokemon battling have fainted
const enemySwitchCounterattack = (newPokemon) => {
if (ourTeamHP[currentPokemon.pokemon.pokedex_number] > 0) {
alert("The opponent triggers a counterattack as you switch in!")
const randomMove = selectRandomMove(cynthiaPokemon)
executeAttack(cynthiaPokemon, newPokemon, randomMove)
}
return
}
Christopher "Leo" Caniones - chsbca999@gmail.com
LinkedIn: https://www.linkedin.com/in/chriscaniones/
This was my personal submission for my final project at Code Platoon.
I created this project as I wanted to have fun with all that I learned in programming bootcamp. Since I've been playing video games for as long as I could remember, I wanted to try and create something that can simulate Pokémon battles. It was definitely way more than I can take, as I underestimated how much logic was put into a battle system that looked simple as a consumer like me. Now that I've created a foundation for what I want to simulate, I want to improve my skills with this project on the side.