game2 / client / test / frames.test.mjs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Golden-vector tests for the wire protocol: every frame must serialize
// byte-identically to what the deployed TypeScript build emits (a scrambled key
// order compiles fine and desyncs every client — dev/docs/CLJS.md), and
// isGameFrame must accept and reject exactly what the TS did.
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";

const v = JSON.parse(readFileSync(new URL("./vectors/frames.json", import.meta.url), "utf8"));
const { A, B } = v.ids;

const bs = (score, over = false, grid = ".".repeat(200), snake = null) =>
  ({ grid, score, lines: 0, level: 1, snake, over });

// One builder per frame shape, in the same order as the recorded table.
const builders = [
  () => lib.mkHi("zed", 2),
  () => lib.mkLv(),
  () => lib.mkLo(),
  () => lib.mkRo(A, [[A, "zed", 1]], "race", 5000, 1),
  () => lib.mkRo(A, [], "survival", 100, 0),
  () => lib.mkSt(3, 12345, [A, B], "race-survival", 2000),
  () => lib.mkBs(3, 1, bs(42)),
  () => lib.mkOv(3, null, "void", [], []),
  () => lib.mkOv(3, A, "survivor", [[A, "zed", 900, 0]], [[A, "zed", 2]]),
  () => lib.mkSn(4, [A], "race", 500),
];

test("every frame serializes byte-identically to the TS original", () => {
  assert.equal(builders.length, v.good.length, "builder table drifted from the fixture");
  for (let i = 0; i < builders.length; i++) {
    assert.equal(JSON.stringify(builders[i]()), v.good[i].json, `frame ${i}`);
  }
});

test("isGameFrame accepts every real frame", () => {
  for (let i = 0; i < builders.length; i++) {
    assert.equal(v.good[i].accepted, true, `fixture ${i} should have been accepted by the TS`);
    assert.equal(Boolean(lib.isGameFrame(JSON.parse(v.good[i].json))), true, v.good[i].json);
    assert.equal(Boolean(lib.isGameFrame(builders[i]())), true, `built frame ${i}`);
  }
});

test("isGameFrame rejects exactly the junk the TS rejected", () => {
  for (const { desc, accepted } of v.junk) {
    assert.equal(accepted, false, `fixture says the TS accepted ${desc}`);
    const value = desc === "undefined" ? undefined : JSON.parse(desc);
    assert.equal(Boolean(lib.isGameFrame(value)), false, desc);
  }
});

test("boardStateOf: key order and grid serialization off a real game", () => {
  for (const { seed, json, grid } of v.boardStates) {
    const g = lib.makeGame({}, seed);
    for (let i = 0; i < 5; i++) lib.gameHardDrop(g);
    assert.deepEqual(
      [...g.board.grid].map((r) => [...r].map((c) => (c === 0 ? "." : c)).join("")),
      grid,
      `seed ${seed}: board`,
    );
    assert.equal(JSON.stringify(lib.boardStateOf(g)), json, `seed ${seed}: bs bytes`);
  }
});

test("serializeGrid drops the hidden spawn rows", () => {
  const b = lib.makeBoard();
  const { HIDDEN_ROWS, COLS, ROWS } = lib.boardConsts();
  b.grid[0][0] = "I";                 // hidden — must not appear
  b.grid[HIDDEN_ROWS][0] = "T";       // first visible cell
  const s = lib.serializeGrid(b.grid);
  assert.equal(s.length, COLS * ROWS);
  assert.equal(s[0], "T");
  assert.ok(!s.includes("I"));
});

test("mode labels and protocol constants are the deployed values", () => {
  assert.deepEqual({ ...lib.modeLabel }, v.modeLabel);
  assert.equal(lib.protocolOf("0123456789abcdefGARBAGE"), "/game2/2/0123456789abcdef");
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/game2.git