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.
236 lines
6.4 KiB
TypeScript
236 lines
6.4 KiB
TypeScript
import { avancarDrones, criarDrone } from "./drones";
|
|
import { lerMapa } from "./mapa";
|
|
import { PROBLEMAS } from "./problemas";
|
|
import type { Drone, Partida, Zumbi } from "./tipos";
|
|
import { DRONE, ESCRITORIO, PASSO } from "./valores";
|
|
|
|
const PASSO_MAX = DRONE.velocidade * PASSO;
|
|
const [PROBLEMA_DRONE, PROBLEMA_ZUMBI] = PROBLEMAS;
|
|
|
|
function partida(x: number, z: number, rumo = 0): Partida {
|
|
return {
|
|
passo: 0,
|
|
rng: 1,
|
|
dificuldade: "normal",
|
|
problemaId: PROBLEMA_DRONE.id,
|
|
hordas: [],
|
|
horda: 0,
|
|
fase: "horda",
|
|
tempoFase: 0,
|
|
tetoAVista: 16,
|
|
mapa: lerMapa(ESCRITORIO),
|
|
jogador: {
|
|
x,
|
|
z,
|
|
rumo,
|
|
inclinacao: 0,
|
|
vida: 100,
|
|
arma: 0,
|
|
municao: 90,
|
|
recarga: 0,
|
|
},
|
|
zumbis: [],
|
|
fila: [],
|
|
drones: [],
|
|
caixas: [],
|
|
feed: [],
|
|
resolvidos: [],
|
|
placar: { jogador: 0, drones: 0 },
|
|
eventos: [],
|
|
proximoId: 1,
|
|
};
|
|
}
|
|
|
|
function zumbi(p: Partida, x: number, z: number, vida = 100): Zumbi {
|
|
const z1: Zumbi = {
|
|
id: p.proximoId++,
|
|
tipo: "comum",
|
|
problemaId: PROBLEMA_ZUMBI.id,
|
|
x,
|
|
z,
|
|
rumo: 0,
|
|
vida,
|
|
estado: "andando",
|
|
tempo: 0,
|
|
};
|
|
p.zumbis.push(z1);
|
|
return z1;
|
|
}
|
|
|
|
function drone(p: Partida, recarga = 1): Drone {
|
|
const d = criarDrone(p, PROBLEMA_DRONE.id);
|
|
d.recarga = recarga;
|
|
return d;
|
|
}
|
|
|
|
describe("criarDrone", () => {
|
|
it("nasce no jogador, com o rumo dele, e entra na lista", () => {
|
|
const p = partida(5.5, 8.5, 1.25);
|
|
const d = criarDrone(p, PROBLEMA_DRONE.id);
|
|
expect(d).toEqual({
|
|
id: 1,
|
|
problemaId: PROBLEMA_DRONE.id,
|
|
x: 5.5,
|
|
z: 8.5,
|
|
rumo: 1.25,
|
|
recarga: DRONE.intervalo,
|
|
eliminacoes: 0,
|
|
});
|
|
expect(p.drones).toEqual([d]);
|
|
expect(p.proximoId).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("avancarDrones: movimento", () => {
|
|
it("anda no máximo DRONE.velocidade * PASSO por passo e chega ao lugar", () => {
|
|
const p = partida(13.5, 8.5);
|
|
const d = drone(p, 1000);
|
|
for (let i = 0; i < 100; i++) {
|
|
const [x, z] = [d.x, d.z];
|
|
avancarDrones(p);
|
|
expect(Math.hypot(d.x - x, d.z - z)).toBeLessThanOrEqual(
|
|
PASSO_MAX + 1e-12,
|
|
);
|
|
}
|
|
expect(d.x).toBeCloseTo(15.5, 9);
|
|
expect(d.z).toBeCloseTo(8.5, 9);
|
|
});
|
|
|
|
it("dá um passo cheio quando o lugar está longe", () => {
|
|
const p = partida(13.5, 8.5);
|
|
const d = drone(p, 1000);
|
|
avancarDrones(p);
|
|
expect(d.x - 13.5).toBeCloseTo(PASSO_MAX, 9);
|
|
expect(d.z).toBeCloseTo(8.5, 9);
|
|
});
|
|
|
|
it("não atravessa parede", () => {
|
|
const p = partida(13.5, 15.5, -Math.PI / 2);
|
|
const d = drone(p, 1000);
|
|
for (let i = 0; i < 100; i++) avancarDrones(p);
|
|
expect(d.z).toBeLessThanOrEqual(16 - 0.2);
|
|
});
|
|
|
|
it("três drones ocupam ângulos diferentes em volta do jogador", () => {
|
|
const p = partida(24.5, 8.5, 0.4);
|
|
const drones = [drone(p, 1000), drone(p, 1000), drone(p, 1000)];
|
|
for (let i = 0; i < 200; i++) avancarDrones(p);
|
|
drones.forEach((d, i) => {
|
|
const a = p.jogador.rumo + Math.PI / 2 + i * ((2 * Math.PI) / 3);
|
|
expect(d.x).toBeCloseTo(24.5 + Math.sin(a) * DRONE.distancia, 6);
|
|
expect(d.z).toBeCloseTo(8.5 + Math.cos(a) * DRONE.distancia, 6);
|
|
});
|
|
expect(
|
|
Math.hypot(drones[0].x - drones[1].x, drones[0].z - drones[1].z),
|
|
).toBeGreaterThan(1);
|
|
expect(
|
|
Math.hypot(drones[1].x - drones[2].x, drones[1].z - drones[2].z),
|
|
).toBeGreaterThan(1);
|
|
});
|
|
});
|
|
|
|
describe("avancarDrones: tiro", () => {
|
|
it("respeita a recarga: desconta até 0, atira e recarrega", () => {
|
|
const p = partida(5.5, 8.5);
|
|
const d = drone(p, 3);
|
|
const alvo = zumbi(p, 11.5, 8.5);
|
|
avancarDrones(p);
|
|
avancarDrones(p);
|
|
expect(d.recarga).toBe(1);
|
|
expect(p.eventos).toEqual([]);
|
|
avancarDrones(p);
|
|
expect(d.recarga).toBe(DRONE.intervalo);
|
|
expect(p.eventos.map((e) => e.tipo)).toEqual(["tiro", "acerto"]);
|
|
expect(alvo.vida).toBe(100 - DRONE.dano);
|
|
avancarDrones(p);
|
|
expect(d.recarga).toBe(DRONE.intervalo - 1);
|
|
expect(p.eventos).toHaveLength(2);
|
|
expect(p.feed).toEqual([]);
|
|
});
|
|
|
|
it("vira para o zumbi e registra tiro e acerto", () => {
|
|
const p = partida(5.5, 8.5);
|
|
const d = drone(p);
|
|
const alvo = zumbi(p, 5.5, 14.5);
|
|
avancarDrones(p);
|
|
const distancia = Math.hypot(alvo.x - d.x, alvo.z - d.z);
|
|
expect(d.rumo).toBeCloseTo(Math.atan2(alvo.x - d.x, alvo.z - d.z), 9);
|
|
expect(p.eventos).toEqual([
|
|
{ tipo: "tiro", autor: d.id, arma: "drone", distancia },
|
|
{
|
|
tipo: "acerto",
|
|
zumbiId: alvo.id,
|
|
cabeca: false,
|
|
x: 5.5,
|
|
y: 1.2,
|
|
z: 14.5,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("escolhe o zumbi mais próximo", () => {
|
|
const p = partida(5.5, 8.5);
|
|
drone(p);
|
|
const longe = zumbi(p, 15.5, 8.5);
|
|
const perto = zumbi(p, 9.5, 8.5);
|
|
avancarDrones(p);
|
|
expect(perto.vida).toBe(100 - DRONE.dano);
|
|
expect(longe.vida).toBe(100);
|
|
});
|
|
|
|
it("mantém o mais próximo quando o seguinte está mais longe", () => {
|
|
const p = partida(5.5, 8.5);
|
|
drone(p);
|
|
const perto = zumbi(p, 9.5, 8.5);
|
|
const longe = zumbi(p, 15.5, 8.5);
|
|
avancarDrones(p);
|
|
expect(perto.vida).toBe(100 - DRONE.dano);
|
|
expect(longe.vida).toBe(100);
|
|
});
|
|
|
|
it("ignora zumbi morrendo", () => {
|
|
const p = partida(5.5, 8.5);
|
|
drone(p);
|
|
const morto = zumbi(p, 8.5, 8.5);
|
|
morto.estado = "morrendo";
|
|
const vivo = zumbi(p, 15.5, 8.5);
|
|
avancarDrones(p);
|
|
expect(morto.vida).toBe(100);
|
|
expect(vivo.vida).toBe(100 - DRONE.dano);
|
|
});
|
|
|
|
it("não atira com parede no meio e deixa a recarga em 0", () => {
|
|
const p = partida(9.5, 2.5);
|
|
const d = drone(p);
|
|
const alvo = zumbi(p, 12.5, 2.5);
|
|
avancarDrones(p);
|
|
expect(alvo.vida).toBe(100);
|
|
expect(p.eventos).toEqual([]);
|
|
expect(d.recarga).toBe(0);
|
|
});
|
|
|
|
it("não atira além do alcance", () => {
|
|
const p = partida(2.5, 8.5);
|
|
const d = drone(p);
|
|
const alvo = zumbi(p, 2.5 + DRONE.alcance + 1, 8.5);
|
|
avancarDrones(p);
|
|
expect(alvo.vida).toBe(100);
|
|
expect(p.eventos).toEqual([]);
|
|
expect(d.recarga).toBe(0);
|
|
});
|
|
|
|
it("a morte gera a linha do feed com a solução do drone e o nome do zumbi", () => {
|
|
const p = partida(5.5, 8.5);
|
|
drone(p);
|
|
const alvo = zumbi(p, 9.5, 8.5, DRONE.dano);
|
|
avancarDrones(p);
|
|
expect(alvo.estado).toBe("morrendo");
|
|
expect(p.feed).toEqual([
|
|
{
|
|
passo: 0,
|
|
texto: `${PROBLEMA_DRONE.solucao} eliminou ${PROBLEMA_ZUMBI.nome}`,
|
|
},
|
|
]);
|
|
});
|
|
});
|