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:
29
.vscode/ios-sim-debug.sh
vendored
Executable file
29
.vscode/ios-sim-debug.sh
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# preLaunchTask for "Debug SxChess (iOS sim)": build SxChess for the iOS
|
||||
# simulator with debug info, install it to a single simulator, and launch it
|
||||
# PAUSED for a debugger (--wait-for-debugger). The launch.json `custom` config
|
||||
# then `target create`s the local binary (so the .dSYM resolves breakpoints)
|
||||
# and `process attach -n SxChess`es the waiting process.
|
||||
set -e
|
||||
|
||||
SX="/Users/agra/projects/sx/zig-out/bin/sx"
|
||||
DSYMUTIL="/opt/homebrew/opt/llvm@19/bin/dsymutil"
|
||||
# Reuse ONE simulator (sx-test-ios18). Swap the UDID for a different device.
|
||||
SIM="E8DF755C-997D-49D7-9DB0-CFA48F8254CD"
|
||||
APP="sx-out/ios/M3te.app"
|
||||
BUNDLE="co.swipelab.m3te"
|
||||
|
||||
# Debug build: -O0 + DWARF (--emit-obj keeps the object), then a portable .dSYM.
|
||||
"$SX" build --target ios-sim --emit-obj main.sx
|
||||
"$DSYMUTIL" "$APP/M3te"
|
||||
|
||||
# Boot the sim (idempotent) and surface the Simulator UI.
|
||||
xcrun simctl boot "$SIM" 2>/dev/null || true
|
||||
xcrun simctl bootstatus "$SIM" >/dev/null 2>&1 || true
|
||||
open -a Simulator
|
||||
|
||||
xcrun simctl install "$SIM" "$APP"
|
||||
xcrun simctl terminate "$SIM" "$BUNDLE" >/dev/null 2>&1 || true
|
||||
|
||||
# Launch paused; the app waits at its entry until lldb attaches and continues.
|
||||
xcrun simctl launch --wait-for-debugger "$SIM" "$BUNDLE"
|
||||
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug M3te (macOS)",
|
||||
"program": "${workspaceFolder}/sx-out/macos/M3te.app/Contents/MacOS/M3te",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "sx build (macos debug)"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "custom",
|
||||
"name": "Debug M3te (iOS sim)",
|
||||
"preLaunchTask": "sx build+launch (ios-sim debug)",
|
||||
"targetCreateCommands": [
|
||||
"target create '${workspaceFolder}/sx-out/ios/M3te.app/M3te'"
|
||||
],
|
||||
"processCreateCommands": [
|
||||
"process attach -n M3te",
|
||||
"process handle SIGSTOP --notify false --stop false --pass false",
|
||||
"process continue"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.vscode/tasks.json
vendored
Normal file
23
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "sx build (macos debug)",
|
||||
"detail": "Build M3te for macOS with DWARF (-O0) and collect a .dSYM for lldb.",
|
||||
"type": "shell",
|
||||
"command": "/Users/agra/projects/sx/zig-out/bin/sx build --target macos --emit-obj main.sx && /opt/homebrew/opt/llvm@19/bin/dsymutil sx-out/macos/M3te.app/Contents/MacOS/M3te",
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "sx build+launch (ios-sim debug)",
|
||||
"detail": "Build for the iOS simulator (-O0 + .dSYM), install, and launch paused for the debugger.",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/.vscode/ios-sim-debug.sh",
|
||||
"options": { "cwd": "${workspaceFolder}" },
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
14
audio.sx
14
audio.sx
@@ -15,12 +15,12 @@
|
||||
|
||||
// AudioToolbox — System Sound Services. SystemSoundID is a UInt32; OSStatus a
|
||||
// SInt32 (0 == noErr); the clip's file is passed as a CFURLRef (opaque ptr).
|
||||
AudioServicesCreateSystemSoundID :: (url: *void, out_id: *u32) -> s32 #foreign;
|
||||
AudioServicesCreateSystemSoundID :: (url: *void, out_id: *u32) -> i32 #foreign;
|
||||
AudioServicesPlaySystemSound :: (sound_id: u32) #foreign;
|
||||
|
||||
// CoreFoundation — build a file CFURL from an absolute path. `len` is a CFIndex
|
||||
// (long); `is_dir` a Boolean (unsigned char); a NULL allocator = default.
|
||||
CFURLCreateFromFileSystemRepresentation :: (allocator: *void, buffer: *u8, len: s64, is_dir: s8) -> *void #foreign;
|
||||
CFURLCreateFromFileSystemRepresentation :: (allocator: *void, buffer: *u8, len: i64, is_dir: i8) -> *void #foreign;
|
||||
CFRelease :: (cf: *void) #foreign;
|
||||
|
||||
// libc — getcwd to absolutize the bundle-relative asset path. The platform
|
||||
@@ -80,7 +80,7 @@ GameAudio :: struct {
|
||||
|
||||
// Pick the ascending cascade clip by clamping the cascade depth into the
|
||||
// combo1..combo5 range (see `cascade_cue_index`).
|
||||
play_cascade :: (self: *GameAudio, depth: s64) {
|
||||
play_cascade :: (self: *GameAudio, depth: i64) {
|
||||
inline if OS != .ios { return; }
|
||||
if !self.loaded { return; }
|
||||
idx := cascade_cue_index(depth);
|
||||
@@ -107,7 +107,7 @@ GameAudio :: struct {
|
||||
// `log show` shows the clip stepping up with cascade depth. Literals only — the
|
||||
// string→NSString bridge needs NUL-terminated bytes (a formatted string may not
|
||||
// be). `idx` is a clamped `cascade_cue_index`, so it is always 0..COMBO_CLIPS-1.
|
||||
cascade_cue_name :: (idx: s64) -> string {
|
||||
cascade_cue_name :: (idx: i64) -> string {
|
||||
if idx <= 0 { return "[sx] audio: cue combo1"; }
|
||||
if idx == 1 { return "[sx] audio: cue combo2"; }
|
||||
if idx == 2 { return "[sx] audio: cue combo3"; }
|
||||
@@ -118,7 +118,7 @@ cascade_cue_name :: (idx: s64) -> string {
|
||||
// Cascade depth (number of cleared rounds) → combo clip index 0..COMBO_CLIPS-1
|
||||
// (combo1..combo5). Clamps: depth <= 1 → 0, depth >= 5 → 4. Pure arithmetic and
|
||||
// OS-agnostic so it can be snapshot-tested headlessly (P10.4).
|
||||
cascade_cue_index :: (depth: s64) -> s64 {
|
||||
cascade_cue_index :: (depth: i64) -> i64 {
|
||||
if depth <= 1 { return 0; }
|
||||
if depth >= COMBO_CLIPS { return COMBO_CLIPS - 1; }
|
||||
depth - 1
|
||||
@@ -145,7 +145,7 @@ load_system_sound :: (name: string) -> u32 {
|
||||
if getcwd(@cwd_buf[0], 1024) == null { return 0; }
|
||||
cwd : string = ---;
|
||||
cwd.ptr = @cwd_buf[0];
|
||||
cwd.len = cast(s64) c_strlen(@cwd_buf[0]);
|
||||
cwd.len = cast(i64) c_strlen(@cwd_buf[0]);
|
||||
|
||||
// CFURLCreateFromFileSystemRepresentation takes an explicit byte length, so
|
||||
// the formatted path needs no NUL terminator.
|
||||
@@ -169,6 +169,6 @@ g_audio : *GameAudio = null;
|
||||
|
||||
sfx_swap :: () { if g_audio != null { g_audio.play_swap(); } }
|
||||
sfx_match :: () { if g_audio != null { g_audio.play_match(); } }
|
||||
sfx_cascade :: (depth: s64) { if g_audio != null { g_audio.play_cascade(depth); } }
|
||||
sfx_cascade :: (depth: i64) { if g_audio != null { g_audio.play_cascade(depth); } }
|
||||
sfx_win :: () { if g_audio != null { g_audio.play_win(); } }
|
||||
sfx_lose :: () { if g_audio != null { g_audio.play_lose(); } }
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ bad_swap_bounce :: (t: f32) -> f32 {
|
||||
// ease_in_cubic so each column still accelerates under gravity within its window.
|
||||
// `tests/easing.sx` pins f(0)=0, f(1)=1, monotonicity, and the cascade ordering.
|
||||
FALL_STAGGER_MAX :f32: 0.30;
|
||||
fall_stagger_t :: (t: f32, col: s64) -> f32 {
|
||||
fall_stagger_t :: (t: f32, col: i64) -> f32 {
|
||||
delay := FALL_STAGGER_MAX * (cast(f32) col / cast(f32) (BOARD_COLS - 1));
|
||||
window := 1.0 - FALL_STAGGER_MAX;
|
||||
lt := (t - delay) / window;
|
||||
@@ -123,7 +123,7 @@ fall_stagger_t :: (t: f32, col: s64) -> f32 {
|
||||
// at `1 - FALL_STAGGER_MAX`; the last column lands exactly at 1.0. The landing
|
||||
// squash-bounce (P17.3) ages from this instant per column, so the squash begins
|
||||
// the moment a gem touches its cell rather than at a flat whole-row settle.
|
||||
fall_landing_frac :: (col: s64) -> f32 {
|
||||
fall_landing_frac :: (col: i64) -> f32 {
|
||||
(1.0 - FALL_STAGGER_MAX) + FALL_STAGGER_MAX * (cast(f32) col / cast(f32) (BOARD_COLS - 1))
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ fall_landing_frac :: (col: s64) -> f32 {
|
||||
// per-round bounce ages from. Round k's fall starts after the swap, k clear+fall
|
||||
// pairs, and that round's own clear; column `col` then lands `fall_landing_frac`
|
||||
// of the fall window into it. Pure + headless, mirrors `phase`'s segment walk.
|
||||
round_land_time :: (k: s64, col: s64) -> f32 {
|
||||
round_land_time :: (k: i64, col: i64) -> f32 {
|
||||
SWAP_ANIM_DUR + cast(f32) k * (CLEAR_ANIM_DUR + FALL_ANIM_DUR) + CLEAR_ANIM_DUR
|
||||
+ fall_landing_frac(col) * FALL_ANIM_DUR
|
||||
}
|
||||
@@ -159,10 +159,10 @@ clear_ripple_t :: (t: f32, u: f32) -> f32 {
|
||||
|
||||
// The diagonal (col+row) extent of a round's matched cells — the span the ripple
|
||||
// ranks each matched gem across. `hi < lo` only if the mask is empty.
|
||||
ClearDiag :: struct { lo: s64; hi: s64; }
|
||||
ClearDiag :: struct { lo: i64; hi: i64; }
|
||||
clear_diag_span :: (m: *MatchMask) -> ClearDiag {
|
||||
lo : s64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1;
|
||||
hi : s64 = -1;
|
||||
lo : i64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1;
|
||||
hi : i64 = -1;
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if m.cells[i] {
|
||||
d := (i % BOARD_COLS) + (i / BOARD_COLS);
|
||||
@@ -178,7 +178,7 @@ clear_diag_span :: (m: *MatchMask) -> ClearDiag {
|
||||
// PER ROUND (not across the board) lets even a small 3-match ripple across the
|
||||
// full stagger budget. A degenerate span (every matched cell on one diagonal)
|
||||
// ranks all gems 0, so they pop together rather than dividing by zero.
|
||||
clear_rank :: (span: ClearDiag, col: s64, row: s64) -> f32 {
|
||||
clear_rank :: (span: ClearDiag, col: i64, row: i64) -> f32 {
|
||||
if span.hi <= span.lo { return 0.0; }
|
||||
cast(f32) ((col + row) - span.lo) / cast(f32) (span.hi - span.lo)
|
||||
}
|
||||
@@ -193,7 +193,7 @@ clear_rank :: (span: ClearDiag, col: s64, row: s64) -> f32 {
|
||||
AnimRound :: struct {
|
||||
before: [BOARD_CELLS]Gem;
|
||||
matched: MatchMask;
|
||||
src: [BOARD_CELLS]s64;
|
||||
src: [BOARD_CELLS]i64;
|
||||
after: [BOARD_CELLS]Gem;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ AnimMove :: struct {
|
||||
pre: [BOARD_CELLS]Gem;
|
||||
rounds: List(AnimRound);
|
||||
final: [BOARD_CELLS]Gem;
|
||||
awarded: s64;
|
||||
awarded: i64;
|
||||
}
|
||||
|
||||
// The most recent round at or before `kmax` that dropped a MOVED gem onto
|
||||
@@ -221,7 +221,7 @@ AnimMove :: struct {
|
||||
// ages from its LATEST arrival, never a stale earlier one. Pure + headless: the
|
||||
// per-round bounce (render_fall/clear) and the final-settle stamp share this so
|
||||
// one envelope plays continuously across every seam.
|
||||
delivering_round :: (mv: *AnimMove, i: s64, kmax: s64) -> s64 {
|
||||
delivering_round :: (mv: *AnimMove, i: i64, kmax: i64) -> i64 {
|
||||
row := i / BOARD_COLS;
|
||||
k := kmax;
|
||||
while k >= 0 {
|
||||
@@ -312,7 +312,7 @@ AnimPhaseKind :: enum { swap; clear; fall; done; }
|
||||
|
||||
AnimPhase :: struct {
|
||||
kind: AnimPhaseKind;
|
||||
round: s64;
|
||||
round: i64;
|
||||
t: f32;
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ BoardAnim :: struct {
|
||||
// so the frame loop's per-round SFX is edge-triggered: a round's cue fires once,
|
||||
// when its clear begins, never re-fired every frame. Reset whenever a move
|
||||
// (re)starts; advanced by the frame loop as rounds clear.
|
||||
cascade_fired: s64;
|
||||
cascade_fired: i64;
|
||||
|
||||
init :: (self: *BoardAnim) {
|
||||
self.active = false;
|
||||
@@ -383,11 +383,11 @@ BoardAnim :: struct {
|
||||
// have fired by now (clamped to the move's round count). The frame loop diffs it
|
||||
// against `BoardAnim.cascade_fired` to play one cue per newly-cleared round. Pure +
|
||||
// headless so the per-round playback is snapshot-testable without audio.
|
||||
cascade_rounds_started :: (elapsed: f32, num_rounds: s64) -> s64 {
|
||||
cascade_rounds_started :: (elapsed: f32, num_rounds: i64) -> i64 {
|
||||
if num_rounds <= 0 { return 0; }
|
||||
if elapsed < SWAP_ANIM_DUR { return 0; }
|
||||
seg := CLEAR_ANIM_DUR + FALL_ANIM_DUR;
|
||||
started := cast(s64) ((elapsed - SWAP_ANIM_DUR) / seg) + 1;
|
||||
started := cast(i64) ((elapsed - SWAP_ANIM_DUR) / seg) + 1;
|
||||
if started > num_rounds { return num_rounds; }
|
||||
started
|
||||
}
|
||||
|
||||
40
board_fx.sx
40
board_fx.sx
@@ -48,7 +48,7 @@ FX_COMBO_LABEL_GAP :f32: 0.12; // gap (cell units) between the label and +poin
|
||||
// order (red, orange, yellow, green, blue, purple). Saturated a touch past the
|
||||
// pastel — the low channel is trimmed while the dominant/mid channel is lifted —
|
||||
// so every burst pops as a punchier colour without losing luminance.
|
||||
fx_tint :: (i: s64) -> Color {
|
||||
fx_tint :: (i: i64) -> Color {
|
||||
if i == 0 { return Color.{ r = 255, g = 92, b = 62, a = 255 }; }
|
||||
if i == 1 { return Color.{ r = 255, g = 164, b = 44, a = 255 }; }
|
||||
if i == 2 { return Color.{ r = 255, g = 240, b = 72, a = 255 }; }
|
||||
@@ -67,7 +67,7 @@ FX_POPUP_COMBO_HOT :: Color.{ r = 255, g = 248, b = 214, a = 255 }; // hot n
|
||||
// lockstep with the cascade SFX cue. Pure arithmetic, OS-agnostic, and the
|
||||
// equivalence to `cascade_cue_index` is locked headlessly (tests/fx_combo.sx).
|
||||
FX_COMBO_MAX_LEVEL :: 4; // == audio.sx COMBO_CLIPS - 1
|
||||
fx_combo_level :: (depth: s64) -> s64 {
|
||||
fx_combo_level :: (depth: i64) -> i64 {
|
||||
if depth <= 1 { return 0; }
|
||||
if depth >= FX_COMBO_MAX_LEVEL + 1 { return FX_COMBO_MAX_LEVEL; }
|
||||
depth - 1
|
||||
@@ -76,14 +76,14 @@ fx_combo_level :: (depth: s64) -> s64 {
|
||||
// Popup font size for a cascade `depth` rounds deep: a single clear (depth <= 1)
|
||||
// uses the plain size; a combo starts at the base combo size and grows one step
|
||||
// per combo level past the first, clamped at the deepest level.
|
||||
fx_popup_font :: (depth: s64) -> f32 {
|
||||
fx_popup_font :: (depth: i64) -> f32 {
|
||||
if depth <= 1 { return FX_POPUP_FONT; }
|
||||
FX_POPUP_COMBO_FONT + FX_POPUP_COMBO_STEP * cast(f32) (fx_combo_level(depth) - 1)
|
||||
}
|
||||
|
||||
// Popup colour for a cascade `depth` rounds deep: white for a single clear, else
|
||||
// the gold lerped toward a hot near-white as the cascade deepens.
|
||||
fx_popup_color :: (depth: s64) -> Color {
|
||||
fx_popup_color :: (depth: i64) -> Color {
|
||||
if depth <= 1 { return FX_POPUP_COLOR; }
|
||||
t := cast(f32) (fx_combo_level(depth) - 1) / cast(f32) (FX_COMBO_MAX_LEVEL - 1);
|
||||
Color.{
|
||||
@@ -101,7 +101,7 @@ fx_lerp_u8 :: (lo: u8, hi: u8, t: f32) -> u8 {
|
||||
// Upload an RGBA buffer as a texture, returning its handle. Mirrors
|
||||
// board_view.load_texture's upload half but takes an in-memory buffer (the
|
||||
// per-colour tinted particle) instead of a file path.
|
||||
upload_rgba :: (pixels: [*]u8, w: s32, h: s32, gpu: ?GPU) -> u32 {
|
||||
upload_rgba :: (pixels: [*]u8, w: i32, h: i32, gpu: ?GPU) -> u32 {
|
||||
if gpu != null {
|
||||
return xx gpu.create_texture(w, h, .rgba8, xx pixels);
|
||||
}
|
||||
@@ -128,22 +128,22 @@ BoardFxAssets :: struct {
|
||||
}
|
||||
|
||||
load :: (self: *BoardFxAssets, gpu: ?GPU) {
|
||||
w : s32 = 0;
|
||||
h : s32 = 0;
|
||||
ch : s32 = 0;
|
||||
w : i32 = 0;
|
||||
h : i32 = 0;
|
||||
ch : i32 = 0;
|
||||
src : [*]u8 = xx stbi_load("assets/fx/particle.png", @w, @h, @ch, 4);
|
||||
if xx src == 0 {
|
||||
out("WARNING: could not load assets/fx/particle.png\n");
|
||||
self.loaded = false;
|
||||
return;
|
||||
}
|
||||
n := cast(s64) w * cast(s64) h;
|
||||
n := cast(i64) w * cast(i64) h;
|
||||
buf : [*]u8 = xx context.allocator.alloc_bytes(n * 4);
|
||||
// Loop locals are hoisted: a block-scoped local declared inside a body
|
||||
// that runs hundreds of thousands of times grows the stack per iteration
|
||||
// (sx codegen), so the per-pixel tint loop only ASSIGNS pre-declared vars.
|
||||
i : s64 = 0;
|
||||
o : s64 = 0;
|
||||
i : i64 = 0;
|
||||
o : i64 = 0;
|
||||
for 0..GEM_COUNT (t) {
|
||||
col := fx_tint(t);
|
||||
i = 0;
|
||||
@@ -168,7 +168,7 @@ BoardFxAssets :: struct {
|
||||
FxParticle :: struct {
|
||||
col: f32;
|
||||
row: f32;
|
||||
tint: s64;
|
||||
tint: i64;
|
||||
delay: f32;
|
||||
age: f32;
|
||||
life: f32;
|
||||
@@ -184,8 +184,8 @@ FxParticle :: struct {
|
||||
FxPopup :: struct {
|
||||
col: f32;
|
||||
row: f32;
|
||||
points: s64;
|
||||
depth: s64;
|
||||
points: i64;
|
||||
depth: i64;
|
||||
delay: f32;
|
||||
age: f32;
|
||||
life: f32;
|
||||
@@ -237,7 +237,7 @@ BoardFx :: struct {
|
||||
self.particles.append(FxParticle.{
|
||||
col = cast(f32) col + 0.5,
|
||||
row = cast(f32) row + 0.5,
|
||||
tint = cast(s64) g,
|
||||
tint = cast(i64) g,
|
||||
delay = t0 + rdelay,
|
||||
age = 0.0,
|
||||
life = FX_BURST_LIFE,
|
||||
@@ -250,9 +250,9 @@ BoardFx :: struct {
|
||||
|
||||
// One popup for the whole move at the first clear's centroid.
|
||||
rd0 := @mv.rounds.items[0];
|
||||
sc : s64 = 0;
|
||||
sr : s64 = 0;
|
||||
cnt : s64 = 0;
|
||||
sc : i64 = 0;
|
||||
sr : i64 = 0;
|
||||
cnt : i64 = 0;
|
||||
for 0..BOARD_CELLS (idx) {
|
||||
if rd0.matched.cells[idx] {
|
||||
sc += idx % BOARD_COLS;
|
||||
@@ -275,8 +275,8 @@ BoardFx :: struct {
|
||||
// Advance every live FX by `dt` and drop those past their lifetime. Kept
|
||||
// simple: compact each list in place by overwriting dead entries.
|
||||
tick :: (self: *BoardFx, dt: f32) {
|
||||
w : s64 = 0;
|
||||
i : s64 = 0;
|
||||
w : i64 = 0;
|
||||
i : i64 = 0;
|
||||
while i < self.particles.len {
|
||||
p := self.particles.items[i];
|
||||
p.age += dt;
|
||||
|
||||
@@ -25,7 +25,7 @@ BoardLayout :: struct {
|
||||
};
|
||||
}
|
||||
|
||||
cell_frame :: (self: *BoardLayout, col: s64, row: s64) -> Frame {
|
||||
cell_frame :: (self: *BoardLayout, col: i64, row: i64) -> Frame {
|
||||
Frame.make(
|
||||
self.origin.x + xx col * self.cell_size,
|
||||
self.origin.y + xx row * self.cell_size,
|
||||
@@ -44,8 +44,8 @@ BoardLayout :: struct {
|
||||
fx := (p.x - self.origin.x) / self.cell_size;
|
||||
fy := (p.y - self.origin.y) / self.cell_size;
|
||||
if fx < 0.0 or fy < 0.0 { return null; }
|
||||
col : s64 = xx fx;
|
||||
row : s64 = xx fy;
|
||||
col : i64 = xx fx;
|
||||
row : i64 = xx fy;
|
||||
if col >= BOARD_COLS or row >= BOARD_ROWS { return null; }
|
||||
Cell.{ col = col, row = row }
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ BoardAssets :: struct {
|
||||
self.loaded = self.bg_tex != 0 and self.cell_tex != 0 and self.gems_tex != 0;
|
||||
}
|
||||
|
||||
gem_uv :: (self: *BoardAssets, index: s64) -> GemUV {
|
||||
gem_uv :: (self: *BoardAssets, index: i64) -> GemUV {
|
||||
u0 : f32 = xx index * self.cell_u;
|
||||
GemUV.{
|
||||
uv_min = Point.{ x = u0, y = 0.0 },
|
||||
@@ -139,9 +139,9 @@ BoardAssets :: struct {
|
||||
// failure). When a GPU backend is bound (iOS Metal) it owns the upload; the
|
||||
// desktop GL path falls back to a plain GL_TEXTURE_2D.
|
||||
load_texture :: (path: [:0]u8, gpu: ?GPU) -> u32 {
|
||||
w : s32 = 0;
|
||||
h : s32 = 0;
|
||||
ch : s32 = 0;
|
||||
w : i32 = 0;
|
||||
h : i32 = 0;
|
||||
ch : i32 = 0;
|
||||
pixels := stbi_load(path, @w, @h, @ch, 4);
|
||||
if pixels == null {
|
||||
out("WARNING: could not load texture: ");
|
||||
@@ -237,7 +237,7 @@ BoardView :: struct {
|
||||
safe: EdgeInsets;
|
||||
// Seed for `restart`: the same fixed seed main seeded the board with, so the
|
||||
// restart button reproduces the identical starting level.
|
||||
seed: s64;
|
||||
seed: i64;
|
||||
// FPS dev overlay (P20.1). `fps_on` gates the corner readout (off by default,
|
||||
// set only by the M3TE_FPS env pin); `fps` is the smoothed reciprocal frame
|
||||
// rate computed in the frame loop. Purely a render overlay.
|
||||
@@ -266,7 +266,7 @@ BoardView :: struct {
|
||||
}
|
||||
|
||||
// Draw gem `gem_index`'s sprite-sheet column into `gf`.
|
||||
draw_gem :: (self: *BoardView, ctx: *RenderContext, gf: Frame, gem_index: s64) {
|
||||
draw_gem :: (self: *BoardView, ctx: *RenderContext, gf: Frame, gem_index: i64) {
|
||||
uv := self.assets.gem_uv(gem_index);
|
||||
ctx.add_image_uv(gf, self.assets.gems_tex, uv.uv_min, uv.uv_max);
|
||||
}
|
||||
@@ -285,7 +285,7 @@ BoardView :: struct {
|
||||
|
||||
// Frame for a gem shrunk by `scale` about its cell centre — the clear
|
||||
// scale-out. At scale 0 the gem is a zero-size frame (gone).
|
||||
gem_frame_scaled :: (self: *BoardView, col: s64, row: s64, dim: f32, scale: f32) -> Frame {
|
||||
gem_frame_scaled :: (self: *BoardView, col: i64, row: i64, dim: f32, scale: f32) -> Frame {
|
||||
cs := self.layout.cell_size;
|
||||
cx := self.layout.origin.x + cast(f32) col * cs + cs * 0.5;
|
||||
cy := self.layout.origin.y + cast(f32) row * cs + cs * 0.5;
|
||||
@@ -297,7 +297,7 @@ BoardView :: struct {
|
||||
// sprite is scaled about its cell centre and nudged by the pose offset (both
|
||||
// in cell units). A resting pose reproduces gem_frame exactly, so the t==0
|
||||
// idle pose draws identically to the static sprite.
|
||||
gem_pose_frame :: (self: *BoardView, col: s64, row: s64, dim: f32, pose: GemPose) -> Frame {
|
||||
gem_pose_frame :: (self: *BoardView, col: i64, row: i64, dim: f32, pose: GemPose) -> Frame {
|
||||
cs := self.layout.cell_size;
|
||||
cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs + pose.dx * cs;
|
||||
cy := self.layout.origin.y + (cast(f32) row + 0.5) * cs + pose.dy * cs;
|
||||
@@ -311,7 +311,7 @@ BoardView :: struct {
|
||||
// — the wide-and-short landing impact. sq==0 reproduces gem_frame's centred
|
||||
// placement EXACTLY, so a gem still mid-fall (or one that never moved) draws
|
||||
// byte-identically to the plain fall; only a landed gem flattens.
|
||||
gem_squash_frame :: (self: *BoardView, col: s64, frow: f32, dim: f32, sq: f32) -> Frame {
|
||||
gem_squash_frame :: (self: *BoardView, col: i64, frow: f32, dim: f32, sq: f32) -> Frame {
|
||||
cs := self.layout.cell_size;
|
||||
cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs;
|
||||
cy := self.layout.origin.y + (frow + 0.5) * cs;
|
||||
@@ -323,7 +323,7 @@ BoardView :: struct {
|
||||
// The per-gem animation pose for a settled cell: the always-on idle breath,
|
||||
// plus a squash-bounce if the cell landed recently, plus a pop if it is the
|
||||
// selected cell. Purely visual — composed from gem_anim's pure functions.
|
||||
gem_pose_at :: (self: *BoardView, col: s64, row: s64) -> GemPose {
|
||||
gem_pose_at :: (self: *BoardView, col: i64, row: i64) -> GemPose {
|
||||
pose := idle_pose(self.motion.clock, col, row);
|
||||
|
||||
sq := land_squash(self.motion.land_local(Board.idx(col, row)));
|
||||
@@ -347,7 +347,7 @@ BoardView :: struct {
|
||||
// (land_squash → 0, so it draws unsquashed) and one that never moved reads 0.
|
||||
// render_fall passes the current round; render_clear the previous (its board is
|
||||
// that round's `after`), so the one bounce plays on across the fall→clear seam.
|
||||
rest_squash :: (self: *BoardView, i: s64, kmax: s64, elapsed: f32) -> f32 {
|
||||
rest_squash :: (self: *BoardView, i: i64, kmax: i64, elapsed: f32) -> f32 {
|
||||
m := delivering_round(@self.anim.move, i, kmax);
|
||||
if m < 0 { return 0.0; }
|
||||
col := i % BOARD_COLS;
|
||||
@@ -363,7 +363,7 @@ BoardView :: struct {
|
||||
if g != .empty {
|
||||
pose := self.gem_pose_at(col, row);
|
||||
gf := self.gem_pose_frame(col, row, dim, pose);
|
||||
self.draw_gem(ctx, gf, cast(s64) g);
|
||||
self.draw_gem(ctx, gf, cast(i64) g);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,7 +436,7 @@ BoardView :: struct {
|
||||
if g != .empty {
|
||||
sq := self.rest_squash(i, last, e);
|
||||
gf := self.gem_squash_frame(i % BOARD_COLS, cast(f32) (i / BOARD_COLS), dim, sq);
|
||||
self.draw_gem(ctx, gf, cast(s64) g);
|
||||
self.draw_gem(ctx, gf, cast(i64) g);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,7 @@ BoardView :: struct {
|
||||
g := mv.pre[i];
|
||||
if g != .empty {
|
||||
gf := self.gem_frame(cast(f32) (i % BOARD_COLS), cast(f32) (i / BOARD_COLS), inset, dim);
|
||||
self.draw_gem(ctx, gf, cast(s64) g);
|
||||
self.draw_gem(ctx, gf, cast(i64) g);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,18 +482,18 @@ BoardView :: struct {
|
||||
ga := mv.pre[ai];
|
||||
if ga != .empty {
|
||||
gf := self.gem_frame(afc + (bfc - afc) * p, afr + (bfr - afr) * p, inset, dim);
|
||||
self.draw_gem(ctx, gf, cast(s64) ga);
|
||||
self.draw_gem(ctx, gf, cast(i64) ga);
|
||||
}
|
||||
gb := mv.pre[bi];
|
||||
if gb != .empty {
|
||||
gf := self.gem_frame(bfc + (afc - bfc) * p, bfr + (afr - bfr) * p, inset, dim);
|
||||
self.draw_gem(ctx, gf, cast(s64) gb);
|
||||
self.draw_gem(ctx, gf, cast(i64) gb);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear segment: matched gems pop outward then collapse to nothing (a
|
||||
// satisfying pop, composing with the particle burst); the rest hold position.
|
||||
render_clear :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) {
|
||||
render_clear :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: i64, e: f32, dim: f32, t: f32) {
|
||||
span := clear_diag_span(@rd.matched);
|
||||
for 0..BOARD_CELLS (i) {
|
||||
g := rd.before[i];
|
||||
@@ -507,7 +507,7 @@ BoardView :: struct {
|
||||
// reaches scale 0 by t==1, keeping the seam to the fall clean.
|
||||
pop := clear_pop_scale(clear_ripple_t(t, clear_rank(span, col, row)));
|
||||
gf := self.gem_frame_scaled(col, row, dim, pop);
|
||||
self.draw_gem(ctx, gf, cast(s64) g);
|
||||
self.draw_gem(ctx, gf, cast(i64) g);
|
||||
} else {
|
||||
// before[k] is round k-1's settled board, so a survivor here still
|
||||
// carries the bounce from the round that dropped it in — continue it
|
||||
@@ -515,7 +515,7 @@ BoardView :: struct {
|
||||
// (nothing has fallen yet), keeping that frame byte-identical.
|
||||
sq := self.rest_squash(i, k - 1, e);
|
||||
gf := self.gem_squash_frame(col, cast(f32) row, dim, sq);
|
||||
self.draw_gem(ctx, gf, cast(s64) g);
|
||||
self.draw_gem(ctx, gf, cast(i64) g);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,7 +593,7 @@ BoardView :: struct {
|
||||
// lockstep row; ease_in_cubic pins each column's f(1)=1, and fall_stagger_t
|
||||
// guarantees every column reaches 1 by t==1, so each gem lands exactly on its
|
||||
// cell and the seam to the next round / settled board stays invisible.
|
||||
render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) {
|
||||
render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: i64, e: f32, dim: f32, t: f32) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
g := rd.after[i];
|
||||
if g == .empty { continue; }
|
||||
@@ -608,7 +608,7 @@ BoardView :: struct {
|
||||
// has reached its cell flattens wide-and-short, then wobbles out.
|
||||
sq := self.rest_squash(i, k, e);
|
||||
gf := self.gem_squash_frame(col, cur_row, dim, sq);
|
||||
self.draw_gem(ctx, gf, cast(s64) g);
|
||||
self.draw_gem(ctx, gf, cast(i64) g);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ BoardView :: struct {
|
||||
// pin) is set, so the unset render path is byte-identical. A bright halo under
|
||||
// the dark text keeps the digits legible over the light background art.
|
||||
render_fps_overlay :: (self: *BoardView, ctx: *RenderContext, frame: Frame) {
|
||||
n := cast(s64) (self.fps + 0.5);
|
||||
n := cast(i64) (self.fps + 0.5);
|
||||
txt := format("FPS {}", n);
|
||||
sz := measure_text(txt, FPS_FONT);
|
||||
x := frame.origin.x + self.safe.left + FPS_PAD;
|
||||
|
||||
@@ -25,9 +25,9 @@ the step asks for an honest accounting.
|
||||
|
||||
## Category: Numerics / parsing
|
||||
|
||||
### 1. String → number parsing (`parse_s64`, `parse_f32`)
|
||||
- **m3te location:** `main.sx:156` (`parse_s64`), `main.sx:122` (`parse_f32`).
|
||||
- **What it does:** decimal ASCII `string` → `s64` / `f32` (sign + integer +
|
||||
### 1. String → number parsing (`parse_i64`, `parse_f32`)
|
||||
- **m3te location:** `main.sx:156` (`parse_i64`), `main.sx:122` (`parse_f32`).
|
||||
- **What it does:** decimal ASCII `string` → `i64` / `f32` (sign + integer +
|
||||
optional fractional part).
|
||||
- **Why general-purpose:** the exact inverse of the formatters everyone already
|
||||
uses; needed by any program that reads numbers from argv, env, config, or a
|
||||
@@ -46,7 +46,7 @@ the step asks for an honest accounting.
|
||||
- **m3te location:** `board.sx:55` (`Rng` struct: `next_u32` `board.sx:59`,
|
||||
`next_range` `board.sx:66`), `board.sx:71` (`rng_seeded`), constants
|
||||
`board.sx:51-53`.
|
||||
- **What it does:** a 32-bit linear-congruential generator carried in `s64`
|
||||
- **What it does:** a 32-bit linear-congruential generator carried in `i64`
|
||||
and masked to 32 bits — `next_u32()`, `next_range(n)` (uniform-ish `[0,n)`),
|
||||
`rng_seeded(seed)`.
|
||||
- **Why general-purpose:** a seedable, reproducible RNG is a textbook stdlib
|
||||
@@ -172,7 +172,7 @@ them, so they are explicitly **not** counted above:
|
||||
|
||||
## Summary (top gaps, ranked)
|
||||
|
||||
1. **String → number parsing** (`parse_s64` / `parse_f32`) — clean, universally
|
||||
1. **String → number parsing** (`parse_i64` / `parse_f32`) — clean, universally
|
||||
needed, the missing inverse of the existing formatters.
|
||||
2. **Seedable PRNG** (`Rng` LCG) — textbook stdlib primitive, entirely absent.
|
||||
3. **`from_cstr` C-string bridge** (+ public `strlen`) — the most-repeated FFI
|
||||
|
||||
10
gem_anim.sx
10
gem_anim.sx
@@ -38,11 +38,11 @@ IDLE_BOB_A :f32: 0.024; // vertical bob amplitude (cell units)
|
||||
IDLE_RAMP :f32: 0.45; // seconds to ease the idle up from full rest
|
||||
|
||||
// Smooth per-cell phase: a diagonal gradient wrapped into one breath period.
|
||||
gem_idle_phase :: (col: s64, row: s64) -> f32 {
|
||||
gem_idle_phase :: (col: i64, row: i64) -> f32 {
|
||||
cast(f32) ((col * 2 + row * 3) % 8) / 8.0 * TAU
|
||||
}
|
||||
|
||||
idle_pose :: (t: f32, col: s64, row: s64) -> GemPose {
|
||||
idle_pose :: (t: f32, col: i64, row: i64) -> GemPose {
|
||||
ramp := clamp(t / IDLE_RAMP, 0.0, 1.0);
|
||||
w := t / IDLE_PERIOD * TAU + gem_idle_phase(col, row);
|
||||
s := IDLE_SCALE_A * sin(w) * ramp;
|
||||
@@ -135,7 +135,7 @@ GemMotion :: struct {
|
||||
for 0..BOARD_CELLS (i) { self.land_at[i] = -1000.0; }
|
||||
}
|
||||
|
||||
stamp_land :: (self: *GemMotion, i: s64) {
|
||||
stamp_land :: (self: *GemMotion, i: i64) {
|
||||
self.stamp_land_at(i, self.clock);
|
||||
}
|
||||
|
||||
@@ -143,11 +143,11 @@ GemMotion :: struct {
|
||||
// can BACK-DATE the stamp to when the gem actually touched down mid-fall (each
|
||||
// column lands at a staggered instant): land_squash then resumes the per-round
|
||||
// bounce exactly where render_fall left it, with no double-pop at the seam.
|
||||
stamp_land_at :: (self: *GemMotion, i: s64, at: f32) {
|
||||
stamp_land_at :: (self: *GemMotion, i: i64, at: f32) {
|
||||
self.land_at[i] = at;
|
||||
}
|
||||
|
||||
land_local :: (self: *GemMotion, i: s64) -> f32 {
|
||||
land_local :: (self: *GemMotion, i: i64) -> f32 {
|
||||
self.clock - self.land_at[i]
|
||||
}
|
||||
}
|
||||
|
||||
44
main.sx
44
main.sx
@@ -117,40 +117,40 @@ build_ui :: () -> View {
|
||||
// be captured without injecting a tap. Absent → normal live behaviour.
|
||||
read_env :: (name: [:0]u8) -> ?string {
|
||||
p := getenv(name);
|
||||
addr : s64 = xx p;
|
||||
addr : i64 = xx p;
|
||||
if addr == 0 { return null; }
|
||||
n := cast(s64) strlen(p);
|
||||
n := cast(i64) strlen(p);
|
||||
if n == 0 { return ""; }
|
||||
buf := cstring(n);
|
||||
memcpy(buf.ptr, xx p, n);
|
||||
buf
|
||||
}
|
||||
|
||||
// Digit arithmetic runs entirely in s64; the result converts to f32 only once at
|
||||
// Digit arithmetic runs entirely in i64; the result converts to f32 only once at
|
||||
// the end. Doing the digit math in f32 would unify the ASCII literals (45/46/48/
|
||||
// 57) to f32 across the comparisons, which mis-types the byte compares.
|
||||
parse_f32 :: (s: string) -> f32 {
|
||||
i : s64 = 0;
|
||||
i : i64 = 0;
|
||||
neg : bool = false;
|
||||
if s.len > 0 {
|
||||
c0 : s64 = xx s[0];
|
||||
c0 : i64 = xx s[0];
|
||||
if c0 == 45 { neg = true; i = 1; } // '-'
|
||||
}
|
||||
intval : s64 = 0;
|
||||
intval : i64 = 0;
|
||||
while i < s.len {
|
||||
c : s64 = xx s[i];
|
||||
c : i64 = xx s[i];
|
||||
if c < 48 or c > 57 { break; }
|
||||
intval = intval * 10 + (c - 48);
|
||||
i += 1;
|
||||
}
|
||||
fracval : s64 = 0;
|
||||
fracdiv : s64 = 1;
|
||||
fracval : i64 = 0;
|
||||
fracdiv : i64 = 1;
|
||||
if i < s.len {
|
||||
d : s64 = xx s[i];
|
||||
d : i64 = xx s[i];
|
||||
if d == 46 { // '.'
|
||||
i += 1;
|
||||
while i < s.len {
|
||||
c : s64 = xx s[i];
|
||||
c : i64 = xx s[i];
|
||||
if c < 48 or c > 57 { break; }
|
||||
fracval = fracval * 10 + (c - 48);
|
||||
fracdiv = fracdiv * 10;
|
||||
@@ -163,11 +163,11 @@ parse_f32 :: (s: string) -> f32 {
|
||||
v
|
||||
}
|
||||
|
||||
parse_s64 :: (s: string) -> s64 {
|
||||
i : s64 = 0;
|
||||
v : s64 = 0;
|
||||
parse_i64 :: (s: string) -> i64 {
|
||||
i : i64 = 0;
|
||||
v : i64 = 0;
|
||||
while i < s.len {
|
||||
c : s64 = xx s[i];
|
||||
c : i64 = xx s[i];
|
||||
if c < 48 or c > 57 { break; }
|
||||
v = v * 10 + (c - 48);
|
||||
i += 1;
|
||||
@@ -405,7 +405,7 @@ main :: () -> void {
|
||||
g_motion.clock = parse_f32(t);
|
||||
}
|
||||
if sc := read_env("M3TE_SELECT") {
|
||||
idx := parse_s64(sc);
|
||||
idx := parse_i64(sc);
|
||||
if idx >= 0 and idx < BOARD_CELLS {
|
||||
g_sel.active = true;
|
||||
g_sel.cell = Cell.{ col = idx % BOARD_COLS, row = idx / BOARD_COLS };
|
||||
@@ -418,7 +418,7 @@ main :: () -> void {
|
||||
// committed golden stay byte-identical. Purely a render overlay — no board /
|
||||
// score / move / animation state changes and it never gates input.
|
||||
if fp := read_env("M3TE_FPS") {
|
||||
if parse_s64(fp) != 0 { g_fps_on = true; }
|
||||
if parse_i64(fp) != 0 { g_fps_on = true; }
|
||||
}
|
||||
|
||||
// Match-FX capture hook (P11.1). The bursts/popups spawn off a committed move,
|
||||
@@ -433,7 +433,7 @@ main :: () -> void {
|
||||
if fx := read_env("M3TE_FX") {
|
||||
swaps := legal_swaps(g_board);
|
||||
if swaps.len > 0 {
|
||||
n := parse_s64(fx);
|
||||
n := parse_i64(fx);
|
||||
if n < 1 { n = 1; }
|
||||
if n > swaps.len { n = swaps.len; }
|
||||
sw := swaps.items[n - 1];
|
||||
@@ -457,7 +457,7 @@ main :: () -> void {
|
||||
if bs := read_env("M3TE_BADSWAP") {
|
||||
bad := illegal_swaps(g_board);
|
||||
if bad.len > 0 {
|
||||
n := parse_s64(bs);
|
||||
n := parse_i64(bs);
|
||||
if n < 1 { n = 1; }
|
||||
if n > bad.len { n = bad.len; }
|
||||
sw := bad.items[n - 1];
|
||||
@@ -473,10 +473,10 @@ main :: () -> void {
|
||||
// M3TE_MOVE_LIMIT=0 makes it read LOST (budget spent below the goal). With
|
||||
// M3TE_RESTART set non-zero the board is then restart()-ed, capturing the
|
||||
// fresh in_progress board the restart button produces.
|
||||
if tg := read_env("M3TE_TARGET") { g_board.target_score = parse_s64(tg); }
|
||||
if ml := read_env("M3TE_MOVE_LIMIT") { g_board.move_limit = parse_s64(ml); }
|
||||
if tg := read_env("M3TE_TARGET") { g_board.target_score = parse_i64(tg); }
|
||||
if ml := read_env("M3TE_MOVE_LIMIT") { g_board.move_limit = parse_i64(ml); }
|
||||
if rs := read_env("M3TE_RESTART") {
|
||||
if parse_s64(rs) != 0 { g_board.restart(BOARD_SEED); }
|
||||
if parse_i64(rs) != 0 { g_board.restart(BOARD_SEED); }
|
||||
}
|
||||
|
||||
g_pipeline.set_body(closure(build_ui));
|
||||
|
||||
@@ -30,8 +30,8 @@ boards_equal :: (x: *Board, y: *Board) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
fails : s64 = 0;
|
||||
main :: () -> i32 {
|
||||
fails : i64 = 0;
|
||||
|
||||
// ── Legal swap: plan == model, timeline ends on the model ───────────────
|
||||
// (5,4)->(6,4): brings R into (5,4), completing R,R,R across cols 3-5 of row
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#import "modules/std.sx";
|
||||
t :: #import "test.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
t.expect(2 + 2 == 4, "two plus two is four");
|
||||
t.expect(7 % 3 == 1, "seven mod three is one");
|
||||
t.expect(10 - 4 == 6, "ten minus four is six");
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
irect :: (f: Frame) -> string {
|
||||
format("({},{},{},{})",
|
||||
cast(s64) f.origin.x, cast(s64) f.origin.y,
|
||||
cast(s64) f.size.width, cast(s64) f.size.height)
|
||||
cast(i64) f.origin.x, cast(i64) f.origin.y,
|
||||
cast(i64) f.size.width, cast(i64) f.size.height)
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
// 800×600, no safe inset → 600px square grid, cell 75, origin (100,0): the
|
||||
// same layout tests/hit_test.sx pins, so the numbers are checkable by hand.
|
||||
lay : BoardLayout = ---;
|
||||
@@ -36,18 +36,18 @@ main :: () -> s32 {
|
||||
print("title {}\n", irect(bl.title));
|
||||
print("button {}\n", irect(bl.button));
|
||||
|
||||
fails : s64 = 0;
|
||||
fails : i64 = 0;
|
||||
|
||||
// The button is horizontally centered on the grid (centred banner).
|
||||
bcx := bl.button.mid_x();
|
||||
if cast(s64) bcx != cast(s64) grid.mid_x() { fails += 1; }
|
||||
print("button mid_x {} grid mid_x {}\n", cast(s64) bcx, cast(s64) grid.mid_x());
|
||||
if cast(i64) bcx != cast(i64) grid.mid_x() { fails += 1; }
|
||||
print("button mid_x {} grid mid_x {}\n", cast(i64) bcx, cast(i64) grid.mid_x());
|
||||
|
||||
// The whole button sits inside the panel — its four corners are contained,
|
||||
// so it can never spill outside the drawn card.
|
||||
bx0 := bl.button.origin.x; by0 := bl.button.origin.y;
|
||||
bx1 := bl.button.max_x(); by1 := bl.button.max_y();
|
||||
corners_in : s64 = 0;
|
||||
corners_in : i64 = 0;
|
||||
if bl.panel.contains(Point.{ x = bx0, y = by0 }) { corners_in += 1; }
|
||||
if bl.panel.contains(Point.{ x = bx1, y = by0 }) { corners_in += 1; }
|
||||
if bl.panel.contains(Point.{ x = bx0, y = by1 }) { corners_in += 1; }
|
||||
@@ -67,7 +67,7 @@ main :: () -> s32 {
|
||||
// in the button, so each leaves the level frozen.
|
||||
corner_cell := Point.{ x = grid.origin.x + lay.cell_size * 0.5, y = grid.origin.y + lay.cell_size * 0.5 };
|
||||
outside := Point.{ x = bl.panel.origin.x - 5.0, y = bl.panel.mid_y() };
|
||||
off_hits : s64 = 0;
|
||||
off_hits : i64 = 0;
|
||||
if bl.button.contains(corner_cell) { off_hits += 1; }
|
||||
if bl.button.contains(outside) { off_hits += 1; }
|
||||
if off_hits != 0 { fails += 1; }
|
||||
|
||||
@@ -10,8 +10,8 @@ SEED :: 1337;
|
||||
// Count every horizontal or vertical window of three consecutive same-type
|
||||
// gems. A correctly initialized board has zero. This walks the finished board
|
||||
// independently of the placement logic, so it's a real check, not a tautology.
|
||||
count_three_runs :: (b: *Board) -> s32 {
|
||||
runs : s32 = 0;
|
||||
count_three_runs :: (b: *Board) -> i32 {
|
||||
runs : i32 = 0;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..(BOARD_COLS - 2) (col) {
|
||||
g := b.at(col, row);
|
||||
@@ -27,7 +27,7 @@ count_three_runs :: (b: *Board) -> s32 {
|
||||
runs
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
board : Board = ---;
|
||||
board.init(SEED);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ checker_board :: () -> Board {
|
||||
b
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== cascade (resolution loop) ==\n");
|
||||
|
||||
// Drive the loop one round at a time so each post-round board is visible in
|
||||
@@ -94,7 +94,7 @@ main :: () -> s32 {
|
||||
out(board_dump(@b));
|
||||
|
||||
depth := 0;
|
||||
counts := List(s64).{};
|
||||
counts := List(i64).{};
|
||||
while true {
|
||||
n := resolve_step(@b);
|
||||
if n == 0 { break; }
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#import "modules/std.sx";
|
||||
#import "audio.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== cascade cue selection (depth -> combo cue) ==\n");
|
||||
|
||||
// Walk a representative depth range (0..9) so both clamps and the monotonic
|
||||
// middle are visible: depths 0,1 pin to the first cue; depths >= 5 pin to
|
||||
// the last; 2,3,4 step up one cue at a time.
|
||||
prev : s64 = -1;
|
||||
prev : i64 = -1;
|
||||
for 0..10 (depth) {
|
||||
idx := cascade_cue_index(depth);
|
||||
print("depth {} -> idx {} ({})\n", depth, idx, cascade_cue_name(idx));
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#import "board_anim.sx";
|
||||
#import "audio.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== per-round cascade cue timing ==\n");
|
||||
|
||||
// `cascade_rounds_started` = how many cascade rounds have BEGUN clearing by
|
||||
@@ -21,7 +21,7 @@ main :: () -> s32 {
|
||||
// Round k (0-based) starts clearing at 0.16 + k*0.36; sampled safely INSIDE
|
||||
// each round window so the integer step is unambiguous. Locked for 5 rounds.
|
||||
print("-- started-count across a 5-round chain --\n");
|
||||
rounds : s64 = 5;
|
||||
rounds : i64 = 5;
|
||||
print("e=0.00 -> {}\n", cascade_rounds_started(0.00, rounds));
|
||||
print("e=0.10 -> {}\n", cascade_rounds_started(0.10, rounds));
|
||||
print("e=0.20 -> {}\n", cascade_rounds_started(0.20, rounds));
|
||||
@@ -37,7 +37,7 @@ main :: () -> s32 {
|
||||
// ascending. This IS the loop main's frame loop runs; the emitted run is the
|
||||
// locked acceptance ordering.
|
||||
print("-- ascending per-round run --\n");
|
||||
fired : s64 = 0;
|
||||
fired : i64 = 0;
|
||||
elapsed : f32 = 0.0;
|
||||
while fired < rounds {
|
||||
started := cascade_rounds_started(elapsed, rounds);
|
||||
|
||||
@@ -38,7 +38,7 @@ load_board :: (rows: []string) -> Board {
|
||||
// Detect→clear one scene, snapshot before/after, and assert the three clear
|
||||
// invariants against the matched-cell set: every flagged cell is now a hole,
|
||||
// every unflagged cell is unchanged, and the returned count is exact.
|
||||
scene :: (name: string, rows: []string, want_cleared: s64) {
|
||||
scene :: (name: string, rows: []string, want_cleared: i64) {
|
||||
b := load_board(rows);
|
||||
orig := load_board(rows); // pristine copy for the unchanged check
|
||||
|
||||
@@ -65,7 +65,7 @@ scene :: (name: string, rows: []string, want_cleared: s64) {
|
||||
t.expect(cleared == want_cleared, concat(name, ": cleared count exact"));
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== clear (detect -> clear) ==\n");
|
||||
|
||||
// Single horizontal 3-run (row 3, cols 2-4) → three holes there only.
|
||||
|
||||
@@ -79,7 +79,7 @@ scene :: (name: string, rows: []string, want_moved: bool) {
|
||||
t.expect(moved == want_moved, concat(name, ": moved flag exact"));
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== collapse (gravity) ==\n");
|
||||
|
||||
// Eight independent columns, one case each (top-to-bottom):
|
||||
|
||||
@@ -31,7 +31,7 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
|
||||
// Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars),
|
||||
// seeded RNG, running score zeroed so `board.score` ends equal to the payout.
|
||||
load_board :: (rows: []string, seed: s64) -> Board {
|
||||
load_board :: (rows: []string, seed: i64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
@@ -51,14 +51,14 @@ load_board :: (rows: []string, seed: s64) -> Board {
|
||||
// identical board awards `want_mult` into `Board.score` and reports it as
|
||||
// `Cascade.awarded` at the same depth. A depth-1 settle must equal the flat sum
|
||||
// (no bonus); a deeper chain must strictly exceed it.
|
||||
scene :: (name: string, rows: []string, seed: s64, want_flat: s64, want_mult: s64) {
|
||||
scene :: (name: string, rows: []string, seed: i64, want_flat: i64, want_mult: i64) {
|
||||
print("== {} ==\n", name);
|
||||
b := load_board(rows, seed);
|
||||
out(board_dump(@b));
|
||||
|
||||
flat : s64 = 0;
|
||||
mult : s64 = 0;
|
||||
depth : s64 = 0;
|
||||
flat : i64 = 0;
|
||||
mult : i64 = 0;
|
||||
depth : i64 = 0;
|
||||
while true {
|
||||
base := score_round(@b);
|
||||
n := resolve_step(@b);
|
||||
@@ -90,7 +90,7 @@ scene :: (name: string, rows: []string, seed: s64, want_flat: s64, want_mult: s6
|
||||
t.expect(b2.score == want_mult, concat(name, ": resolve accumulates into board.score"));
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== combo (cascade multiplier) ==\n");
|
||||
|
||||
// Single-round clear (seed 0): one RRR clears and the refill makes no new
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
fabs :: (x: f32) -> f32 { if x < 0.0 then 0.0 - x else x }
|
||||
approx :: (a: f32, b: f32) -> bool { fabs(a - b) < 0.0001 }
|
||||
|
||||
main :: () -> s32 {
|
||||
fails : s64 = 0;
|
||||
main :: () -> i32 {
|
||||
fails : i64 = 0;
|
||||
|
||||
// 1. Endpoints are locked: every curve starts/ends exactly on its rest value
|
||||
// (the in/out curves at 1, the spring at 1, the squash envelope at 0).
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
#import "modules/std.sx";
|
||||
#import "board_fx.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== combo emphasis selection (depth -> fx level / popup font) ==\n");
|
||||
|
||||
// The cascade-cue index per depth 0..9, copied from cascade_cue.stdout. The
|
||||
// FX level must equal this entry for entry — the audio/visual lockstep.
|
||||
expect_level : [10]s64 = .{ 0, 0, 1, 2, 3, 4, 4, 4, 4, 4 };
|
||||
expect_level : [10]i64 = .{ 0, 0, 1, 2, 3, 4, 4, 4, 4, 4 };
|
||||
|
||||
prev : s64 = -1;
|
||||
prev : i64 = -1;
|
||||
for 0..10 (depth) {
|
||||
lvl := fx_combo_level(depth);
|
||||
font := fx_popup_font(depth);
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
fabs :: (x: f32) -> f32 { if x < 0.0 then 0.0 - x else x }
|
||||
approx :: (a: f32, b: f32) -> bool { fabs(a - b) < 0.0001 }
|
||||
|
||||
main :: () -> s32 {
|
||||
fails : s64 = 0;
|
||||
main :: () -> i32 {
|
||||
fails : i64 = 0;
|
||||
|
||||
// 1. t==0 idle pose is EXACTLY rest for every cell (the determinism invariant).
|
||||
print("== idle t=0 is rest for all cells ==\n");
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
#import "board.sx";
|
||||
#import "board_layout.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
// 800×600 with no safe inset → a 600px square grid, cell 75, centered: the
|
||||
// grid origin lands at (100, 0). Integer math keeps the dump deterministic.
|
||||
lay : BoardLayout = ---;
|
||||
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
||||
|
||||
print("grid origin ({},{}) cell {}\n",
|
||||
cast(s64) lay.origin.x, cast(s64) lay.origin.y, cast(s64) lay.cell_size);
|
||||
cast(i64) lay.origin.x, cast(i64) lay.origin.y, cast(i64) lay.cell_size);
|
||||
|
||||
fails : s64 = 0;
|
||||
fails : i64 = 0;
|
||||
|
||||
// Every cell center must map back to its own cell.
|
||||
hits : s64 = 0;
|
||||
hits : i64 = 0;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
cf := lay.cell_frame(col, row);
|
||||
@@ -42,8 +42,8 @@ main :: () -> s32 {
|
||||
// A cell's top-left corner belongs to that cell (the leading edge is
|
||||
// inclusive), so corner-of-(3,5) resolves to (3,5).
|
||||
corner := Point.{ x = lay.origin.x + 3.0 * lay.cell_size, y = lay.origin.y + 5.0 * lay.cell_size };
|
||||
corner_col : s64 = -1;
|
||||
corner_row : s64 = -1;
|
||||
corner_col : i64 = -1;
|
||||
corner_row : i64 = -1;
|
||||
if h := lay.point_to_cell(corner) { corner_col = h.col; corner_row = h.row; }
|
||||
if corner_col != 3 or corner_row != 5 { fails += 1; }
|
||||
print("corner maps to ({},{})\n", corner_col, corner_row);
|
||||
@@ -53,7 +53,7 @@ main :: () -> s32 {
|
||||
off_left := Point.{ x = lay.origin.x - 5.0, y = lay.origin.y + 10.0 };
|
||||
off_above := Point.{ x = lay.origin.x + 10.0, y = lay.origin.y - 5.0 };
|
||||
off_right := Point.{ x = lay.origin.x + 8.0 * lay.cell_size + 1.0, y = lay.origin.y + 10.0 };
|
||||
on_board : s64 = 0;
|
||||
on_board : i64 = 0;
|
||||
if h := lay.point_to_cell(off_left) { on_board += 1; print("off_left hit ({},{})\n", h.col, h.row); }
|
||||
if h := lay.point_to_cell(off_above) { on_board += 1; print("off_above hit ({},{})\n", h.col, h.row); }
|
||||
if h := lay.point_to_cell(off_right) { on_board += 1; print("off_right hit ({},{})\n", h.col, h.row); }
|
||||
|
||||
@@ -34,7 +34,7 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars),
|
||||
// seeded RNG, running score zeroed, the turn counters reset to a fresh game, and
|
||||
// the per-level goal set.
|
||||
load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> Board {
|
||||
load_board :: (rows: []string, seed: i64, move_limit: i64, target_score: i64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
@@ -55,7 +55,7 @@ boards_equal :: (a: *Board, b: *Board) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== level (turn / goal state machine) ==\n");
|
||||
|
||||
// ── Start: a fresh seeded board reads in_progress with the default goal ──
|
||||
|
||||
@@ -20,7 +20,7 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// Build a scene: load an 8x8 board from `rows` (top row first, each exactly
|
||||
// BOARD_COLS gem characters), detect matches, print board + matched dump, and
|
||||
// assert the matched-cell count.
|
||||
scene :: (name: string, rows: []string, want_count: s64) {
|
||||
scene :: (name: string, rows: []string, want_count: i64) {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
@@ -38,7 +38,7 @@ scene :: (name: string, rows: []string, want_count: s64) {
|
||||
t.expect(m.count() == want_count, name);
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
// Single horizontal 3-run (row 3, cols 2-4).
|
||||
scene("horizontal-3", .[
|
||||
"OGOGOGOG",
|
||||
|
||||
@@ -41,8 +41,8 @@ load_board :: (rows: []string) -> Board {
|
||||
b
|
||||
}
|
||||
|
||||
count_empties :: (b: *Board) -> s64 {
|
||||
n : s64 = 0;
|
||||
count_empties :: (b: *Board) -> i64 {
|
||||
n : i64 = 0;
|
||||
for 0..BOARD_CELLS (i) { if b.cells[i] == .empty { n += 1; } }
|
||||
n
|
||||
}
|
||||
@@ -72,7 +72,7 @@ fresh_board :: () -> Board {
|
||||
b
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== refill (seeded) ==\n");
|
||||
|
||||
// Pipeline, snapshotting each stage.
|
||||
@@ -123,7 +123,7 @@ main :: () -> s32 {
|
||||
// filled, then refill again. The board's RNG has advanced past the first
|
||||
// fill, so the second fill draws new gems — proof it does NOT reseed per call.
|
||||
holes_n := 0;
|
||||
hole_idx : [BOARD_CELLS]s64 = ---;
|
||||
hole_idx : [BOARD_CELLS]i64 = ---;
|
||||
fill1 : [BOARD_CELLS]Gem = ---;
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if pre.cells[i] == .empty {
|
||||
|
||||
@@ -42,7 +42,7 @@ load_board :: (rows: []string) -> Board {
|
||||
|
||||
// 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: s64) {
|
||||
scene :: (name: string, rows: []string, want_points: i64) {
|
||||
b := load_board(rows);
|
||||
runs := find_runs(@b);
|
||||
|
||||
@@ -58,7 +58,7 @@ scene :: (name: string, rows: []string, want_points: s64) {
|
||||
concat(name, ": add_round_score accumulates into board.score"));
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== score (base match scoring) ==\n");
|
||||
|
||||
// Single length-3 horizontal run (row 3, cols 2-4) -> SCORE_RUN_3 = 30.
|
||||
|
||||
@@ -44,11 +44,11 @@ boards_equal :: (x: *Board, y: *Board) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
cell :: (col: s64, row: s64) -> Cell {
|
||||
cell :: (col: i64, row: i64) -> Cell {
|
||||
Cell.{ col = col, row = row }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== swap & legality ==\n");
|
||||
|
||||
// Board whose ONLY swap-formable match is the adjacent (2,3)<->(3,3)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
SEED :: 1337;
|
||||
|
||||
cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point {
|
||||
cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point {
|
||||
cf := lay.cell_frame(col, row);
|
||||
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
||||
}
|
||||
@@ -32,14 +32,14 @@ boards_equal :: (x: *Board, y: *Board) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
// 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A
|
||||
// 60px drag clears the cell*0.5 = 37.5px swipe threshold on the dominant axis.
|
||||
lay : BoardLayout = ---;
|
||||
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
||||
D : f32 = 60.0;
|
||||
|
||||
fails : s64 = 0;
|
||||
fails : i64 = 0;
|
||||
|
||||
// ── ILLEGAL swipe reverts ──────────────────────────────────────────────
|
||||
// (0,0) and (1,0) are both red on the seed board, so swapping them forms no
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
#import "board_layout.sx";
|
||||
#import "swipe.sx";
|
||||
|
||||
cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point {
|
||||
cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point {
|
||||
cf := lay.cell_frame(col, row);
|
||||
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
||||
}
|
||||
|
||||
// Print the resolved intent (locked in the golden) and report whether it matches
|
||||
// the expected adjacent pair (A, B). Drives the exit code alongside the dump.
|
||||
expect_swap :: (label: string, got: ?Swap, ac: s64, ar: s64, bc: s64, br: s64) -> bool {
|
||||
expect_swap :: (label: string, got: ?Swap, ac: i64, ar: i64, bc: i64, br: i64) -> bool {
|
||||
if s := got {
|
||||
print("{}: ({},{})->({},{})\n", label, s.a.col, s.a.row, s.b.col, s.b.row);
|
||||
return s.a.col == ac and s.a.row == ar and s.b.col == bc and s.b.row == br;
|
||||
@@ -39,14 +39,14 @@ expect_none :: (label: string, got: ?Swap) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
lay : BoardLayout = ---;
|
||||
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
||||
print("grid origin ({},{}) cell {} threshold {}\n",
|
||||
cast(s64) lay.origin.x, cast(s64) lay.origin.y, cast(s64) lay.cell_size,
|
||||
cast(s64) (lay.cell_size * SWIPE_THRESHOLD_FRACTION));
|
||||
cast(i64) lay.origin.x, cast(i64) lay.origin.y, cast(i64) lay.cell_size,
|
||||
cast(i64) (lay.cell_size * SWIPE_THRESHOLD_FRACTION));
|
||||
|
||||
fails : s64 = 0;
|
||||
fails : i64 = 0;
|
||||
|
||||
// A known interior cell; every cardinal swipe from it stays on the board.
|
||||
start := cell_center(@lay, 3, 5);
|
||||
|
||||
@@ -36,7 +36,7 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
|
||||
// Load an 8x8 board from `rows` (top row first), seeded RNG, score zeroed, turn
|
||||
// counters reset to a fresh game, and the per-level goal set.
|
||||
load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> Board {
|
||||
load_board :: (rows: []string, seed: i64, move_limit: i64, target_score: i64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
@@ -52,13 +52,13 @@ load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) ->
|
||||
b
|
||||
}
|
||||
|
||||
cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point {
|
||||
cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point {
|
||||
cf := lay.cell_frame(col, row);
|
||||
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
fails : s64 = 0;
|
||||
main :: () -> i32 {
|
||||
fails : i64 = 0;
|
||||
|
||||
// 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A 60px
|
||||
// drag clears the cell*0.5 = 37.5px swipe threshold on the dominant axis — the
|
||||
|
||||
@@ -37,7 +37,7 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars),
|
||||
// seeded RNG, running score zeroed, and the turn counters reset to a fresh game
|
||||
// (no moves made, the given move budget).
|
||||
load_board :: (rows: []string, seed: s64, move_limit: s64) -> Board {
|
||||
load_board :: (rows: []string, seed: i64, move_limit: i64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
@@ -60,7 +60,7 @@ boards_equal :: (a: *Board, b: *Board) -> bool {
|
||||
// One flag scene: snapshot the board, then count its single round's special
|
||||
// runs and assert the tallies (and the boolean flags derived from them) are
|
||||
// exactly the documented values. No RNG, no clear — pure detection.
|
||||
flag_scene :: (name: string, rows: []string, want_len4: s64, want_len5_plus: s64) {
|
||||
flag_scene :: (name: string, rows: []string, want_len4: i64, want_len5_plus: i64) {
|
||||
print("== {} ==\n", name);
|
||||
b := load_board(rows, 0, LIMIT);
|
||||
out(board_dump(@b));
|
||||
@@ -71,7 +71,7 @@ flag_scene :: (name: string, rows: []string, want_len4: s64, want_len5_plus: s64
|
||||
t.expect(sp.len5_plus == want_len5_plus, concat(name, ": len5_plus count exact"));
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
main :: () -> i32 {
|
||||
print("== turn (accounting + special-match flagging) ==\n");
|
||||
|
||||
// ── Special-match flagging (single round, no RNG) ──────────────────────
|
||||
|
||||
@@ -33,14 +33,14 @@ GRAY_TOL :: 24; // max channel spread still considered neutral gray
|
||||
LUM_MARGIN :: 4; // lum headroom above the light checker shade
|
||||
|
||||
is_gray :: (r: u8, g: u8, b: u8) -> bool {
|
||||
hi := max(max(cast(s64) r, cast(s64) g), cast(s64) b);
|
||||
lo := min(min(cast(s64) r, cast(s64) g), cast(s64) b);
|
||||
hi := max(max(cast(i64) r, cast(i64) g), cast(i64) b);
|
||||
lo := min(min(cast(i64) r, cast(i64) g), cast(i64) b);
|
||||
hi - lo <= GRAY_TOL
|
||||
}
|
||||
|
||||
// Mark pixel `i` as removed background and queue it, if it is unvisited checker
|
||||
// (near-neutral gray no brighter than the light checker shade + margin).
|
||||
fd_seed :: (i: s64, bg: [*]u8, lum: [*]s64, src: [*]u8, stack: [*]s64, sp: *s64, lim: s64) {
|
||||
fd_seed :: (i: i64, bg: [*]u8, lum: [*]i64, src: [*]u8, stack: [*]i64, sp: *i64, lim: i64) {
|
||||
if bg[i] != 0 { return; }
|
||||
p := i * 4;
|
||||
if lum[i] <= lim and is_gray(src[p], src[p+1], src[p+2]) {
|
||||
@@ -50,35 +50,35 @@ fd_seed :: (i: s64, bg: [*]u8, lum: [*]s64, src: [*]u8, stack: [*]s64, sp: *s64,
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
w : s32 = 0;
|
||||
h : s32 = 0;
|
||||
ch : s32 = 0;
|
||||
main :: () -> i32 {
|
||||
w : i32 = 0;
|
||||
h : i32 = 0;
|
||||
ch : i32 = 0;
|
||||
src : [*]u8 = xx stbi_load(SRC_PATH, @w, @h, @ch, 4);
|
||||
if xx src == 0 {
|
||||
print("FATAL: could not load {}\n", SRC_PATH);
|
||||
return 1;
|
||||
}
|
||||
W := cast(s64) w;
|
||||
H := cast(s64) h;
|
||||
W := cast(i64) w;
|
||||
H := cast(i64) h;
|
||||
N := W * H;
|
||||
print("loaded {}x{} ({} src channels)\n", w, h, ch);
|
||||
|
||||
// Hoisted working locals (see codegen note above).
|
||||
y : s64 = 0;
|
||||
x : s64 = 0;
|
||||
i : s64 = 0;
|
||||
p : s64 = 0;
|
||||
r : s64 = 0;
|
||||
g : s64 = 0;
|
||||
b : s64 = 0;
|
||||
l : s64 = 0;
|
||||
y : i64 = 0;
|
||||
x : i64 = 0;
|
||||
i : i64 = 0;
|
||||
p : i64 = 0;
|
||||
r : i64 = 0;
|
||||
g : i64 = 0;
|
||||
b : i64 = 0;
|
||||
l : i64 = 0;
|
||||
|
||||
// Per-pixel luminance, plus the checker shades read off the border ring
|
||||
// (the border is pure checker — the glow never reaches the corners).
|
||||
lum : [*]s64 = xx context.allocator.alloc_bytes(N * size_of(s64));
|
||||
c_lo : s64 = 255;
|
||||
c_hi : s64 = 0;
|
||||
lum : [*]i64 = xx context.allocator.alloc_bytes(N * size_of(i64));
|
||||
c_lo : i64 = 255;
|
||||
c_hi : i64 = 0;
|
||||
y = 0;
|
||||
while y < H {
|
||||
x = 0;
|
||||
@@ -104,8 +104,8 @@ main :: () -> s32 {
|
||||
// border pixel. `bg[i]==1` marks a removed (transparent) background pixel.
|
||||
bg : [*]u8 = xx context.allocator.alloc_bytes(N);
|
||||
memset(xx bg, 0, N);
|
||||
stack : [*]s64 = xx context.allocator.alloc_bytes(N * size_of(s64));
|
||||
sp : s64 = 0;
|
||||
stack : [*]i64 = xx context.allocator.alloc_bytes(N * size_of(i64));
|
||||
sp : i64 = 0;
|
||||
checker_lim := c_hi + LUM_MARGIN;
|
||||
|
||||
x = 0;
|
||||
@@ -121,12 +121,12 @@ main :: () -> s32 {
|
||||
y += 1;
|
||||
}
|
||||
|
||||
cx : s64 = 0;
|
||||
cy : s64 = 0;
|
||||
dx : s64 = 0;
|
||||
dy : s64 = 0;
|
||||
nx : s64 = 0;
|
||||
ny : s64 = 0;
|
||||
cx : i64 = 0;
|
||||
cy : i64 = 0;
|
||||
dx : i64 = 0;
|
||||
dy : i64 = 0;
|
||||
nx : i64 = 0;
|
||||
ny : i64 = 0;
|
||||
while sp > 0 {
|
||||
sp -= 1;
|
||||
i = stack[sp];
|
||||
@@ -154,8 +154,8 @@ main :: () -> s32 {
|
||||
denom := cast(f32) (255 - c_hi);
|
||||
if denom < 1.0 { denom = 1.0; }
|
||||
alpha : [*]f32 = xx context.allocator.alloc_bytes(N * size_of(f32));
|
||||
kept : s64 = 0;
|
||||
n_bg : s64 = 0;
|
||||
kept : i64 = 0;
|
||||
n_bg : i64 = 0;
|
||||
a : f32 = 0.0;
|
||||
i = 0;
|
||||
while i < N {
|
||||
@@ -178,26 +178,26 @@ main :: () -> s32 {
|
||||
sxf := cast(f32) W / cast(f32) OUT_DIM;
|
||||
syf := cast(f32) H / cast(f32) OUT_DIM;
|
||||
max_a : f32 = 0.0;
|
||||
ty : s64 = 0;
|
||||
tx : s64 = 0;
|
||||
x0 : s64 = 0;
|
||||
x1 : s64 = 0;
|
||||
y0 : s64 = 0;
|
||||
y1 : s64 = 0;
|
||||
ty : i64 = 0;
|
||||
tx : i64 = 0;
|
||||
x0 : i64 = 0;
|
||||
x1 : i64 = 0;
|
||||
y0 : i64 = 0;
|
||||
y1 : i64 = 0;
|
||||
sum : f32 = 0.0;
|
||||
cnt : s64 = 0;
|
||||
sy : s64 = 0;
|
||||
sx : s64 = 0;
|
||||
cnt : i64 = 0;
|
||||
sy : i64 = 0;
|
||||
sx : i64 = 0;
|
||||
av : f32 = 0.0;
|
||||
o : s64 = 0;
|
||||
o : i64 = 0;
|
||||
ty = 0;
|
||||
while ty < OUT_DIM {
|
||||
tx = 0;
|
||||
while tx < OUT_DIM {
|
||||
x0 = cast(s64) (cast(f32) tx * sxf);
|
||||
x1 = cast(s64) (cast(f32) (tx + 1) * sxf);
|
||||
y0 = cast(s64) (cast(f32) ty * syf);
|
||||
y1 = cast(s64) (cast(f32) (ty + 1) * syf);
|
||||
x0 = cast(i64) (cast(f32) tx * sxf);
|
||||
x1 = cast(i64) (cast(f32) (tx + 1) * sxf);
|
||||
y0 = cast(i64) (cast(f32) ty * syf);
|
||||
y1 = cast(i64) (cast(f32) (ty + 1) * syf);
|
||||
if x1 <= x0 { x1 = x0 + 1; }
|
||||
if y1 <= y0 { y1 = y0 + 1; }
|
||||
sum = 0.0;
|
||||
|
||||
Reference in New Issue
Block a user