🎮ArcadeLab

Luz de Cueva - VideojuCan

by CosmicCobra59
467 lines17.4 KB
▶ Play
<!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.