import { alvoNaMira, atirar, raioNoCirculo, recolherCaixas, resfriar, trocarArma, } from "./armas"; import { lerMapa } from "./mapa"; import type { ArmaId, Partida, TipoZumbi, Zumbi } from "./tipos"; import { ARMAS, JOGADOR, ZUMBIS } from "./valores"; const LARGURA = 7; function mapaAberto() { const linhas = ["#######", "#N...J#"]; for (let i = 0; i < 13; i++) linhas.push("#.....#"); linhas.push("#######"); return lerMapa(linhas); } function zumbi( id: number, x: number, z: number, tipo: TipoZumbi = "comum", ): Zumbi { return { id, tipo, problemaId: "site-lento", x, z, rumo: 0, vida: ZUMBIS[tipo].vida, estado: "andando", tempo: 0, }; } /** Jogador em (3.5, 2.5) olhando para +z, com a pistola. */ function partida(arma: ArmaId = 0): Partida { return { passo: 0, rng: 1, dificuldade: "normal", problemaId: "site-lento", hordas: [], horda: 0, fase: "horda", tempoFase: 0, tetoAVista: 16, mapa: mapaAberto(), jogador: { x: 3.5, z: 2.5, rumo: 0, inclinacao: 0, vida: JOGADOR.vida, arma, municao: 10, recarga: 0, }, zumbis: [], fila: [], drones: [], caixas: [], feed: [], resolvidos: [], placar: { jogador: 0, drones: 0 }, eventos: [], proximoId: 100, }; } describe("alvoNaMira", () => { it("é nulo com a faca", () => { const p = partida(2); p.zumbis = [zumbi(1, 3.5, 3.5)]; expect(alvoNaMira(p)).toBeNull(); }); it("é nulo sem zumbi na linha", () => { const p = partida(); p.zumbis = [zumbi(1, 1.5, 5.5)]; expect(alvoNaMira(p)).toBeNull(); }); it("devolve o corpo, com a distância e a altura", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5)]; p.jogador.inclinacao = Math.atan(-0.2); const alvo = alvoNaMira(p); expect(alvo?.zumbi.id).toBe(1); expect(alvo?.cabeca).toBe(false); expect(alvo?.distancia).toBeCloseTo(2.65); expect(alvo?.y).toBeCloseTo(1.6 - 0.2 * 2.65); }); it("devolve a cabeça e não fere ninguém", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5)]; expect(alvoNaMira(p)?.cabeca).toBe(true); expect(p.zumbis[0].vida).toBe(100); expect(p.eventos).toHaveLength(0); }); }); describe("raioNoCirculo", () => { it("devolve a distância até a primeira interseção", () => { expect(raioNoCirculo(0, 0, 0, 1, 0, 5, 1)).toBeCloseTo(4); }); it("devolve null quando o raio passa ao lado", () => { expect(raioNoCirculo(0, 0, 0, 1, 3, 5, 1)).toBeNull(); }); it("devolve null quando o círculo está atrás", () => { expect(raioNoCirculo(0, 0, 0, 1, 0, -5, 1)).toBeNull(); }); it("devolve 0 com a origem dentro do círculo", () => { expect(raioNoCirculo(0, 0.2, 0, 1, 0, 0, 1)).toBe(0); }); }); describe("trocarArma", () => { it("troca e põe a recarga em 10", () => { const p = partida(0); trocarArma(p, 1); expect(p.jogador.arma).toBe(1); expect(p.jogador.recarga).toBe(10); }); it("não faz nada para a mesma arma", () => { const p = partida(0); trocarArma(p, 0); expect(p.jogador.recarga).toBe(0); }); }); describe("resfriar", () => { it("desconta 1 da recarga", () => { const p = partida(); p.jogador.recarga = 3; resfriar(p); expect(p.jogador.recarga).toBe(2); }); it("para em 0", () => { const p = partida(); resfriar(p); expect(p.jogador.recarga).toBe(0); }); }); describe("recolherCaixas", () => { it("recolhe a caixa perto e deixa a longe", () => { const p = partida(); p.caixas = [ { id: 1, x: 3.5, z: 3.0 }, { id: 2, x: 3.5, z: 8.5 }, ]; recolherCaixas(p); expect(p.caixas.map((c) => c.id)).toEqual([2]); expect(p.jogador.municao).toBe(40); expect(p.eventos).toEqual([{ tipo: "caixa", balas: 30 }]); }); }); describe("atirar: regras gerais", () => { it("não faz nada com recarga pendente", () => { const p = partida(); p.jogador.recarga = 1; atirar(p); expect(p.eventos).toEqual([]); expect(p.jogador.recarga).toBe(1); }); it("automática sem bala não atira e não gasta recarga", () => { const p = partida(1); p.jogador.municao = 0; atirar(p); expect(p.eventos).toEqual([]); expect(p.jogador.recarga).toBe(0); }); it("automática gasta uma bala e arma a recarga", () => { const p = partida(1); atirar(p); expect(p.jogador.municao).toBe(9); expect(p.jogador.recarga).toBe(ARMAS[1].intervalo); }); it("pistola nunca gasta bala, nem com a munição zerada", () => { const p = partida(0); p.jogador.municao = 0; atirar(p); expect(p.jogador.municao).toBe(0); expect(p.jogador.recarga).toBe(ARMAS[0].intervalo); expect(p.eventos).toHaveLength(1); }); }); describe("atirar: tiro por raio", () => { it("sem zumbi, o tiro vai até a parede", () => { const p = partida(); atirar(p); expect(p.eventos).toHaveLength(1); const e = p.eventos[0]; expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(12.5); }); it("acerta o corpo com o dano da arma", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5)]; p.jogador.inclinacao = Math.atan(-0.2); atirar(p); expect(p.zumbis[0].vida).toBe(100 - ARMAS[0].dano); const [tiro, acerto] = p.eventos; expect(tiro).toMatchObject({ tipo: "tiro", autor: "jogador", arma: 0 }); expect(tiro.tipo === "tiro" && tiro.distancia).toBeCloseTo(2.65); expect(acerto).toMatchObject({ tipo: "acerto", zumbiId: 1, cabeca: false, }); expect(acerto.tipo === "acerto" && acerto.y).toBeCloseTo(1.6 - 0.2 * 2.65); expect(acerto.tipo === "acerto" && acerto.x).toBeCloseTo(3.5); expect(acerto.tipo === "acerto" && acerto.z).toBeCloseTo(5.15); }); it("acerta a cabeça com o dano dobrado", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100 - ARMAS[0].dano * 2); expect(p.eventos[1]).toMatchObject({ tipo: "acerto", cabeca: true }); }); it("erra por cima com a inclinação alta", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5)]; p.jogador.inclinacao = 1; atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.eventos).toHaveLength(1); const e = p.eventos[0]; expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(12.5); }); it("erra por baixo, no chão", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5)]; p.jogador.inclinacao = Math.atan(-1); atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.eventos).toHaveLength(1); }); it("o raio que passa ao lado não acerta", () => { const p = partida(); p.zumbis = [zumbi(1, 5.5, 5.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); }); it("parede na frente do zumbi o protege", () => { const p = partida(); p.mapa.opaco[8 * LARGURA + 3] = 1; p.zumbis = [zumbi(1, 3.5, 10.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); const e = p.eventos[0]; expect(p.eventos).toHaveLength(1); expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(5.5); }); it("o zumbi da frente protege o de trás", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 9.5), zumbi(2, 3.5, 5.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.zumbis[1].vida).toBe(32); }); it("zumbi morrendo é atravessado", () => { const p = partida(); const morto = zumbi(1, 3.5, 5.5); morto.estado = "morrendo"; p.zumbis = [morto, zumbi(2, 3.5, 9.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.zumbis[1].vida).toBe(32); }); it("o tiro que passa por cima do da frente pode acertar o de trás", () => { const p = partida(); p.zumbis = [zumbi(1, 3.5, 5.5, "rapido"), zumbi(2, 3.5, 9.5, "chefao")]; p.jogador.inclinacao = Math.atan(0.05); atirar(p); expect(p.zumbis[0].vida).toBe(50); expect(p.zumbis[1].vida).toBeLessThan(1500); }); it("matar registra a morte pelo ferir", () => { const p = partida(); const fraco = zumbi(1, 3.5, 5.5); fraco.vida = 10; p.zumbis = [fraco]; atirar(p); expect(fraco.estado).toBe("morrendo"); expect(p.eventos.some((e) => e.tipo === "morte")).toBe(true); }); it("a automática usa o dano da automática", () => { const p = partida(1); p.zumbis = [zumbi(1, 3.5, 5.5)]; p.jogador.inclinacao = Math.atan(-0.2); atirar(p); expect(p.zumbis[0].vida).toBe(100 - ARMAS[1].dano); }); }); describe("atirar: faca", () => { it("fere o mais próximo dentro do alcance e do arco", () => { const p = partida(2); p.zumbis = [zumbi(1, 3.5, 3.9), zumbi(2, 3.5, 3.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.zumbis[1].vida).toBe(100 - ARMAS[2].dano); expect(p.eventos[0]).toMatchObject({ tipo: "tiro", arma: 2 }); const acerto = p.eventos.find((e) => e.tipo === "acerto"); expect(acerto).toMatchObject({ zumbiId: 2, cabeca: false, y: ZUMBIS.comum.altura / 2, }); }); it("não acerta fora do arco", () => { const p = partida(2); p.zumbis = [zumbi(1, 3.5, 1.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.eventos).toEqual([ { tipo: "tiro", autor: "jogador", arma: 2, distancia: ARMAS[2].alcance }, ]); }); it("não acerta fora do alcance", () => { const p = partida(2); p.zumbis = [zumbi(1, 3.5, 5.5)]; atirar(p); expect(p.zumbis[0].vida).toBe(100); expect(p.eventos).toHaveLength(1); }); it("ignora o zumbi morrendo", () => { const p = partida(2); const morto = zumbi(1, 3.5, 3.5); morto.estado = "morrendo"; p.zumbis = [morto]; atirar(p); expect(p.eventos).toHaveLength(1); }); it("a distância do tiro é a do zumbi ferido", () => { const p = partida(2); p.zumbis = [zumbi(1, 3.5, 3.5)]; atirar(p); const e = p.eventos[0]; expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(0.65); }); });