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.
310 lines
9.4 KiB
TypeScript
310 lines
9.4 KiB
TypeScript
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]);
|
||
});
|
||
});
|