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.
376 lines
9.9 KiB
TypeScript
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);
|
|
});
|
|
});
|