Files
portifolio-evandrodevbr/lib/jogo/partida.ts
T
evandro 67b32fcab3
CI / audit (push) Successful in 10m48s
CI / verify (push) Successful in 19m2s
CI / deploy (push) Successful in 2m14s
CI / e2e (push) Failing after 36m15s
feat(jogo): expediente dos mortos, jogo de zumbis em primeira pessoa
Etapa 1 inteira (logica com 100% de cobertura, cena 3D, toque, sons, bancada de desempenho, e2e) e a base da etapa 2 (mapa novo no Tiled, texturas, imagens dos itens, sons conferidos, golpe recortado e medido). Pagina /jogo fora do menu e do sitemap ate a medicao no celular.
2026-11-10 19:37:10 -03:00

165 lines
4.5 KiB
TypeScript

/** Integra a lógica do jogo: cria a partida e avança um passo por vez. Sem three, React nem DOM. */
import { atirar, recolherCaixas, resfriar, trocarArma } from "./armas";
import { avancarDrones, criarDrone } from "./drones";
import { registrarResolvido } from "./feed";
import { escolherHordas, montarFila } from "./hordas";
import { campoDeFluxo, lerMapa, moverComColisao } from "./mapa";
import { semear } from "./rng";
import type { Dificuldade, Entrada, Fase, Partida } from "./tipos";
import {
ESCRITORIO,
HORDAS,
JOGADOR,
MUNICAO,
PASSO,
TETO_A_VISTA,
} from "./valores";
import { avancarZumbis, nascer } from "./zumbis";
const HORDA_DO_CHEFAO = 3;
export function criarPartida(opcoes: {
semente: number;
dificuldade: Dificuldade;
problemaId: string;
tetoAVista?: number;
mapa?: readonly string[];
}): Partida {
const sorteio = { rng: semear(opcoes.semente) };
const hordas = escolherHordas(sorteio, opcoes.problemaId);
const mapa = lerMapa(opcoes.mapa ?? ESCRITORIO);
return {
passo: 0,
rng: sorteio.rng,
dificuldade: opcoes.dificuldade,
problemaId: opcoes.problemaId,
hordas,
horda: 0,
fase: "inicio",
tempoFase: 0,
tetoAVista: opcoes.tetoAVista ?? TETO_A_VISTA,
mapa,
jogador: {
x: mapa.inicio[0],
z: mapa.inicio[1],
rumo: 0,
inclinacao: 0,
vida: JOGADOR.vida,
arma: 0,
municao: MUNICAO.inicial,
recarga: 0,
},
zumbis: [],
fila: [],
drones: [],
caixas: [],
feed: [],
resolvidos: [],
placar: { jogador: 0, drones: 0 },
eventos: [],
proximoId: 1,
};
}
const campos = new WeakMap<Partida, { celula: number; campo: Int16Array }>();
/** Só recalcula o campo de fluxo quando o jogador muda de célula. */
function campoAtual(p: Partida): Int16Array {
const { x, z } = p.jogador;
const celula = Math.floor(z) * p.mapa.largura + Math.floor(x);
const guardado = campos.get(p);
if (guardado?.celula === celula) return guardado.campo;
const campo = campoDeFluxo(p.mapa, x, z);
campos.set(p, { celula, campo });
return campo;
}
function moverJogador(p: Partida, entrada: Entrada): void {
const j = p.jogador;
j.rumo += entrada.olhar[0];
j.inclinacao = Math.min(
JOGADOR.inclinacaoMax,
Math.max(-JOGADOR.inclinacaoMax, j.inclinacao + entrada.olhar[1]),
);
const [lado, frente] = entrada.mover;
const escala = Math.min(1, 1 / Math.hypot(lado, frente));
const passo = escala * JOGADOR.velocidade * PASSO;
const dx = (Math.sin(j.rumo) * frente - Math.cos(j.rumo) * lado) * passo;
const dz = (Math.cos(j.rumo) * frente + Math.sin(j.rumo) * lado) * passo;
[j.x, j.z] = moverComColisao(p.mapa, j.x, j.z, dx, dz, JOGADOR.raio);
}
function usarArma(p: Partida, entrada: Entrada): void {
trocarArma(p, entrada.arma);
resfriar(p);
if (entrada.atirar) atirar(p);
recolherCaixas(p);
}
function mudarFase(p: Partida, fase: Fase): void {
p.fase = fase;
p.eventos.push({ tipo: "fase", fase });
}
/** Começa a horda `p.horda`: a última é a do chefão. */
function comecarHorda(p: Partida): void {
p.tempoFase = 0;
p.fila = montarFila(p.horda, p.hordas[p.horda], p.dificuldade);
mudarFase(p, p.horda === HORDA_DO_CHEFAO ? "chefao" : "horda");
}
function fecharHorda(p: Partida): void {
const problemaId = p.hordas[p.horda];
p.resolvidos.push(problemaId);
registrarResolvido(p, problemaId);
if (p.fase === "chefao") {
p.eventos.push({ tipo: "resolvido", problemaId, droneId: -1 });
mudarFase(p, "vitoria");
return;
}
const drone = criarDrone(p, problemaId);
p.eventos.push({ tipo: "resolvido", problemaId, droneId: drone.id });
p.jogador.vida = Math.min(
JOGADOR.vida,
p.jogador.vida + JOGADOR.curaPorHorda,
);
p.tempoFase = 0;
mudarFase(p, "pausa");
}
function combater(p: Partida): void {
nascer(p);
avancarZumbis(p, campoAtual(p));
avancarDrones(p);
if (p.jogador.vida <= 0) {
mudarFase(p, "derrota");
} else if (p.fila.length === 0 && p.zumbis.length === 0) {
fecharHorda(p);
}
}
function avancarFase(p: Partida): void {
if (p.fase === "inicio") {
if (p.tempoFase >= HORDAS.preparo) comecarHorda(p);
} else if (p.fase === "pausa") {
avancarDrones(p);
if (p.tempoFase >= HORDAS.pausa) {
p.horda++;
comecarHorda(p);
}
} else {
combater(p);
}
}
export function avancar(p: Partida, entrada: Entrada): void {
p.eventos.length = 0;
if (p.fase === "vitoria" || p.fase === "derrota") return;
p.passo++;
p.tempoFase++;
moverJogador(p, entrada);
usarArma(p, entrada);
avancarFase(p);
}