/** 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(); /** 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); }