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]); }); });