import { Juego } from "./main"; import { Sprite } from "./sprite"; import { Box, boxCollision, drawText, isMobile, isTouching, posInBox, randomFromArray, } from "./utils"; const ENEMIES_NUM = 12; const SEED_COOLDOWN = 300; const MAP_MIN = 1000; const MAP_MAX = 10000; const MAP_SIZE = MAP_MAX - MAP_MIN; const SIGNATURES_TO_WIN = 20000; const CITIZENS = SIGNATURES_TO_WIN / 1000; const TIME = 2 * 60 * 1000; const TIME_LOST_WHEN_HIT = 20 * 1000; type Citizen = { x: number; sprite: Sprite }; export type State = { current: "jugando"; pos: { x: number }; view: { x: number }; side: "left" | "right"; enemies: { x: number; sprite: Sprite }[]; seeds: { x: number; velocity: { x: number } }[]; trees: { x: number; sprite: Sprite }[]; citizens: Citizen[]; signatures: number; time: number; seedCooldown: number; }; export function createJugandoState(juego: Juego): State { let citizens = []; for (let i = 0; i < CITIZENS; i++) { citizens.push({ x: MAP_MIN + (i + 1) * (MAP_SIZE / CITIZENS), sprite: randomFromArray([ juego.sprites.ciudadanx1, juego.sprites.ciudadanx2, juego.sprites.ciudadanx3, ]), }); } console.log(CITIZENS, citizens); return { current: "jugando", pos: { x: MAP_MIN + 100 }, view: { x: 0 }, side: "right", enemies: [], seeds: [], trees: [], signatures: 0, citizens, time: TIME, seedCooldown: 0, }; } function touchControls(juego: Juego): { left: Box; right: Box; center: Box; } { return { left: { x: 0, y: 0, width: juego.canvas.width / 7, height: juego.canvas.height, }, right: { x: juego.canvas.width - juego.canvas.width / 7, y: 0, width: juego.canvas.width / 7, height: juego.canvas.height, }, center: { x: juego.canvas.width / 7, y: 0, width: juego.canvas.width - (juego.canvas.width / 7) * 2, height: juego.canvas.height, }, }; } export function update(juego: Juego, dt: number) { juego.state.time -= dt; if (juego.state.time < 0) { (juego as Juego).state = { current: "lose" }; return; } const playerSpeed = juego.canvas.width * 0.15; const enemySpeed = juego.canvas.width * 0.05; const { left, right, center } = touchControls(juego); if ( juego.keyboard.keys.d || juego.keyboard.keys.ArrowRight || isTouching(juego, right) ) { juego.state.side = "right"; juego.state.pos.x += (dt / 1000) * playerSpeed; } if ( juego.keyboard.keys.a || juego.keyboard.keys.ArrowLeft || isTouching(juego, left) ) { juego.state.side = "left"; juego.state.pos.x -= (dt / 1000) * playerSpeed; } if (juego.state.pos.x < MAP_MIN) juego.state.pos.x = MAP_MIN; if (juego.state.pos.x > MAP_MAX) juego.state.pos.x = MAP_MAX; juego.state.seedCooldown -= dt; const citizen = detectCitizen(juego); if (juego.keyboard.keys[" "] || isTouching(juego, center)) { if (citizen) { juego.state.signatures += 1000; juego.state.citizens = juego.state.citizens.filter((c) => c != citizen); } else { if (juego.state.seedCooldown < 0) { const seedSpeed = juego.canvas.width * 0.7; juego.state.seeds.push({ x: juego.state.pos.x, velocity: { x: juego.state.side === "left" ? -seedSpeed : seedSpeed }, }); } } juego.state.seedCooldown = SEED_COOLDOWN; } for (const seed of juego.state.seeds) { seed.x += (dt / 1000) * seed.velocity.x; seed.velocity.x *= 0.97; for (const enemy of juego.state.enemies) { if ( boxCollision( getFlooredBox(juego, juego.sprites.semilla, seed.x), getFlooredBox(juego, enemy.sprite, enemy.x) ) ) { juego.state.seeds = juego.state.seeds.filter((s) => s.x !== seed.x); juego.state.enemies = juego.state.enemies.filter( (e) => e.x !== enemy.x ); juego.state.trees.push({ x: enemy.x, sprite: randomFromArray([juego.sprites.arbol1, juego.sprites.arbol2]), }); } } if (Math.abs(seed.velocity.x) < 100) juego.state.seeds = juego.state.seeds.filter( (s) => s.velocity.x !== seed.velocity.x ); } if (juego.state.signatures >= SIGNATURES_TO_WIN) { (juego as Juego).state = { current: "win" }; return; } while (juego.state.enemies.length < ENEMIES_NUM) { const x = Math.random() * MAP_SIZE + MAP_MIN; // Don't spawn enemies too close if (Math.abs(juego.state.pos.x - x) < 300) continue; juego.state.enemies.push({ x, sprite: randomFromArray([ juego.sprites.larreta, juego.sprites.millonarioMalo, ]), }); } for (const enemy of juego.state.enemies) { const distance = enemy.x - juego.state.pos.x; if (distance < 0) { enemy.x += (dt / 1000) * enemySpeed; } else { enemy.x -= (dt / 1000) * enemySpeed; } if ( boxCollision( getFlooredBox(juego, juego.sprites.jugadorx, juego.state.pos.x), getFlooredBox(juego, enemy.sprite, enemy.x) ) ) { juego.state.enemies = juego.state.enemies.filter((e) => e.x !== enemy.x); juego.state.time -= TIME_LOST_WHEN_HIT; } } juego.state.view.x = -juego.state.pos.x + juego.canvas.width / 2 - juego.sprites.jugadorx.getWidth(juego) / 2; } function getFlooredBox(juego: Juego, sprite: Sprite, x: number): Box { const height = sprite.getHeight(juego); return { x, y: getFloorY(juego) - height, width: sprite.getWidth(juego), height, }; } function detectCitizen(juego: Juego): Citizen | undefined { const jugadorxBox = getFlooredBox( juego, juego.sprites.jugadorx, juego.state.pos.x ); return juego.state.citizens.find((c) => boxCollision(jugadorxBox, getFlooredBox(juego, c.sprite, c.x)) ); } function drawJugadorx(juego: Juego) { const floorY = getFloorY(juego); juego.sprites.jugadorx.draw( juego, juego.state.pos.x + juego.state.view.x, floorY - juego.sprites.jugadorx.getHeight(juego), 0, juego.state.side === "left" ); } function drawTrees(juego: Juego) { const floorY = getFloorY(juego); for (const tree of juego.state.trees) { const height = tree.sprite.getHeight(juego); tree.sprite.draw(juego, tree.x + juego.state.view.x, floorY - height); } } function drawEnemies(juego: Juego) { const floorY = getFloorY(juego); for (const enemy of juego.state.enemies) { const height = enemy.sprite.getHeight(juego); enemy.sprite.draw(juego, enemy.x + juego.state.view.x, floorY - height); } } function drawCitizens(juego: Juego) { const floorY = getFloorY(juego); for (const citizen of juego.state.citizens) { const height = citizen.sprite.getHeight(juego); citizen.sprite.draw(juego, citizen.x + juego.state.view.x, floorY - height); } } function drawSeeds(juego: Juego) { const height = juego.sprites.semilla.getHeight(juego); const floorY = getFloorY(juego); for (const seed of juego.state.seeds) { juego.sprites.semilla.draw( juego, seed.x + juego.state.view.x, floorY - height ); } } function getFloorY(juego: Juego): number { return (juego.canvas.height * 65) / 96; } function drawBackground( juego: Juego, y: number, height: number, img: HTMLImageElement, sourceBox?: Box, opacity?: number ) { const aspect = img.width / img.height; const width = height * aspect; if (opacity !== undefined) { juego.ctx.globalAlpha = opacity; } for (let i = 0; i < 10; i++) { if (sourceBox) { juego.ctx.drawImage( img, sourceBox.x, sourceBox.y, sourceBox.width, sourceBox.height, i * (width - (opacity === undefined ? 1 : 0)) + juego.state.view.x, y, width, height ); } else { juego.ctx.drawImage( img, i * (width - (opacity === undefined ? 1 : 0)) + juego.state.view.x, y, width, height ); } } if (opacity !== undefined) { juego.ctx.globalAlpha = 1; } } export function draw(juego: Juego, timestamp: number) { drawBackground(juego, 0, juego.canvas.height, juego.assets.cieloRioCalle); drawBackground(juego, 0, getFloorY(juego), juego.assets.edificios, { x: 0, y: 0, width: 89, height: 48, }); drawBackground( juego, 0, juego.canvas.height, juego.assets.cielo, undefined, juego.state.signatures / SIGNATURES_TO_WIN ); drawBackground( juego, 0, juego.canvas.height, juego.assets.parquePublicoRio, undefined, juego.state.signatures / SIGNATURES_TO_WIN ); drawTrees(juego); drawCitizens(juego); drawEnemies(juego); drawJugadorx(juego); drawSeeds(juego); if (isMobile(juego)) { const { left, right } = touchControls(juego); juego.ctx.fillStyle = "rgba(0, 0, 0, 0.3)"; juego.ctx.fillRect(left.x, left.y, left.width, left.height); juego.ctx.fillRect(right.x, right.y, right.width, right.height); const width = juego.sprites.flecha.getWidth(juego); const height = juego.sprites.flecha.getHeight(juego); juego.sprites.flecha.draw( juego, left.x + left.width - width, left.height / 2 - height / 2, 0, false ); juego.sprites.flecha.draw( juego, right.x, right.height / 2 - height / 2, 0, true ); } juego.ctx.fillStyle = "white"; const firmasBox = drawText( juego, `Firmas: ${juego.state.signatures}/${SIGNATURES_TO_WIN}`, { x: juego.canvas.width - 10, y: 10 }, { bold: true, align: "right" } ); const tiempoBox = drawText( juego, `Tiempo restante`, { x: 10, y: 10 }, { bold: false, align: "left" } ); juego.ctx.fillRect( 10, tiempoBox.y + tiempoBox.height, (juego.canvas.width - firmasBox.width - 20) * (juego.state.time / TIME), 30 ); }