salva-la-costanera/src/jugando.ts

554 lines
14 KiB
TypeScript

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<any>): 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<State>): {
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<State>): {
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<State>): {
[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<State>, 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<AllState>).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<State>, 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<State>): 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<State>) {
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<State>) {
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<State>) {
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<State>) {
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<State>) {
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<State>): number {
return (juego.canvas.height * 65) / 96;
}
export function drawBackground(
juego: Juego<AllState>,
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<State>, 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);
}
}
}