Luz de Cueva - VideojuCan
by CosmicCobra59467 lines17.4 KB
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Luz de Cueva - VideojuCan</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Courier New', monospace;
overflow: hidden;
touch-action: none;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
}
#gameContainer {
position: relative;
width: 100vw;
height: 100vh;
max-width: 500px;
max-height: 800px;
background: #0a0a12;
overflow: hidden;
cursor: none;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
#ui {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
pointer-events: none;
color: #f1c40f;
font-size: 16px;
font-weight: bold;
text-shadow: 0 0 10px #f1c40f;
}
#message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f1c40f;
font-size: 22px;
text-align: center;
pointer-events: none;
text-shadow: 0 0 15px #f1c40f;
opacity: 0;
transition: opacity 0.5s;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="ui">
<span>🕯️ <span id="waxDisplay">100</span>%</span>
<span>🪲 <span id="bugCount">0</span></span>
<span>⏱️ <span id="timeDisplay">0</span>s</span>
</div>
<div id="message"></div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W, H;
function resize() {
const container = document.getElementById('gameContainer');
W = container.clientWidth;
H = container.clientHeight;
canvas.width = W;
canvas.height = H;
}
resize();
window.addEventListener('resize', resize);
// ========== ESTADO DEL JUEGO ==========
let candle = { x: W / 2, y: H / 2, r: 18 };
let wax = 100;
let score = 0;
let fireflies = [];
let drops = [];
let waxDrops = [];
let particles = [];
let time = 0;
let gameRunning = true;
let mouseActive = false;
// ========== GOTAS DE AGUA ==========
function spawnDrop() {
return {
x: Math.random() * W,
y: -10,
r: 3 + Math.random() * 4,
speed: 1 + Math.random() * 2.5,
wobble: Math.random() * Math.PI * 2,
wobbleSpeed: 0.01 + Math.random() * 0.03,
wobbleAmp: 0.5 + Math.random() * 1.5
};
}
// ========== GOTAS DE CERA ==========
function spawnWaxDrop() {
const angle = Math.random() * Math.PI * 2;
const dist = 80 + Math.random() * 150;
return {
x: candle.x + Math.cos(angle) * dist,
y: candle.y + Math.sin(angle) * dist,
r: 2 + Math.random() * 3,
glow: 0.5 + Math.random() * 0.5,
collected: false
};
}
// ========== LUCIÉRNAGAS ==========
function spawnFirefly() {
const angle = Math.random() * Math.PI * 2;
const dist = 120 + Math.random() * 200;
return {
x: candle.x + Math.cos(angle) * dist,
y: candle.y + Math.sin(angle) * dist,
r: 2 + Math.random() * 3,
angle: Math.random() * Math.PI * 2,
speed: 0.3 + Math.random() * 0.7,
dist: 30 + Math.random() * 80,
glow: 0,
following: false,
offsetAngle: Math.random() * Math.PI * 2,
offsetDist: 40 + Math.random() * 80
};
}
// ========== PARTÍCULAS DE FUEGO ==========
function spawnFireParticle() {
const angle = Math.random() * Math.PI * 2;
const dist = Math.random() * candle.r;
return {
x: candle.x + Math.cos(angle) * dist,
y: candle.y + Math.sin(angle) * dist - candle.r * 0.5,
vx: (Math.random() - 0.5) * 0.5,
vy: -1 - Math.random() * 2,
life: 15 + Math.random() * 25,
r: 1 + Math.random() * 2.5,
color: Math.random() < 0.6 ? '#f1c40f' : (Math.random() < 0.5 ? '#ffaa00' : '#ff6600')
};
}
// Inicializar
for (let i = 0; i < 8; i++) drops.push(spawnDrop());
for (let i = 0; i < 3; i++) waxDrops.push(spawnWaxDrop());
for (let i = 0; i < 2; i++) fireflies.push(spawnFirefly());
// ========== CONTROLES ==========
canvas.addEventListener('mousedown', (e) => { mouseActive = true; moveCandle(e); });
canvas.addEventListener('mousemove', (e) => { if (mouseActive) moveCandle(e); });
canvas.addEventListener('mouseup', () => { mouseActive = false; });
canvas.addEventListener('mouseleave', () => { mouseActive = false; });
canvas.addEventListener('touchstart', (e) => { e.preventDefault(); mouseActive = true; moveCandle(e); });
canvas.addEventListener('touchmove', (e) => { e.preventDefault(); if (mouseActive) moveCandle(e); });
canvas.addEventListener('touchend', () => { mouseActive = false; });
function moveCandle(e) {
if (!gameRunning) return;
const rect = canvas.getBoundingClientRect();
let cx, cy;
if (e.touches) {
cx = (e.touches[0].clientX - rect.left) * (W / rect.width);
cy = (e.touches[0].clientY - rect.top) * (H / rect.height);
} else {
cx = (e.clientX - rect.left) * (W / rect.width);
cy = (e.clientY - rect.top) * (H / rect.height);
}
const dx = cx - candle.x;
const dy = cy - candle.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const speed = Math.min(dist, 6);
if (dist > 1) {
candle.x += (dx / dist) * speed;
candle.y += (dy / dist) * speed;
}
candle.x = Math.max(candle.r, Math.min(W - candle.r, candle.x));
candle.y = Math.max(candle.r, Math.min(H - candle.r, candle.y));
}
// ========== ACTUALIZACIÓN ==========
function update() {
if (!gameRunning) return;
time++;
// Partículas de fuego
if (time % 2 === 0) particles.push(spawnFireParticle());
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
// Gotas de agua
if (time % 50 === 0 && drops.length < 12) drops.push(spawnDrop());
for (let i = drops.length - 1; i >= 0; i--) {
const d = drops[i];
d.y += d.speed;
d.wobble += d.wobbleSpeed;
d.x += Math.sin(d.wobble) * d.wobbleAmp;
if (d.y > H + 20) { drops.splice(i, 1); continue; }
const dx = candle.x - d.x;
const dy = candle.y - d.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < candle.r + d.r) {
wax -= 15;
drops.splice(i, 1);
if (wax <= 0) { wax = 0;
gameRunning = false;
showMessage('🕯️ Se apagó...\n¡Pero puedes volver a encenderla!', 2500); }
}
}
// Gotas de cera
if (time % 120 === 0 && waxDrops.length < 5) waxDrops.push(spawnWaxDrop());
for (let i = waxDrops.length - 1; i >= 0; i--) {
const wd = waxDrops[i];
if (wd.collected) { waxDrops.splice(i, 1); continue; }
const dx = candle.x - wd.x;
const dy = candle.y - wd.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < candle.r + 30) {
wax = Math.min(100, wax + 12);
wd.collected = true;
score += 5;
}
}
// Luciérnagas
if (time % 300 === 0 && fireflies.length < 5) fireflies.push(spawnFirefly());
for (let f of fireflies) {
const dx = candle.x - f.x;
const dy = candle.y - f.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 80) {
f.following = true;
}
if (f.following) {
const targetX = candle.x + Math.cos(f.offsetAngle) * f.offsetDist;
const targetY = candle.y + Math.sin(f.offsetAngle) * f.offsetDist;
f.x += (targetX - f.x) * 0.03;
f.y += (targetY - f.y) * 0.03;
f.glow = Math.min(1, f.glow + 0.02);
} else {
f.angle += f.speed * 0.02;
f.x += Math.cos(f.angle) * f.speed;
f.y += Math.sin(f.angle) * f.speed * 0.5;
f.glow = Math.max(0.3, f.glow - 0.01);
}
f.x = Math.max(0, Math.min(W, f.x));
f.y = Math.max(0, Math.min(H, f.y));
f.offsetAngle += 0.01;
}
// Cera se gasta lentamente
if (time % 40 === 0) wax = Math.max(0, wax - 0.3);
// UI
document.getElementById('waxDisplay').textContent = Math.floor(wax);
document.getElementById('bugCount').textContent = fireflies.filter(f => f.following).length;
document.getElementById('timeDisplay').textContent = Math.floor(time / 60);
}
function showMessage(text, duration) {
const msg = document.getElementById('message');
msg.textContent = text;
msg.style.opacity = '1';
setTimeout(() => { msg.style.opacity = '0'; }, duration);
}
// ========== RENDERIZADO ==========
function draw() {
ctx.clearRect(0, 0, W, H);
// Fondo cálido de cueva
const bg = ctx.createRadialGradient(candle.x, candle.y, 30, candle.x, candle.y, Math.max(W, H));
bg.addColorStop(0, '#1a1508');
bg.addColorStop(0.3, '#0d0d1a');
bg.addColorStop(1, '#000008');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, W, H);
// Rocas de la cueva (estalactitas)
ctx.fillStyle = '#111122';
for (let i = 0; i < 8; i++) {
const rx = i * (W / 7);
const rh = 40 + Math.sin(i * 2.5 + time * 0.001) * 20;
ctx.beginPath();
ctx.moveTo(rx - 15, 0);
ctx.lineTo(rx + 10, rh);
ctx.lineTo(rx + 35, 0);
ctx.fill();
}
// Estalagmitas abajo
for (let i = 0; i < 6; i++) {
const rx = i * (W / 5) + 30;
const rh = 30 + Math.cos(i * 3 + time * 0.001) * 15;
ctx.beginPath();
ctx.moveTo(rx - 10, H);
ctx.lineTo(rx + 5, H - rh);
ctx.lineTo(rx + 25, H);
ctx.fill();
}
// Luz de la vela
const light = ctx.createRadialGradient(candle.x, candle.y, candle.r * 0.5, candle.x, candle.y, 180);
light.addColorStop(0, 'rgba(241,196,15,0.5)');
light.addColorStop(0.3, 'rgba(241,180,15,0.2)');
light.addColorStop(0.7, 'rgba(241,150,15,0.05)');
light.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = light;
ctx.beginPath();
ctx.arc(candle.x, candle.y, 180, 0, Math.PI * 2);
ctx.fill();
// Gotas de cera
for (let wd of waxDrops) {
if (wd.collected) continue;
const glow = ctx.createRadialGradient(wd.x, wd.y, 0, wd.x, wd.y, wd.r * 3);
glow.addColorStop(0, 'rgba(241,196,15,0.6)');
glow.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(wd.x, wd.y, wd.r * 3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#f1c40f';
ctx.beginPath();
ctx.arc(wd.x, wd.y, wd.r, 0, Math.PI * 2);
ctx.fill();
}
// Luciérnagas
for (let f of fireflies) {
const fglow = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, 10 + f.glow * 10);
fglow.addColorStop(0, `rgba(100,255,100,${0.4 + f.glow * 0.4})`);
fglow.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = fglow;
ctx.beginPath();
ctx.arc(f.x, f.y, 10 + f.glow * 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#aaffaa';
ctx.beginPath();
ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2);
ctx.fill();
}
// Gotas de agua
for (let d of drops) {
ctx.fillStyle = 'rgba(150,180,220,0.6)';
ctx.beginPath();
ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(200,220,255,0.8)';
ctx.beginPath();
ctx.arc(d.x - d.r * 0.3, d.y - d.r * 0.3, d.r * 0.4, 0, Math.PI * 2);
ctx.fill();
}
// Partículas de fuego
for (let p of particles) {
const alpha = p.life / 25;
ctx.fillStyle = p.color.replace(')', `, ${alpha})`).replace('rgb',
'rgba');
if (p.color.startsWith('#')) {
ctx.globalAlpha = alpha;
ctx.fillStyle = p.color;
}
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// Vela
// Cuerpo
ctx.fillStyle = '#f5e6c8';
ctx.fillRect(candle.x - 6, candle.y + 2, 12, 20);
ctx.fillStyle = '#e8d5a3';
ctx.fillRect(candle.x - 4, candle.y + 4, 8, 16);
// Mecha
ctx.fillStyle = '#333';
ctx.fillRect(candle.x - 1, candle.y - 2, 2, 8);
// Llama
const flameGrad = ctx.createLinearGradient(candle.x, candle.y - 5, candle.x, candle.y - 25 - Math.random() * 5);
flameGrad.addColorStop(0, '#fff');
flameGrad.addColorStop(0.2, '#f1c40f');
flameGrad.addColorStop(0.6, '#ff8800');
flameGrad.addColorStop(1, 'rgba(255,50,0,0)');
ctx.fillStyle = flameGrad;
ctx.beginPath();
ctx.moveTo(candle.x - 5, candle.y - 2);
ctx.quadraticCurveTo(candle.x - 8, candle.y - 18, candle.x, candle.y - 25 - Math.random() * 5);
ctx.quadraticCurveTo(candle.x + 8, candle.y - 18, candle.x + 5, candle.y - 2);
ctx.fill();
// Brillo de la llama
const flameGlow = ctx.createRadialGradient(candle.x, candle.y - 10, 2, candle.x, candle.y - 5, 25);
flameGlow.addColorStop(0, 'rgba(255,200,50,0.8)');
flameGlow.addColorStop(0.5, 'rgba(255,150,20,0.3)');
flameGlow.addColorStop(1, 'rgba(255,50,0,0)');
ctx.fillStyle = flameGlow;
ctx.beginPath();
ctx.arc(candle.x, candle.y - 8, 25, 0, Math.PI * 2);
ctx.fill();
}
// ========== REINICIO ==========
function restartGame() {
candle.x = W / 2;
candle.y = H / 2;
wax = 100;
score = 0;
time = 0;
gameRunning = true;
drops = [];
waxDrops = [];
fireflies = [];
particles = [];
for (let i = 0; i < 8; i++) drops.push(spawnDrop());
for (let i = 0; i < 3; i++) waxDrops.push(spawnWaxDrop());
for (let i = 0; i < 2; i++) fireflies.push(spawnFirefly());
}
canvas.addEventListener('dblclick', restartGame);
canvas.addEventListener('click', (e) => {
if (!gameRunning && e.detail === 1) {
setTimeout(() => {
if (!gameRunning) restartGame();
}, 300);
}
});
// ========== BUCLE PRINCIPAL ==========
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>Game Source: Luz de Cueva - VideojuCan
Creator: CosmicCobra59
Libraries: none
Complexity: complex (467 lines, 17.4 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: luz-de-cueva-videojucan-cosmiccobra59" to link back to the original. Then publish at arcadelab.ai/publish.