top of page
Buscar

Dungeon Sword Fighter - Juego de Caballeros vs Esqueletos

<!DOCTYPE html>

<html lang="es">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>⚔️ Dungeon Sword Fighter</title>

<style>

* {

margin: 0;

padding: 0;

box-sizing: border-box;

}


body {

font-family: 'Courier New', monospace;

background: linear-gradient(135deg, #1a1a1a 0%, #2d1b69 50%, #0f0f0f 100%);

color: #ffffff;

overflow: hidden;

height: 100vh;

user-select: none;

}


.game-container {

position: relative;

width: 100%;

height: 100vh;

overflow: hidden;

}


.game-area {

position: relative;

width: 100%;

height: 100vh;

background:

radial-gradient(circle at 20% 20%, rgba(139, 69, 19, 0.3) 0%, transparent 50%),

radial-gradient(circle at 80% 80%, rgba(101, 67, 33, 0.2) 0%, transparent 50%),

linear-gradient(45deg, #1a1a1a 25%, transparent 25%),

linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),

linear-gradient(45deg, transparent 75%, #1a1a1a 75%),

linear-gradient(-45deg, transparent 75%, #1a1a1a 75%);

background-size: 60px 60px, 60px 60px, 30px 30px, 30px 30px, 30px 30px, 30px 30px;

background-position: 0 0, 0 0, 0 0, 0 0, 15px 15px, 15px 15px;

}


.character {

position: absolute;

width: 60px;

height: 80px;

transition: all 0.3s ease;

cursor: pointer;

z-index: 10;

}


.character.player {

background: linear-gradient(180deg, #4a90e2 0%, #357abd 50%, #1e5f99 100%);

border-radius: 15px 15px 5px 5px;

box-shadow:

0 0 20px rgba(74, 144, 226, 0.5),

inset 0 2px 4px rgba(255, 255, 255, 0.3);

left: 50px;

top: 50%;

transform: translateY(-50%);

}


.character.player::before {

content: '🛡️';

position: absolute;

top: -5px;

left: 50%;

transform: translateX(-50%);

font-size: 1.2rem;

}


.character.player::after {

content: '👤';

position: absolute;

top: 15px;

left: 50%;

transform: translateX(-50%);

font-size: 1.5rem;

}


.character.skeleton {

background: linear-gradient(180deg, #8b7355 0%, #6b5b47 50%, #4a3f35 100%);

border-radius: 12px 12px 4px 4px;

box-shadow:

0 0 15px rgba(139, 115, 85, 0.4),

inset 0 2px 4px rgba(255, 255, 255, 0.2);

}


.character.skeleton::before {

content: '⚔️';

position: absolute;

top: -8px;

right: -5px;

font-size: 1rem;

transform: rotate(45deg);

}


.character.skeleton::after {

content: '💀';

position: absolute;

top: 15px;

left: 50%;

transform: translateX(-50%);

font-size: 1.3rem;

}


.sword {

position: absolute;

width: 40px;

height: 8px;

background: linear-gradient(90deg, #c0c0c0 0%, #ffffff 50%, #c0c0c0 100%);

border-radius: 4px;

box-shadow: 0 0 10px rgba(192, 192, 192, 0.8);

transform-origin: left center;

transition: all 0.2s ease;

z-index: 15;

}


.sword::before {

content: '';

position: absolute;

left: -8px;

top: 50%;

transform: translateY(-50%);

width: 12px;

height: 12px;

background: #8b4513;

border-radius: 2px;

}


.sword.attacking {

transform: rotate(90deg) scale(1.2);

box-shadow: 0 0 20px rgba(255, 255, 0, 0.8);

}


.health-bar {

position: absolute;

top: -15px;

left: 50%;

transform: translateX(-50%);

width: 70px;

height: 8px;

background: rgba(0, 0, 0, 0.5);

border-radius: 4px;

overflow: hidden;

}


.health-fill {

height: 100%;

background: linear-gradient(90deg, #ff4757, #ffa502, #2ed573);

border-radius: 4px;

transition: width 0.3s ease;

}


.health-fill.low {

background: linear-gradient(90deg, #ff4757, #ff6b7a);

}


.health-fill.medium {

background: linear-gradient(90deg, #ffa502, #ffb142);

}


.damage-text {

position: absolute;

color: #ff4757;

font-weight: bold;

font-size: 1.2rem;

text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);

pointer-events: none;

z-index: 20;

animation: damageFloat 1s ease-out forwards;

}


@keyframes damageFloat {

0% {

opacity: 1;

transform: translateY(0) scale(1);

}

100% {

opacity: 0;

transform: translateY(-50px) scale(1.5);

}

}


.ui-panel {

position: fixed;

top: 20px;

left: 20px;

background: rgba(0, 0, 0, 0.8);

backdrop-filter: blur(10px);

border: 2px solid rgba(74, 144, 226, 0.5);

border-radius: 15px;

padding: 20px;

min-width: 250px;

z-index: 50;

}


.ui-panel h2 {

color: #4a90e2;

margin-bottom: 15px;

text-align: center;

font-size: 1.5rem;

}


.stat {

display: flex;

justify-content: space-between;

margin-bottom: 10px;

padding: 5px;

background: rgba(255, 255, 255, 0.1);

border-radius: 5px;

}


.stat-label {

color: #cccccc;

}


.stat-value {

color: #4a90e2;

font-weight: bold;

}


.controls {

position: fixed;

bottom: 20px;

left: 50%;

transform: translateX(-50%);

display: flex;

gap: 15px;

z-index: 50;

}


.control-btn {

background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);

border: none;

color: white;

padding: 12px 20px;

border-radius: 25px;

cursor: pointer;

font-weight: bold;

transition: all 0.3s ease;

text-transform: uppercase;

font-size: 0.9rem;

box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);

}


.control-btn:hover {

transform: translateY(-2px);

box-shadow: 0 6px 20px rgba(74, 144, 226, 0.5);

}


.control-btn:active {

transform: translateY(0);

}


.control-btn.attack {

background: linear-gradient(135deg, #ff4757 0%, #ff3742 100%);

box-shadow: 0 4px 15px rgba(255, 71, 87, 0.3);

}


.control-btn.attack:hover {

box-shadow: 0 6px 20px rgba(255, 71, 87, 0.5);

}


.wave-indicator {

position: fixed;

top: 50%;

left: 50%;

transform: translate(-50%, -50%);

font-size: 3rem;

font-weight: bold;

color: #4a90e2;

text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.8);

opacity: 0;

z-index: 100;

transition: all 0.5s ease;

}


.wave-indicator.show {

opacity: 1;

animation: waveAnnounce 2s ease-out;

}


@keyframes waveAnnounce {

0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; }

50% { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }

100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }

}


.particle-effect {

position: absolute;

width: 4px;

height: 4px;

background: #ffd700;

border-radius: 50%;

pointer-events: none;

z-index: 25;

animation: sparkle 0.8s ease-out forwards;

}


@keyframes sparkle {

0% {

opacity: 1;

transform: scale(1);

}

100% {

opacity: 0;

transform: scale(0) translate(var(--dx), var(--dy));

}

}


.game-over {

position: fixed;

top: 0;

left: 0;

width: 100%;

height: 100%;

background: rgba(0, 0, 0, 0.9);

display: none;

align-items: center;

justify-content: center;

flex-direction: column;

z-index: 200;

}


.game-over.show {

display: flex;

animation: gameOverShow 0.5s ease-out;

}


@keyframes gameOverShow {

from { opacity: 0; }

to { opacity: 1; }

}


.game-over h1 {

font-size: 4rem;

margin-bottom: 20px;

color: #ff4757;

text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.8);

}


.game-over p {

font-size: 1.5rem;

margin-bottom: 30px;

color: #cccccc;

}


.restart-btn {

background: linear-gradient(135deg, #2ed573 0%, #1dd1a1 100%);

border: none;

color: white;

padding: 15px 30px;

border-radius: 30px;

cursor: pointer;

font-weight: bold;

font-size: 1.1rem;

transition: all 0.3s ease;

text-transform: uppercase;

}


.restart-btn:hover {

transform: scale(1.1);

box-shadow: 0 8px 25px rgba(46, 213, 115, 0.4);

}


@media (max-width: 768px) {

.ui-panel {

top: 10px;

left: 10px;

right: 10px;

min-width: auto;

padding: 15px;

}

.controls {

bottom: 10px;

flex-wrap: wrap;

justify-content: center;

}

.control-btn {

padding: 10px 16px;

font-size: 0.8rem;

}

.character {

width: 50px;

height: 70px;

}

}

</style>

</head>

<body>

<div class="game-container">

<div class="game-area" id="gameArea">

<!-- Characters will be spawned here -->

</div>


<div class="ui-panel">

<h2>⚔️ Caballero Dungeon</h2>

<div class="stat">

<span class="stat-label">Vida:</span>

<span class="stat-value" id="playerHealth">100</span>

</div>

<div class="stat">

<span class="stat-label">Oleada:</span>

<span class="stat-value" id="currentWave">1</span>

</div>

<div class="stat">

<span class="stat-label">Enemigos:</span>

<span class="stat-value" id="enemiesLeft">0</span>

</div>

<div class="stat">

<span class="stat-label">Puntuación:</span>

<span class="stat-value" id="score">0</span>

</div>

</div>


<div class="controls">

<button class="control-btn" onclick="movePlayer('up')">↑ Arriba</button>

<button class="control-btn" onclick="movePlayer('down')">↓ Abajo</button>

<button class="control-btn attack" onclick="playerAttack()">⚔️ Atacar</button>

<button class="control-btn" onclick="togglePause()">⏸️ Pausa</button>

</div>


<div class="wave-indicator" id="waveIndicator"></div>


<div class="game-over" id="gameOver">

<h1>💀 Game Over</h1>

<p>Has caído valientemente en batalla...</p>

<p>Oleada alcanzada: <span id="finalWave">1</span></p>

<p>Puntuación final: <span id="finalScore">0</span></p>

<button class="restart-btn" onclick="restartGame()">🔄 Jugar de Nuevo</button>

</div>

</div>


<script>

// Game state

let gameState = {

player: {

x: 50,

y: 50,

health: 100,

maxHealth: 100,

attacking: false,

element: null

},

enemies: [],

wave: 1,

score: 0,

gameRunning: true,

paused: false,

enemySpawnTimer: 0,

enemyMoveTimer: 0

};


// Initialize game

function initGame() {

createPlayer();

spawnWave();

gameLoop();

updateUI();

}


function createPlayer() {

const gameArea = document.getElementById('gameArea');

const player = document.createElement('div');

player.className = 'character player';

player.id = 'player';

player.style.left = gameState.player.x + 'px';

player.style.top = gameState.player.y + '%';

// Add sword to player

const sword = document.createElement('div');

sword.className = 'sword';

sword.id = 'playerSword';

sword.style.right = '-35px';

sword.style.top = '50%';

sword.style.transform = 'translateY(-50%)';

player.appendChild(sword);


// Add health bar

const healthBar = createHealthBar(gameState.player.health, gameState.player.maxHealth);

player.appendChild(healthBar);


gameArea.appendChild(player);

gameState.player.element = player;

}


function createHealthBar(current, max) {

const healthBar = document.createElement('div');

healthBar.className = 'health-bar';

const healthFill = document.createElement('div');

healthFill.className = 'health-fill';

const percentage = (current / max) * 100;

healthFill.style.width = percentage + '%';

if (percentage < 30) healthFill.classList.add('low');

else if (percentage < 60) healthFill.classList.add('medium');

healthBar.appendChild(healthFill);

return healthBar;

}


function spawnWave() {

const waveIndicator = document.getElementById('waveIndicator');

waveIndicator.textContent = `🌊 Oleada ${gameState.wave}`;

waveIndicator.classList.add('show');

setTimeout(() => {

waveIndicator.classList.remove('show');

}, 2000);


// Spawn enemies based on wave

const enemyCount = Math.min(2 + gameState.wave, 8);

for (let i = 0; i < enemyCount; i++) {

setTimeout(() => spawnEnemy(), i * 1000);

}

}


function spawnEnemy() {

const gameArea = document.getElementById('gameArea');

const enemy = document.createElement('div');

enemy.className = 'character skeleton';

const enemyData = {

x: window.innerWidth - 100,

y: Math.random() * (window.innerHeight - 200) + 100,

health: 50 + (gameState.wave * 10),

maxHealth: 50 + (gameState.wave * 10),

element: enemy,

lastAttack: 0,

moveDirection: Math.random() > 0.5 ? 1 : -1

};


enemy.style.left = enemyData.x + 'px';

enemy.style.top = enemyData.y + 'px';


// Add sword to enemy

const sword = document.createElement('div');

sword.className = 'sword';

sword.style.left = '-35px';

sword.style.top = '50%';

sword.style.transform = 'translateY(-50%) scaleX(-1)';

enemy.appendChild(sword);


// Add health bar

const healthBar = createHealthBar(enemyData.health, enemyData.maxHealth);

enemy.appendChild(healthBar);


gameArea.appendChild(enemy);

gameState.enemies.push(enemyData);

updateUI();

}


function movePlayer(direction) {

if (!gameState.gameRunning || gameState.paused) return;


const player = gameState.player;

const step = 60;


switch(direction) {

case 'up':

player.y = Math.max(50, player.y - step);

break;

case 'down':

player.y = Math.min(window.innerHeight - 150, player.y + step);

break;

}


player.element.style.top = player.y + 'px';

}


function playerAttack() {

if (!gameState.gameRunning || gameState.paused || gameState.player.attacking) return;


gameState.player.attacking = true;

const sword = document.getElementById('playerSword');

sword.classList.add('attacking');


// Check for hits

const playerRect = gameState.player.element.getBoundingClientRect();

gameState.enemies.forEach(enemy => {

const enemyRect = enemy.element.getBoundingClientRect();

const distance = Math.sqrt(

Math.pow(playerRect.left - enemyRect.left, 2) +

Math.pow(playerRect.top - enemyRect.top, 2)

);


if (distance < 120) {

const damage = 25 + Math.floor(Math.random() * 15);

damageEnemy(enemy, damage);

createParticleEffect(enemyRect.left + 30, enemyRect.top + 40);

}

});


setTimeout(() => {

gameState.player.attacking = false;

sword.classList.remove('attacking');

}, 300);

}


function damageEnemy(enemy, damage) {

enemy.health -= damage;

showDamageText(enemy.element, damage);

updateEnemyHealthBar(enemy);


if (enemy.health <= 0) {

killEnemy(enemy);

gameState.score += 100 * gameState.wave;

}

}


function killEnemy(enemy) {

// Remove from DOM

enemy.element.remove();

// Remove from enemies array

const index = gameState.enemies.indexOf(enemy);

if (index > -1) {

gameState.enemies.splice(index, 1);

}


// Check if wave is complete

if (gameState.enemies.length === 0) {

gameState.wave++;

setTimeout(() => spawnWave(), 2000);

}

updateUI();

}


function enemyAI(enemy, deltaTime) {

const playerRect = gameState.player.element.getBoundingClientRect();

const enemyRect = enemy.element.getBoundingClientRect();

// Move towards player

const dx = playerRect.left - enemyRect.left;

const dy = playerRect.top - enemyRect.top;

const distance = Math.sqrt(dx * dx + dy * dy);


if (distance > 100) {

// Move towards player

const speed = 0.5;

enemy.x += (dx / distance) * speed * deltaTime;

enemy.y += (dy / distance) * speed * deltaTime;

// Keep enemy in bounds

enemy.x = Math.max(0, Math.min(window.innerWidth - 60, enemy.x));

enemy.y = Math.max(0, Math.min(window.innerHeight - 80, enemy.y));

enemy.element.style.left = enemy.x + 'px';

enemy.element.style.top = enemy.y + 'px';

} else {

// Attack player if close enough

const now = Date.now();

if (now - enemy.lastAttack > 2000) {

enemyAttack(enemy);

enemy.lastAttack = now;

}

}

}


function enemyAttack(enemy) {

const sword = enemy.element.querySelector('.sword');

sword.classList.add('attacking');


const damage = 15 + Math.floor(Math.random() * 10);

gameState.player.health -= damage;

showDamageText(gameState.player.element, damage);

updatePlayerHealthBar();


createParticleEffect(

gameState.player.element.getBoundingClientRect().left + 30,

gameState.player.element.getBoundingClientRect().top + 40

);


if (gameState.player.health <= 0) {

gameOver();

}


setTimeout(() => {

sword.classList.remove('attacking');

}, 300);

}


function updatePlayerHealthBar() {

const healthFill = gameState.player.element.querySelector('.health-fill');

const percentage = Math.max(0, (gameState.player.health / gameState.player.maxHealth) * 100);

healthFill.style.width = percentage + '%';

healthFill.className = 'health-fill';

if (percentage < 30) healthFill.classList.add('low');

else if (percentage < 60) healthFill.classList.add('medium');

}


function updateEnemyHealthBar(enemy) {

const healthFill = enemy.element.querySelector('.health-fill');

const percentage = Math.max(0, (enemy.health / enemy.maxHealth) * 100);

healthFill.style.width = percentage + '%';

healthFill.className = 'health-fill';

if (percentage < 30) healthFill.classList.add('low');

else if (percentage < 60) healthFill.classList.add('medium');

}


function showDamageText(element, damage) {

const rect = element.getBoundingClientRect();

const damageText = document.createElement('div');

damageText.className = 'damage-text';

damageText.textContent = '-' + damage;

damageText.style.left = (rect.left + 30) + 'px';

damageText.style.top = rect.top + 'px';

document.body.appendChild(damageText);

setTimeout(() => {

damageText.remove();

}, 1000);

}


function createParticleEffect(x, y) {

for (let i = 0; i < 8; i++) {

const particle = document.createElement('div');

particle.className = 'particle-effect';

particle.style.left = x + 'px';

particle.style.top = y + 'px';

const dx = (Math.random() - 0.5) * 100;

const dy = (Math.random() - 0.5) * 100;

particle.style.setProperty('--dx', dx + 'px');

particle.style.setProperty('--dy', dy + 'px');

document.body.appendChild(particle);

setTimeout(() => {

particle.remove();

}, 800);

}

}


function updateUI() {

document.getElementById('playerHealth').textContent = Math.max(0, gameState.player.health);

document.getElementById('currentWave').textContent = gameState.wave;

document.getElementById('enemiesLeft').textContent = gameState.enemies.length;

document.getElementById('score').textContent = gameState.score;

}


function gameOver() {

gameState.gameRunning = false;

document.getElementById('finalWave').textContent = gameState.wave;

document.getElementById('finalScore').textContent = gameState.score;

document.getElementById('gameOver').classList.add('show');

}


function restartGame() {

// Reset game state

gameState = {

player: { x: 50, y: 50, health: 100, maxHealth: 100, attacking: false, element: null },

enemies: [],

wave: 1,

score: 0,

gameRunning: true,

paused: false,

enemySpawnTimer: 0,

enemyMoveTimer: 0

};


// Clear game area

const gameArea = document.getElementById('gameArea');

gameArea.innerHTML = '';

// Hide game over screen

document.getElementById('gameOver').classList.remove('show');

// Restart game

initGame();

}


function togglePause() {

gameState.paused = !gameState.paused;

const btn = event.target;

btn.textContent = gameState.paused ? '▶️ Reanudar' : '⏸️ Pausa';

}


function gameLoop() {

if (!gameState.gameRunning) return;


if (!gameState.paused) {

// Update enemy AI

gameState.enemies.forEach(enemy => {

enemyAI(enemy, 16);

});

}


requestAnimationFrame(gameLoop);

}


// Keyboard controls

document.addEventListener('keydown', function(e) {

switch(e.key.toLowerCase()) {

case 'w':

case 'arrowup':

e.preventDefault();

movePlayer('up');

break;

case 's':

case 'arrowdown':

e.preventDefault();

movePlayer('down');

break;

case ' ':

case 'enter':

e.preventDefault();

playerAttack();

break;

case 'p':

e.preventDefault();

togglePause();

break;

case 'r':

if (!gameState.gameRunning) {

e.preventDefault();

restartGame();

}

break;

}

});


// Touch controls for mobile

let touchStartY = 0;

document.addEventListener('touchstart', function(e) {

touchStartY = e.touches[0].clientY;

});


document.addEventListener('touchend', function(e) {

if (!gameState.gameRunning || gameState.paused) return;

const touchEndY = e.changedTouches[0].clientY;

const diff = touchStartY - touchEndY;

if (Math.abs(diff) > 30) {

if (diff > 0) {

movePlayer('up');

} else {

movePlayer('down');

}

} else {

playerAttack();

}

});


// Initialize game when page loads

document.addEventListener('DOMContentLoaded', function() {

initGame();

});

</script>

</body>

</html>

ree

 
 
 

Comentarios


bottom of page