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.
This commit is contained in:
1 parent
c7d09d63d4
commit
67b32fcab3
118 files changed
+16730
No files matched your search
@@ -0,0 +1,131 @@
|
||||
import { campoDeFluxo, direcaoNoCampo, lerMapa } from "../mapa";
|
||||
import { AMBIENTES, ESCRITORIO, PORTAS } from "./escritorio";
|
||||
|
||||
const MAPA = lerMapa(ESCRITORIO);
|
||||
const { largura, altura } = MAPA;
|
||||
const andavel = (bloqueio: Uint8Array, x: number, z: number) =>
|
||||
x >= 0 &&
|
||||
z >= 0 &&
|
||||
x < largura &&
|
||||
z < altura &&
|
||||
bloqueio[z * largura + x] === 0;
|
||||
|
||||
/** Quantas células andáveis a busca em largura alcança a partir do início. */
|
||||
function alcancaveis(bloqueio: Uint8Array): number {
|
||||
const inicio =
|
||||
Math.floor(MAPA.inicio[1]) * largura + Math.floor(MAPA.inicio[0]);
|
||||
const visto = new Uint8Array(largura * altura);
|
||||
const fila = [inicio];
|
||||
visto[inicio] = 1;
|
||||
for (let k = 0; k < fila.length; k++) {
|
||||
const x = fila[k] % largura;
|
||||
const z = Math.floor(fila[k] / largura);
|
||||
for (const [dx, dz] of [
|
||||
[1, 0],
|
||||
[-1, 0],
|
||||
[0, 1],
|
||||
[0, -1],
|
||||
]) {
|
||||
const i = (z + dz) * largura + x + dx;
|
||||
if (andavel(bloqueio, x + dx, z + dz) && !visto[i]) {
|
||||
visto[i] = 1;
|
||||
fila.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fila.length;
|
||||
}
|
||||
|
||||
const totalAndavel = MAPA.solido.filter((c) => c === 0).length;
|
||||
|
||||
describe("mapa do escritório", () => {
|
||||
it("tem pelo menos 40 × 28 células e é lido por lerMapa", () => {
|
||||
expect(largura).toBeGreaterThanOrEqual(40);
|
||||
expect(altura).toBeGreaterThanOrEqual(28);
|
||||
expect(() => lerMapa(ESCRITORIO)).not.toThrow();
|
||||
});
|
||||
|
||||
it("tem os 7 ambientes com nome", () => {
|
||||
const nomes = AMBIENTES.map((a) => a.nome);
|
||||
for (const nome of [
|
||||
"recepção",
|
||||
"área de mesas",
|
||||
"sala de reunião",
|
||||
"copa",
|
||||
"sala do servidor",
|
||||
"diretoria",
|
||||
"almoxarifado",
|
||||
]) {
|
||||
expect(nomes).toContain(nome);
|
||||
}
|
||||
});
|
||||
|
||||
it("tem 6 ou mais nascedouros, todos a 8 m ou mais do início", () => {
|
||||
expect(MAPA.nascedouros.length).toBeGreaterThanOrEqual(6);
|
||||
for (const [x, z] of MAPA.nascedouros) {
|
||||
expect(
|
||||
Math.hypot(x - MAPA.inicio[0], z - MAPA.inicio[1]),
|
||||
).toBeGreaterThanOrEqual(8);
|
||||
}
|
||||
});
|
||||
|
||||
it("alcança todo o chão a partir do início", () => {
|
||||
expect(alcancaveis(MAPA.solido)).toBe(totalAndavel);
|
||||
});
|
||||
|
||||
it("continua alcançando todo o chão sem qualquer uma das portas", () => {
|
||||
expect(PORTAS.length).toBeGreaterThan(0);
|
||||
for (const porta of PORTAS) {
|
||||
const bloqueio = MAPA.solido.slice();
|
||||
for (let z = porta.z; z < porta.z + porta.altura; z++) {
|
||||
for (let x = porta.x; x < porta.x + porta.largura; x++) {
|
||||
expect(andavel(bloqueio, x, z)).toBe(true);
|
||||
bloqueio[z * largura + x] = 1;
|
||||
}
|
||||
}
|
||||
expect(alcancaveis(bloqueio)).toBe(
|
||||
totalAndavel - porta.largura * porta.altura,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("não tem passagem com menos de 2 células: toda célula andável está num 2 × 2 andável", () => {
|
||||
const livre = (x: number, z: number) => andavel(MAPA.solido, x, z);
|
||||
for (let z = 0; z < altura; z++) {
|
||||
for (let x = 0; x < largura; x++) {
|
||||
if (!livre(x, z)) continue;
|
||||
const emQuadrado = [
|
||||
[-1, -1],
|
||||
[-1, 0],
|
||||
[0, -1],
|
||||
[0, 0],
|
||||
].some(
|
||||
([ox, oz]) =>
|
||||
livre(x + ox, z + oz) &&
|
||||
livre(x + ox + 1, z + oz) &&
|
||||
livre(x + ox, z + oz + 1) &&
|
||||
livre(x + ox + 1, z + oz + 1),
|
||||
);
|
||||
expect([x, z, emQuadrado]).toEqual([x, z, true]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("o campo de fluxo leva cada nascedouro ao início em menos de 400 passos", () => {
|
||||
const campo = campoDeFluxo(MAPA, MAPA.inicio[0], MAPA.inicio[1]);
|
||||
const alvo = [Math.floor(MAPA.inicio[0]), Math.floor(MAPA.inicio[1])];
|
||||
for (const [nx, nz] of MAPA.nascedouros) {
|
||||
let x = Math.floor(nx);
|
||||
let z = Math.floor(nz);
|
||||
let passos = 0;
|
||||
while ((x !== alvo[0] || z !== alvo[1]) && passos < 400) {
|
||||
const [dx, dz] = direcaoNoCampo(MAPA, campo, x + 0.5, z + 0.5);
|
||||
x += Math.round(dx);
|
||||
z += Math.round(dz);
|
||||
passos++;
|
||||
}
|
||||
expect([x, z]).toEqual(alvo);
|
||||
expect(passos).toBeLessThan(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
// Gerado por `bash 3d/mapa.sh` (`python3 3d/mapa.py converter`) a partir de 3d/mapa/escritorio.tmx: não edite à mão.
|
||||
|
||||
/** Planta do escritório para `lerMapa`: `#` parede ou móvel alto, `M` móvel baixo, `.` chão, `J` início, `N` nascedouro. */
|
||||
export const ESCRITORIO: readonly string[] = [
|
||||
"############################################",
|
||||
"#..MMM.........#............#MMM..........##",
|
||||
"#..............#............#.............##",
|
||||
"#M......MMMM...#..MMMMMMMM.........MMMM...##",
|
||||
"#M.............#..MMMMMMMM.........MMMM...##",
|
||||
"#M.....J.......#............#..............#",
|
||||
"#M.............#............#..............#",
|
||||
"####..######..####..####..####..############",
|
||||
"#........#........................#........#",
|
||||
"#........#........................#........#",
|
||||
"#........#..#########..#####..##..#..#######",
|
||||
"#M..MM......#.................##...........#",
|
||||
"#M..MM......#.................##...........#",
|
||||
"#M.......#..#..MMMMM...MMMMM..##..#..#######",
|
||||
"#M.......#..#..MMMMM...MMMMM...#..#........#",
|
||||
"#MMMM..###........................#........#",
|
||||
"##########........................####..####",
|
||||
"###..MMMM#..#..MMMMM...MMMMM...#..#.......##",
|
||||
"###......#..#..MMMMM...MMMMM...#..#.......##",
|
||||
"###.........#..................#..........##",
|
||||
"###.........#..................#.....###...#",
|
||||
"###...N..#..#########..#########..#........#",
|
||||
"#........#........................#......N.#",
|
||||
"#........#........................###......#",
|
||||
"####..#######..#####..######..########..####",
|
||||
"#................#......#..................#",
|
||||
"#................#......#..................#",
|
||||
"#.N....N.............####...............MMM#",
|
||||
"#....................####...............MMM#",
|
||||
"####..###........#.N.####.N................#",
|
||||
"####..###..MMMM..#...####..######........N.#",
|
||||
"############################################",
|
||||
];
|
||||
|
||||
/** Salas com nome, em células (x para a direita, z para baixo). */
|
||||
export const AMBIENTES: readonly {
|
||||
nome: string;
|
||||
x: number;
|
||||
z: number;
|
||||
largura: number;
|
||||
altura: number;
|
||||
}[] = [
|
||||
{ nome: "recepção", x: 1, z: 1, largura: 14, altura: 6 },
|
||||
{ nome: "sala de reunião", x: 16, z: 1, largura: 12, altura: 6 },
|
||||
{ nome: "diretoria", x: 29, z: 1, largura: 14, altura: 6 },
|
||||
{ nome: "copa", x: 1, z: 8, largura: 8, altura: 8 },
|
||||
{ nome: "banheiros", x: 1, z: 17, largura: 8, altura: 7 },
|
||||
{ nome: "área de mesas", x: 13, z: 11, largura: 18, altura: 10 },
|
||||
{ nome: "sala do servidor", x: 35, z: 8, largura: 8, altura: 8 },
|
||||
{ nome: "almoxarifado", x: 35, z: 17, largura: 8, altura: 7 },
|
||||
{ nome: "hall dos elevadores", x: 1, z: 25, largura: 16, altura: 6 },
|
||||
{ nome: "escada", x: 18, z: 25, largura: 6, altura: 6 },
|
||||
{ nome: "área de serviço", x: 25, z: 25, largura: 18, altura: 6 },
|
||||
];
|
||||
|
||||
/** Células abertas nas paredes, em células. */
|
||||
export const PORTAS: readonly {
|
||||
x: number;
|
||||
z: number;
|
||||
largura: number;
|
||||
altura: number;
|
||||
}[] = [
|
||||
{ x: 4, z: 7, largura: 2, altura: 1 },
|
||||
{ x: 12, z: 7, largura: 2, altura: 1 },
|
||||
{ x: 18, z: 7, largura: 2, altura: 1 },
|
||||
{ x: 24, z: 7, largura: 2, altura: 1 },
|
||||
{ x: 28, z: 3, largura: 1, altura: 2 },
|
||||
{ x: 30, z: 7, largura: 2, altura: 1 },
|
||||
{ x: 9, z: 11, largura: 1, altura: 2 },
|
||||
{ x: 9, z: 19, largura: 1, altura: 2 },
|
||||
{ x: 4, z: 24, largura: 2, altura: 1 },
|
||||
{ x: 13, z: 24, largura: 2, altura: 1 },
|
||||
{ x: 17, z: 27, largura: 1, altura: 2 },
|
||||
{ x: 20, z: 24, largura: 2, altura: 1 },
|
||||
{ x: 28, z: 24, largura: 2, altura: 1 },
|
||||
{ x: 38, z: 24, largura: 2, altura: 1 },
|
||||
{ x: 34, z: 19, largura: 1, altura: 2 },
|
||||
{ x: 38, z: 16, largura: 2, altura: 1 },
|
||||
{ x: 34, z: 11, largura: 1, altura: 2 },
|
||||
{ x: 21, z: 10, largura: 2, altura: 1 },
|
||||
{ x: 28, z: 10, largura: 2, altura: 1 },
|
||||
{ x: 21, z: 21, largura: 2, altura: 1 },
|
||||
{ x: 12, z: 15, largura: 1, altura: 2 },
|
||||
{ x: 31, z: 15, largura: 1, altura: 2 },
|
||||
];
|
||||
Reference in new issue
Block a user