Files
portifolio-evandrodevbr/lib/jogo/piloto.test.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

227 lines
6.4 KiB
TypeScript

import { avancar, criarPartida } from "./partida";
import { pilotar } from "./piloto";
import type { Partida, TipoZumbi, Zumbi } from "./tipos";
import { ESCRITORIO, ZUMBIS } from "./valores";
/** 21 x 15, parede por dentro em x = 10, de z = 5 a z = 9. */
const MAPA = Array.from({ length: 15 }, (_, z) => {
if (z === 0 || z === 14) return "#".repeat(21);
const miolo = Array.from({ length: 19 }, (_, i) => {
if (z === 1 && i === 0) return "N";
if (z === 2 && i === 0) return "J";
return i === 9 && z >= 5 && z <= 9 ? "#" : ".";
});
return `#${miolo.join("")}#`;
});
const JX = 5.5;
const JZ = 7.5;
function zumbi(
id: number,
x: number,
z: number,
tipo: TipoZumbi = "comum",
estado: Zumbi["estado"] = "andando",
): Zumbi {
return {
id,
tipo,
problemaId: "site-lento",
x,
z,
rumo: 0,
vida: ZUMBIS[tipo].vida,
estado,
tempo: 0,
};
}
/** Jogador em (5.5, 7.5), rumo 0, com os zumbis dados. */
function cena(...zumbis: Zumbi[]): Partida {
const p = criarPartida({
semente: 1,
dificuldade: "facil",
problemaId: "site-lento",
mapa: MAPA,
});
p.jogador.x = JX;
p.jogador.z = JZ;
p.zumbis = zumbis;
return p;
}
/** Zumbi a `distancia` metros do jogador, no rumo dado. */
function aoRumo(
id: number,
rumo: number,
distancia: number,
tipo: TipoZumbi = "comum",
): Zumbi {
return zumbi(
id,
JX + Math.sin(rumo) * distancia,
JZ + Math.cos(rumo) * distancia,
tipo,
);
}
describe("pilotar: sem alvo", () => {
it("fica parado sem zumbis", () => {
expect(pilotar(cena())).toEqual({
mover: [0, 0],
olhar: [0, 0],
atirar: false,
arma: 0,
});
});
it("ignora zumbis morrendo", () => {
const p = cena(zumbi(1, 5.5, 9.5, "comum", "morrendo"));
expect(pilotar(p).atirar).toBe(false);
expect(pilotar(p).mover).toEqual([0, 0]);
});
});
describe("pilotar: escolha do alvo", () => {
it("prefere o mais próximo com linha de visão", () => {
const atras = zumbi(1, 11.5, 7.5);
const livre = zumbi(2, 1.5, 1.5);
expect(pilotar(cena(atras, livre)).olhar[0]).toBe(-0.12);
});
it("sem visão de ninguém, escolhe o mais próximo", () => {
const perto = zumbi(1, 11.5, 7.5);
const longe = zumbi(2, 11.5, 5.5);
expect(pilotar(cena(longe, perto)).olhar[0]).toBe(0.12);
});
it("não atira sem linha de visão, mesmo alinhado", () => {
const p = cena(zumbi(1, 11.5, 7.5));
p.jogador.rumo = Math.PI / 2;
expect(pilotar(p).atirar).toBe(false);
});
});
describe("pilotar: mira", () => {
it("gira no máximo 0,12 rad por passo", () => {
expect(pilotar(cena(aoRumo(1, 1, 5))).olhar[0]).toBe(0.12);
expect(pilotar(cena(aoRumo(1, -1, 5))).olhar[0]).toBe(-0.12);
});
it("gira só o que falta quando o erro é menor que o limite", () => {
const p = cena(aoRumo(1, 0.03, 5));
expect(pilotar(p).olhar[0]).toBeCloseTo(0.03);
});
it("normaliza a diferença de rumo para −π a π", () => {
const p = cena(aoRumo(1, -3, 5));
p.jogador.rumo = 3;
expect(pilotar(p).olhar[0]).toBe(0.12);
});
it("inclina no máximo 0,05 rad por passo, para baixo e para cima", () => {
const p = cena(aoRumo(1, 0, 5));
expect(pilotar(p).olhar[1]).toBe(-0.05);
p.jogador.inclinacao = -1;
expect(pilotar(p).olhar[1]).toBe(0.05);
});
it("mira a 65% da altura do tipo", () => {
const p = cena(aoRumo(1, 0, 5));
p.jogador.inclinacao = Math.atan2(0.65 * ZUMBIS.comum.altura - 1.6, 5);
expect(pilotar(p).olhar[1]).toBeCloseTo(0);
});
it("atira quando o erro de rumo é menor que 0,05 e há visão", () => {
expect(pilotar(cena(aoRumo(1, 0.04, 5))).atirar).toBe(true);
});
it("não atira com erro de rumo de 0,05 ou mais", () => {
expect(pilotar(cena(aoRumo(1, 0.06, 5))).atirar).toBe(false);
});
});
describe("pilotar: arma", () => {
it("usa a faca quando a borda está a 1,3 m ou menos", () => {
const comum = ZUMBIS.comum.raio;
expect(pilotar(cena(aoRumo(1, 0, 1.25 + comum))).arma).toBe(2);
expect(pilotar(cena(aoRumo(1, 0, 1.4 + comum))).arma).toBe(0);
});
it("usa a automática com 3 zumbis a até 12 m", () => {
const tres = cena(aoRumo(1, 0, 5), aoRumo(2, 1, 6), aoRumo(3, 2, 12));
expect(pilotar(tres).arma).toBe(1);
});
it("fica na pistola com só 2 zumbis a até 12 m", () => {
const dois = cena(aoRumo(1, 0, 5), aoRumo(2, 1, 6), aoRumo(3, 2, 12.5));
expect(pilotar(dois).arma).toBe(0);
});
it("usa a automática contra blindado e contra chefão", () => {
expect(pilotar(cena(aoRumo(1, 0, 6, "blindado"))).arma).toBe(1);
expect(pilotar(cena(aoRumo(1, 0, 6, "chefao"))).arma).toBe(1);
});
it("fica na pistola contra um comum ou um rápido", () => {
expect(pilotar(cena(aoRumo(1, 0, 6, "rapido"))).arma).toBe(0);
});
it("sem bala não usa a automática", () => {
const p = cena(aoRumo(1, 0, 6, "blindado"));
p.jogador.municao = 0;
expect(pilotar(p).arma).toBe(0);
});
});
describe("pilotar: distância", () => {
it("anda para trás com o alvo a menos de 4 m", () => {
expect(pilotar(cena(aoRumo(1, 0, 3.9))).mover).toEqual([0, -1]);
});
it("fica parado com o alvo a 4 m ou mais", () => {
expect(pilotar(cena(aoRumo(1, 0, 4))).mover).toEqual([0, 0]);
});
});
describe("pilotar: partida inteira no escritório", () => {
function jogar(dificuldade: Partida["dificuldade"] = "facil") {
const p = criarPartida({
semente: 1,
dificuldade,
problemaId: "site-lento",
mapa: ESCRITORIO,
});
while (p.fase !== "vitoria" && p.fase !== "derrota" && p.passo < 36000) {
avancar(p, pilotar(p));
}
return p;
}
it("vence em menos de 36.000 passos, com 3 drones e 4 problemas resolvidos", () => {
const p = jogar();
expect(p.fase).toBe("vitoria");
expect(p.passo).toBeLessThan(36000);
expect(p.drones).toHaveLength(3);
expect(p.resolvidos).toHaveLength(4);
expect(p.placar.drones).toBeGreaterThan(0);
});
it.each(["normal", "dificil"] as const)(
"termina em vitória ou derrota antes de 36.000 passos no %s",
(dificuldade) => {
const p = jogar(dificuldade);
expect(["vitoria", "derrota"]).toContain(p.fase);
expect(p.passo).toBeLessThan(36000);
},
);
it("é determinística: mesma partida, mesmo passo e mesmo placar", () => {
const a = jogar();
const b = jogar();
expect(b.passo).toBe(a.passo);
expect(b.placar).toEqual(a.placar);
});
});