dnd-style-card-game / index.html
Juno360219's picture
Add 1 files
c77b1f4 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arcane Chronicles: Battle of Realms</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Fauna+One&display=swap');
body {
font-family: 'Fauna One', serif;
background-color: #0f172a;
color: #e2e8f0;
background-image: url('https://images.unsplash.com/photo-1518562180175-34a163b1a9fa?q=80&w=2070&auto=format&fit=crop');
background-size: cover;
background-attachment: fixed;
}
.card {
transition: all 0.3s ease;
transform-style: preserve-3d;
perspective: 1000px;
}
.card:hover {
transform: translateY(-10px) scale(1.03);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
}
.card-front {
backface-visibility: hidden;
}
.card-back {
backface-visibility: hidden;
transform: rotateY(180deg);
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
}
.flipped {
transform: rotateY(180deg);
}
.health-bar {
height: 6px;
transition: width 0.3s ease;
}
.lane {
min-height: 250px;
background: rgba(30, 41, 59, 0.5);
border: 2px solid rgba(148, 163, 184, 0.3);
border-radius: 8px;
}
.attack-animation {
animation: attack 0.5s ease;
}
@keyframes attack {
0% { transform: translateX(0); }
50% { transform: translateX(20px); }
100% { transform: translateX(0); }
}
.damage-text {
animation: floatUp 1s ease forwards;
position: absolute;
color: #f87171;
font-weight: bold;
pointer-events: none;
font-size: 1.5rem;
}
@keyframes floatUp {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(-50px); opacity: 0; }
}
.title-font {
font-family: 'Cinzel', serif;
}
.card-tooltip {
visibility: hidden;
width: 250px;
background-color: #1e293b;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 10px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
border: 1px solid #475569;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
}
.card:hover .card-tooltip {
visibility: visible;
opacity: 1;
}
.end-turn-btn {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 40;
transition: all 0.3s ease;
}
.end-turn-btn:hover {
transform: scale(1.05);
}
/* Larger card styles */
.hand-card {
width: 160px;
height: 240px;
}
.board-card {
width: 180px;
height: 270px;
}
.card-image {
height: 160px;
background-size: cover;
background-position: center;
}
.board-card .card-image {
height: 180px;
}
.card-stats {
font-size: 1.1rem;
}
.card-name {
font-size: 1.1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-cost {
width: 28px;
height: 28px;
font-size: 1rem;
}
.card-type {
font-size: 0.9rem;
}
.card-keyword {
font-size: 0.8rem;
}
</style>
</head>
<body class="min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="flex flex-col items-center mb-8">
<h1 class="title-font text-5xl font-bold text-amber-400 mb-2 text-center">Arcane Chronicles</h1>
<h2 class="title-font text-2xl text-amber-200 mb-6 text-center">Battle of Realms</h2>
<div class="flex space-x-4 mb-6">
<button id="new-game-btn" class="bg-amber-600 hover:bg-amber-700 text-white px-6 py-2 rounded-lg font-semibold transition">
<i class="fas fa-play mr-2"></i>New Game
</button>
<button id="rules-btn" class="bg-slate-700 hover:bg-slate-600 text-white px-6 py-2 rounded-lg font-semibold transition">
<i class="fas fa-scroll mr-2"></i>Rules
</button>
</div>
<div class="flex justify-between w-full max-w-2xl bg-slate-800 p-4 rounded-lg">
<div class="text-center">
<div class="text-xl font-semibold text-amber-300">Player</div>
<div id="player-health" class="text-3xl font-bold">30</div>
</div>
<div class="text-center">
<div class="text-xl font-semibold">Mana</div>
<div id="current-mana" class="text-3xl font-bold text-blue-400">0/10</div>
</div>
<div class="text-center">
<div class="text-xl font-semibold text-red-300">Enemy</div>
<div id="enemy-health" class="text-3xl font-bold">30</div>
</div>
</div>
</header>
<!-- Game Board -->
<div class="game-board mb-8">
<!-- Enemy Lane -->
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="lane" id="enemy-field-lane">
<!-- Enemy cards will appear here -->
</div>
<div class="lane" id="enemy-shadow-lane">
<!-- Enemy cards will appear here -->
</div>
</div>
<!-- Player Lane -->
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="lane" id="player-field-lane">
<!-- Player cards will appear here -->
</div>
<div class="lane" id="player-shadow-lane">
<!-- Player cards will appear here -->
</div>
</div>
</div>
<!-- Player Hand -->
<div class="player-hand-container bg-slate-900 bg-opacity-70 p-4 rounded-t-lg">
<h3 class="text-xl font-semibold mb-4 text-center">Your Hand</h3>
<div class="flex justify-center flex-wrap gap-4 min-h-40" id="player-hand">
<!-- Player cards will appear here -->
</div>
</div>
<!-- End Turn Button -->
<button id="end-turn-btn" class="end-turn-btn bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-full font-semibold transition shadow-lg hidden">
<i class="fas fa-hourglass-end mr-2"></i>End Turn
</button>
<!-- Modals -->
<div id="rules-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50">
<div class="bg-slate-800 rounded-lg p-6 max-w-2xl max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-2xl font-bold text-amber-300">Game Rules</h3>
<button id="close-rules" class="text-slate-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<h4 class="text-lg font-semibold text-amber-200 mb-2">Objective</h4>
<p>Reduce your opponent's health to zero by strategically playing creatures and using abilities.</p>
</div>
<div>
<h4 class="text-lg font-semibold text-amber-200 mb-2">Lanes</h4>
<p>The battlefield has two lanes: Field Lane and Shadow Lane.</p>
<ul class="list-disc pl-5 mt-2 space-y-1">
<li><span class="font-semibold">Field Lane:</span> Creatures here can attack the turn they're played.</li>
<li><span class="font-semibold">Shadow Lane:</span> Creatures here gain Cover for one turn, protecting them from attacks.</li>
</ul>
</div>
<div>
<h4 class="text-lg font-semibold text-amber-200 mb-2">Mana System</h4>
<p>You start with 1 mana and gain 1 additional mana each turn (max 10).</p>
</div>
<div>
<h4 class="text-lg font-semibold text-amber-200 mb-2">Card Types</h4>
<ul class="list-disc pl-5 mt-2 space-y-1">
<li><span class="font-semibold">Creatures:</span> Can attack enemy creatures or the opponent directly.</li>
<li><span class="font-semibold">Actions:</span> One-time effects that can deal damage, heal, or modify the board.</li>
<li><span class="font-semibold">Supports:</span> Ongoing effects that remain on the board until destroyed.</li>
</ul>
</div>
<div>
<h4 class="text-lg font-semibold text-amber-200 mb-2">Keywords</h4>
<div class="grid grid-cols-2 gap-2 mt-2">
<div class="bg-slate-700 p-2 rounded">
<span class="font-semibold">Breakthrough:</span> Excess damage goes to the opponent.
</div>
<div class="bg-slate-700 p-2 rounded">
<span class="font-semibold">Drain:</span> Heals your health equal to damage dealt.
</div>
<div class="bg-slate-700 p-2 rounded">
<span class="font-semibold">Guard:</span> Must be attacked before other targets.
</div>
<div class="bg-slate-700 p-2 rounded">
<span class="font-semibold">Lethal:</span> Destroys any creature it damages.
</div>
</div>
</div>
</div>
</div>
</div>
<div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50">
<div class="bg-slate-800 rounded-lg p-8 max-w-md text-center">
<h3 id="game-result" class="text-3xl font-bold mb-4">Victory!</h3>
<p id="game-over-text" class="mb-6">You have defeated your opponent!</p>
<button id="play-again-btn" class="bg-amber-600 hover:bg-amber-700 text-white px-6 py-2 rounded-lg font-semibold transition">
<i class="fas fa-redo mr-2"></i>Play Again
</button>
</div>
</div>
<div id="turn-indicator" class="fixed top-4 right-4 bg-slate-800 bg-opacity-80 px-4 py-2 rounded-lg shadow-lg hidden">
<div class="flex items-center">
<div id="turn-arrow" class="text-amber-400 mr-2">
<i class="fas fa-arrow-right"></i>
</div>
<span id="turn-text" class="font-semibold">Your Turn</span>
</div>
</div>
</div>
<script>
// Game State
const gameState = {
playerHealth: 30,
enemyHealth: 30,
playerMana: 0,
maxMana: 0,
turn: 'player', // 'player' or 'enemy'
turnCount: 0,
playerHand: [],
playerFieldLane: [],
playerShadowLane: [],
enemyFieldLane: [],
enemyShadowLane: [],
playerDeck: [],
enemyDeck: [],
enemyHand: [],
gameStarted: false
};
// Card Database with more low-cost cards
const cardDatabase = [
// Low cost creatures (1-3 mana)
{
id: 1,
name: "Arcane Apprentice",
type: "creature",
cost: 1,
attack: 1,
health: 2,
keywords: [],
lane: "field",
description: "A novice mage learning the arcane arts.",
image: "https://images.unsplash.com/photo-1542273917363-3b1817f69a2b?q=80&w=2074&auto=format&fit=crop"
},
{
id: 2,
name: "Forest Sprite",
type: "creature",
cost: 1,
attack: 1,
health: 1,
keywords: [],
lane: "shadow",
description: "A tiny woodland spirit that moves swiftly.",
image: "https://images.unsplash.com/photo-1519457431-44ccd64a7b47?q=80&w=1935&auto=format&fit=crop"
},
{
id: 3,
name: "Town Guard",
type: "creature",
cost: 2,
attack: 2,
health: 2,
keywords: ["Guard"],
lane: "field",
description: "Protects the town from threats.",
image: "https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0?q=80&w=2070&auto=format&fit=crop"
},
{
id: 4,
name: "Shadow Thief",
type: "creature",
cost: 2,
attack: 3,
health: 1,
keywords: [],
lane: "shadow",
description: "Strikes quickly from the darkness.",
image: "https://images.unsplash.com/photo-1518709268805-4e9042af9f23?q=80&w=2048&auto=format&fit=crop"
},
{
id: 5,
name: "Arcane Sentinel",
type: "creature",
cost: 3,
attack: 2,
health: 3,
keywords: ["Guard"],
lane: "field",
description: "A mystical guardian that protects its allies.",
image: "https://images.unsplash.com/photo-1476231682828-37e571bc172f?q=80&w=1974&auto=format&fit=crop"
},
{
id: 6,
name: "Shadow Assassin",
type: "creature",
cost: 3,
attack: 4,
health: 2,
keywords: ["Lethal"],
lane: "shadow",
description: "Strikes from the darkness with deadly precision.",
image: "https://images.unsplash.com/photo-1519457431-44ccd64a7b47?q=80&w=1935&auto=format&fit=crop"
},
{
id: 7,
name: "Mountain Wolf",
type: "creature",
cost: 3,
attack: 3,
health: 3,
keywords: [],
lane: "field",
description: "A fierce predator of the highlands.",
image: "https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0?q=80&w=2070&auto=format&fit=crop"
},
// Mid cost creatures (4-6 mana)
{
id: 8,
name: "Stormcaller",
type: "creature",
cost: 4,
attack: 3,
health: 4,
keywords: ["Breakthrough"],
lane: "field",
description: "Channels the fury of the storm into devastating attacks.",
image: "https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0?q=80&w=2070&auto=format&fit=crop"
},
{
id: 9,
name: "Vampire Noble",
type: "creature",
cost: 5,
attack: 4,
health: 4,
keywords: ["Drain"],
lane: "shadow",
description: "Feeds on the life force of its enemies.",
image: "https://images.unsplash.com/photo-1518709268805-4e9042af9f23?q=80&w=2048&auto=format&fit=crop"
},
{
id: 10,
name: "Ancient Treant",
type: "creature",
cost: 6,
attack: 2,
health: 6,
keywords: ["Guard"],
lane: "field",
description: "An ancient protector of the forests.",
image: "https://images.unsplash.com/photo-1476231682828-37e571bc172f?q=80&w=1974&auto=format&fit=crop"
},
// Actions
{
id: 11,
name: "Spark",
type: "action",
cost: 1,
effect: "dealDamage",
value: 2,
description: "Deals 2 damage to a target.",
image: "https://images.unsplash.com/photo-1518562180175-34a163b1a9fa?q=80&w=2070&auto=format&fit=crop"
},
{
id: 12,
name: "Minor Heal",
type: "action",
cost: 1,
effect: "heal",
value: 3,
description: "Restore 3 health to your hero.",
image: "https://images.unsplash.com/photo-1534447677768-be436bb09401?q=80&w=2094&auto=format&fit=crop"
},
{
id: 13,
name: "Fireball",
type: "action",
cost: 3,
effect: "dealDamage",
value: 4,
description: "Hurl a fiery projectile that deals 4 damage.",
image: "https://images.unsplash.com/photo-1518562180175-34a163b1a9fa?q=80&w=2070&auto=format&fit=crop"
},
{
id: 14,
name: "Healing Light",
type: "action",
cost: 2,
effect: "heal",
value: 5,
description: "Restore 5 health to your hero.",
image: "https://images.unsplash.com/photo-1534447677768-be436bb09401?q=80&w=2094&auto=format&fit=crop"
},
{
id: 15,
name: "Soul Trap",
type: "action",
cost: 4,
effect: "destroyCreature",
description: "Destroy an enemy creature with 3 or less attack.",
image: "https://images.unsplash.com/photo-1534447677768-be436bb09401?q=80&w=2094&auto=format&fit=crop"
},
// Supports
{
id: 16,
name: "Training Grounds",
type: "support",
cost: 2,
effect: "buffCreatures",
value: 1,
description: "Your creatures have +1 attack.",
image: "https://images.unsplash.com/photo-1535905557558-3c2747c3d7a3?q=80&w=2070&auto=format&fit=crop"
},
{
id: 17,
name: "Arcane Library",
type: "support",
cost: 4,
effect: "drawCard",
value: 1,
description: "At the start of your turn, draw an extra card.",
image: "https://images.unsplash.com/photo-1535905557558-3c2747c3d7a3?q=80&w=2070&auto=format&fit=crop"
},
{
id: 18,
name: "Ward of Protection",
type: "support",
cost: 3,
effect: "reduceDamage",
value: 1,
description: "Your hero takes 1 less damage from all sources.",
image: "https://images.unsplash.com/photo-1505506874110-0a91101d6d41?q=80&w=2070&auto=format&fit=crop"
}
];
// DOM Elements
const playerHealthEl = document.getElementById('player-health');
const enemyHealthEl = document.getElementById('enemy-health');
const currentManaEl = document.getElementById('current-mana');
const playerHandEl = document.getElementById('player-hand');
const playerFieldLaneEl = document.getElementById('player-field-lane');
const playerShadowLaneEl = document.getElementById('player-shadow-lane');
const enemyFieldLaneEl = document.getElementById('enemy-field-lane');
const enemyShadowLaneEl = document.getElementById('enemy-shadow-lane');
const newGameBtn = document.getElementById('new-game-btn');
const rulesBtn = document.getElementById('rules-btn');
const closeRulesBtn = document.getElementById('close-rules');
const rulesModal = document.getElementById('rules-modal');
const gameOverModal = document.getElementById('game-over-modal');
const gameResultEl = document.getElementById('game-result');
const gameOverTextEl = document.getElementById('game-over-text');
const playAgainBtn = document.getElementById('play-again-btn');
const turnIndicator = document.getElementById('turn-indicator');
const turnText = document.getElementById('turn-text');
const turnArrow = document.getElementById('turn-arrow');
const endTurnBtn = document.getElementById('end-turn-btn');
// Event Listeners
newGameBtn.addEventListener('click', startNewGame);
rulesBtn.addEventListener('click', () => rulesModal.classList.remove('hidden'));
closeRulesBtn.addEventListener('click', () => rulesModal.classList.add('hidden'));
playAgainBtn.addEventListener('click', startNewGame);
endTurnBtn.addEventListener('click', endPlayerTurn);
// Game Functions
function startNewGame() {
// Reset game state
gameState.playerHealth = 30;
gameState.enemyHealth = 30;
gameState.playerMana = 0;
gameState.maxMana = 0;
gameState.turn = 'player';
gameState.turnCount = 0;
gameState.playerHand = [];
gameState.playerFieldLane = [];
gameState.playerShadowLane = [];
gameState.enemyFieldLane = [];
gameState.enemyShadowLane = [];
gameState.playerDeck = [];
gameState.enemyDeck = [];
gameState.enemyHand = [];
gameState.gameStarted = true;
// Hide modals
gameOverModal.classList.add('hidden');
rulesModal.classList.add('hidden');
// Update UI
playerHealthEl.textContent = gameState.playerHealth;
enemyHealthEl.textContent = gameState.enemyHealth;
currentManaEl.textContent = `${gameState.playerMana}/${gameState.maxMana}`;
// Clear board
playerHandEl.innerHTML = '';
playerFieldLaneEl.innerHTML = '';
playerShadowLaneEl.innerHTML = '';
enemyFieldLaneEl.innerHTML = '';
enemyShadowLaneEl.innerHTML = '';
// Create decks
createDecks();
// Draw starting hands
drawStartingHands();
// Start first turn
startPlayerTurn();
// Show turn indicator
turnIndicator.classList.remove('hidden');
updateTurnIndicator();
// Show end turn button
endTurnBtn.classList.remove('hidden');
}
function createDecks() {
// Create player deck (30 cards)
for (let i = 0; i < 30; i++) {
const randomCard = {...cardDatabase[Math.floor(Math.random() * cardDatabase.length)]};
gameState.playerDeck.push(randomCard);
}
// Create enemy deck (30 cards)
for (let i = 0; i < 30; i++) {
const randomCard = {...cardDatabase[Math.floor(Math.random() * cardDatabase.length)]};
gameState.enemyDeck.push(randomCard);
}
}
function drawStartingHands() {
// Player draws 3 cards
for (let i = 0; i < 3; i++) {
drawCard('player');
}
// Enemy draws 3 cards
for (let i = 0; i < 3; i++) {
drawCard('enemy');
}
}
function drawCard(who) {
if (who === 'player') {
if (gameState.playerDeck.length > 0) {
const card = gameState.playerDeck.pop();
gameState.playerHand.push(card);
renderPlayerHand();
}
} else {
if (gameState.enemyDeck.length > 0) {
const card = gameState.enemyDeck.pop();
gameState.enemyHand.push(card);
}
}
}
function startPlayerTurn() {
gameState.turn = 'player';
gameState.turnCount++;
// Increase max mana (up to 10)
if (gameState.maxMana < 10) {
gameState.maxMana++;
}
// Refill mana
gameState.playerMana = gameState.maxMana;
// Draw a card
drawCard('player');
// Update UI
updateTurnIndicator();
currentManaEl.textContent = `${gameState.playerMana}/${gameState.maxMana}`;
// Check for summoning sickness
updateSummoningSickness();
// Enable player actions
enablePlayerActions();
// Show end turn button
endTurnBtn.classList.remove('hidden');
}
function endPlayerTurn() {
// Hide end turn button
endTurnBtn.classList.add('hidden');
// Start enemy turn
startEnemyTurn();
}
function startEnemyTurn() {
gameState.turn = 'enemy';
updateTurnIndicator();
// Disable player actions
disablePlayerActions();
// Enemy draws a card
drawCard('enemy');
// Simple AI: Play cards and attack
setTimeout(() => {
enemyPlayCards();
setTimeout(() => {
enemyAttack();
// End enemy turn after a delay
setTimeout(() => {
startPlayerTurn();
}, 1500);
}, 1500);
}, 1500);
}
function enemyPlayCards() {
// Simple AI: Play cards if possible
const playableCards = gameState.enemyHand.filter(card => card.cost <= gameState.maxMana);
for (const card of playableCards) {
if (Math.random() > 0.3) { // 70% chance to play each card
// Determine lane
const lane = card.lane || (Math.random() > 0.5 ? 'field' : 'shadow');
// Play the card
playCard('enemy', card, lane);
// Remove from hand
const cardIndex = gameState.enemyHand.findIndex(c => c.id === card.id);
if (cardIndex !== -1) {
gameState.enemyHand.splice(cardIndex, 1);
}
}
}
}
function enemyAttack() {
// Simple AI: Attack with all creatures
const allEnemyCreatures = [...gameState.enemyFieldLane, ...gameState.enemyShadowLane];
for (const creature of allEnemyCreatures) {
if (!creature.hasAttacked && !creature.summoningSickness) {
// Find a target (simplified - just attack player directly)
attackTarget('enemy', creature, {type: 'player'});
}
}
}
function playCard(who, card, lane) {
if (who === 'player') {
// Check if player has enough mana
if (gameState.playerMana < card.cost) {
alert("Not enough mana!");
return;
}
// Spend mana
gameState.playerMana -= card.cost;
currentManaEl.textContent = `${gameState.playerMana}/${gameState.maxMana}`;
// Remove card from hand
const cardIndex = gameState.playerHand.findIndex(c => c.id === card.id);
if (cardIndex !== -1) {
gameState.playerHand.splice(cardIndex, 1);
}
// Add to appropriate lane
if (card.type === 'creature') {
const creature = {...card, hasAttacked: false, summoningSickness: true};
if (lane === 'field') {
gameState.playerFieldLane.push(creature);
} else {
gameState.playerShadowLane.push(creature);
}
}
// Re-render
renderPlayerHand();
renderPlayerLanes();
} else {
// Enemy plays a card
if (card.type === 'creature') {
const creature = {...card, hasAttacked: false, summoningSickness: true};
if (lane === 'field') {
gameState.enemyFieldLane.push(creature);
} else {
gameState.enemyShadowLane.push(creature);
}
}
renderEnemyLanes();
}
}
function attackTarget(attackerWho, attacker, target) {
if (attackerWho === 'player') {
if (attacker.summoningSickness) {
alert("This creature has summoning sickness and can't attack this turn!");
return;
}
if (attacker.hasAttacked) {
alert("This creature has already attacked this turn!");
return;
}
// Mark as attacked
attacker.hasAttacked = true;
// Apply damage
if (target.type === 'creature') {
// Creature vs creature combat
target.health -= attacker.attack;
attacker.health -= target.attack;
// Check for deaths
checkForDeaths();
// Show damage animation
showDamageAnimation(target, attacker.attack);
showDamageAnimation(attacker, target.attack);
} else if (target.type === 'player') {
// Attack enemy directly
gameState.enemyHealth -= attacker.attack;
enemyHealthEl.textContent = gameState.enemyHealth;
// Show damage animation
showDamageAnimation({element: enemyHealthEl}, attacker.attack);
// Check for game over
if (gameState.enemyHealth <= 0) {
endGame('player');
}
}
// Re-render
renderPlayerLanes();
renderEnemyLanes();
} else {
// Enemy attacks
if (target.type === 'creature') {
// Creature vs creature combat
target.health -= attacker.attack;
attacker.health -= target.attack;
// Check for deaths
checkForDeaths();
// Show damage animation
showDamageAnimation(target, attacker.attack);
showDamageAnimation(attacker, target.attack);
} else if (target.type === 'player') {
// Attack player directly
gameState.playerHealth -= attacker.attack;
playerHealthEl.textContent = gameState.playerHealth;
// Show damage animation
showDamageAnimation({element: playerHealthEl}, attacker.attack);
// Check for game over
if (gameState.playerHealth <= 0) {
endGame('enemy');
}
}
// Re-render
renderPlayerLanes();
renderEnemyLanes();
}
}
function checkForDeaths() {
// Check player field lane
gameState.playerFieldLane = gameState.playerFieldLane.filter(creature => creature.health > 0);
// Check player shadow lane
gameState.playerShadowLane = gameState.playerShadowLane.filter(creature => creature.health > 0);
// Check enemy field lane
gameState.enemyFieldLane = gameState.enemyFieldLane.filter(creature => creature.health > 0);
// Check enemy shadow lane
gameState.enemyShadowLane = gameState.enemyShadowLane.filter(creature => creature.health > 0);
}
function endGame(winner) {
gameState.gameStarted = false;
if (winner === 'player') {
gameResultEl.textContent = "Victory!";
gameOverTextEl.textContent = "You have defeated your opponent!";
} else {
gameResultEl.textContent = "Defeat";
gameOverTextEl.textContent = "Your opponent has defeated you!";
}
gameOverModal.classList.remove('hidden');
turnIndicator.classList.add('hidden');
endTurnBtn.classList.add('hidden');
}
function updateSummoningSickness() {
// Remove summoning sickness from player creatures
gameState.playerFieldLane.forEach(creature => {
if (creature.summoningSickness) {
creature.summoningSickness = false;
}
creature.hasAttacked = false;
});
gameState.playerShadowLane.forEach(creature => {
if (creature.summoningSickness) {
creature.summoningSickness = false;
}
creature.hasAttacked = false;
});
// Re-render
renderPlayerLanes();
}
function enablePlayerActions() {
// Enable hand cards
const handCards = document.querySelectorAll('.hand-card');
handCards.forEach(card => {
card.classList.remove('opacity-50', 'cursor-not-allowed');
card.classList.add('cursor-pointer', 'hover:scale-105');
});
// Enable creatures to attack
const playerCreatures = document.querySelectorAll('#player-field-lane .creature, #player-shadow-lane .creature');
playerCreatures.forEach(creature => {
if (!creature.dataset.hasAttacked && !creature.dataset.summoningSickness) {
creature.classList.add('cursor-pointer', 'hover:scale-105');
}
});
}
function disablePlayerActions() {
// Disable hand cards
const handCards = document.querySelectorAll('.hand-card');
handCards.forEach(card => {
card.classList.add('opacity-50', 'cursor-not-allowed');
card.classList.remove('cursor-pointer', 'hover:scale-105');
});
// Disable creatures from attacking
const playerCreatures = document.querySelectorAll('#player-field-lane .creature, #player-shadow-lane .creature');
playerCreatures.forEach(creature => {
creature.classList.remove('cursor-pointer', 'hover:scale-105');
});
}
function updateTurnIndicator() {
if (gameState.turn === 'player') {
turnText.textContent = "Your Turn";
turnArrow.innerHTML = '<i class="fas fa-arrow-right"></i>';
} else {
turnText.textContent = "Enemy Turn";
turnArrow.innerHTML = '<i class="fas fa-arrow-left"></i>';
}
}
function showDamageAnimation(target, damage) {
const element = target.element || document.getElementById(`creature-${target.id}`);
if (!element) return;
const rect = element.getBoundingClientRect();
const damageText = document.createElement('div');
damageText.className = 'damage-text';
damageText.textContent = `-${damage}`;
damageText.style.left = `${rect.left + rect.width / 2}px`;
damageText.style.top = `${rect.top}px`;
document.body.appendChild(damageText);
// Remove after animation
setTimeout(() => {
damageText.remove();
}, 1000);
}
// Rendering Functions
function renderPlayerHand() {
playerHandEl.innerHTML = '';
gameState.playerHand.forEach((card, index) => {
const cardEl = createCardElement(card, 'hand');
cardEl.dataset.index = index;
playerHandEl.appendChild(cardEl);
});
}
function renderPlayerLanes() {
playerFieldLaneEl.innerHTML = '';
playerShadowLaneEl.innerHTML = '';
// Field Lane
gameState.playerFieldLane.forEach((creature, index) => {
const creatureEl = createCreatureElement(creature, 'player', index);
creatureEl.id = `creature-player-${index}`;
creatureEl.dataset.hasAttacked = creature.hasAttacked;
creatureEl.dataset.summoningSickness = creature.summoningSickness;
playerFieldLaneEl.appendChild(creatureEl);
});
// Shadow Lane
gameState.playerShadowLane.forEach((creature, index) => {
const creatureEl = createCreatureElement(creature, 'player', index);
creatureEl.id = `creature-player-${index}`;
creatureEl.dataset.hasAttacked = creature.hasAttacked;
creatureEl.dataset.summoningSickness = creature.summoningSickness;
playerShadowLaneEl.appendChild(creatureEl);
});
}
function renderEnemyLanes() {
enemyFieldLaneEl.innerHTML = '';
enemyShadowLaneEl.innerHTML = '';
// Field Lane
gameState.enemyFieldLane.forEach((creature, index) => {
const creatureEl = createCreatureElement(creature, 'enemy', index);
creatureEl.id = `creature-enemy-${index}`;
enemyFieldLaneEl.appendChild(creatureEl);
});
// Shadow Lane
gameState.enemyShadowLane.forEach((creature, index) => {
const creatureEl = createCreatureElement(creature, 'enemy', index);
creatureEl.id = `creature-enemy-${index}`;
enemyShadowLaneEl.appendChild(creatureEl);
});
}
function createCardElement(card, location) {
const cardEl = document.createElement('div');
cardEl.className = `card relative ${location === 'hand' ? 'hand-card' : 'board-card'} bg-slate-700 rounded-lg overflow-hidden shadow-lg`;
cardEl.dataset.id = card.id;
// Card front
const cardFront = document.createElement('div');
cardFront.className = 'card-front w-full h-full flex flex-col';
// Card image
const imgContainer = document.createElement('div');
imgContainer.className = `card-image ${location === 'hand' ? '' : 'board-card-image'}`;
imgContainer.style.backgroundImage = `url(${card.image})`;
// Card info
const infoContainer = document.createElement('div');
infoContainer.className = 'bg-slate-800 p-2';
// Name and cost
const nameCost = document.createElement('div');
nameCost.className = 'flex justify-between items-center mb-1';
const nameEl = document.createElement('div');
nameEl.className = 'font-semibold card-name text-amber-200';
nameEl.textContent = card.name;
const costEl = document.createElement('div');
costEl.className = 'bg-blue-500 text-white font-bold rounded-full card-cost flex items-center justify-center';
costEl.textContent = card.cost;
nameCost.appendChild(nameEl);
nameCost.appendChild(costEl);
// Type and stats (if creature)
const typeStats = document.createElement('div');
typeStats.className = 'flex justify-between items-center';
const typeEl = document.createElement('div');
typeEl.className = 'text-slate-300 italic card-type';
typeEl.textContent = card.type;
typeStats.appendChild(typeEl);
if (card.type === 'creature') {
const statsEl = document.createElement('div');
statsEl.className = 'flex space-x-2 card-stats';
const attackEl = document.createElement('div');
attackEl.className = 'bg-red-500 text-white font-bold rounded-full w-7 h-7 flex items-center justify-center';
attackEl.textContent = card.attack;
const healthEl = document.createElement('div');
healthEl.className = 'bg-green-500 text-white font-bold rounded-full w-7 h-7 flex items-center justify-center';
healthEl.textContent = card.health;
statsEl.appendChild(attackEl);
statsEl.appendChild(healthEl);
typeStats.appendChild(statsEl);
}
infoContainer.appendChild(nameCost);
infoContainer.appendChild(typeStats);
cardFront.appendChild(imgContainer);
cardFront.appendChild(infoContainer);
// Tooltip
const tooltip = document.createElement('div');
tooltip.className = 'card-tooltip';
const tooltipName = document.createElement('div');
tooltipName.className = 'font-bold text-amber-300 mb-1';
tooltipName.textContent = card.name;
const tooltipType = document.createElement('div');
tooltipType.className = 'text-xs text-slate-300 mb-2';
tooltipType.textContent = `${card.type.charAt(0).toUpperCase() + card.type.slice(1)}${card.cost} mana`;
const tooltipDesc = document.createElement('div');
tooltipDesc.className = 'text-sm';
tooltipDesc.textContent = card.description;
if (card.keywords && card.keywords.length > 0) {
const tooltipKeywords = document.createElement('div');
tooltipKeywords.className = 'mt-2 flex flex-wrap gap-1';
card.keywords.forEach(keyword => {
const keywordEl = document.createElement('span');
keywordEl.className = 'bg-slate-600 text-xs px-2 py-1 rounded card-keyword';
keywordEl.textContent = keyword;
tooltipKeywords.appendChild(keywordEl);
});
tooltip.appendChild(tooltipKeywords);
}
tooltip.appendChild(tooltipName);
tooltip.appendChild(tooltipType);
tooltip.appendChild(tooltipDesc);
cardEl.appendChild(cardFront);
cardEl.appendChild(tooltip);
// Add event listeners for hand cards
if (location === 'hand') {
cardEl.addEventListener('click', () => {
if (gameState.turn === 'player' && !cardEl.classList.contains('cursor-not-allowed')) {
// For creatures, ask which lane to play in
if (card.type === 'creature') {
const lane = confirm("Play in Field Lane? (OK for Field, Cancel for Shadow)") ? 'field' : 'shadow';
playCard('player', card, lane);
} else {
// For actions/supports, just play them
playCard('player', card);
}
}
});
}
return cardEl;
}
function createCreatureElement(creature, who, index) {
const creatureEl = document.createElement('div');
creatureEl.className = `creature relative inline-block m-2 ${creature.summoningSickness ? 'opacity-70' : ''}`;
creatureEl.id = `creature-${who}-${index}`;
const cardEl = createCardElement(creature, 'board');
// Add health bar
const healthBar = document.createElement('div');
healthBar.className = 'health-bar bg-green-500 rounded-b';
healthBar.style.width = '100%';
const infoContainer = cardEl.querySelector('.bg-slate-800');
infoContainer.appendChild(healthBar);
// Update stats if damaged
const originalCard = cardDatabase.find(c => c.id === creature.id);
if (creature.health < originalCard.health) {
const healthEl = cardEl.querySelector('.bg-green-500');
if (healthEl) healthEl.textContent = creature.health;
healthBar.style.width = `${(creature.health / originalCard.health) * 100}%`;
if (creature.health <= 2) {
healthBar.className = 'health-bar bg-red-500 rounded-b';
}
}
// Add summoning sickness indicator
if (creature.summoningSickness) {
const sicknessIcon = document.createElement('div');
sicknessIcon.className = 'absolute top-0 right-0 bg-yellow-500 text-black text-xs font-bold rounded-full w-6 h-6 flex items-center justify-center';
sicknessIcon.title = "Summoning Sickness";
sicknessIcon.innerHTML = '<i class="fas fa-hourglass-start"></i>';
cardEl.appendChild(sicknessIcon);
}
// Add attack indicator if hasn't attacked
if (!creature.hasAttacked && !creature.summoningSickness && gameState.turn === who) {
const attackIcon = document.createElement('div');
attackIcon.className = 'absolute top-0 left-0 bg-red-500 text-white text-xs font-bold rounded-full w-6 h-6 flex items-center justify-center';
attackIcon.title = "Ready to Attack";
attackIcon.innerHTML = '<i class="fas fa-bolt"></i>';
cardEl.appendChild(attackIcon);
}
creatureEl.appendChild(cardEl);
// Add event listener for attacking
if (who === 'player' && !creature.hasAttacked && !creature.summoningSickness && gameState.turn === 'player') {
creatureEl.classList.add('cursor-pointer', 'hover:scale-105');
creatureEl.addEventListener('click', () => {
// For simplicity, just attack enemy directly
attackTarget('player', creature, {type: 'player'});
});
}
return creatureEl;
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
// Show welcome message
setTimeout(() => {
rulesModal.classList.remove('hidden');
}, 500);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Juno360219/dnd-style-card-game" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>