<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Pokémon Battle Simulator</title>
<!-- Basic Reset & Apple-Inspired Minimal Styling -->
<style>
* {
0;
padding: 0;
box-sizing: border-box;
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
body {
background: linear-gradient(to bottom right, #f2f2f2, #dcdcdc);
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
height: 100vh;
padding-top: 1rem;
}
h1 {
1rem;
font-weight: 600;
color: #444;
}
/* Battle Arena Container */
.battle-arena {
position: relative;
width: 80%;
height: 400px;
border-radius: 12px;
background: linear-gradient(to bottom right, #fdfdfd, #ffffff);
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
}
/* Pokémon Avatars and Info */
.pokemon-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 150px;
}
.pokemon-sprite {
width: 80px;
height: 80px;
border-radius: 40px;
background-color: #eee;
color: #555;
display: flex;
align-items: center;
justify-content: center;
0.5rem;
font-weight: 500;
position: relative;
}
.hp-bar-container {
width: 100%;
height: 10px;
border: 1px solid #bbb;
border-radius: 5px;
overflow: hidden;
0.5rem;
}
.hp-bar {
height: 100%;
background-color: #66bb6a;
transition: width 0.4s ease-in-out;
}
/* Active Pokémon Bouncing Animation */
.active {
animation: bounce 1.2s infinite alternate ease-in-out;
}
@keyframes bounce {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-10px);
}
}
/* Attack Icon for Fire, Grass, Water, etc. */
.attack-icon {
position: absolute;
font-size: 1.5rem;
opacity: 0;
transform: translate(0, 0);
transition: transform 0.8s ease, opacity 0.8s ease;
}
/* Example color icons: Feel free to add more if you like */
.attack-icon.fire {
color: #ff5722;
}
.attack-icon.grass {
color: #4caf50;
}
.attack-icon.water {
color: #2196f3;
}
</style>
</head>
<body>
<h1>Pokémon Battle Simulator</h1>
<div class="battle-arena">
<!-- Left Side (Attacker 1) -->
<div class="pokemon-container">
<div id="pokemon1Sprite" class="pokemon-sprite">
<!-- Name goes here dynamically -->
</div>
<div class="hp-bar-container">
<div id="pokemon1HpBar" class="hp-bar"></div>
</div>
<div id="pokemon1HpText"></div>
</div>
<!-- Right Side (Attacker 2) -->
<div class="pokemon-container">
<div id="pokemon2Sprite" class="pokemon-sprite">
<!-- Name goes here dynamically -->
</div>
<div class="hp-bar-container">
<div id="pokemon2HpBar" class="hp-bar"></div>
</div>
<div id="pokemon2HpText"></div>
</div>
</div>
<script>
/*********************************************
* 1. Pokémon Representation
*********************************************/
const charmander = {
name: "Charmander",
hp: 39,
maxHp: 39,
types: ["Fire"],
moves: [
{ name: "Ember", type: "Fire", power: 40, accuracy: 1.0 },
{ name: "Scratch", type: "Normal", power: 40, accuracy: 1.0 }
],
resonance: 0 // Start resonance at 0
};
const bulbasaur = {
name: "Bulbasaur",
hp: 45,
maxHp: 45,
types: ["Grass"],
moves: [
{ name: "Vine Whip", type: "Grass", power: 45, accuracy: 1.0 },
{ name: "Tackle", type: "Normal", power: 40, accuracy: 1.0 }
],
resonance: 0
};
/*********************************************
* 2. Type Effectiveness Representation
*
* Fire > Grass (2x)
* Grass > Water (2x)
* Water > Fire (2x)
* Fire < Water (0.5x)
* Grass < Fire (0.5x)
* Water < Grass (0.5x)
* All else 1x
*********************************************/
const typeEffectivenessChart = {
Fire: { Grass: 2, Water: 0.5, Fire: 1 },
Grass: { Water: 2, Fire: 0.5, Grass: 1 },
Water: { Fire: 2, Grass: 0.5, Water: 1 }
// For simplicity, we only map these interactions.
// Any type not listed defaults to 1.0 as needed in the function below.
};
/**
* calculateTypeEffectiveness(moveType, targetTypes)
* Returns the total effectiveness multiplier
* based on the target's dual-types (or single type).
*/
function calculateTypeEffectiveness(moveType, targetTypes) {
let multiplier = 1;
targetTypes.forEach(targetType => {
const effectivenessMap = typeEffectivenessChart[moveType] || {};
const typeMultiplier = effectivenessMap[targetType] || 1;
multiplier *= typeMultiplier;
});
return multiplier;
}
/*********************************************
* 3. Damage Calculation w/ Synergy Resonance
*********************************************/
function calculateDamage(move, attacker, defender) {
// Check resonance: If attacker has 3 or more,
// and the move's type matches one of attacker’s own types => +10 to power
let movePower = move.power;
if (attacker.resonance >= 3 && attacker.types.includes(move.type)) {
console.log(`${attacker.name}'s Synergy Resonance activates! +10 Power to ${move.name}.`);
movePower += 10;
attacker.resonance = 0; // Reset resonance
}
const effectiveness = calculateTypeEffectiveness(move.type, defender.types);
const damage = movePower * effectiveness;
// Adjust resonance if super/not very effective
if (effectiveness > 1) {
attacker.resonance++;
console.log(`${attacker.name}'s resonance increased to ${attacker.resonance} (super effective)!`);
} else if (effectiveness < 1) {
defender.resonance++;
console.log(`${defender.name}'s resonance increased to ${defender.resonance} (resisted attack)!`);
}
return damage;
}
/**
* Executes a single attack turn
* @param {Object} attacker
* @param {Object} defender
* @param {Object} move
*/
function executeTurn(attacker, defender, move) {
// Announce the move
console.log(`${attacker.name} used ${move.name}!`);
// Roll accuracy
if (Math.random() <= move.accuracy) {
const dmg = calculateDamage(move, attacker, defender);
const finalDamage = Math.min(dmg, defender.hp); // can't go below 0
defender.hp -= finalDamage;
// Check effectiveness text
const effectiveness = calculateTypeEffectiveness(move.type, defender.types);
if (effectiveness > 1) {
console.log("It's super effective!");
} else if (effectiveness < 1) {
console.log("It's not very effective...");
}
console.log(`${defender.name} took ${finalDamage.toFixed(2)} damage. (HP left: ${defender.hp})`);
if (defender.hp <= 0) {
console.log(`${defender.name} fainted!`);
}
// Trigger a little attack animation
animateAttack(move.type, attacker, defender);
} else {
console.log(`${attacker.name}'s attack missed!`);
}
// Update UI after turn
updateHpBarsUI();
}
/*********************************************
* 4. Animate Attack
* For a "Fire" type move, add a fire icon that
* travels from attacker to defender.
*********************************************/
function animateAttack(moveType, attacker, defender) {
const attackerSprite =
(attacker === pokemon1) ? document.getElementById("pokemon1Sprite")
: document.getElementById("pokemon2Sprite");
const defenderSprite =
(defender === pokemon1) ? document.getElementById("pokemon1Sprite")
: document.getElementById("pokemon2Sprite");
// Create an icon (🔥, 💧, or something else)
let iconSymbol = "💥";
if (moveType.toLowerCase() === "fire") iconSymbol = "🔥";
if (moveType.toLowerCase() === "water") iconSymbol = "💧";
if (moveType.toLowerCase() === "grass") iconSymbol = "🌱";
const icon = document.createElement("div");
icon.classList.add("attack-icon", moveType.toLowerCase());
icon.textContent = iconSymbol;
document.body.appendChild(icon);
// Position it at the center of the attacker sprite
const attackerRect = attackerSprite.getBoundingClientRect();
const defenderRect = defenderSprite.getBoundingClientRect();
icon.style.left = attackerRect.left + attackerRect.width/2 + "px";
icon.style.top = attackerRect.top + attackerRect.height/2 + "px";
icon.style.opacity = 1;
// Force reflow so transition will trigger
void icon.offsetWidth;
// Animate it to the defender
icon.style.transform = `translate(${defenderRect.left - attackerRect.left}px,
${defenderRect.top - attackerRect.top}px)`;
// Fade out after traveling
setTimeout(() => {
icon.style.opacity = 0;
}, 500);
// Remove from DOM after completed
setTimeout(() => {
if (icon.parentNode) {
icon.parentNode.removeChild(icon);
}
}, 1300);
}
/*********************************************
* 5. UI Updaters
*********************************************/
let pokemon1 = null;
let pokemon2 = null;
function updateHpBarsUI() {
const p1HpBar = document.getElementById("pokemon1HpBar");
const p2HpBar = document.getElementById("pokemon2HpBar");
const p1HpText = document.getElementById("pokemon1HpText");
const p2HpText = document.getElementById("pokemon2HpText");
p1HpBar.style.width = `${(pokemon1.hp / pokemon1.maxHp) * 100}%`;
p2HpBar.style.width = `${(pokemon2.hp / pokemon2.maxHp) * 100}%`;
p1HpText.textContent = `${pokemon1.hp}/${pokemon1.maxHp}`;
p2HpText.textContent = `${pokemon2.hp}/${pokemon2.maxHp}`;
}
function setActivePokemonUI(activeIndex) {
const p1Sprite = document.getElementById("pokemon1Sprite");
const p2Sprite = document.getElementById("pokemon2Sprite");
p1Sprite.classList.remove("active");
p2Sprite.classList.remove("active");
if (activeIndex === 1) {
p1Sprite.classList.add("active");
} else {
p2Sprite.classList.add("active");
}
}
/*********************************************
* 6. Start Battle - Turn Loop
*********************************************/
function startBattle(p1, p2) {
pokemon1 = p1;
pokemon2 = p2;
// Initialize UI
document.getElementById("pokemon1Sprite").textContent = pokemon1.name;
document.getElementById("pokemon2Sprite").textContent = pokemon2.name;
updateHpBarsUI();
// Simple alternating turn-based battle
console.log(`A wild battle begins between ${pokemon1.name} and ${pokemon2.name}!`);
let active = 1; // 1 => pokemon1's turn, 2 => pokemon2's turn
let turnCount = 0;
const turnInterval = setInterval(() => {
if (pokemon1.hp <= 0 || pokemon2.hp <= 0) {
// Battle ends
clearInterval(turnInterval);
console.log("Battle concluded.");
return;
}
turnCount++;
console.log(`\n--- TURN ${turnCount} ---`);
// Set active UI
setActivePokemonUI(active);
// Choose a random move
if (active === 1) {
const moveIndex = Math.floor(Math.random() * pokemon1.moves.length);
executeTurn(pokemon1, pokemon2, pokemon1.moves[moveIndex]);
active = 2;
} else {
const moveIndex = Math.floor(Math.random() * pokemon2.moves.length);
executeTurn(pokemon2, pokemon1, pokemon2.moves[moveIndex]);
active = 1;
}
}, 2000); // 2 seconds per turn for demonstration
}
// Example scenario: Charmander (Fire) vs. Bulbasaur (Grass)
// The synergy resonance will naturally play out if you watch the console logs.
startBattle(charmander, bulbasaur);
</script>
</body>
</html>