Files
m3te/tests/score.sx
swipelab 6f7d2f4db2 lang migration: rename signed integer types sN -> iN
Mechanical sweep of all .sx sources, plan docs, and tests/expected
snapshots for the sx language rename (s8/s16/s32/s64 -> i8/i16/i32/i64).
Verified: tools/run_tests.sh 23/23.

Note: the ios-sim build has 2 pre-existing 'restart' dot-call errors
from the sx opt-in UFCS change (sx a47ea14) — independent of this
rename (present pre-sweep); migrated in the follow-up commit.
2026-06-12 09:36:51 +03:00

145 lines
4.6 KiB
Plaintext

// Base-scoring golden (P3.1): score several HAND-CRAFTED single-round boards by
// RUN LENGTH and snapshot the runs + points. Every board sits on the run-free
// O/G checkerboard (adjacent cells always differ, so zero stray runs) with only
// the runs under test painted in, so the score is purely the painted runs'.
//
// Scheme (named constants in board.sx): run length 3 -> 30, length 4 -> 60,
// length 5+ -> 100. L/T rule: each maximal run scores INDEPENDENTLY by its own
// length, so an overlapping L/T scores horizontal + vertical (the shared corner
// counts toward both runs). The scene names encode the expected points.
//
// For each scene the board, its enumerated runs, and the round's points are
// printed, and two facts are asserted independently of the dump: `score_round`
// equals the documented value, and `add_round_score` adds exactly that into the
// board's running `score` total.
#import "modules/std.sx";
#import "board.sx";
t :: #import "test.sx";
// Inverse of `gem_char`: map a board character back to its Gem so each board can
// be written as a human-readable grid. The hole glyph maps to `.empty`.
char_to_gem :: (c: u8) -> Gem {
if c == EMPTY_CHAR { return .empty; }
for 0..GEM_COUNT (i) {
if GEM_CHARS[i] == c { return cast(Gem) i; }
}
.red
}
// Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars),
// with the running score zeroed so the accumulation check starts from a known base.
load_board :: (rows: []string) -> Board {
b : Board = ---;
for 0..BOARD_ROWS (row) {
line := rows[row];
for 0..BOARD_COLS (col) {
b.set(col, row, char_to_gem(line[col]));
}
}
b.score = 0;
b
}
// Score one scene: snapshot board + enumerated runs + points, then assert
// `score_round` is exact and `add_round_score` accumulates it into `board.score`.
scene :: (name: string, rows: []string, want_points: i64) {
b := load_board(rows);
runs := find_runs(@b);
print("== {} ==\n", name);
out(board_dump(@b));
out("--\n");
out(dump_runs(@runs));
print("points {}\n", score_round(@b));
t.expect(score_round(@b) == want_points, concat(name, ": score_round exact"));
added := add_round_score(@b);
t.expect(added == want_points and b.score == want_points,
concat(name, ": add_round_score accumulates into board.score"));
}
main :: () -> i32 {
print("== score (base match scoring) ==\n");
// Single length-3 horizontal run (row 3, cols 2-4) -> SCORE_RUN_3 = 30.
scene("len3-run-30", .[
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GORRROGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 30);
// Single length-4 horizontal run (row 3, cols 2-5) -> SCORE_RUN_4 = 60.
scene("len4-run-60", .[
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GORRRRGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 60);
// Single length-5 horizontal run (row 1, cols 1-5) -> SCORE_RUN_5_PLUS = 100.
scene("len5-run-100", .[
"OGOGOGOG",
"GRRRRRGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 100);
// Multiple disjoint runs, points = sum of per-run values: horizontal R len3
// (row 0, cols 0-2 -> 30), horizontal B len4 (row 2, cols 2-5 -> 60),
// horizontal P len5 (row 4, cols 1-5 -> 100) = 190.
scene("disjoint-30+60+100", .[
"RRRGOGOG",
"GOGOGOGO",
"GOBBBBOG",
"OGOGOGOG",
"GPPPPPGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
], 190);
// Overlapping L and T, scored per-run independently: the L is a horizontal R
// len3 (row 1, cols 1-3) meeting a vertical R len3 (col 1, rows 1-3) at corner
// (1,1) -> 30 + 30; the T is a horizontal Y len3 (row 5, cols 3-5) meeting a
// vertical Y len3 (col 4, rows 5-7) at (4,5) -> 30 + 30. Total 120 (the shared
// corners count toward both runs, unlike the unioned clear set).
scene("overlap-LT-per-run-120", .[
"OGOGOGOG",
"GRRRGOGO",
"OROGOGOG",
"GRGOGOGO",
"OGOGOGOG",
"GOGYYYGO",
"OGOGYGOG",
"GOGOYOGO",
], 120);
// No match: the bare checkerboard has no run -> 0 points.
scene("no-match-0", .[
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 0);
print("ok: base scoring over hand-crafted boards\n");
return 0;
}