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,160 @@
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import ts from "typescript";
|
||||
|
||||
const LIMITE = 10;
|
||||
const PASTA = __dirname;
|
||||
|
||||
type Medida = { nome: string; linha: number; complexidade: number };
|
||||
|
||||
const OPERADORES = new Set([
|
||||
ts.SyntaxKind.AmpersandAmpersandToken,
|
||||
ts.SyntaxKind.BarBarToken,
|
||||
ts.SyntaxKind.QuestionQuestionToken,
|
||||
]);
|
||||
|
||||
function ehDecisao(no: ts.Node): boolean {
|
||||
if (ts.isBinaryExpression(no)) return OPERADORES.has(no.operatorToken.kind);
|
||||
return (
|
||||
ts.isIfStatement(no) ||
|
||||
ts.isConditionalExpression(no) ||
|
||||
ts.isForStatement(no) ||
|
||||
ts.isForInStatement(no) ||
|
||||
ts.isForOfStatement(no) ||
|
||||
ts.isWhileStatement(no) ||
|
||||
ts.isDoStatement(no) ||
|
||||
ts.isCaseClause(no) ||
|
||||
ts.isCatchClause(no)
|
||||
);
|
||||
}
|
||||
|
||||
function ehFuncao(no: ts.Node): no is ts.FunctionLikeDeclaration {
|
||||
return (
|
||||
ts.isFunctionDeclaration(no) ||
|
||||
ts.isFunctionExpression(no) ||
|
||||
ts.isArrowFunction(no) ||
|
||||
ts.isMethodDeclaration(no) ||
|
||||
ts.isConstructorDeclaration(no) ||
|
||||
ts.isGetAccessorDeclaration(no) ||
|
||||
ts.isSetAccessorDeclaration(no)
|
||||
);
|
||||
}
|
||||
|
||||
function nomeDaFuncao(no: ts.FunctionLikeDeclaration): string {
|
||||
if (ts.isConstructorDeclaration(no)) return "constructor";
|
||||
if (no.name) return no.name.getText();
|
||||
const pai = no.parent;
|
||||
if (ts.isVariableDeclaration(pai) || ts.isPropertyAssignment(pai)) {
|
||||
return pai.name.getText();
|
||||
}
|
||||
return "(anônima)";
|
||||
}
|
||||
|
||||
/** Complexidade ciclomática de cada função do código-fonte (cada uma medida à parte). */
|
||||
export function medirComplexidade(codigo: string): Medida[] {
|
||||
const fonte = ts.createSourceFile(
|
||||
"medida.ts",
|
||||
codigo,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
);
|
||||
const medidas: Medida[] = [];
|
||||
|
||||
function medir(funcao: ts.FunctionLikeDeclaration): void {
|
||||
let complexidade = 1;
|
||||
const visitar = (no: ts.Node): void => {
|
||||
if (ehFuncao(no)) {
|
||||
medir(no);
|
||||
return;
|
||||
}
|
||||
if (ehDecisao(no)) complexidade++;
|
||||
ts.forEachChild(no, visitar);
|
||||
};
|
||||
ts.forEachChild(funcao, visitar);
|
||||
const { line } = fonte.getLineAndCharacterOfPosition(funcao.getStart());
|
||||
medidas.push({
|
||||
nome: nomeDaFuncao(funcao),
|
||||
linha: line + 1,
|
||||
complexidade,
|
||||
});
|
||||
}
|
||||
|
||||
const percorrer = (no: ts.Node): void => {
|
||||
if (ehFuncao(no)) medir(no);
|
||||
else ts.forEachChild(no, percorrer);
|
||||
};
|
||||
percorrer(fonte);
|
||||
return medidas.sort((a, b) => a.linha - b.linha);
|
||||
}
|
||||
|
||||
function comDecisoes(n: number): string {
|
||||
const ifs = Array.from({ length: n }, (_, i) => `if (x > ${i}) y++;`);
|
||||
return `function f(x: number) { let y = 0; ${ifs.join(" ")} return y; }`;
|
||||
}
|
||||
|
||||
describe("medirComplexidade", () => {
|
||||
it("função sem decisão vale 1", () => {
|
||||
expect(
|
||||
medirComplexidade("function f() { return 1; }")[0].complexidade,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it("10 decisões valem 11 e são reprovadas", () => {
|
||||
const [medida] = medirComplexidade(comDecisoes(10));
|
||||
expect(medida.complexidade).toBe(11);
|
||||
expect(medida.complexidade > LIMITE).toBe(true);
|
||||
});
|
||||
|
||||
it("9 decisões valem 10 e passam", () => {
|
||||
const [medida] = medirComplexidade(comDecisoes(9));
|
||||
expect(medida.complexidade).toBe(10);
|
||||
expect(medida.complexidade > LIMITE).toBe(false);
|
||||
});
|
||||
|
||||
it("decisões de função aninhada não contam na de fora", () => {
|
||||
const medidas = medirComplexidade(
|
||||
"function fora(a: number) { const dentro = () => { if (a) return 1; return a ? 2 : 3; }; return dentro; }",
|
||||
);
|
||||
expect(medidas.find((m) => m.nome === "fora")?.complexidade).toBe(1);
|
||||
expect(medidas.find((m) => m.nome === "dentro")?.complexidade).toBe(3);
|
||||
});
|
||||
|
||||
it("default não conta, case conta", () => {
|
||||
const [medida] = medirComplexidade(
|
||||
"function f(x: number) { switch (x) { case 1: return 1; case 2: return 2; default: return 0; } }",
|
||||
);
|
||||
expect(medida.complexidade).toBe(3);
|
||||
});
|
||||
|
||||
it("&&, || e ?? contam", () => {
|
||||
const [medida] = medirComplexidade(
|
||||
"function f(a: any, b: any, c: any) { return (a && b) || (c ?? a); }",
|
||||
);
|
||||
expect(medida.complexidade).toBe(4);
|
||||
});
|
||||
|
||||
it("laços, catch, do e ternário contam; métodos são medidos", () => {
|
||||
const medidas = medirComplexidade(
|
||||
"class A { m(l: number[]) { for (const i of l) {} for (const k in l) {} for (;;) {} while (1) {} do {} while (0); try {} catch {} return l ? 1 : 2; } }",
|
||||
);
|
||||
expect(medidas[0]).toMatchObject({ nome: "m", complexidade: 8 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("complexidade ciclomática de lib/jogo", () => {
|
||||
it(`nenhuma função passa de ${LIMITE}`, () => {
|
||||
const reprovadas: string[] = [];
|
||||
const arquivos = readdirSync(PASTA).filter(
|
||||
(f) => f.endsWith(".ts") && !f.endsWith(".test.ts"),
|
||||
);
|
||||
for (const arquivo of arquivos) {
|
||||
const codigo = readFileSync(join(PASTA, arquivo), "utf8");
|
||||
for (const m of medirComplexidade(codigo)) {
|
||||
if (m.complexidade > LIMITE) {
|
||||
reprovadas.push(`${arquivo}:${m.linha} ${m.nome} ${m.complexidade}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(reprovadas).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user