Bolas Versión Homero
by PixelDolphin31575 lines16.2 KB
<!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.