// 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; }