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

310 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
caminhoLivre,
campoDeFluxo,
direcaoNoCampo,
ehOpaco,
ehSolido,
lerMapa,
moverComColisao,
raioAteParede,
temLinhaDeVisao,
} from "./mapa";
import { ESCRITORIO } from "./valores";
const SALA = lerMapa(["#######", "#J...N#", "#.....#", "#..M..#", "#######"]);
const DIVIDIDA = lerMapa(["#########", "#J..#..N#", "#...#...#", "#########"]);
describe("lerMapa", () => {
it("lê dimensões, células, início e nascedouros", () => {
const m = lerMapa(["#####", "#J.N#", "#.M.#", "#####"]);
expect(m.largura).toBe(5);
expect(m.altura).toBe(4);
expect(m.solido[2 * 5 + 2]).toBe(1);
expect(m.opaco[2 * 5 + 2]).toBe(0);
expect(m.solido[1 * 5 + 1]).toBe(0);
expect(m.opaco[0]).toBe(1);
expect(m.inicio).toEqual([1.5, 1.5]);
expect(m.nascedouros).toEqual([[3.5, 1.5]]);
});
it("recusa lista vazia", () => {
expect(() => lerMapa([])).toThrow(/ao menos uma linha/);
});
it("recusa linhas de larguras diferentes", () => {
expect(() => lerMapa(["JN", "N"])).toThrow(/larguras diferentes/);
});
it("recusa mapa sem início", () => {
expect(() => lerMapa(["N."])).toThrow(/exatamente um início/);
});
it("recusa mapa com dois inícios", () => {
expect(() => lerMapa(["JJN"])).toThrow(/exatamente um início/);
});
it("recusa mapa sem nascedouro", () => {
expect(() => lerMapa(["J."])).toThrow(/nascedouro/);
});
it("recusa caractere desconhecido", () => {
expect(() => lerMapa(["JNX"])).toThrow(/Caractere desconhecido "X"/);
});
it("lê o ESCRITORIO: 28×17, 4 nascedouros, todos com caminho até o início", () => {
const m = lerMapa(ESCRITORIO);
expect(m.largura).toBe(28);
expect(m.altura).toBe(17);
expect(m.nascedouros).toHaveLength(4);
const campo = campoDeFluxo(m, m.inicio[0], m.inicio[1]);
for (const [x, z] of m.nascedouros) {
expect(
campo[Math.floor(z) * m.largura + Math.floor(x)],
).toBeGreaterThanOrEqual(0);
}
});
});
describe("ehSolido e ehOpaco", () => {
it("parede é sólida e opaca", () => {
expect(ehSolido(SALA, 0.5, 0.5)).toBe(true);
expect(ehOpaco(SALA, 0.5, 0.5)).toBe(true);
});
it("mesa é sólida e não opaca", () => {
expect(ehSolido(SALA, 3.5, 3.5)).toBe(true);
expect(ehOpaco(SALA, 3.5, 3.5)).toBe(false);
});
it("chão é livre", () => {
expect(ehSolido(SALA, 2.5, 2.5)).toBe(false);
expect(ehOpaco(SALA, 2.5, 2.5)).toBe(false);
});
it.each([
[-0.1, 2.5],
[2.5, -0.1],
[7.1, 2.5],
[2.5, 5.1],
])("fora do mapa (%f, %f) é sólido e opaco", (x, z) => {
expect(ehSolido(SALA, x, z)).toBe(true);
expect(ehOpaco(SALA, x, z)).toBe(true);
});
});
describe("raioAteParede", () => {
it("anda em +x", () => {
expect(raioAteParede(SALA, 1.5, 2.5, Math.PI / 2, 99)).toBeCloseTo(4.5, 6);
});
it("anda em -x", () => {
expect(raioAteParede(SALA, 1.5, 2.5, -Math.PI / 2, 99)).toBeCloseTo(0.5, 6);
});
it("anda em +z atravessando a mesa", () => {
expect(raioAteParede(SALA, 3.5, 1.5, 0, 99)).toBeCloseTo(2.5, 6);
});
it("anda em -z", () => {
expect(raioAteParede(SALA, 3.5, 3.5, Math.PI, 99)).toBeCloseTo(2.5, 6);
});
it("anda em diagonal, trocando de eixo", () => {
expect(raioAteParede(SALA, 1.5, 1.5, 0.5, 99)).toBeCloseTo(
2.5 / Math.cos(0.5),
6,
);
});
it("devolve o alcance se não bater antes", () => {
expect(raioAteParede(SALA, 1.5, 2.5, Math.PI / 2, 2)).toBe(2);
});
it("devolve 0 se já começa em célula opaca", () => {
expect(raioAteParede(SALA, 0.5, 0.5, 1, 5)).toBe(0);
});
});
describe("temLinhaDeVisao", () => {
it("vê em campo aberto", () => {
expect(temLinhaDeVisao(DIVIDIDA, 1.5, 1.5, 3.5, 2.5)).toBe(true);
});
it("não vê através de parede", () => {
expect(temLinhaDeVisao(DIVIDIDA, 1.5, 1.5, 6.5, 1.5)).toBe(false);
});
it("vê através de mesa", () => {
expect(temLinhaDeVisao(SALA, 1.5, 3.5, 5.5, 3.5)).toBe(true);
});
it("não vê um ponto dentro da parede", () => {
expect(temLinhaDeVisao(DIVIDIDA, 1.5, 1.5, 4.5, 1.5)).toBe(false);
});
it("não vê a partir de dentro da parede", () => {
expect(temLinhaDeVisao(DIVIDIDA, 0.5, 0.5, 1.5, 1.5)).toBe(false);
});
it("o mesmo ponto livre se vê; o mesmo ponto em parede, não", () => {
expect(temLinhaDeVisao(DIVIDIDA, 1.5, 1.5, 1.5, 1.5)).toBe(true);
expect(temLinhaDeVisao(DIVIDIDA, 0.5, 0.5, 0.5, 0.5)).toBe(false);
});
});
/** Mesa em x de 3 a 4, z de 3 a 4. */
const COM_MESA = lerMapa([
"#######",
"#J....#",
"#.....#",
"#..M..#",
"#....N#",
"#######",
]);
describe("caminhoLivre", () => {
it("livre em campo aberto, com folga dos dois lados", () => {
expect(caminhoLivre(COM_MESA, 1.5, 1.5, 5.5, 1.5, 0.35)).toBe(true);
});
it("a mesa no meio do segmento bloqueia, mesmo sem bloquear a visão", () => {
expect(caminhoLivre(COM_MESA, 1.5, 3.5, 5.5, 3.5, 0.35)).toBe(false);
expect(temLinhaDeVisao(COM_MESA, 1.5, 3.5, 5.5, 3.5)).toBe(true);
});
it("bloqueia quando só o paralelo de um lado cruza a mesa", () => {
expect(caminhoLivre(COM_MESA, 1.5, 2.7, 5.5, 2.7, 0.35)).toBe(false);
expect(caminhoLivre(COM_MESA, 1.5, 2.7, 5.5, 2.7, 0.25)).toBe(true);
});
it("bloqueia quando só o paralelo do outro lado cruza a mesa", () => {
expect(caminhoLivre(COM_MESA, 1.5, 4.3, 5.5, 4.3, 0.35)).toBe(false);
expect(caminhoLivre(COM_MESA, 1.5, 4.3, 5.5, 4.3, 0.25)).toBe(true);
});
it("o mesmo ponto: livre em chão, bloqueado em célula sólida", () => {
expect(caminhoLivre(COM_MESA, 1.5, 1.5, 1.5, 1.5, 0.35)).toBe(true);
expect(caminhoLivre(COM_MESA, 3.5, 3.5, 3.5, 3.5, 0.35)).toBe(false);
});
});
describe("moverComColisao", () => {
it("anda livre nos dois eixos", () => {
const [x, z] = moverComColisao(SALA, 2.5, 2.5, 0.5, 0.2, 0.25);
expect(x).toBeCloseTo(3.0, 9);
expect(z).toBeCloseTo(2.7, 9);
});
it("desliza em z quando x bate na parede (canto de trás, em cima)", () => {
expect(moverComColisao(SALA, 1.5, 1.5, -0.5, 0.3, 0.25)).toEqual([
1.5, 1.8,
]);
});
it("desliza em x quando z bate na parede", () => {
const [x, z] = moverComColisao(SALA, 1.5, 1.5, 0.3, -0.5, 0.25);
expect(x).toBeCloseTo(1.8, 9);
expect(z).toBe(1.5);
});
it("para quando os dois eixos batem", () => {
expect(moverComColisao(SALA, 1.5, 1.5, -0.5, -0.5, 0.25)).toEqual([
1.5, 1.5,
]);
});
it("confere o canto da frente, em cima (parede à direita)", () => {
expect(moverComColisao(SALA, 5.5, 2.5, 0.5, 0.1, 0.25)).toEqual([5.5, 2.6]);
});
it("confere o canto de trás, embaixo (mesa por baixo)", () => {
expect(moverComColisao(SALA, 3.5, 2.5, 0, 0.3, 0.25)).toEqual([3.5, 2.5]);
});
it("confere o canto da frente, embaixo (mesa na diagonal)", () => {
const [x, z] = moverComColisao(SALA, 2.5, 2.5, 0.3, 0.3, 0.25);
expect(x).toBeCloseTo(2.8, 9);
expect(z).toBe(2.5);
});
});
const FLUXO = lerMapa(["######", "#J.#N#", "#..#.#", "#M.###", "######"]);
describe("campoDeFluxo", () => {
it("guarda distâncias, -1 em sólidas e inalcançáveis", () => {
const campo = campoDeFluxo(FLUXO, 1.5, 1.5);
expect(Array.from(campo)).toEqual([
...[-1, -1, -1, -1, -1, -1],
...[-1, 0, 1, -1, -1, -1],
...[-1, 1, 2, -1, -1, -1],
...[-1, -1, 3, -1, -1, -1],
...[-1, -1, -1, -1, -1, -1],
]);
expect(campo).toBeInstanceOf(Int16Array);
});
it("tudo -1 se o alvo está em célula sólida", () => {
expect(
Array.from(campoDeFluxo(FLUXO, 1.5, 3.5)).every((v) => v === -1),
).toBe(true);
});
it("tudo -1 se o alvo está fora do mapa", () => {
expect(
Array.from(campoDeFluxo(FLUXO, -3, 1.5)).every((v) => v === -1),
).toBe(true);
});
});
const ABERTO = lerMapa(["J..", "...", "..N"]);
describe("direcaoNoCampo", () => {
const campo = campoDeFluxo(ABERTO, 0.5, 0.5);
it("vai na diagonal quando livre", () => {
const [dx, dz] = direcaoNoCampo(ABERTO, campo, 2.5, 2.5);
expect(dx).toBeCloseTo(-Math.SQRT1_2, 9);
expect(dz).toBeCloseTo(-Math.SQRT1_2, 9);
});
it("aponta para o centro da melhor vizinha a partir da posição real", () => {
const [dx, dz] = direcaoNoCampo(ABERTO, campo, 2.9, 2.1);
const tamanho = Math.hypot(1.4, 0.6);
expect(dx).toBeCloseTo(-1.4 / tamanho, 9);
expect(dz).toBeCloseTo(-0.6 / tamanho, 9);
});
it("vai em linha reta na borda do mapa", () => {
expect(direcaoNoCampo(ABERTO, campo, 1.5, 0.5)).toEqual([-1, 0]);
});
it("devolve [0, 0] na célula do alvo", () => {
expect(direcaoNoCampo(ABERTO, campo, 0.5, 0.5)).toEqual([0, 0]);
});
it("devolve [0, 0] fora do mapa", () => {
expect(direcaoNoCampo(ABERTO, campo, -1, 0.5)).toEqual([0, 0]);
expect(direcaoNoCampo(ABERTO, campo, 0.5, -1)).toEqual([0, 0]);
expect(direcaoNoCampo(ABERTO, campo, 3.5, 0.5)).toEqual([0, 0]);
expect(direcaoNoCampo(ABERTO, campo, 0.5, 3.5)).toEqual([0, 0]);
});
it("devolve [0, 0] em célula sem caminho", () => {
const campoF = campoDeFluxo(FLUXO, 1.5, 1.5);
expect(direcaoNoCampo(FLUXO, campoF, 4.5, 1.5)).toEqual([0, 0]);
});
it("não corta quina: parede em cima da diagonal", () => {
const m = lerMapa(["J#.", "...", "..N"]);
const c = campoDeFluxo(m, 0.5, 0.5);
expect(direcaoNoCampo(m, c, 1.5, 1.5)).toEqual([-1, 0]);
});
it("não corta quina: parede ao lado da diagonal", () => {
const m = lerMapa(["J..", "#..", "..N"]);
const c = campoDeFluxo(m, 0.5, 0.5);
expect(direcaoNoCampo(m, c, 1.5, 1.5)).toEqual([0, -1]);
});
});