🎮ArcadeLab

Bolas Versión Homero

by PixelDolphin31
575 lines16.2 KB
▶ Play
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Bolas Versión Homero</title>
  <style>
    body {
      margin: 0;
      background: #222;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      font-family: sans-serif;
    }
    canvas {
      border: 2px solid #fff;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <canvas id="game" width="800" height="500"></canvas>
  <script>
    const canvas = document.getElementById('game');
    const ctx = canvas.getContext('2d');
    const W = 800, H = 500;
    const GRAVITY = 0.5;
    const FRICTION = 0.8;

    // ====== ESTADOS ======
    let state = 'menu'; // menu, mapSelect, playing, driving, win, gameover
    let currentMap = 0;
    let score = 0;
    let collected = 0;
    const requiredBalls = 3;

    // ====== MAPAS ======
    const maps = [
      { name: 'Jardín', bg: '#87CEEB', groundColor: '#4CAF50', groundY: 400, cabinX: 650 },
      { name: 'Estación', bg: '#FFE0B2', groundColor: '#795548', groundY: 380, cabinX: 620 }
    ];
    let map = maps[0];

    // ====== PERSONAJE (Homero) ======
    let player = {
      x: 100, y: 300, vx: 0, vy: 0, radius: 20,
      onGround: false, facing: 1, health: 3, attackCooldown: 0
    };

    // ====== CABINA / TREN ======
    let cabin = {
      x: 650, y: 320, w: 130, h: 80, vx: 0
    };

    // ====== ENTIDADES ======
    let enemies = [];
    let collectibles = [];
    let projectiles = [];
    let enemySpawnTimer = 0;
    let keys = {};

    // ====== ENTRADA ======
    window.addEventListener('keydown', e => {
      keys[e.key] = true;
      if (state === 'menu' && e.key === 'Enter') state = 'mapSelect';
      if (state === 'mapSelect' && e.key === '1') { currentMap = 0; startGame(); }
      if (state === 'mapSelect' && e.key === '2') { currentMap = 1; startGame(); }
      if (state === 'playing' && (e.key === ' ' || e.key === 'x' || e.key === 'X')) attack();
      if (state === 'driving' && (e.key === 'e' || e.key === 'E')) exitCabin();
      if ((state === 'win' || state === 'gameover') && e.key === 'Enter') state = 'menu';
    });
    window.addEventListener('keyup', e => { keys[e.key] = false; });

    canvas.addEventListener('click', e => {
      const rect = canvas.getBoundingClientRect();
      const mx = e.clientX - rect.left;
      const my = e.clientY - rect.top;
      if (state === 'menu') {
        if (isInRect(mx, my, 300, 200, 200, 50)) state = 'mapSelect';
        if (isInRect(mx, my, 300, 270, 200, 50)) {
          alert('Controles: Flechas/A,D para moverte. Arriba/W para saltar. Espacio/X para atacar. Recoge 3 bolas y entra a la cabina. Luego conduce con las flechas.');
        }
      } else if (state === 'mapSelect') {
        if (isInRect(mx, my, 200, 200, 180, 50)) { currentMap = 0; startGame(); }
        if (isInRect(mx, my, 420, 200, 180, 50)) { currentMap = 1; startGame(); }
      } else if (state === 'playing') {
        attack();
      } else if (state === 'win' || state === 'gameover') {
        if (isInRect(mx, my, 300, 300, 200, 50)) state = 'menu';
      }
    });

    function isInRect(px, py, rx, ry, rw, rh) {
      return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh;
    }

    // ====== INICIAR PARTIDA ======
    function startGame() {
      map = maps[currentMap];
      groundY = map.groundY;
      cabin.x = map.cabinX;
      cabin.y = groundY - cabin.h;
      cabin.vx = 0;
      player.x = 100;
      player.y = groundY - player.radius;
      player.vx = 0;
      player.vy = 0;
      player.health = 3;
      player.attackCooldown = 0;
      player.onGround = true;
      player.facing = 1;
      collected = 0;
      score = 0;
      enemies = [];
      collectibles = [];
      projectiles = [];
      enemySpawnTimer = 120;
      state = 'playing';
    }

    function attack() {
      if (state !== 'playing') return;
      if (player.attackCooldown > 0) return;
      player.attackCooldown = 15;
      projectiles.push({
        x: player.x + player.facing * (player.radius + 5),
        y: player.y,
        vx: player.facing * 7,
        vy: 0,
        radius: 8,
        life: 60
      });
    }

    function spawnEnemy() {
      const x = Math.random() < 0.5 ? 30 : W - 30;
      enemies.push({
        x: x,
        y: groundY - 15,
        vx: 0,
        vy: 0,
        radius: 15,
        onGround: true
      });
    }

    function exitCabin() {
      if (state !== 'driving') return;
      state = 'playing';
      player.x = cabin.x + cabin.w / 2;
      player.y = cabin.y + 40;
      player.vx = 0;
      player.vy = 0;
      player.onGround = true;
    }

    // ====== ACTUALIZACIÓN ======
    function update() {
      if (state === 'playing') {
        updatePlayer();
        updateEnemies();
        updateProjectiles();
        enemySpawnTimer--;
        if (enemySpawnTimer <= 0 && enemies.length < 5) {
          spawnEnemy();
          enemySpawnTimer = 180;
        }
      } else if (state === 'driving') {
        updateDriving();
      }
    }

    function updatePlayer() {
      if (player.attackCooldown > 0) player.attackCooldown--;

      // Movimiento horizontal
      if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
        player.vx = -4;
        player.facing = -1;
      } else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
        player.vx = 4;
        player.facing = 1;
      } else {
        player.vx *= FRICTION;
      }

      // Salto (no usar espacio para saltar, espacio ataca)
      if ((keys['ArrowUp'] || keys['w'] || keys['W']) && player.onGround) {
        player.vy = -10;
        player.onGround = false;
      }

      // Gravedad
      player.vy += GRAVITY;
      player.x += player.vx;
      player.y += player.vy;

      // Colisión con el piso (no atraviesa el piso)
      if (player.y + player.radius > groundY) {
        player.y = groundY - player.radius;
        player.vy = 0;
        player.onGround = true;
      } else {
        player.onGround = false;
      }

      // Límites laterales
      if (player.x - player.radius < 0) {
        player.x = player.radius;
        player.vx = 0;
      }
      if (player.x + player.radius > W) {
        player.x = W - player.radius;
        player.vx = 0;
      }

      // Recoger bolas coleccionables
      collectibles = collectibles.filter(c => {
        const dx = player.x - c.x;
        const dy = player.y - c.y;
        if (Math.hypot(dx, dy) < player.radius + c.radius) {
          collected++;
          score += 25;
          return false;
        }
        return true;
      });

      // Daño por enemigos
      enemies.forEach(enemy => {
        const dx = player.x - enemy.x;
        const dy = player.y - enemy.y;
        if (Math.hypot(dx, dy) < player.radius + enemy.radius) {
          player.health--;
          player.vx = (player.x > enemy.x ? 5 : -5);
          player.vy = -5;
          enemy.vx = (player.x > enemy.x ? -5 : 5);
          if (player.health <= 0) {
            state = 'gameover';
          }
        }
      });

      // Entrar a la cabina
      if (collected >= requiredBalls && isCollidingWithCabin(player)) {
        state = 'driving';
        cabin.vx = 0;
      }
    }

    function updateEnemies() {
      enemies.forEach(enemy => {
        // Gravedad
        enemy.vy += GRAVITY;
        enemy.x += enemy.vx;
        enemy.y += enemy.vy;

        // Colisión con piso
        if (enemy.y + enemy.radius > groundY) {
          enemy.y = groundY - enemy.radius;
          enemy.vy = 0;
          enemy.onGround = true;
        } else {
          enemy.onGround = false;
        }

        // Perseguir al jugador
        if (player.x < enemy.x) enemy.vx = -1.5;
        else enemy.vx = 1.5;

        // Límites laterales
        if (enemy.x - enemy.radius < 0) {
          enemy.x = enemy.radius;
          enemy.vx *= -1;
        }
        if (enemy.x + enemy.radius > W) {
          enemy.x = W - enemy.radius;
          enemy.vx *= -1;
        }
      });
    }

    function updateProjectiles() {
      projectiles.forEach(p => {
        p.x += p.vx;
        p.life--;
      });
      projectiles = projectiles.filter(p => p.life > 0 && p.x > -20 && p.x < W + 20);

      // Colisión con enemigos
      for (let i = projectiles.length - 1; i >= 0; i--) {
        const p = projectiles[i];
        for (let j = enemies.length - 1; j >= 0; j--) {
          const e = enemies[j];
          if (Math.hypot(p.x - e.x, p.y - e.y) < p.radius + e.radius) {
            // Destruir enemigo y soltar bola coleccionable
            collectibles.push({ x: e.x, y: groundY - 10, radius: 10 });
            enemies.splice(j, 1);
            projectiles.splice(i, 1);
            score += 50;
            break;
          }
        }
      }
    }

    function updateDriving() {
      if (keys['ArrowLeft'] || keys['a'] || keys['A']) cabin.vx = -3;
      else if (keys['ArrowRight'] || keys['d'] || keys['D']) cabin.vx = 3;
      else cabin.vx *= 0.8;

      cabin.x += cabin.vx;

      // El tren no está anclado y no atraviesa el piso
      cabin.y = groundY - cabin.h;

      // Límites laterales
      if (cabin.x < 0) {
        cabin.x = 0;
        cabin.vx = 0;
      }
      if (cabin.x + cabin.w > W) {
        cabin.x = W - cabin.w;
        cabin.vx = 0;
        // Victoria al llegar al borde derecho
        state = 'win';
      }
    }

    function isCollidingWithCabin(obj) {
      return obj.x + obj.radius > cabin.x && obj.x - obj.radius < cabin.x + cabin.w &&
             obj.y + obj.radius > cabin.y && obj.y - obj.radius < cabin.y + cabin.h;
    }

    // ====== DIBUJO ======
    function draw() {
      ctx.clearRect(0, 0, W, H);

      if (state === 'menu') drawMenu();
      else if (state === 'mapSelect') drawMapSelect();
      else if (state === 'playing' || state === 'driving' || state === 'win' || state === 'gameover') {
        drawGame();
        if (state === 'win') drawWin();
        if (state === 'gameover') drawGameOver();
      }
    }

    function drawMenu() {
      // Fondo
      ctx.fillStyle = '#2C3E50';
      ctx.fillRect(0, 0, W, H);

      // Bolas decorativas flotando
      for (let i = 0; i < 6; i++) {
        const x = 100 + i * 120;
        const y = 80 + Math.sin(Date.now() / 500 + i) * 20;
        drawBall(x, y, 20, '#FFD700');
      }

      // Título
      ctx.fillStyle = '#FFD700';
      ctx.font = 'bold 48px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('BOLAS VERSIÓN HOMERO', W / 2, 150);

      // Botones
      drawButton(300, 200, 200, 50, 'JUGAR');
      drawButton(300, 270, 200, 50, 'AYUDA');

      ctx.fillStyle = '#fff';
      ctx.font = '20px sans-serif';
      ctx.fillText('Haz clic en un botón o presiona Enter', W / 2, 380);
    }

    function drawMapSelect() {
      ctx.fillStyle = '#34495E';
      ctx.fillRect(0, 0, W, H);

      ctx.fillStyle = '#FFD700';
      ctx.font = 'bold 40px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('SELECCIONA MAPA', W / 2, 120);

      drawButton(200, 200, 180, 50, 'JARDÍN');
      drawButton(420, 200, 180, 50, 'ESTACIÓN');

      ctx.fillStyle = '#fff';
      ctx.font = '18px sans-serif';
      ctx.fillText('Presiona 1 o 2', W / 2, 320);
    }

    function drawGame() {
      // Fondo del mapa
      ctx.fillStyle = map.bg;
      ctx.fillRect(0, 0, W, H);

      // Piso (sólido, no se atraviesa)
      ctx.fillStyle = map.groundColor;
      ctx.fillRect(0, groundY, W, H - groundY);
      ctx.fillStyle = '#000';
      ctx.fillRect(0, groundY, W, 3);

      // Cabina / Tren
      drawCabin();

      // Bolas coleccionables
      collectibles.forEach(c => drawBall(c.x, c.y, c.radius, '#2196F3'));

      // Enemigos
      enemies.forEach(e => drawEnemyBall(e.x, e.y, e.radius));

      // Proyectiles
      projectiles.forEach(p => drawBall(p.x, p.y, p.radius, '#FF5722'));

      // Jugador (Homero)
      if (state === 'playing') drawHomeroBall(player.x, player.y, player.radius);
      else if (state === 'driving') {
        // Dibujar a Homero dentro de la cabina
        drawHomeroBall(cabin.x + cabin.w / 2, cabin.y + 40, 15);
      }

      // HUD
      drawHUD();
    }

    function drawCabin() {
      const x = cabin.x, y = cabin.y, w = cabin.w, h = cabin.h;
      // Cuerpo del tren
      ctx.fillStyle = '#D32F2F';
      ctx.fillRect(x, y, w, h);
      ctx.fillStyle = '#B71C1C';
      ctx.fillRect(x, y, w, 20);

      // Ventanas
      ctx.fillStyle = '#B3E5FC';
      ctx.fillRect(x + 10, y + 30, 25, 20);
      ctx.fillRect(x + 50, y + 30, 25, 20);
      ctx.fillRect(x + 90, y + 30, 25, 20);

      // Ruedas
      ctx.fillStyle = '#333';
      ctx.beginPath();
      ctx.arc(x + 20, groundY, 10, 0, Math.PI * 2);
      ctx.fill();
      ctx.beginPath();
      ctx.arc(x + w - 20, groundY, 10, 0, Math.PI * 2);
      ctx.fill();
    }

    function drawHUD() {
      ctx.fillStyle = '#000';
      ctx.font = 'bold 18px sans-serif';
      ctx.textAlign = 'left';
      ctx.fillText('Vida: ' + player.health, 20, 30);
      ctx.fillText('Puntaje: ' + score, 20, 55);
      ctx.fillText('Bolas: ' + collected + '/' + requiredBalls, 20, 80);
      if (state === 'driving') {
        ctx.fillStyle = '#FFD700';
        ctx.fillText('Conduciendo... presiona E para salir', 300, 30);
      }
    }

    function drawHomeroBall(x, y, r) {
      // Cuerpo amarillo
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.fillStyle = '#FFD700';
      ctx.fill();
      ctx.strokeStyle = '#000';
      ctx.lineWidth = 2;
      ctx.stroke();

      // Ojos blancos
      ctx.fillStyle = '#FFF';
      ctx.beginPath();
      ctx.arc(x - r * 0.3, y - r * 0.2, r * 0.25, 0, Math.PI * 2);
      ctx.arc(x + r * 0.3, y - r * 0.2, r * 0.25, 0, Math.PI * 2);
      ctx.fill();

      // Pupilas
      ctx.fillStyle = '#000';
      ctx.beginPath();
      ctx.arc(x - r * 0.3, y - r * 0.2, r * 0.1, 0, Math.PI * 2);
      ctx.arc(x + r * 0.3, y - r * 0.2, r * 0.1, 0, Math.PI * 2);
      ctx.fill();

      // Boca
      ctx.beginPath();
      ctx.arc(x, y + r * 0.15, r * 0.4, 0.1, Math.PI - 0.1);
      ctx.strokeStyle = '#000';
      ctx.lineWidth = 2;
      ctx.stroke();
    }

    function drawEnemyBall(x, y, r) {
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.fillStyle = '#E53935';
      ctx.fill();
      ctx.strokeStyle = '#000';
      ctx.lineWidth = 2;
      ctx.stroke();

      // Ojos enojados
      ctx.fillStyle = '#FFF';
      ctx.beginPath();
      ctx.arc(x - r * 0.3, y - r * 0.2, r * 0.22, 0, Math.PI * 2);
      ctx.arc(x + r * 0.3, y - r * 0.2, r * 0.22, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillStyle = '#000';
      ctx.beginPath();
      ctx.arc(x - r * 0.3, y - r * 0.2, r * 0.1, 0, Math.PI * 2);
      ctx.arc(x + r * 0.3, y - r * 0.2, r * 0.1, 0, Math.PI * 2);
      ctx.fill();
    }

    function drawBall(x, y, r, color) {
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.fillStyle = color;
      ctx.fill();
      ctx.strokeStyle = '#000';
      ctx.lineWidth = 2;
      ctx.stroke();
    }

    function drawButton(x, y, w, h, text) {
      ctx.fillStyle = '#FFD700';
      ctx.fillRect(x, y, w, h);
      ctx.strokeStyle = '#000';
      ctx.lineWidth = 3;
      ctx.strokeRect(x, y, w, h);
      ctx.fillStyle = '#000';
      ctx.font = 'bold 20px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText(text, x + w / 2, y + h / 2 + 7);
    }

    function drawWin() {
      ctx.fillStyle = 'rgba(0,0,0,0.7)';
      ctx.fillRect(0, 0, W, H);
      ctx.fillStyle = '#FFD700';
      ctx.font = 'bold 50px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('¡GANASTE!', W / 2, 220);
      ctx.fillStyle = '#FFF';
      ctx.font = '20px sans-serif';
      ctx.fillText('El tren llegó al final sin atravesar el piso.', W / 2, 270);
      drawButton(300, 300, 200, 50, 'VOLVER AL MENÚ');
    }

    function drawGameOver() {
      ctx.fillStyle = 'rgba(0,0,0,0.8)';
      ctx.fillRect(0, 0, W, H);
      ctx.fillStyle = '#E53935';
      ctx.font = 'bold 50px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('GAME OVER', W / 2, 220);
      ctx.fillStyle = '#FFF';
      ctx.font = '20px sans-serif';
      ctx.fillText('Homero perdió todas sus vidas.', W / 2, 270);
      drawButton(300, 300, 200, 50, 'VOLVER AL MENÚ');
    }

    // ====== BUCLE PRINCIPAL ======
    function loop() {
      update();
      draw();
      requestAnimationFrame(loop);
    }
    loop();
  </script>
</body>
</html>

Game Source: Bolas Versión Homero

Creator: PixelDolphin31

Libraries: none

Complexity: complex (575 lines, 16.2 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: bolas-versi-n-homero-pixeldolphin31" to link back to the original. Then publish at arcadelab.ai/publish.