chsbca / Pokemon-Battle-Simulator

Personal project where I used my newly learned fullstack skills to try and recreate the famous Pokemon battle system
0 stars 0 forks source link

Welcome to the Pokémon Battle Simulator!

About the Project

Screenshot

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.

Features

Built With

Usage

Summary of Logic

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:

Screenshot

Battle System

Let me show you a step-by-step rundown of what happens when the user selects an attack for their Pokémon to use.

  1. 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)
            }
        }
    };
  2. 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);
    
    };
  3. 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
    }

Opponent AI

At the moment, I've only implemented rudimentary AI, specifically choosing moves and Pokemon at random.

  1. selectRandomMove

    const selectRandomMove = (pokemon) => {
        const randomIndex = Math.floor(Math.random() * pokemon.chosen_moves.length)
        return pokemon.chosen_moves[randomIndex];
    };
  2. 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
        }
    };
  3. 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
    }

Roadmap

Improving Battle Logic

Improving AI

Contact

Christopher "Leo" Caniones - chsbca999@gmail.com

LinkedIn: https://www.linkedin.com/in/chriscaniones/

Creator's Thoughts

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.