import { avancarDrones, criarDrone } from "./drones"; import { lerMapa } from "./mapa"; import { PROBLEMAS } from "./problemas"; import type { Drone, Partida, Zumbi } from "./tipos"; import { DRONE, ESCRITORIO, PASSO } from "./valores"; const PASSO_MAX = DRONE.velocidade * PASSO; const [PROBLEMA_DRONE, PROBLEMA_ZUMBI] = PROBLEMAS; function partida(x: number, z: number, rumo = 0): Partida { return { passo: 0, rng: 1, dificuldade: "normal", problemaId: PROBLEMA_DRONE.id, hordas: [], horda: 0, fase: "horda", tempoFase: 0, tetoAVista: 16, mapa: lerMapa(ESCRITORIO), jogador: { x, z, rumo, inclinacao: 0, vida: 100, arma: 0, municao: 90, recarga: 0, }, zumbis: [], fila: [], drones: [], caixas: [], feed: [], resolvidos: [], placar: { jogador: 0, drones: 0 }, eventos: [], proximoId: 1, }; } function zumbi(p: Partida, x: number, z: number, vida = 100): Zumbi { const z1: Zumbi = { id: p.proximoId++, tipo: "comum", problemaId: PROBLEMA_ZUMBI.id, x, z, rumo: 0, vida, estado: "andando", tempo: 0, }; p.zumbis.push(z1); return z1; } function drone(p: Partida, recarga = 1): Drone { const d = criarDrone(p, PROBLEMA_DRONE.id); d.recarga = recarga; return d; } describe("criarDrone", () => { it("nasce no jogador, com o rumo dele, e entra na lista", () => { const p = partida(5.5, 8.5, 1.25); const d = criarDrone(p, PROBLEMA_DRONE.id); expect(d).toEqual({ id: 1, problemaId: PROBLEMA_DRONE.id, x: 5.5, z: 8.5, rumo: 1.25, recarga: DRONE.intervalo, eliminacoes: 0, }); expect(p.drones).toEqual([d]); expect(p.proximoId).toBe(2); }); }); describe("avancarDrones: movimento", () => { it("anda no máximo DRONE.velocidade * PASSO por passo e chega ao lugar", () => { const p = partida(13.5, 8.5); const d = drone(p, 1000); for (let i = 0; i < 100; i++) { const [x, z] = [d.x, d.z]; avancarDrones(p); expect(Math.hypot(d.x - x, d.z - z)).toBeLessThanOrEqual( PASSO_MAX + 1e-12, ); } expect(d.x).toBeCloseTo(15.5, 9); expect(d.z).toBeCloseTo(8.5, 9); }); it("dá um passo cheio quando o lugar está longe", () => { const p = partida(13.5, 8.5); const d = drone(p, 1000); avancarDrones(p); expect(d.x - 13.5).toBeCloseTo(PASSO_MAX, 9); expect(d.z).toBeCloseTo(8.5, 9); }); it("não atravessa parede", () => { const p = partida(13.5, 15.5, -Math.PI / 2); const d = drone(p, 1000); for (let i = 0; i < 100; i++) avancarDrones(p); expect(d.z).toBeLessThanOrEqual(16 - 0.2); }); it("três drones ocupam ângulos diferentes em volta do jogador", () => { const p = partida(24.5, 8.5, 0.4); const drones = [drone(p, 1000), drone(p, 1000), drone(p, 1000)]; for (let i = 0; i < 200; i++) avancarDrones(p); drones.forEach((d, i) => { const a = p.jogador.rumo + Math.PI / 2 + i * ((2 * Math.PI) / 3); expect(d.x).toBeCloseTo(24.5 + Math.sin(a) * DRONE.distancia, 6); expect(d.z).toBeCloseTo(8.5 + Math.cos(a) * DRONE.distancia, 6); }); expect( Math.hypot(drones[0].x - drones[1].x, drones[0].z - drones[1].z), ).toBeGreaterThan(1); expect( Math.hypot(drones[1].x - drones[2].x, drones[1].z - drones[2].z), ).toBeGreaterThan(1); }); }); describe("avancarDrones: tiro", () => { it("respeita a recarga: desconta até 0, atira e recarrega", () => { const p = partida(5.5, 8.5); const d = drone(p, 3); const alvo = zumbi(p, 11.5, 8.5); avancarDrones(p); avancarDrones(p); expect(d.recarga).toBe(1); expect(p.eventos).toEqual([]); avancarDrones(p); expect(d.recarga).toBe(DRONE.intervalo); expect(p.eventos.map((e) => e.tipo)).toEqual(["tiro", "acerto"]); expect(alvo.vida).toBe(100 - DRONE.dano); avancarDrones(p); expect(d.recarga).toBe(DRONE.intervalo - 1); expect(p.eventos).toHaveLength(2); expect(p.feed).toEqual([]); }); it("vira para o zumbi e registra tiro e acerto", () => { const p = partida(5.5, 8.5); const d = drone(p); const alvo = zumbi(p, 5.5, 14.5); avancarDrones(p); const distancia = Math.hypot(alvo.x - d.x, alvo.z - d.z); expect(d.rumo).toBeCloseTo(Math.atan2(alvo.x - d.x, alvo.z - d.z), 9); expect(p.eventos).toEqual([ { tipo: "tiro", autor: d.id, arma: "drone", distancia }, { tipo: "acerto", zumbiId: alvo.id, cabeca: false, x: 5.5, y: 1.2, z: 14.5, }, ]); }); it("escolhe o zumbi mais próximo", () => { const p = partida(5.5, 8.5); drone(p); const longe = zumbi(p, 15.5, 8.5); const perto = zumbi(p, 9.5, 8.5); avancarDrones(p); expect(perto.vida).toBe(100 - DRONE.dano); expect(longe.vida).toBe(100); }); it("mantém o mais próximo quando o seguinte está mais longe", () => { const p = partida(5.5, 8.5); drone(p); const perto = zumbi(p, 9.5, 8.5); const longe = zumbi(p, 15.5, 8.5); avancarDrones(p); expect(perto.vida).toBe(100 - DRONE.dano); expect(longe.vida).toBe(100); }); it("ignora zumbi morrendo", () => { const p = partida(5.5, 8.5); drone(p); const morto = zumbi(p, 8.5, 8.5); morto.estado = "morrendo"; const vivo = zumbi(p, 15.5, 8.5); avancarDrones(p); expect(morto.vida).toBe(100); expect(vivo.vida).toBe(100 - DRONE.dano); }); it("não atira com parede no meio e deixa a recarga em 0", () => { const p = partida(9.5, 2.5); const d = drone(p); const alvo = zumbi(p, 12.5, 2.5); avancarDrones(p); expect(alvo.vida).toBe(100); expect(p.eventos).toEqual([]); expect(d.recarga).toBe(0); }); it("não atira além do alcance", () => { const p = partida(2.5, 8.5); const d = drone(p); const alvo = zumbi(p, 2.5 + DRONE.alcance + 1, 8.5); avancarDrones(p); expect(alvo.vida).toBe(100); expect(p.eventos).toEqual([]); expect(d.recarga).toBe(0); }); it("a morte gera a linha do feed com a solução do drone e o nome do zumbi", () => { const p = partida(5.5, 8.5); drone(p); const alvo = zumbi(p, 9.5, 8.5, DRONE.dano); avancarDrones(p); expect(alvo.estado).toBe("morrendo"); expect(p.feed).toEqual([ { passo: 0, texto: `${PROBLEMA_DRONE.solucao} eliminou ${PROBLEMA_ZUMBI.nome}`, }, ]); }); });