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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 | // Golden-vector tests for the deterministic core: RNG, 7-bag, SRS geometry and
// kicks, the scoring/gravity/snake curves, and the board (collision, lock,
// line clears, the hidden-row top-out convention). Fixtures were extracted
// from the TypeScript implementation while it was canon.
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/core.json", import.meta.url), "utf8"));
test("constants", () => {
assert.deepEqual({ ...lib.boardConsts() }, v.consts);
assert.deepEqual([...lib.pieceTypes()], v.pieceTypes);
});
test("mulberry32 reproduces every TS draw bit-for-bit", () => {
for (const { seed, draws } of v.rng) {
const r = lib.mulberry32(seed);
for (let i = 0; i < draws.length; i++) {
assert.equal(r(), draws[i], `seed ${seed} draw ${i}`);
}
}
});
test("the snake schedule rides its own stream (seed ^ 0x5f356495)", () => {
for (const { seed, xor, draws } of v.rngSnakeStream) {
assert.equal(seed ^ 0x5f356495, xor);
const r = lib.mulberry32(xor);
for (let i = 0; i < draws.length; i++) assert.equal(r(), draws[i], `seed ${seed} draw ${i}`);
}
});
test("7-bag sequences and peek match TS exactly", () => {
for (const { seed, peek3, peek10, draws } of v.bag) {
const b = lib.makeBag(seed);
assert.deepEqual([...lib.bagPeek(b, 3)], peek3, `seed ${seed} peek3`);
assert.deepEqual([...lib.bagPeek(b, 10)], peek10, `seed ${seed} peek10`);
const got = [];
for (let i = 0; i < draws.length; i++) got.push(lib.bagNext(b));
assert.deepEqual(got, draws, `seed ${seed} draws`);
}
});
test("every seven consecutive draws hold all seven tetrominoes", () => {
const b = lib.makeBag(4242);
for (const expected of v.bagInvariant) {
const g = [];
for (let i = 0; i < 7; i++) g.push(lib.bagNext(b));
assert.equal([...g].sort().join(""), expected);
}
});
test("SRS shapes, incl. out-of-range rotation normalization", () => {
for (const { type, rotations } of v.shapes) {
for (const { r, cells } of rotations) {
assert.deepEqual(
[...lib.cellsFor(type, r)].map((c) => [...c]),
cells,
`${type} rot ${r}`,
);
}
}
});
test("wall kicks: every type, from-state and direction", () => {
for (const { type, from, cw, kicks } of v.kicks) {
assert.deepEqual(
[...lib.kicksFor(type, from, cw)].map((k) => [...k]),
kicks,
`${type} from ${from} cw=${cw}`,
);
}
});
test("scoring: line points (a 20-row wipe really does score 0)", () => {
for (const { lines, level, pts } of v.pointsForLines) {
assert.equal(lib.pointsForLines(lines, level), pts, `${lines} lines at level ${level}`);
}
});
test("gravity, level and snake curves", () => {
for (const { level, s } of v.gravitySeconds) assert.equal(lib.gravitySeconds(level), s, `g lv${level}`);
for (const { lines, level } of v.levelForLines) assert.equal(lib.levelForLines(lines), level, `lv ${lines}`);
for (const { level, s } of v.snakeInterval) assert.equal(lib.snakeInterval(level), s, `si lv${level}`);
for (const { level, s } of v.snakeDuration) assert.equal(lib.snakeDuration(level), s, `sd lv${level}`);
});
const gridOf = (board) => [...board.grid].map((row) => [...row].map((c) => (c === 0 ? "." : c)).join(""));
const fill = (board, rows) => {
for (const [y, cells] of rows) for (const [x, c] of cells) board.grid[y][x] = c;
};
test("board: collision probes on an empty field", () => {
const b = lib.makeBoard();
for (const c of v.board.filter((x) => x.case === "collide-empty")) {
assert.equal(
lib.collides(b, { type: c.type, rotation: c.rot, x: c.x, y: c.y }),
c.hit,
`${c.type} at ${c.x},${c.y}`,
);
}
});
test("board: lock + a two-row clear", () => {
const c = v.board.find((x) => x.case === "double-clear");
const b = lib.makeBoard();
const { COLS, TOTAL_ROWS } = lib.boardConsts();
for (let x = 0; x < COLS; x++) if (x !== 4 && x !== 5) b.grid[TOTAL_ROWS - 1][x] = "J";
for (let x = 0; x < COLS; x++) if (x !== 4 && x !== 5) b.grid[TOTAL_ROWS - 2][x] = "L";
assert.deepEqual(gridOf(b), c.before);
lib.boardLock(b, { type: "O", rotation: 0, x: 3, y: TOTAL_ROWS - 2 });
assert.deepEqual(gridOf(b), c.locked);
assert.deepEqual([...lib.clearLines(b)], c.cleared);
assert.deepEqual(gridOf(b), c.after);
});
test("board: the non-contiguous clear (ascending splice + unshift)", () => {
const c = v.board.find((x) => x.case === "noncontiguous-clear");
const b = lib.makeBoard();
const { COLS, TOTAL_ROWS } = lib.boardConsts();
for (const y of [TOTAL_ROWS - 1, TOTAL_ROWS - 3, TOTAL_ROWS - 4, TOTAL_ROWS - 7]) {
for (let x = 0; x < COLS; x++) b.grid[y][x] = "I";
}
b.grid[TOTAL_ROWS - 2][0] = "T";
b.grid[TOTAL_ROWS - 5][9] = "S";
assert.deepEqual(gridOf(b), c.before);
assert.deepEqual([...lib.clearLines(b)], c.cleared);
assert.deepEqual(gridOf(b), c.after);
});
test("board: the hidden-row top-out convention", () => {
const { HIDDEN_ROWS } = lib.boardConsts();
const hidden = v.board.find((x) => x.case === "topped-out-hidden");
const b = lib.makeBoard();
b.grid[1][0] = "N";
assert.equal(lib.isToppedOut(b), hidden.toppedOut);
assert.deepEqual(gridOf(b), hidden.grid);
const visible = v.board.find((x) => x.case === "not-topped-out-visible");
const c = lib.makeBoard();
c.grid[HIDDEN_ROWS][0] = "N";
assert.equal(lib.isToppedOut(c), visible.toppedOut);
assert.deepEqual(gridOf(c), visible.grid);
});
|