Dungeon Sword Fighter - Juego de Caballeros vs Esqueletos
- samuel gaitan
- 16 sept
- 8 Min. de lectura
<!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;
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 {
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 {
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;
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%;
border-radius: 4px;
transition: width 0.3s ease;
}
.health-fill.low {
}
.health-fill.medium {
}
.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 {
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 {
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 {
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>




Comentarios