import { Juego, State as AllState } from "./main"; import { Sprite } from "./sprite"; import { Box, boxCollision, drawText, isMobile, isTouching, posInBox, randomFromArray, share, } from "./utils"; import * as welcome from "./welcome"; const ENEMIES_NUM = 12; const SEED_COOLDOWN = 150; 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"; has: "lost" | "won" | null; playing: boolean; 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; justSigned: 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, ]), }); } return { current: "jugando", has: null, playing: false, pos: { x: MAP_MIN + 100 }, view: { x: 0 }, side: "right", enemies: [], seeds: [], trees: [], signatures: 0, citizens, justSigned: 0, 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, }, }; } function shareButton(juego: Juego): { box: Box; sprite: Sprite; } { const sprite = juego.state.has === "won" ? juego.sprites.botonCompartirFelicitaciones : juego.sprites.botonCompartirPerdiste; const width = sprite.getWidth(juego); const height = sprite.getHeight(juego); return { box: { x: juego.canvas.width - width - 5, y: juego.canvas.height * 0.85 - height, width, height, }, sprite, }; } const socialUrls = { facebook: "https://www.facebook.com/Fpg.caba", instagram: "https://www.instagram.com/fpg.caba/", twitter: "https://twitter.com/FtePatriaGrande", }; function socialButtons(juego: Juego): { [key in "facebook" | "instagram" | "twitter"]: { box: Box; sprite: Sprite; }; } { let x = juego.canvas.width - 5; let s: any = {}; for (const network of ["facebook", "instagram", "twitter"]) { const sprite = (juego.sprites as { [key in string]: Sprite })[network]; const width = sprite.getWidth(juego); const height = sprite.getHeight(juego); x -= width + 20; s[network] = { box: { x, y: juego.canvas.height * 0.95 - height, width, height, }, sprite, }; } return s; } export function update(juego: Juego, dt: number) { if (juego.state.has) { const button = shareButton(juego); if ( isTouching(juego, button.box) || (juego.mouse.down && posInBox(button.box, juego.mouse)) ) { share(); (juego as Juego).state = welcome.createState(); } for (const [network, { box }] of Object.entries(socialButtons(juego))) { if ( isTouching(juego, box) || (juego.mouse.down && posInBox(box, juego.mouse)) ) { location.href = (socialUrls as any)[network]; } } return; } if (!juego.state.playing) { juego.assets.fondo.play(); juego.state.playing = true; } juego.state.time -= dt; if (juego.state.time < 0) { juego.state.has = "lost"; 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; juego.state.justSigned -= 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); juego.state.justSigned = 1000; } 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.state.has = "won"; return; } while (juego.state.enemies.length < ENEMIES_NUM) { const x = Math.random() * 6000 + juego.state.pos.x; // 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); const width = juego.sprites.jugadorx.getWidth(juego); const height = juego.sprites.jugadorx.getHeight(juego); juego.sprites.jugadorx.draw( juego, juego.state.pos.x + juego.state.view.x, floorY - height, 0, juego.state.side === "left" ); if (juego.state.justSigned > 0) { juego.sprites.dialogoFirma.draw( juego, juego.state.pos.x + juego.state.view.x + width, floorY - height - juego.sprites.dialogoFirma.getHeight(juego) ); } } 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; } export function drawBackground( juego: Juego, y: number, height: number, img: HTMLImageElement, offset: number = 0, 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 < 50; i++) { if (sourceBox) { juego.ctx.drawImage( img, sourceBox.x, sourceBox.y, sourceBox.width, sourceBox.height, i * (width - (opacity === undefined ? 1 : 0)) + offset, y, width, height ); } else { juego.ctx.drawImage( img, i * (width - (opacity === undefined ? 1 : 0)) + offset, 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, -juego.state.pos.x ); drawBackground( juego, 0, getFloorY(juego), juego.assets.edificios, -juego.state.pos.x, { x: 0, y: 0, width: 89, height: 48, } ); drawBackground( juego, 0, juego.canvas.height, juego.assets.cielo, -juego.state.pos.x, undefined, juego.state.signatures / SIGNATURES_TO_WIN ); drawBackground( juego, 0, juego.canvas.height, juego.assets.parquePublicoRio, -juego.state.pos.x, undefined, juego.state.signatures / SIGNATURES_TO_WIN ); drawTrees(juego); drawCitizens(juego); drawEnemies(juego); drawJugadorx(juego); drawSeeds(juego); if (isMobile(juego) && !juego.state.has) { 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 ); if (detectCitizen(juego)) { drawText( juego, `¡Apreta para recolectar firmas!`, { x: juego.canvas.width / 2, y: juego.canvas.height * 0.7 }, { align: "center", maxWidth: juego.canvas.width } ); } if (juego.state.has) { juego.sprites.logoFPGFDTBlanco.draw( juego, 0, juego.canvas.height - juego.sprites.logoFPGFDTBlanco.getHeight(juego) - 10 ); if (juego.state.has === "lost") { juego.sprites.placaPerdiste.draw(juego, 10, juego.canvas.height * 0.1); juego.sprites.baldosa.draw( juego, juego.canvas.width - juego.sprites.baldosa.getWidth(juego) - 30, juego.canvas.height * 0.3 ); } else if (juego.state.has === "won") { const width = juego.sprites.placaFelicitaciones.getWidth(juego); juego.sprites.placaFelicitaciones.draw( juego, juego.canvas.width / 2 - width / 2, juego.canvas.height * 0.1 ); } const button = shareButton(juego); button.sprite.draw(juego, button.box.x, button.box.y); const socials = socialButtons(juego); for (const { sprite, box } of Object.values(socials)) { sprite.draw(juego, box.x, box.y); } } }