domingo, 8 de noviembre de 2026

Juego de undir barcos; echo con IA en html;

https://sites.google.com/d/15uLD9-tJFcokAlDUWjDnrxnKI2vMwo0V/p/1lw4dvx0vBlilHPFaOm1iWPE_R1tUydmr/edit 



<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Batalla Naval Pirata 3D</title>
    <!-- Tailwind CSS para el diseño de la UI -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- Three.js para la representación 3D -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <!-- FontAwesome para iconos -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        body {
            margin: 0;
            overflow: hidden;
            font-family: 'Inter', sans-serif;
            user-select: none;
            background-color: #0f172a;
        }
        #canvas-container {
            width: 100vw;
            height: 100vh;
            display: block;
        }
        /* Estilos personalizados para los Sliders */
        input[type=range] {
            accent-color: #f59e0b;
        }
        .glass-panel {
            background: rgba(15, 23, 42, 0.75);
            backdrop-filter: blur(12px);
            border: 1px solid rgba(255, 255, 255, 0.15);
        }
    </style>
</head>
<body class="relative">

    <!-- Canvas principal para Three.js -->
    <div id="canvas-container"></div>

    <!-- HUD / Interfaz de Usuario de Puntuación y Estado -->
    <div class="absolute top-4 left-4 flex flex-col gap-3 pointer-events-none z-10">
        <div class="glass-panel text-white p-4 rounded-xl shadow-2xl flex items-center gap-4">
            <div class="bg-amber-500/20 p-3 rounded-lg border border-amber-500/30 text-amber-400">
                <i class="fa-solid me-1 fa-skull-crossbones text-3xl"></i>
            </div>
            <div>
                <p class="text-xs uppercase tracking-wider text-slate-400 font-semibold">Barcos Hundidos</p>
                <h1 id="score-text" class="text-3xl font-black text-amber-400">0</h1>
            </div>
        </div>
       
        <div class="glass-panel text-white p-3 rounded-xl shadow-lg flex items-center justify-between min-w-[200px]">
            <span class="text-xs text-slate-300 font-medium"><i class="fa-solid fa-ship me-2 text-cyan-400"></i>Enemigos activos:</span>
            <span id="enemy-count" class="font-bold text-cyan-400 text-lg">0</span>
        </div>
    </div>

    <!-- Panel de Calibración de Velocidades (Sliders) -->
    <div class="absolute top-4 right-4 glass-panel text-white p-5 rounded-2xl shadow-2xl w-80 z-10 border border-slate-700">
        <div class="flex items-center justify-between border-b border-slate-700/80 pb-3 mb-4">
            <h2 class="font-bold text-amber-400 flex items-center gap-2">
                <i class="fa-solid fa-sliders text-amber-500"></i> Calibrador Naval
            </h2>
            <button id="btn-pause" class="bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs px-2.5 py-1 rounded-md transition border border-slate-600">
                <i class="fa-solid fa-pause me-1"></i> Pausa
            </button>
        </div>

        <!-- Slider Velocidad del Jugador -->
        <div class="mb-4">
            <div class="flex justify-between text-xs font-semibold mb-1">
                <label for="player-speed-slider" class="text-emerald-400 flex items-center gap-1">
                    <i class="fa-solid fa-gauge-high"></i> Tu Velocidad:
                </label>
                <span id="player-speed-val" class="text-emerald-400 font-mono">1.0x</span>
            </div>
            <input type="range" id="player-speed-slider" min="0.2" max="3.0" step="0.1" value="1.0" class="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer">
            <div class="flex justify-between text-[10px] text-slate-400 mt-1">
                <span>Lento</span>
                <span>Normal</span>
                <span>Súper Rápido</span>
            </div>
        </div>

        <!-- Slider Velocidad Enemigos -->
        <div>
            <div class="flex justify-between text-xs font-semibold mb-1">
                <label for="enemy-speed-slider" class="text-rose-400 flex items-center gap-1">
                    <i class="fa-solid fa-turtle"></i> Velocidad Enemigos:
                </label>
                <span id="enemy-speed-val" class="text-rose-400 font-mono">0.2x</span>
            </div>
            <input type="range" id="enemy-speed-slider" min="0.02" max="0.8" step="0.02" value="0.2" class="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer">
            <div class="flex justify-between text-[10px] text-slate-400 mt-1">
                <span>Casi Estáticos</span>
                <span>Muy Lento</span>
                <span>Moderado</span>
            </div>
        </div>
    </div>

    <!-- Guía de Controles Móviles / Escritorio (Inferior) -->
    <div class="absolute bottom-4 left-1/2 -translate-x-1/2 glass-panel text-slate-300 px-6 py-3 rounded-full text-xs flex items-center gap-6 shadow-xl z-10 border border-slate-700/60">
        <div class="flex items-center gap-2">
            <span class="bg-slate-800 text-amber-400 px-2 py-0.5 rounded font-mono border border-slate-600">W / S</span> o <span class="bg-slate-800 text-amber-400 px-2 py-0.5 rounded font-mono border border-slate-600">↑ / ↓</span>
            <span>Avanzar / Atrás</span>
        </div>
        <div class="h-4 w-px bg-slate-700"></div>
        <div class="flex items-center gap-2">
            <span class="bg-slate-800 text-amber-400 px-2 py-0.5 rounded font-mono border border-slate-600">A / D</span> o <span class="bg-slate-800 text-amber-400 px-2 py-0.5 rounded font-mono border border-slate-600">← / →</span>
            <span>Girar</span>
        </div>
        <div class="h-4 w-px bg-slate-700"></div>
        <div class="flex items-center gap-2">
            <span class="bg-slate-800 text-amber-400 px-3 py-0.5 rounded font-mono border border-slate-600">ESPACIO</span> o <span class="bg-slate-800 text-amber-400 px-2 py-0.5 rounded font-mono border border-slate-600">CLIC</span>
            <span class="text-amber-400 font-bold">¡FUEGO! (Cañones)</span>
        </div>
    </div>

    <!-- Controles Táctiles para Móviles -->
    <div class="md:hidden absolute bottom-20 left-4 z-20 flex flex-col gap-2">
        <div class="flex justify-center">
            <button id="btn-up" class="w-12 h-12 glass-panel active:bg-amber-500/40 rounded-full text-white text-xl flex items-center justify-center border border-slate-600"><i class="fa-solid fa-arrow-up"></i></button>
        </div>
        <div class="flex gap-4">
            <button id="btn-left" class="w-12 h-12 glass-panel active:bg-amber-500/40 rounded-full text-white text-xl flex items-center justify-center border border-slate-600"><i class="fa-solid fa-arrow-left"></i></button>
            <button id="btn-down" class="w-12 h-12 glass-panel active:bg-amber-500/40 rounded-full text-white text-xl flex items-center justify-center border border-slate-600"><i class="fa-solid fa-arrow-down"></i></button>
            <button id="btn-right" class="w-12 h-12 glass-panel active:bg-amber-500/40 rounded-full text-white text-xl flex items-center justify-center border border-slate-600"><i class="fa-solid fa-arrow-right"></i></button>
        </div>
    </div>
    <div class="md:hidden absolute bottom-20 right-4 z-20">
        <button id="btn-fire" class="w-20 h-20 bg-gradient-to-tr from-amber-600 to-red-600 active:scale-95 rounded-full text-white font-bold text-sm shadow-2xl flex flex-col items-center justify-center border-2 border-amber-300">
            <i class="fa-solid fa-bomb text-xl mb-1"></i>
            FUEGO
        </button>
    </div>

    <!-- LÓGICA COMPLETA DEL JUEGO CON THREE.JS -->
    <script>
        // --- CONFIGURACIÓN E INICIALIZACIÓN ---
        let scene, camera, renderer;
        let playerBoat, waterMesh;
        let cannonballs = [];
        let enemies = [];
        let particles = [];
        let score = 0;
        let isPaused = false;

        // Factores de calibración (Modificados por Sliders)
        let playerSpeedMultiplier = 1.0;
        let enemySpeedMultiplier = 0.2;

        // Estado de teclas
        const keys = {
            forward: false,
            backward: false,
            left: false,
            right: false
        };

        // Configuración del jugador
        const playerStats = {
            speed: 0,
            maxSpeed: 0.45,
            acceleration: 0.015,
            friction: 0.96,
            turnSpeed: 0.035,
            cooldown: 0,
            cooldownMax: 22 // Cuadros entre disparos
        };

        const ENEMY_COUNT = 7;
        const MAP_SIZE = 400;

        // Inicializar el escenario cuando cargue la ventana
        window.onload = function () {
            initScene();
            createWater();
            createLights();
            playerBoat = createBoat(0x2563eb, true); // Barco azul para el jugador
            scene.add(playerBoat);

            // Generar barcos enemigos iniciales
            for (let i = 0; i < ENEMY_COUNT; i++) {
                spawnEnemy();
            }

            setupControls();
            setupUI();

            // Iniciar bucle de animación
            animate();
        };

        // --- CONSTRUCCIÓN 3D DE OBJETOS (Three.js Primitivas) ---

        function initScene() {
            const container = document.getElementById('canvas-container');
           
            // Escena
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x7dd3fc); // Cielo azul claro
            scene.fog = new THREE.FogExp2(0x7dd3fc, 0.0035);

            // Cámara en tercera persona / vista aérea isométrica suave
            camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 35, 45);

            // Renderizador
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            container.appendChild(renderer.domElement);

            // Redimensionamiento de ventana
            window.addEventListener('resize', onWindowResize, false);
        }

        function createLights() {
            // Luz Ambiental
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
            scene.add(ambientLight);

            // Luz Sol (Direccional con sombras)
            const sunLight = new THREE.DirectionalLight(0xfffbeb, 0.9);
            sunLight.position.set(100, 150, 50);
            sunLight.castShadow = true;
            sunLight.shadow.mapSize.width = 2048;
            sunLight.shadow.mapSize.height = 2048;
            sunLight.shadow.camera.near = 0.5;
            sunLight.shadow.camera.far = 500;
            const d = 150;
            sunLight.shadow.camera.left = -d;
            sunLight.shadow.camera.right = d;
            sunLight.shadow.camera.top = d;
            sunLight.shadow.camera.bottom = -d;
            scene.add(sunLight);
        }

        // Creación del Mar
        function createWater() {
            const waterGeometry = new THREE.PlaneGeometry(MAP_SIZE * 2, MAP_SIZE * 2, 64, 64);
            const waterMaterial = new THREE.MeshStandardMaterial({
                color: 0x0284c7,
                roughness: 0.1,
                metalness: 0.1,
                flatShading: true
            });

            waterMesh = new THREE.Mesh(waterGeometry, waterMaterial);
            waterMesh.rotation.x = -Math.PI / 2;
            waterMesh.receiveShadow = true;
            scene.add(waterMesh);
        }

        // Constructor de Barco (Pirata o Enemigo)
        function createBoat(hullColorHex, isPlayer = false) {
            const boatGroup = new THREE.Group();

            // Materiales
            const hullMat = new THREE.MeshStandardMaterial({ color: hullColorHex, roughness: 0.6 });
            const deckMat = new THREE.MeshStandardMaterial({ color: 0x78350f, roughness: 0.8 }); // Madera
            const sailMat = new THREE.MeshStandardMaterial({ color: isPlayer ? 0xf8fafc : 0xef4444, roughness: 0.5, side: THREE.DoubleSide });
            const woodMat = new THREE.MeshStandardMaterial({ color: 0x451a03 });
            const ironMat = new THREE.MeshStandardMaterial({ color: 0x1e293b, metalness: 0.8 });

            // 1. Casco del barco (Forma básica con bloque estilizado)
            const hullGeo = new THREE.BoxGeometry(3.2, 1.6, 7);
            const hull = new THREE.Mesh(hullGeo, hullMat);
            hull.position.y = 0.6;
            hull.castShadow = true;
            hull.receiveShadow = true;
            boatGroup.add(hull);

            // Proa puntiaguda
            const bowGeo = new THREE.ConeGeometry(1.6, 2.8, 4);
            const bow = new THREE.Mesh(bowGeo, hullMat);
            bow.rotation.x = Math.PI / 2;
            bow.rotation.y = Math.PI / 4;
            bow.position.set(0, 0.6, 4.4);
            bow.scale.set(1, 1, 0.8);
            bow.castShadow = true;
            boatGroup.add(bow);

            // Cubierta
            const deckGeo = new THREE.BoxGeometry(3.0, 0.2, 6.6);
            const deck = new THREE.Mesh(deckGeo, deckMat);
            deck.position.y = 1.45;
            boatGroup.add(deck);

            // Mástiles y Velas
            const mastPositions = isPlayer ? [0.8, -1.2] : [0];
            mastPositions.forEach(posZ => {
                // Mástil
                const mastGeo = new THREE.CylinderGeometry(0.12, 0.15, 6.5);
                const mast = new THREE.Mesh(mastGeo, woodMat);
                mast.position.set(0, 4.5, posZ);
                mast.castShadow = true;
                boatGroup.add(mast);

                // Velas (Curvadas ligeramente)
                const sailGeo = new THREE.PlaneGeometry(3.8, 3.2, 4, 4);
                // Deformar la geometría para dar efecto de viento a la vela
                const pos = sailGeo.attributes.position;
                for (let i = 0; i < pos.count; i++) {
                    const z = pos.getZ(i);
                    pos.setZ(i, z + Math.sin(pos.getY(i)) * 0.4);
                }
                sailGeo.computeVertexNormals();

                const sail = new THREE.Mesh(sailGeo, sailMat);
                sail.position.set(0, 5, posZ + 0.1);
                sail.castShadow = true;
                boatGroup.add(sail);
            });

            // Cañones en los laterales
            const cannonGeo = new THREE.CylinderGeometry(0.15, 0.2, 1.2);
            cannonGeo.rotateX(Math.PI / 2);
           
            [-1.6, 1.6].forEach(sideX => {
                [-1, 1].forEach(posZ => {
                    const cannon = new THREE.Mesh(cannonGeo, ironMat);
                    cannon.position.set(sideX, 1.3, posZ);
                    cannon.rotation.y = sideX > 0 ? Math.PI / 2 : -Math.PI / 2;
                    cannon.castShadow = true;
                    boatGroup.add(cannon);
                });
            });

            // Bandera superior
            const flagGeo = new THREE.PlaneGeometry(0.8, 0.5);
            const flagMat = new THREE.MeshBasicMaterial({ color: isPlayer ? 0xf59e0b : 0x111827, side: THREE.DoubleSide });
            const flag = new THREE.Mesh(flagGeo, flagMat);
            flag.position.set(0, 7.5, mastPositions[0]);
            boatGroup.add(flag);

            return boatGroup;
        }

        // Spawn de Barcos Enemigos en los márgenes
        function spawnEnemy() {
            const enemyMesh = createBoat(0x991b1b, false); // Rojo oscuro
           
            // Posición aleatoria alrededor del jugador a cierta distancia
            const angle = Math.random() * Math.PI * 2;
            const distance = 80 + Math.random() * 60;
           
            enemyMesh.position.x = playerBoat ? playerBoat.position.x + Math.cos(angle) * distance : Math.cos(angle) * distance;
            enemyMesh.position.z = playerBoat ? playerBoat.position.z + Math.sin(angle) * distance : Math.sin(angle) * distance;
            enemyMesh.rotation.y = Math.atan2(playerBoat ? playerBoat.position.x - enemyMesh.position.x : 0, playerBoat ? playerBoat.position.z - enemyMesh.position.z : 0);

            // Estado del enemigo
            const enemyData = {
                mesh: enemyMesh,
                isSinking: false,
                sinkTimer: 0,
                health: 1
            };

            scene.add(enemyMesh);
            enemies.push(enemyData);

            document.getElementById('enemy-count').innerText = enemies.filter(e => !e.isSinking).length;
        }

        // --- SISTEMA DE DISPARO Y BALAS DE CAÑÓN ---

        function firePlayerCannons() {
            if (playerStats.cooldown > 0 || isPaused) return;

            playerStats.cooldown = playerStats.cooldownMax;

            // Disparar en ráfaga lateral izquierda y derecha + frontal ligero
            const sides = [-1, 1]; // Izquierda y Derecha
           
            sides.forEach(side => {
                [-1, 1].forEach(offsetZ => {
                    createCannonball(side, offsetZ);
                });
            });
        }

        function createCannonball(sideDirection, offsetZ) {
            const ballGeo = new THREE.SphereGeometry(0.35, 12, 12);
            const ballMat = new THREE.MeshStandardMaterial({ color: 0x171717, metalness: 0.9, roughness: 0.2 });
            const ball = new THREE.Mesh(ballGeo, ballMat);
            ball.castShadow = true;

            // Posición inicial (Lado del barco)
            const spawnOffset = new THREE.Vector3(sideDirection * 1.8, 1.3, offsetZ);
            spawnOffset.applyAxisAngle(new THREE.Vector3(0, 1, 0), playerBoat.rotation.y);
            ball.position.copy(playerBoat.position).add(spawnOffset);

            // Dirección del proyectil (Perpendicular al barco + ligero impulso)
            const angle = playerBoat.rotation.y + (sideDirection * Math.PI / 2);
            const speed = 1.3;
           
            const velocity = new THREE.Vector3(
                Math.sin(angle) * speed,
                0.25, // Elevación para trayectoria parabólica
                Math.cos(angle) * speed
            );

            scene.add(ball);
            cannonballs.push({
                mesh: ball,
                velocity: velocity,
                life: 60 // Cuadros antes de caer al agua
            });

            // Pequeño humo en el cañón al disparar
            createExplosionParticles(ball.position, 0xf59e0b, 4, 0.2);
        }

        // --- EFECTOS DE PARTICULAS (EXPLOSIÓN Y SALPICADURAS) ---

        function createExplosionParticles(position, colorHex, count = 15, scale = 0.5) {
            for (let i = 0; i < count; i++) {
                const pGeo = new THREE.SphereGeometry((Math.random() * 0.3 + 0.1) * scale, 6, 6);
                const pMat = new THREE.MeshBasicMaterial({ color: colorHex, transparent: true, opacity: 0.9 });
                const p = new THREE.Mesh(pGeo, pMat);
                p.position.copy(position);

                const vel = new THREE.Vector3(
                    (Math.random() - 0.5) * 0.6,
                    Math.random() * 0.5 + 0.1,
                    (Math.random() - 0.5) * 0.6
                );

                scene.add(p);
                particles.push({
                    mesh: p,
                    velocity: vel,
                    life: 25 + Math.random() * 15,
                    maxLife: 40
                });
            }
        }

        // --- CONTROLES E INTERFAZ (UI) ---

        function setupControls() {
            // Teclado
            window.addEventListener('keydown', (e) => {
                if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.forward = true;
                if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.backward = true;
                if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.left = true;
                if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.right = true;
                if (e.code === 'Space') firePlayerCannons();
            });

            window.addEventListener('keyup', (e) => {
                if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.forward = false;
                if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.backward = false;
                if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.left = false;
                if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.right = false;
            });

            // Clic de ratón para disparar
            window.addEventListener('pointerdown', (e) => {
                // Evitar disparar si se hace clic en elementos UI
                if (!e.target.closest('.glass-panel') && !e.target.closest('button') && !e.target.closest('input')) {
                    firePlayerCannons();
                }
            });

            // Botones táctiles móviles
            bindTouchBtn('btn-up', 'forward');
            bindTouchBtn('btn-down', 'backward');
            bindTouchBtn('btn-left', 'left');
            bindTouchBtn('btn-right', 'right');
           
            document.getElementById('btn-fire').addEventListener('click', firePlayerCannons);
        }

        function bindTouchBtn(id, keyProp) {
            const btn = document.getElementById(id);
            btn.addEventListener('touchstart', (e) => { e.preventDefault(); keys[keyProp] = true; });
            btn.addEventListener('touchend', (e) => { e.preventDefault(); keys[keyProp] = false; });
            btn.addEventListener('mousedown', () => { keys[keyProp] = true; });
            btn.addEventListener('mouseup', () => { keys[keyProp] = false; });
        }

        function setupUI() {
            // Sliders para calibración
            const playerSlider = document.getElementById('player-speed-slider');
            const playerValLabel = document.getElementById('player-speed-val');
           
            playerSlider.addEventListener('input', (e) => {
                playerSpeedMultiplier = parseFloat(e.target.value);
                playerValLabel.innerText = playerSpeedMultiplier.toFixed(1) + 'x';
            });

            const enemySlider = document.getElementById('enemy-speed-slider');
            const enemyValLabel = document.getElementById('enemy-speed-val');

            enemySlider.addEventListener('input', (e) => {
                enemySpeedMultiplier = parseFloat(e.target.value);
                enemyValLabel.innerText = enemySpeedMultiplier.toFixed(2) + 'x';
            });

            // Botón Pausa
            const btnPause = document.getElementById('btn-pause');
            btnPause.addEventListener('click', () => {
                isPaused = !isPaused;
                btnPause.innerHTML = isPaused ? '<i class="fa-solid fa-play me-1"></i> Reanudar' : '<i class="fa-solid fa-pause me-1"></i> Pausa';
            });
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        // --- BUCLE PRINCIPAL DE JUEGO (ANIMACIÓN Y LÓGICA) ---

        function animate() {
            requestAnimationFrame(animate);

            if (!isPaused) {
                updatePlayer();
                updateEnemies();
                updateCannonballs();
                updateParticles();
                updateCamera();
                animateWater();
            }

            renderer.render(scene, camera);
        }

        // Movimiento suave del mar
        function animateWater() {
            const time = Date.now() * 0.0015;
            const pos = waterMesh.geometry.attributes.position;
            for (let i = 0; i < pos.count; i++) {
                const u = pos.getX(i);
                const v = pos.getY(i);
                const z = Math.sin(u * 0.1 + time) * 0.3 + Math.cos(v * 0.1 + time) * 0.3;
                pos.setZ(i, z);
            }
            waterMesh.geometry.computeVertexNormals();
            waterMesh.geometry.attributes.position.needsUpdate = true;
        }

        // Lógica de movimiento del Jugador
        function updatePlayer() {
            if (playerStats.cooldown > 0) playerStats.cooldown--;

            // Aceleración y Frenado según Sliders
            const effectiveMaxSpeed = playerStats.maxSpeed * playerSpeedMultiplier;

            if (keys.forward) {
                playerStats.speed = Math.min(playerStats.speed + playerStats.acceleration, effectiveMaxSpeed);
            } else if (keys.backward) {
                playerStats.speed = Math.max(playerStats.speed - playerStats.acceleration, -effectiveMaxSpeed * 0.4);
            } else {
                playerStats.speed *= playerStats.friction;
            }

            // Giro
            if (Math.abs(playerStats.speed) > 0.01) {
                const dir = playerStats.speed > 0 ? 1 : -1;
                if (keys.left) playerBoat.rotation.y += playerStats.turnSpeed * dir;
                if (keys.right) playerBoat.rotation.y -= playerStats.turnSpeed * dir;
            }

            // Desplazamiento
            playerBoat.position.x += Math.sin(playerBoat.rotation.y) * playerStats.speed;
            playerBoat.position.z += Math.cos(playerBoat.rotation.y) * playerStats.speed;

            // Balanceo simulado del barco por el oleaje
            const time = Date.now() * 0.003;
            playerBoat.rotation.z = Math.sin(time) * 0.04;
            playerBoat.rotation.x = Math.cos(time * 0.7) * 0.03;
            playerBoat.position.y = Math.sin(time * 1.5) * 0.15;
        }

        // Lógica Barcos Enemigos (Navegación MUY LENTA hacia el jugador)
        function updateEnemies() {
            const time = Date.now() * 0.002;

            enemies.forEach((enemy, index) => {
                if (enemy.isSinking) {
                    // Animación de hundimiento lenta (se inclina y desciende)
                    enemy.sinkTimer += 0.015;
                    enemy.mesh.position.y -= 0.04;
                    enemy.mesh.rotation.z += 0.01;
                    enemy.mesh.rotation.x += 0.015;

                    if (enemy.mesh.position.y < -6) {
                        // Eliminar completamente y reaparecer otro enemigo
                        scene.remove(enemy.mesh);
                        enemies.splice(index, 1);
                        spawnEnemy();
                    }
                    return;
                }

                // Balanceo normal
                enemy.mesh.position.y = Math.sin(time + index) * 0.1;

                // Rotación hacia el jugador (Persecución Lenta)
                const dx = playerBoat.position.x - enemy.mesh.position.x;
                const dz = playerBoat.position.z - enemy.mesh.position.z;
                const targetAngle = Math.atan2(dx, dz);

                // Suavizar giro del enemigo
                let diff = targetAngle - enemy.mesh.rotation.y;
                while (diff < -Math.PI) diff += Math.PI * 2;
                while (diff > Math.PI) diff -= Math.PI * 2;
                enemy.mesh.rotation.y += diff * 0.02;

                // Movimiento MUY LENTO hacia adelante regulado por el slider de velocidad enemigo
                const baseEnemySpeed = 0.08;
                const currentEnemySpeed = baseEnemySpeed * enemySpeedMultiplier;

                enemy.mesh.position.x += Math.sin(enemy.mesh.rotation.y) * currentEnemySpeed;
                enemy.mesh.position.z += Math.cos(enemy.mesh.rotation.y) * currentEnemySpeed;
            });
        }

        // Lógica Proyectiles de Cañón y Colisiones
        function updateCannonballs() {
            for (let i = cannonballs.length - 1; i >= 0; i--) {
                const b = cannonballs[i];
               
                // Mover bala
                b.mesh.position.add(b.velocity);
                b.velocity.y -= 0.008; // Gravedad suave
                b.life--;

                // Verificar colisión con agua o barcos enemigos
                let hit = false;

                // Colisión con enemigos
                enemies.forEach(enemy => {
                    if (!enemy.isSinking && !hit) {
                        const dist = b.mesh.position.distanceTo(enemy.mesh.position);
                        if (dist < 4.2) { // Radio de colisión del barco
                            hit = true;
                            enemy.isSinking = true;

                            // Efectos visuales de explosión y humo
                            createExplosionParticles(b.mesh.position, 0xf97316, 25, 1.2);
                            createExplosionParticles(b.mesh.position, 0xef4444, 20, 0.8);
                            createExplosionParticles(b.mesh.position, 0x334155, 15, 1.5);

                            // Actualizar Puntuación
                            score++;
                            document.getElementById('score-text').innerText = score;
                            document.getElementById('enemy-count').innerText = enemies.filter(e => !e.isSinking).length;
                        }
                    }
                });

                // Colisión con el agua o límite de vida de la bala
                if (!hit && (b.mesh.position.y <= 0 || b.life <= 0)) {
                    hit = true;
                    // Salpicadura de agua
                    createExplosionParticles(new THREE.Vector3(b.mesh.position.x, 0, b.mesh.position.z), 0xe0f2fe, 12, 0.6);
                }

                if (hit) {
                    scene.remove(b.mesh);
                    cannonballs.splice(i, 1);
                }
            }
        }

        // Actualizar partículas
        function updateParticles() {
            for (let i = particles.length - 1; i >= 0; i--) {
                const p = particles[i];
                p.mesh.position.add(p.velocity);
                p.life--;
               
                // Desvanecer opacidad
                p.mesh.material.opacity = p.life / p.maxLife;

                if (p.life <= 0) {
                    scene.remove(p.mesh);
                    particles.splice(i, 1);
                }
            }
        }

        // Cámara inteligente que sigue al barco del jugador
        function updateCamera() {
            const cameraOffset = new THREE.Vector3(0, 30, -40); // Posición relativa detrás y arriba
            cameraOffset.applyAxisAngle(new THREE.Vector3(0, 1, 0), playerBoat.rotation.y);
           
            const targetCamPos = playerBoat.position.clone().add(cameraOffset);
           
            // Suavizado de movimiento de cámara (Lerp)
            camera.position.lerp(targetCamPos, 0.06);
            camera.lookAt(playerBoat.position.clone().add(new THREE.Vector3(0, 2, 0)));
        }
    </script>
</body>
</html>

No hay comentarios:

Publicar un comentario