Cette 3 powerfull spells that cost mana to be cast and create button to use them - Follow Up Deployment
Browse files- index.html +271 -24
index.html
CHANGED
|
@@ -89,6 +89,12 @@
|
|
| 89 |
box-shadow: 0 0 10px var(--enemy-color);
|
| 90 |
position: relative;
|
| 91 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
.enemy::before {
|
| 93 |
content: attr(data-emoji);
|
| 94 |
position: absolute;
|
|
@@ -376,6 +382,21 @@
|
|
| 376 |
</div>
|
| 377 |
</div>
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
<div class="bg-gray-800 p-4 rounded-lg">
|
| 380 |
<h2 class="text-xl font-bold mb-2 text-yellow-400">Game Log</h2>
|
| 381 |
<div id="gameLog" class="h-32 overflow-y-auto text-sm bg-gray-900 p-2 rounded">
|
|
@@ -512,7 +533,11 @@
|
|
| 512 |
{ name: "Dragonnet", minLevel: 6, health: 40, damage: 9.5, xp: 50, emoji: "🐉" },
|
| 513 |
{ name: "Wyvern", minLevel: 7, health: 50, damage: 11.5, xp: 60, emoji: "🦅" },
|
| 514 |
{ name: "Dragon", minLevel: 8, health: 70, damage: 14.5, xp: 80, emoji: "🐲" },
|
| 515 |
-
{ name: "Beholder", minLevel: 9, health: 100, damage: 19.5, xp: 100, emoji: "👁️" }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
];
|
| 517 |
|
| 518 |
let player = {
|
|
@@ -618,7 +643,46 @@
|
|
| 618 |
dungeon[player.y][player.x] = 'player';
|
| 619 |
|
| 620 |
// Add enemies in rooms (avoid first room)
|
| 621 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
let room;
|
| 623 |
do {
|
| 624 |
room = rooms[Math.floor(Math.random() * rooms.length)];
|
|
@@ -627,20 +691,25 @@
|
|
| 627 |
const x = room.x + 1 + Math.floor(Math.random() * (room.width - 2));
|
| 628 |
const y = room.y + 1 + Math.floor(Math.random() * (room.height - 2));
|
| 629 |
|
| 630 |
-
// Get possible enemies for current level
|
| 631 |
-
const possibleEnemies = enemyTypes.filter(e => e.minLevel <= player.level);
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
}
|
| 645 |
|
| 646 |
// Add items in rooms
|
|
@@ -696,9 +765,18 @@
|
|
| 696 |
if (isVisible || (x === player.x && y === player.y)) {
|
| 697 |
div.classList.add('cell', dungeon[y][x]);
|
| 698 |
if (dungeon[y][x] === 'enemy') {
|
| 699 |
-
const enemy = enemies.find(e =>
|
|
|
|
|
|
|
|
|
|
| 700 |
if (enemy) {
|
| 701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
}
|
| 703 |
}
|
| 704 |
} else {
|
|
@@ -710,6 +788,51 @@
|
|
| 710 |
}
|
| 711 |
}
|
| 712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
function updateStats() {
|
| 714 |
healthEl.textContent = `${player.health}/${player.maxHealth}`;
|
| 715 |
manaEl.textContent = `${player.mana}/${player.maxMana}`;
|
|
@@ -765,7 +888,11 @@
|
|
| 765 |
const enemy = enemies[enemyIndex];
|
| 766 |
|
| 767 |
// Player attacks enemy
|
| 768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
enemy.health -= playerDamage;
|
| 770 |
addLogMessage(`You hit the ${enemy.name} for ${playerDamage} damage!`, 'yellow');
|
| 771 |
|
|
@@ -797,6 +924,13 @@
|
|
| 797 |
if (player.xp >= player.level * 100) {
|
| 798 |
levelUp();
|
| 799 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 800 |
}
|
| 801 |
|
| 802 |
updateStats();
|
|
@@ -842,10 +976,21 @@
|
|
| 842 |
}
|
| 843 |
}
|
| 844 |
else if (targetCell === 'exit') {
|
| 845 |
-
//
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 849 |
}
|
| 850 |
|
| 851 |
// Check if player died from this attack
|
|
@@ -878,9 +1023,12 @@
|
|
| 878 |
});
|
| 879 |
|
| 880 |
function initGame() {
|
|
|
|
|
|
|
| 881 |
generateDungeon();
|
| 882 |
renderDungeon();
|
| 883 |
updateStats();
|
|
|
|
| 884 |
addLogMessage(`Entering dungeon level ${player.level}...`, 'yellow');
|
| 885 |
}
|
| 886 |
|
|
@@ -894,7 +1042,7 @@
|
|
| 894 |
mana: 50,
|
| 895 |
maxMana: 50,
|
| 896 |
xp: 0,
|
| 897 |
-
level: 1,
|
| 898 |
attack: 5,
|
| 899 |
defense: 3,
|
| 900 |
inventory: [
|
|
@@ -957,12 +1105,111 @@
|
|
| 957 |
moveLeft.addEventListener('click', () => movePlayer(-1, 0));
|
| 958 |
moveRight.addEventListener('click', () => movePlayer(1, 0));
|
| 959 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 960 |
function gameOver() {
|
| 961 |
deathLevelEl.textContent = player.level;
|
| 962 |
gameOverModal.style.display = 'flex';
|
| 963 |
addLogMessage("You have died! Game over.", 'red');
|
| 964 |
}
|
| 965 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 966 |
restartGame.addEventListener('click', () => {
|
| 967 |
gameOverModal.style.display = 'none';
|
| 968 |
document.getElementById('newGame').click(); // Trigger new game
|
|
|
|
| 89 |
box-shadow: 0 0 10px var(--enemy-color);
|
| 90 |
position: relative;
|
| 91 |
}
|
| 92 |
+
|
| 93 |
+
.boss-cell {
|
| 94 |
+
background-color: #d64045;
|
| 95 |
+
box-shadow: 0 0 15px #ff0000;
|
| 96 |
+
animation: pulse 0.5s infinite;
|
| 97 |
+
}
|
| 98 |
.enemy::before {
|
| 99 |
content: attr(data-emoji);
|
| 100 |
position: absolute;
|
|
|
|
| 382 |
</div>
|
| 383 |
</div>
|
| 384 |
|
| 385 |
+
<div class="bg-gray-800 p-4 rounded-lg mb-4">
|
| 386 |
+
<h2 class="text-xl font-bold mb-2 text-yellow-400">Spells</h2>
|
| 387 |
+
<div class="grid grid-cols-3 gap-2 mb-4">
|
| 388 |
+
<button id="fireballBtn" class="px-2 py-1 bg-red-800 hover:bg-red-700 rounded flex items-center justify-center">
|
| 389 |
+
<i class="fas fa-fire mr-1"></i>Fireball (15 MP)
|
| 390 |
+
</button>
|
| 391 |
+
<button id="healBtn" class="px-2 py-1 bg-green-800 hover:bg-green-700 rounded flex items-center justify-center">
|
| 392 |
+
<i class="fas fa-heart mr-1"></i>Heal (20 MP)
|
| 393 |
+
</button>
|
| 394 |
+
<button id="lightningBtn" class="px-2 py-1 bg-blue-800 hover:bg-blue-700 rounded flex items-center justify-center">
|
| 395 |
+
<i class="fas fa-bolt mr-1"></i>Lightning (25 MP)
|
| 396 |
+
</button>
|
| 397 |
+
</div>
|
| 398 |
+
</div>
|
| 399 |
+
|
| 400 |
<div class="bg-gray-800 p-4 rounded-lg">
|
| 401 |
<h2 class="text-xl font-bold mb-2 text-yellow-400">Game Log</h2>
|
| 402 |
<div id="gameLog" class="h-32 overflow-y-auto text-sm bg-gray-900 p-2 rounded">
|
|
|
|
| 533 |
{ name: "Dragonnet", minLevel: 6, health: 40, damage: 9.5, xp: 50, emoji: "🐉" },
|
| 534 |
{ name: "Wyvern", minLevel: 7, health: 50, damage: 11.5, xp: 60, emoji: "🦅" },
|
| 535 |
{ name: "Dragon", minLevel: 8, health: 70, damage: 14.5, xp: 80, emoji: "🐲" },
|
| 536 |
+
{ name: "Beholder", minLevel: 9, health: 100, damage: 19.5, xp: 100, emoji: "👁️" },
|
| 537 |
+
// Boss types (stats réduites)
|
| 538 |
+
{ name: "Roi Squelette", minLevel: 4, health: 40, damage: 5, xp: 100, emoji: "💀👑", boss: true },
|
| 539 |
+
{ name: "Dragon Ancien", minLevel: 8, health: 90, damage: 12, xp: 200, emoji: "🐉🔥", boss: true },
|
| 540 |
+
{ name: "Seigneur des Ténèbres", minLevel: 12, health: 150, damage: 20, xp: 300, emoji: "👹🌑", boss: true }
|
| 541 |
];
|
| 542 |
|
| 543 |
let player = {
|
|
|
|
| 643 |
dungeon[player.y][player.x] = 'player';
|
| 644 |
|
| 645 |
// Add enemies in rooms (avoid first room)
|
| 646 |
+
const enemyCount = 5 + player.level;
|
| 647 |
+
|
| 648 |
+
// Add a boss every 4 levels
|
| 649 |
+
if (player.level % 4 === 0) {
|
| 650 |
+
const possibleBosses = enemyTypes.filter(e => e.boss && e.minLevel <= player.level);
|
| 651 |
+
if (possibleBosses.length > 0) {
|
| 652 |
+
const bossType = possibleBosses[Math.floor(Math.random() * possibleBosses.length)];
|
| 653 |
+
const bossRoom = rooms[rooms.length - 1]; // Boss in last room
|
| 654 |
+
|
| 655 |
+
// Boss occupies 2x2 space
|
| 656 |
+
const x = bossRoom.x + Math.floor(bossRoom.width / 2) - 1;
|
| 657 |
+
const y = bossRoom.y + Math.floor(bossRoom.height / 2) - 1;
|
| 658 |
+
|
| 659 |
+
enemies.push({
|
| 660 |
+
x, y,
|
| 661 |
+
name: bossType.name,
|
| 662 |
+
emoji: bossType.emoji,
|
| 663 |
+
health: bossType.health * (1 + (player.level / 4)),
|
| 664 |
+
damage: bossType.damage * (1 + (player.level / 8)),
|
| 665 |
+
xp: bossType.xp * (1 + (player.level / 4)),
|
| 666 |
+
isBoss: true,
|
| 667 |
+
width: 2,
|
| 668 |
+
height: 2
|
| 669 |
+
});
|
| 670 |
+
|
| 671 |
+
// Mark boss cells
|
| 672 |
+
for (let dy = 0; dy < 2; dy++) {
|
| 673 |
+
for (let dx = 0; dx < 2; dx++) {
|
| 674 |
+
if (y + dy < SIZE && x + dx < SIZE) {
|
| 675 |
+
dungeon[y + dy][x + dx] = 'enemy';
|
| 676 |
+
}
|
| 677 |
+
}
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
addLogMessage(`A powerful ${bossType.name} awaits in the depths!`, 'red');
|
| 681 |
+
}
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
// Add regular enemies
|
| 685 |
+
for (let i = 0; i < enemyCount; i++) {
|
| 686 |
let room;
|
| 687 |
do {
|
| 688 |
room = rooms[Math.floor(Math.random() * rooms.length)];
|
|
|
|
| 691 |
const x = room.x + 1 + Math.floor(Math.random() * (room.width - 2));
|
| 692 |
const y = room.y + 1 + Math.floor(Math.random() * (room.height - 2));
|
| 693 |
|
| 694 |
+
// Get possible enemies for current level (excluding bosses)
|
| 695 |
+
const possibleEnemies = enemyTypes.filter(e => !e.boss && e.minLevel <= player.level);
|
| 696 |
+
if (possibleEnemies.length > 0) {
|
| 697 |
+
const enemyType = possibleEnemies[Math.floor(Math.random() * possibleEnemies.length)];
|
| 698 |
+
|
| 699 |
+
enemies.push({
|
| 700 |
+
x,
|
| 701 |
+
y,
|
| 702 |
+
name: enemyType.name,
|
| 703 |
+
emoji: enemyType.emoji,
|
| 704 |
+
health: enemyType.health * (1 + player.level * 0.2),
|
| 705 |
+
damage: enemyType.damage * (1 + player.level * 0.1),
|
| 706 |
+
xp: enemyType.xp * (1 + player.level * 0.1),
|
| 707 |
+
isBoss: false,
|
| 708 |
+
width: 1,
|
| 709 |
+
height: 1
|
| 710 |
+
});
|
| 711 |
+
dungeon[y][x] = 'enemy';
|
| 712 |
+
}
|
| 713 |
}
|
| 714 |
|
| 715 |
// Add items in rooms
|
|
|
|
| 765 |
if (isVisible || (x === player.x && y === player.y)) {
|
| 766 |
div.classList.add('cell', dungeon[y][x]);
|
| 767 |
if (dungeon[y][x] === 'enemy') {
|
| 768 |
+
const enemy = enemies.find(e =>
|
| 769 |
+
x >= e.x && x < e.x + (e.width || 1) &&
|
| 770 |
+
y >= e.y && y < e.y + (e.height || 1)
|
| 771 |
+
);
|
| 772 |
if (enemy) {
|
| 773 |
+
// Only show emoji on first cell of boss
|
| 774 |
+
if ((!enemy.isBoss) || (x === enemy.x && y === enemy.y)) {
|
| 775 |
+
div.setAttribute('data-emoji', enemy.emoji);
|
| 776 |
+
}
|
| 777 |
+
if (enemy.isBoss) {
|
| 778 |
+
div.classList.add('boss-cell');
|
| 779 |
+
}
|
| 780 |
}
|
| 781 |
}
|
| 782 |
} else {
|
|
|
|
| 788 |
}
|
| 789 |
}
|
| 790 |
|
| 791 |
+
function renderInventory() {
|
| 792 |
+
const inventoryEl = document.getElementById('inventory');
|
| 793 |
+
inventoryEl.innerHTML = '';
|
| 794 |
+
|
| 795 |
+
player.inventory.forEach((item, index) => {
|
| 796 |
+
const itemEl = document.createElement('div');
|
| 797 |
+
itemEl.className = `bg-gray-700 p-2 rounded text-center tooltip cursor-pointer hover:bg-gray-600`;
|
| 798 |
+
itemEl.innerHTML = `
|
| 799 |
+
<i class="fas ${item.icon} text-${item.color || 'gray-400'}"></i>
|
| 800 |
+
<span class="tooltip-text">${item.name}${item.damage ? ` (${item.damage} damage)` : ''}${item.defense ? ` (${item.defense} defense)` : ''}${item.heal ? ` (Heals ${item.heal} HP)` : ''}${item.restore ? ` (Restores ${item.restore} MP)` : ''}</span>
|
| 801 |
+
`;
|
| 802 |
+
|
| 803 |
+
itemEl.addEventListener('click', () => useItem(index));
|
| 804 |
+
inventoryEl.appendChild(itemEl);
|
| 805 |
+
});
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
function useItem(index) {
|
| 809 |
+
const item = player.inventory[index];
|
| 810 |
+
|
| 811 |
+
if (item.heal) {
|
| 812 |
+
player.health = Math.min(player.maxHealth, player.health + item.heal);
|
| 813 |
+
addLogMessage(`Used ${item.name}! +${item.heal} HP`, 'green');
|
| 814 |
+
player.inventory.splice(index, 1);
|
| 815 |
+
}
|
| 816 |
+
else if (item.restore) {
|
| 817 |
+
player.mana = Math.min(player.maxMana, player.mana + item.restore);
|
| 818 |
+
addLogMessage(`Used ${item.name}! +${item.restore} MP`, 'blue');
|
| 819 |
+
player.inventory.splice(index, 1);
|
| 820 |
+
}
|
| 821 |
+
else if (item.damage) {
|
| 822 |
+
player.attack += item.damage;
|
| 823 |
+
addLogMessage(`Equipped ${item.name}! +${item.damage} attack`, 'yellow');
|
| 824 |
+
player.inventory.splice(index, 1);
|
| 825 |
+
}
|
| 826 |
+
else if (item.defense) {
|
| 827 |
+
player.defense += item.defense;
|
| 828 |
+
addLogMessage(`Equipped ${item.name}! +${item.defense} defense`, 'purple');
|
| 829 |
+
player.inventory.splice(index, 1);
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
updateStats();
|
| 833 |
+
renderInventory();
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
function updateStats() {
|
| 837 |
healthEl.textContent = `${player.health}/${player.maxHealth}`;
|
| 838 |
manaEl.textContent = `${player.mana}/${player.maxMana}`;
|
|
|
|
| 888 |
const enemy = enemies[enemyIndex];
|
| 889 |
|
| 890 |
// Player attacks enemy
|
| 891 |
+
let playerDamage = Math.max(1, player.attack - Math.floor(enemy.damage / 2));
|
| 892 |
+
// Bosses take reduced damage
|
| 893 |
+
if (enemy.isBoss) {
|
| 894 |
+
playerDamage = Math.max(1, Math.floor(playerDamage * 0.7));
|
| 895 |
+
}
|
| 896 |
enemy.health -= playerDamage;
|
| 897 |
addLogMessage(`You hit the ${enemy.name} for ${playerDamage} damage!`, 'yellow');
|
| 898 |
|
|
|
|
| 924 |
if (player.xp >= player.level * 100) {
|
| 925 |
levelUp();
|
| 926 |
}
|
| 927 |
+
|
| 928 |
+
// If boss was defeated, advance to next level
|
| 929 |
+
if (enemy.isBoss) {
|
| 930 |
+
player.level++;
|
| 931 |
+
addLogMessage(`You defeated the boss! Moving to level ${player.level}`, 'purple');
|
| 932 |
+
initGame();
|
| 933 |
+
}
|
| 934 |
}
|
| 935 |
|
| 936 |
updateStats();
|
|
|
|
| 976 |
}
|
| 977 |
}
|
| 978 |
else if (targetCell === 'exit') {
|
| 979 |
+
// Check if boss was defeated before allowing exit
|
| 980 |
+
const bossDefeated = !enemies.some(e => e.isBoss);
|
| 981 |
+
if (bossDefeated) {
|
| 982 |
+
// Advance to next level
|
| 983 |
+
const nextLevel = player.level + 1;
|
| 984 |
+
if (nextLevel <= 20) { // Maximum dungeon level
|
| 985 |
+
player.level = nextLevel;
|
| 986 |
+
addLogMessage(`You found the exit! Moving to level ${player.level}`, 'purple');
|
| 987 |
+
showLevelComplete();
|
| 988 |
+
} else {
|
| 989 |
+
addLogMessage("You need to complete previous levels first!", 'red');
|
| 990 |
+
}
|
| 991 |
+
} else {
|
| 992 |
+
addLogMessage("You must defeat the boss before using the exit!", 'red');
|
| 993 |
+
}
|
| 994 |
}
|
| 995 |
|
| 996 |
// Check if player died from this attack
|
|
|
|
| 1023 |
});
|
| 1024 |
|
| 1025 |
function initGame() {
|
| 1026 |
+
// Ensure level is at least 1
|
| 1027 |
+
player.level = Math.max(1, player.level);
|
| 1028 |
generateDungeon();
|
| 1029 |
renderDungeon();
|
| 1030 |
updateStats();
|
| 1031 |
+
renderInventory();
|
| 1032 |
addLogMessage(`Entering dungeon level ${player.level}...`, 'yellow');
|
| 1033 |
}
|
| 1034 |
|
|
|
|
| 1042 |
mana: 50,
|
| 1043 |
maxMana: 50,
|
| 1044 |
xp: 0,
|
| 1045 |
+
level: 1, // Always start at level 1
|
| 1046 |
attack: 5,
|
| 1047 |
defense: 3,
|
| 1048 |
inventory: [
|
|
|
|
| 1105 |
moveLeft.addEventListener('click', () => movePlayer(-1, 0));
|
| 1106 |
moveRight.addEventListener('click', () => movePlayer(1, 0));
|
| 1107 |
|
| 1108 |
+
function castFireball() {
|
| 1109 |
+
if (player.mana < 15) {
|
| 1110 |
+
addLogMessage("Not enough mana for Fireball!", "red");
|
| 1111 |
+
return;
|
| 1112 |
+
}
|
| 1113 |
+
|
| 1114 |
+
player.mana -= 15;
|
| 1115 |
+
const damage = 15 + player.level * 2;
|
| 1116 |
+
|
| 1117 |
+
// Find all enemies in range (3 cells)
|
| 1118 |
+
const inRange = enemies.filter(e => {
|
| 1119 |
+
const dx = Math.abs(e.x - player.x);
|
| 1120 |
+
const dy = Math.abs(e.y - player.y);
|
| 1121 |
+
return dx <= 3 && dy <= 3;
|
| 1122 |
+
});
|
| 1123 |
+
|
| 1124 |
+
if (inRange.length === 0) {
|
| 1125 |
+
addLogMessage("Fireball fizzles - no enemies in range!", "red");
|
| 1126 |
+
return;
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
inRange.forEach(enemy => {
|
| 1130 |
+
enemy.health -= damage;
|
| 1131 |
+
addLogMessage(`Fireball hits ${enemy.name} for ${damage} damage!`, "orange");
|
| 1132 |
+
|
| 1133 |
+
if (enemy.health <= 0) {
|
| 1134 |
+
addLogMessage(`${enemy.name} was incinerated! +${Math.floor(enemy.xp)} XP`, "green");
|
| 1135 |
+
player.xp += enemy.xp;
|
| 1136 |
+
enemies.splice(enemies.indexOf(enemy), 1);
|
| 1137 |
+
dungeon[enemy.y][enemy.x] = 'floor';
|
| 1138 |
+
}
|
| 1139 |
+
});
|
| 1140 |
+
|
| 1141 |
+
updateStats();
|
| 1142 |
+
renderDungeon();
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
function castHeal() {
|
| 1146 |
+
if (player.mana < 20) {
|
| 1147 |
+
addLogMessage("Not enough mana for Heal!", "red");
|
| 1148 |
+
return;
|
| 1149 |
+
}
|
| 1150 |
+
|
| 1151 |
+
player.mana -= 20;
|
| 1152 |
+
const healAmount = 30 + player.level * 3;
|
| 1153 |
+
player.health = Math.min(player.maxHealth, player.health + healAmount);
|
| 1154 |
+
addLogMessage(`Healing magic restores ${healAmount} HP!`, "green");
|
| 1155 |
+
|
| 1156 |
+
updateStats();
|
| 1157 |
+
}
|
| 1158 |
+
|
| 1159 |
+
function castLightning() {
|
| 1160 |
+
if (player.mana < 25) {
|
| 1161 |
+
addLogMessage("Not enough mana for Lightning!", "red");
|
| 1162 |
+
return;
|
| 1163 |
+
}
|
| 1164 |
+
|
| 1165 |
+
player.mana -= 25;
|
| 1166 |
+
|
| 1167 |
+
// Find closest enemy
|
| 1168 |
+
let closestEnemy = null;
|
| 1169 |
+
let minDist = Infinity;
|
| 1170 |
+
|
| 1171 |
+
enemies.forEach(enemy => {
|
| 1172 |
+
const dx = Math.abs(enemy.x - player.x);
|
| 1173 |
+
const dy = Math.abs(enemy.y - player.y);
|
| 1174 |
+
const dist = dx + dy;
|
| 1175 |
+
|
| 1176 |
+
if (dist < minDist) {
|
| 1177 |
+
minDist = dist;
|
| 1178 |
+
closestEnemy = enemy;
|
| 1179 |
+
}
|
| 1180 |
+
});
|
| 1181 |
+
|
| 1182 |
+
if (!closestEnemy) {
|
| 1183 |
+
addLogMessage("Lightning fizzles - no enemies found!", "red");
|
| 1184 |
+
return;
|
| 1185 |
+
}
|
| 1186 |
+
|
| 1187 |
+
const damage = 30 + player.level * 3;
|
| 1188 |
+
closestEnemy.health -= damage;
|
| 1189 |
+
addLogMessage(`Lightning strikes ${closestEnemy.name} for ${damage} damage!`, "yellow");
|
| 1190 |
+
|
| 1191 |
+
if (closestEnemy.health <= 0) {
|
| 1192 |
+
addLogMessage(`${closestEnemy.name} was electrocuted! +${Math.floor(closestEnemy.xp)} XP`, "green");
|
| 1193 |
+
player.xp += closestEnemy.xp;
|
| 1194 |
+
enemies.splice(enemies.indexOf(closestEnemy), 1);
|
| 1195 |
+
dungeon[closestEnemy.y][closestEnemy.x] = 'floor';
|
| 1196 |
+
}
|
| 1197 |
+
|
| 1198 |
+
updateStats();
|
| 1199 |
+
renderDungeon();
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
function gameOver() {
|
| 1203 |
deathLevelEl.textContent = player.level;
|
| 1204 |
gameOverModal.style.display = 'flex';
|
| 1205 |
addLogMessage("You have died! Game over.", 'red');
|
| 1206 |
}
|
| 1207 |
|
| 1208 |
+
// Spell buttons
|
| 1209 |
+
document.getElementById('fireballBtn').addEventListener('click', castFireball);
|
| 1210 |
+
document.getElementById('healBtn').addEventListener('click', castHeal);
|
| 1211 |
+
document.getElementById('lightningBtn').addEventListener('click', castLightning);
|
| 1212 |
+
|
| 1213 |
restartGame.addEventListener('click', () => {
|
| 1214 |
gameOverModal.style.display = 'none';
|
| 1215 |
document.getElementById('newGame').click(); // Trigger new game
|