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.
This commit is contained in:
102
board.sx
102
board.sx
@@ -39,36 +39,36 @@ EMPTY_CHAR :: 46; // '.'
|
||||
|
||||
gem_char :: (g: Gem) -> u8 {
|
||||
if g == .empty { return EMPTY_CHAR; }
|
||||
GEM_CHARS[cast(s64) g]
|
||||
GEM_CHARS[cast(i64) g]
|
||||
}
|
||||
|
||||
// ── Deterministic RNG ─────────────────────────────────────────────────────
|
||||
// A 32-bit linear congruential generator (Numerical Recipes constants),
|
||||
// carried in an s64 and masked back to 32 bits after every step so the
|
||||
// carried in an i64 and masked back to 32 bits after every step so the
|
||||
// stream is identical regardless of host integer width. The state*MUL+ADD
|
||||
// product stays well under s64 range, so no intermediate overflow. Any seed
|
||||
// product stays well under i64 range, so no intermediate overflow. Any seed
|
||||
// (including 0) yields a valid stream — an LCG has no forbidden state.
|
||||
RNG_MASK32 :: 0xFFFFFFFF;
|
||||
RNG_MUL :: 1664525;
|
||||
RNG_ADD :: 1013904223;
|
||||
|
||||
Rng :: struct {
|
||||
state: s64;
|
||||
state: i64;
|
||||
|
||||
// Advance and return the next 32-bit value.
|
||||
next_u32 :: (self: *Rng) -> s64 {
|
||||
next_u32 :: (self: *Rng) -> i64 {
|
||||
self.state = (self.state * RNG_MUL + RNG_ADD) & RNG_MASK32;
|
||||
self.state
|
||||
}
|
||||
|
||||
// Uniform-ish value in [0, n). Uses the high bits, whose period is far
|
||||
// longer than the low bits of an LCG.
|
||||
next_range :: (self: *Rng, n: s64) -> s64 {
|
||||
next_range :: (self: *Rng, n: i64) -> i64 {
|
||||
(self.next_u32() >> 16) % n
|
||||
}
|
||||
}
|
||||
|
||||
rng_seeded :: (seed: s64) -> Rng {
|
||||
rng_seeded :: (seed: i64) -> Rng {
|
||||
Rng.{ state = seed & RNG_MASK32 }
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ Board :: struct {
|
||||
// round's base points (see `score_round`), and `resolve` adds each cascade
|
||||
// round's base scaled by `combo_multiplier` (P3.2). The HUD (P4.4) reads this
|
||||
// field. A hand-built board must zero this before accumulating.
|
||||
score: s64;
|
||||
score: i64;
|
||||
|
||||
// Turn accounting (P3.3). `moves_made` counts the swaps actually COMMITTED —
|
||||
// only a legal swap (one that resolved into >=1 match) via `commit_swap`
|
||||
@@ -115,15 +115,15 @@ Board :: struct {
|
||||
// is derived from the two, so there is a single source of truth and the
|
||||
// counters can never drift apart. A hand-built board must set both before
|
||||
// committing swaps.
|
||||
moves_made: s64;
|
||||
move_limit: s64;
|
||||
moves_made: i64;
|
||||
move_limit: i64;
|
||||
|
||||
// Per-level score goal (P7.1). `init` sets it to DEFAULT_TARGET_SCORE;
|
||||
// `level_status` reads it to decide a win (`score >= target_score`). A
|
||||
// hand-built board must set this before its status is read.
|
||||
target_score: s64;
|
||||
target_score: i64;
|
||||
|
||||
idx :: (col: s64, row: s64) -> s64 {
|
||||
idx :: (col: i64, row: i64) -> i64 {
|
||||
row * BOARD_COLS + col
|
||||
}
|
||||
|
||||
@@ -131,15 +131,15 @@ Board :: struct {
|
||||
// to 0 when the budget is spent (and below it only if a caller keeps
|
||||
// committing past the budget — see DEFAULT_MOVE_LIMIT). The turn/goal loop
|
||||
// (P7) reads this to decide when the game ends.
|
||||
moves_remaining :: (self: *Board) -> s64 {
|
||||
moves_remaining :: (self: *Board) -> i64 {
|
||||
self.move_limit - self.moves_made
|
||||
}
|
||||
|
||||
at :: (self: *Board, col: s64, row: s64) -> Gem {
|
||||
at :: (self: *Board, col: i64, row: i64) -> Gem {
|
||||
self.cells[Board.idx(col, row)]
|
||||
}
|
||||
|
||||
set :: (self: *Board, col: s64, row: s64, g: Gem) {
|
||||
set :: (self: *Board, col: i64, row: i64, g: Gem) {
|
||||
self.cells[Board.idx(col, row)] = g;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ Board :: struct {
|
||||
// already-placed cells to its left or above is excluded, and the gem is
|
||||
// drawn from the remaining allowed types. At most two types are ever
|
||||
// excluded, so a choice always remains.
|
||||
init :: (self: *Board, seed: s64) {
|
||||
init :: (self: *Board, seed: i64) {
|
||||
self.rng = rng_seeded(seed);
|
||||
self.score = 0;
|
||||
self.moves_made = 0;
|
||||
@@ -165,7 +165,7 @@ Board :: struct {
|
||||
|
||||
// Choose a gem for (col, row) that can't extend an existing run leftward or
|
||||
// upward. Pure given the board's already-placed prefix and the RNG state.
|
||||
pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem {
|
||||
pick_gem :: (board: *Board, rng: *Rng, col: i64, row: i64) -> Gem {
|
||||
forbidden : [GEM_COUNT]bool = ---;
|
||||
for 0..GEM_COUNT (t) { forbidden[t] = false; }
|
||||
|
||||
@@ -173,14 +173,14 @@ pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem {
|
||||
if col >= 2 {
|
||||
left := board.at(col - 1, row);
|
||||
if left == board.at(col - 2, row) {
|
||||
forbidden[cast(s64) left] = true;
|
||||
forbidden[cast(i64) left] = true;
|
||||
}
|
||||
}
|
||||
// Two same gems immediately above → a third of that type matches.
|
||||
if row >= 2 {
|
||||
up := board.at(col, row - 1);
|
||||
if up == board.at(col, row - 2) {
|
||||
forbidden[cast(s64) up] = true;
|
||||
forbidden[cast(i64) up] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,12 +223,12 @@ board_dump :: (self: *Board) -> string {
|
||||
MatchMask :: struct {
|
||||
cells: [BOARD_CELLS]bool;
|
||||
|
||||
at :: (self: *MatchMask, col: s64, row: s64) -> bool {
|
||||
at :: (self: *MatchMask, col: i64, row: i64) -> bool {
|
||||
self.cells[Board.idx(col, row)]
|
||||
}
|
||||
|
||||
count :: (self: *MatchMask) -> s64 {
|
||||
n : s64 = 0;
|
||||
count :: (self: *MatchMask) -> i64 {
|
||||
n : i64 = 0;
|
||||
for 0..BOARD_CELLS (i) { if self.cells[i] { n += 1; } }
|
||||
n
|
||||
}
|
||||
@@ -237,7 +237,7 @@ MatchMask :: struct {
|
||||
// Mark a closed span of cells along one axis. `vertical` picks the axis; `fixed`
|
||||
// is the constant coordinate (the row for a horizontal span, the column for a
|
||||
// vertical one) and the span covers `start..end` of the moving coordinate.
|
||||
mark_run :: (m: *MatchMask, vertical: bool, fixed: s64, start: s64, end: s64) {
|
||||
mark_run :: (m: *MatchMask, vertical: bool, fixed: i64, start: i64, end: i64) {
|
||||
for start..end (i) {
|
||||
if vertical {
|
||||
m.cells[Board.idx(fixed, i)] = true;
|
||||
@@ -317,8 +317,8 @@ dump_matches :: (b: *Board, m: *MatchMask) -> string {
|
||||
// A board cell address. Kept separate from the row-major index so swap callers
|
||||
// and the move enumeration speak in (col, row) like the rest of the model.
|
||||
Cell :: struct {
|
||||
col: s64;
|
||||
row: s64;
|
||||
col: i64;
|
||||
row: i64;
|
||||
}
|
||||
|
||||
// Exchange the gems of two cells, in place. `swap` is its own inverse: calling
|
||||
@@ -413,8 +413,8 @@ dump_swaps :: (swaps: *List(Swap)) -> string {
|
||||
// unchanged. Returns the number of cells cleared. `mask` is the matched-cell SET
|
||||
// from find_matches, so overlapping L/T shapes (already unioned into a single
|
||||
// `true` per shared cell) clear as one set with no double-counting.
|
||||
clear_cells :: (board: *Board, mask: *MatchMask) -> s64 {
|
||||
cleared : s64 = 0;
|
||||
clear_cells :: (board: *Board, mask: *MatchMask) -> i64 {
|
||||
cleared : i64 = 0;
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if mask.cells[i] {
|
||||
board.cells[i] = .empty;
|
||||
@@ -428,7 +428,7 @@ clear_cells :: (board: *Board, mask: *MatchMask) -> s64 {
|
||||
// cells cleared — 0 when there are no matches, in which case the board is left
|
||||
// unchanged. The count drives later cascade/scoring (P2.2+): a non-zero result
|
||||
// means the board changed and the resolution loop should continue.
|
||||
clear_matches :: (board: *Board) -> s64 {
|
||||
clear_matches :: (board: *Board) -> i64 {
|
||||
m := find_matches(board);
|
||||
clear_cells(board, @m)
|
||||
}
|
||||
@@ -489,9 +489,9 @@ collapse :: (board: *Board) -> bool {
|
||||
// only ever yields ordinals 0..GEM_COUNT, so a hole is never refilled with
|
||||
// `.empty`; afterwards the board has no holes left. Returns the number of cells
|
||||
// filled (0 on a board that had none).
|
||||
refill :: (board: *Board) -> s64 {
|
||||
refill :: (board: *Board) -> i64 {
|
||||
rng := @board.rng;
|
||||
filled : s64 = 0;
|
||||
filled : i64 = 0;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
if board.at(col, row) == .empty {
|
||||
@@ -526,11 +526,11 @@ refill :: (board: *Board) -> s64 {
|
||||
// make "did this settle clear a 4 / 5+ run" observable. `had_len4` /
|
||||
// `had_len5_plus` are the boolean view of the same counts.
|
||||
Cascade :: struct {
|
||||
depth: s64;
|
||||
cleared: List(s64);
|
||||
awarded: s64;
|
||||
len4: s64;
|
||||
len5_plus: s64;
|
||||
depth: i64;
|
||||
cleared: List(i64);
|
||||
awarded: i64;
|
||||
len4: i64;
|
||||
len5_plus: i64;
|
||||
|
||||
had_len4 :: (self: *Cascade) -> bool {
|
||||
self.len4 > 0
|
||||
@@ -546,7 +546,7 @@ Cascade :: struct {
|
||||
// number of cells cleared this round — 0 iff the board was already stable, in
|
||||
// which case nothing moves and no gem is drawn. `resolve` repeats this until it
|
||||
// returns 0.
|
||||
resolve_step :: (board: *Board) -> s64 {
|
||||
resolve_step :: (board: *Board) -> i64 {
|
||||
cleared := clear_matches(board);
|
||||
if cleared == 0 { return 0; }
|
||||
collapse(board);
|
||||
@@ -560,7 +560,7 @@ resolve_step :: (board: *Board) -> s64 {
|
||||
// Each round adds `score_round * combo_multiplier(round)` (round 1-based) to
|
||||
// `Board.score`; an already-stable board returns depth 0, awards 0, untouched.
|
||||
resolve :: (board: *Board) -> Cascade {
|
||||
result := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0, len4 = 0, len5_plus = 0 };
|
||||
result := Cascade.{ depth = 0, cleared = List(i64).{}, awarded = 0, len4 = 0, len5_plus = 0 };
|
||||
while true {
|
||||
// Read the round's base points AND its special-match tally while the runs
|
||||
// are still on the board: `resolve_step` clears them, so both have to be
|
||||
@@ -604,15 +604,15 @@ SCORE_RUN_5_PLUS :: 100;
|
||||
// vertical one) and the run covers `start..start+len` of the moving coordinate.
|
||||
Run :: struct {
|
||||
vertical: bool;
|
||||
fixed: s64;
|
||||
start: s64;
|
||||
len: s64;
|
||||
fixed: i64;
|
||||
start: i64;
|
||||
len: i64;
|
||||
}
|
||||
|
||||
// Base points for a single maximal run, by length. Runs are always length >= 3
|
||||
// (shorter spans are not enumerated), so 3 is the floor; 5 and longer all score
|
||||
// the top tier.
|
||||
run_score :: (len: s64) -> s64 {
|
||||
run_score :: (len: i64) -> i64 {
|
||||
if len <= 3 { return SCORE_RUN_3; }
|
||||
if len == 4 { return SCORE_RUN_4; }
|
||||
SCORE_RUN_5_PLUS
|
||||
@@ -666,9 +666,9 @@ find_runs :: (b: *Board) -> List(Run) {
|
||||
// read-only — it inspects the board but changes nothing, so it must be called
|
||||
// BEFORE the round's clear, while the runs are still on the board. A board with
|
||||
// no run scores 0.
|
||||
score_round :: (board: *Board) -> s64 {
|
||||
score_round :: (board: *Board) -> i64 {
|
||||
runs := find_runs(board);
|
||||
total : s64 = 0;
|
||||
total : i64 = 0;
|
||||
for 0..runs.len (i) {
|
||||
total += run_score(runs.items[i].len);
|
||||
}
|
||||
@@ -679,7 +679,7 @@ score_round :: (board: *Board) -> s64 {
|
||||
// `score` total and return them. The single-round accumulation primitive; the
|
||||
// cascade loop (`resolve`) instead scales each round by `combo_multiplier`
|
||||
// (P3.2). Neither path changes `score_round`.
|
||||
add_round_score :: (board: *Board) -> s64 {
|
||||
add_round_score :: (board: *Board) -> i64 {
|
||||
points := score_round(board);
|
||||
board.score += points;
|
||||
points
|
||||
@@ -694,7 +694,7 @@ add_round_score :: (board: *Board) -> s64 {
|
||||
// multi-round chain strictly beats the same clears scored flat. `resolve`
|
||||
// accumulates `score_round * combo_multiplier(round)` per round into `Board.score`
|
||||
// and reports the sum as `Cascade.awarded`.
|
||||
combo_multiplier :: (round: s64) -> s64 {
|
||||
combo_multiplier :: (round: i64) -> i64 {
|
||||
round
|
||||
}
|
||||
|
||||
@@ -711,8 +711,8 @@ combo_multiplier :: (round: s64) -> s64 {
|
||||
// "did any occur" lives on `Cascade` (`had_len4` / `had_len5_plus`) for the
|
||||
// whole settle; a single round reads these counts directly.
|
||||
SpecialCounts :: struct {
|
||||
len4: s64;
|
||||
len5_plus: s64;
|
||||
len4: i64;
|
||||
len5_plus: i64;
|
||||
}
|
||||
|
||||
// Count the board's currently-matched runs that hit a special length, by the
|
||||
@@ -766,7 +766,7 @@ dump_runs :: (runs: *List(Run)) -> string {
|
||||
PlayerMove :: struct {
|
||||
legal: bool;
|
||||
cascade: Cascade;
|
||||
moves_remaining: s64;
|
||||
moves_remaining: i64;
|
||||
}
|
||||
|
||||
// Attempt the player's intended swap of two adjacent cells. If the swap is legal
|
||||
@@ -779,7 +779,7 @@ PlayerMove :: struct {
|
||||
// spent (that is the P7 turn-loop's call) — see DEFAULT_MOVE_LIMIT.
|
||||
commit_swap :: (board: *Board, a: Cell, b: Cell) -> PlayerMove {
|
||||
if !swap_legal(board, a, b) {
|
||||
empty := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0, len4 = 0, len5_plus = 0 };
|
||||
empty := Cascade.{ depth = 0, cleared = List(i64).{}, awarded = 0, len4 = 0, len5_plus = 0 };
|
||||
return PlayerMove.{ legal = false, cascade = empty, moves_remaining = board.moves_remaining() };
|
||||
}
|
||||
swap(board, a, b);
|
||||
@@ -900,7 +900,7 @@ reshuffle_if_deadlocked :: (board: *Board) -> bool {
|
||||
// seed → identical starting layout), zeroes `score` and `moves_made`, and
|
||||
// restores the default move budget and score goal, so `level_status` reads
|
||||
// `in_progress` again. The entry point P7.2's restart button calls.
|
||||
restart :: (board: *Board, seed: s64) {
|
||||
restart :: (board: *Board, seed: i64) {
|
||||
board.init(seed);
|
||||
}
|
||||
|
||||
@@ -930,7 +930,7 @@ TurnResult :: struct {
|
||||
play_turn :: (board: *Board, a: Cell, b: Cell) -> TurnResult {
|
||||
status := level_status(board);
|
||||
if status != .in_progress {
|
||||
empty := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0, len4 = 0, len5_plus = 0 };
|
||||
empty := Cascade.{ depth = 0, cleared = List(i64).{}, awarded = 0, len4 = 0, len5_plus = 0 };
|
||||
frozen := PlayerMove.{ legal = false, cascade = empty, moves_remaining = board.moves_remaining() };
|
||||
return TurnResult.{ accepted = false, move = frozen, status = status, reshuffled = false };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user