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

376 lines
9.9 KiB
TypeScript

import {
alvoNaMira,
atirar,
raioNoCirculo,
recolherCaixas,
resfriar,
trocarArma,
} from "./armas";
import { lerMapa } from "./mapa";
import type { ArmaId, Partida, TipoZumbi, Zumbi } from "./tipos";
import { ARMAS, JOGADOR, ZUMBIS } from "./valores";
const LARGURA = 7;
function mapaAberto() {
const linhas = ["#######", "#N...J#"];
for (let i = 0; i < 13; i++) linhas.push("#.....#");
linhas.push("#######");
return lerMapa(linhas);
}
function zumbi(
id: number,
x: number,
z: number,
tipo: TipoZumbi = "comum",
): Zumbi {
return {
id,
tipo,
problemaId: "site-lento",
x,
z,
rumo: 0,
vida: ZUMBIS[tipo].vida,
estado: "andando",
tempo: 0,
};
}
/** Jogador em (3.5, 2.5) olhando para +z, com a pistola. */
function partida(arma: ArmaId = 0): Partida {
return {
passo: 0,
rng: 1,
dificuldade: "normal",
problemaId: "site-lento",
hordas: [],
horda: 0,
fase: "horda",
tempoFase: 0,
tetoAVista: 16,
mapa: mapaAberto(),
jogador: {
x: 3.5,
z: 2.5,
rumo: 0,
inclinacao: 0,
vida: JOGADOR.vida,
arma,
municao: 10,
recarga: 0,
},
zumbis: [],
fila: [],
drones: [],
caixas: [],
feed: [],
resolvidos: [],
placar: { jogador: 0, drones: 0 },
eventos: [],
proximoId: 100,
};
}
describe("alvoNaMira", () => {
it("é nulo com a faca", () => {
const p = partida(2);
p.zumbis = [zumbi(1, 3.5, 3.5)];
expect(alvoNaMira(p)).toBeNull();
});
it("é nulo sem zumbi na linha", () => {
const p = partida();
p.zumbis = [zumbi(1, 1.5, 5.5)];
expect(alvoNaMira(p)).toBeNull();
});
it("devolve o corpo, com a distância e a altura", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5)];
p.jogador.inclinacao = Math.atan(-0.2);
const alvo = alvoNaMira(p);
expect(alvo?.zumbi.id).toBe(1);
expect(alvo?.cabeca).toBe(false);
expect(alvo?.distancia).toBeCloseTo(2.65);
expect(alvo?.y).toBeCloseTo(1.6 - 0.2 * 2.65);
});
it("devolve a cabeça e não fere ninguém", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5)];
expect(alvoNaMira(p)?.cabeca).toBe(true);
expect(p.zumbis[0].vida).toBe(100);
expect(p.eventos).toHaveLength(0);
});
});
describe("raioNoCirculo", () => {
it("devolve a distância até a primeira interseção", () => {
expect(raioNoCirculo(0, 0, 0, 1, 0, 5, 1)).toBeCloseTo(4);
});
it("devolve null quando o raio passa ao lado", () => {
expect(raioNoCirculo(0, 0, 0, 1, 3, 5, 1)).toBeNull();
});
it("devolve null quando o círculo está atrás", () => {
expect(raioNoCirculo(0, 0, 0, 1, 0, -5, 1)).toBeNull();
});
it("devolve 0 com a origem dentro do círculo", () => {
expect(raioNoCirculo(0, 0.2, 0, 1, 0, 0, 1)).toBe(0);
});
});
describe("trocarArma", () => {
it("troca e põe a recarga em 10", () => {
const p = partida(0);
trocarArma(p, 1);
expect(p.jogador.arma).toBe(1);
expect(p.jogador.recarga).toBe(10);
});
it("não faz nada para a mesma arma", () => {
const p = partida(0);
trocarArma(p, 0);
expect(p.jogador.recarga).toBe(0);
});
});
describe("resfriar", () => {
it("desconta 1 da recarga", () => {
const p = partida();
p.jogador.recarga = 3;
resfriar(p);
expect(p.jogador.recarga).toBe(2);
});
it("para em 0", () => {
const p = partida();
resfriar(p);
expect(p.jogador.recarga).toBe(0);
});
});
describe("recolherCaixas", () => {
it("recolhe a caixa perto e deixa a longe", () => {
const p = partida();
p.caixas = [
{ id: 1, x: 3.5, z: 3.0 },
{ id: 2, x: 3.5, z: 8.5 },
];
recolherCaixas(p);
expect(p.caixas.map((c) => c.id)).toEqual([2]);
expect(p.jogador.municao).toBe(40);
expect(p.eventos).toEqual([{ tipo: "caixa", balas: 30 }]);
});
});
describe("atirar: regras gerais", () => {
it("não faz nada com recarga pendente", () => {
const p = partida();
p.jogador.recarga = 1;
atirar(p);
expect(p.eventos).toEqual([]);
expect(p.jogador.recarga).toBe(1);
});
it("automática sem bala não atira e não gasta recarga", () => {
const p = partida(1);
p.jogador.municao = 0;
atirar(p);
expect(p.eventos).toEqual([]);
expect(p.jogador.recarga).toBe(0);
});
it("automática gasta uma bala e arma a recarga", () => {
const p = partida(1);
atirar(p);
expect(p.jogador.municao).toBe(9);
expect(p.jogador.recarga).toBe(ARMAS[1].intervalo);
});
it("pistola nunca gasta bala, nem com a munição zerada", () => {
const p = partida(0);
p.jogador.municao = 0;
atirar(p);
expect(p.jogador.municao).toBe(0);
expect(p.jogador.recarga).toBe(ARMAS[0].intervalo);
expect(p.eventos).toHaveLength(1);
});
});
describe("atirar: tiro por raio", () => {
it("sem zumbi, o tiro vai até a parede", () => {
const p = partida();
atirar(p);
expect(p.eventos).toHaveLength(1);
const e = p.eventos[0];
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(12.5);
});
it("acerta o corpo com o dano da arma", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5)];
p.jogador.inclinacao = Math.atan(-0.2);
atirar(p);
expect(p.zumbis[0].vida).toBe(100 - ARMAS[0].dano);
const [tiro, acerto] = p.eventos;
expect(tiro).toMatchObject({ tipo: "tiro", autor: "jogador", arma: 0 });
expect(tiro.tipo === "tiro" && tiro.distancia).toBeCloseTo(2.65);
expect(acerto).toMatchObject({
tipo: "acerto",
zumbiId: 1,
cabeca: false,
});
expect(acerto.tipo === "acerto" && acerto.y).toBeCloseTo(1.6 - 0.2 * 2.65);
expect(acerto.tipo === "acerto" && acerto.x).toBeCloseTo(3.5);
expect(acerto.tipo === "acerto" && acerto.z).toBeCloseTo(5.15);
});
it("acerta a cabeça com o dano dobrado", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100 - ARMAS[0].dano * 2);
expect(p.eventos[1]).toMatchObject({ tipo: "acerto", cabeca: true });
});
it("erra por cima com a inclinação alta", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5)];
p.jogador.inclinacao = 1;
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.eventos).toHaveLength(1);
const e = p.eventos[0];
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(12.5);
});
it("erra por baixo, no chão", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5)];
p.jogador.inclinacao = Math.atan(-1);
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.eventos).toHaveLength(1);
});
it("o raio que passa ao lado não acerta", () => {
const p = partida();
p.zumbis = [zumbi(1, 5.5, 5.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
});
it("parede na frente do zumbi o protege", () => {
const p = partida();
p.mapa.opaco[8 * LARGURA + 3] = 1;
p.zumbis = [zumbi(1, 3.5, 10.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
const e = p.eventos[0];
expect(p.eventos).toHaveLength(1);
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(5.5);
});
it("o zumbi da frente protege o de trás", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 9.5), zumbi(2, 3.5, 5.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.zumbis[1].vida).toBe(32);
});
it("zumbi morrendo é atravessado", () => {
const p = partida();
const morto = zumbi(1, 3.5, 5.5);
morto.estado = "morrendo";
p.zumbis = [morto, zumbi(2, 3.5, 9.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.zumbis[1].vida).toBe(32);
});
it("o tiro que passa por cima do da frente pode acertar o de trás", () => {
const p = partida();
p.zumbis = [zumbi(1, 3.5, 5.5, "rapido"), zumbi(2, 3.5, 9.5, "chefao")];
p.jogador.inclinacao = Math.atan(0.05);
atirar(p);
expect(p.zumbis[0].vida).toBe(50);
expect(p.zumbis[1].vida).toBeLessThan(1500);
});
it("matar registra a morte pelo ferir", () => {
const p = partida();
const fraco = zumbi(1, 3.5, 5.5);
fraco.vida = 10;
p.zumbis = [fraco];
atirar(p);
expect(fraco.estado).toBe("morrendo");
expect(p.eventos.some((e) => e.tipo === "morte")).toBe(true);
});
it("a automática usa o dano da automática", () => {
const p = partida(1);
p.zumbis = [zumbi(1, 3.5, 5.5)];
p.jogador.inclinacao = Math.atan(-0.2);
atirar(p);
expect(p.zumbis[0].vida).toBe(100 - ARMAS[1].dano);
});
});
describe("atirar: faca", () => {
it("fere o mais próximo dentro do alcance e do arco", () => {
const p = partida(2);
p.zumbis = [zumbi(1, 3.5, 3.9), zumbi(2, 3.5, 3.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.zumbis[1].vida).toBe(100 - ARMAS[2].dano);
expect(p.eventos[0]).toMatchObject({ tipo: "tiro", arma: 2 });
const acerto = p.eventos.find((e) => e.tipo === "acerto");
expect(acerto).toMatchObject({
zumbiId: 2,
cabeca: false,
y: ZUMBIS.comum.altura / 2,
});
});
it("não acerta fora do arco", () => {
const p = partida(2);
p.zumbis = [zumbi(1, 3.5, 1.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.eventos).toEqual([
{ tipo: "tiro", autor: "jogador", arma: 2, distancia: ARMAS[2].alcance },
]);
});
it("não acerta fora do alcance", () => {
const p = partida(2);
p.zumbis = [zumbi(1, 3.5, 5.5)];
atirar(p);
expect(p.zumbis[0].vida).toBe(100);
expect(p.eventos).toHaveLength(1);
});
it("ignora o zumbi morrendo", () => {
const p = partida(2);
const morto = zumbi(1, 3.5, 3.5);
morto.estado = "morrendo";
p.zumbis = [morto];
atirar(p);
expect(p.eventos).toHaveLength(1);
});
it("a distância do tiro é a do zumbi ferido", () => {
const p = partida(2);
p.zumbis = [zumbi(1, 3.5, 3.5)];
atirar(p);
const e = p.eventos[0];
expect(e.tipo === "tiro" && e.distancia).toBeCloseTo(0.65);
});
});