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
|
// AudioToolbox — System Sound Services. SystemSoundID is a UInt32; OSStatus a
|
||||||
// SInt32 (0 == noErr); the clip's file is passed as a CFURLRef (opaque ptr).
|
// 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;
|
AudioServicesPlaySystemSound :: (sound_id: u32) #foreign;
|
||||||
|
|
||||||
// CoreFoundation — build a file CFURL from an absolute path. `len` is a CFIndex
|
// CoreFoundation — build a file CFURL from an absolute path. `len` is a CFIndex
|
||||||
// (long); `is_dir` a Boolean (unsigned char); a NULL allocator = default.
|
// (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;
|
CFRelease :: (cf: *void) #foreign;
|
||||||
|
|
||||||
// libc — getcwd to absolutize the bundle-relative asset path. The platform
|
// 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
|
// Pick the ascending cascade clip by clamping the cascade depth into the
|
||||||
// combo1..combo5 range (see `cascade_cue_index`).
|
// combo1..combo5 range (see `cascade_cue_index`).
|
||||||
play_cascade :: (self: *GameAudio, depth: s64) {
|
play_cascade :: (self: *GameAudio, depth: i64) {
|
||||||
inline if OS != .ios { return; }
|
inline if OS != .ios { return; }
|
||||||
if !self.loaded { return; }
|
if !self.loaded { return; }
|
||||||
idx := cascade_cue_index(depth);
|
idx := cascade_cue_index(depth);
|
||||||
@@ -107,7 +107,7 @@ GameAudio :: struct {
|
|||||||
// `log show` shows the clip stepping up with cascade depth. Literals only — the
|
// `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
|
// 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.
|
// 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 <= 0 { return "[sx] audio: cue combo1"; }
|
||||||
if idx == 1 { return "[sx] audio: cue combo2"; }
|
if idx == 1 { return "[sx] audio: cue combo2"; }
|
||||||
if idx == 2 { return "[sx] audio: cue combo3"; }
|
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
|
// 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
|
// (combo1..combo5). Clamps: depth <= 1 → 0, depth >= 5 → 4. Pure arithmetic and
|
||||||
// OS-agnostic so it can be snapshot-tested headlessly (P10.4).
|
// 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 <= 1 { return 0; }
|
||||||
if depth >= COMBO_CLIPS { return COMBO_CLIPS - 1; }
|
if depth >= COMBO_CLIPS { return COMBO_CLIPS - 1; }
|
||||||
depth - 1
|
depth - 1
|
||||||
@@ -145,7 +145,7 @@ load_system_sound :: (name: string) -> u32 {
|
|||||||
if getcwd(@cwd_buf[0], 1024) == null { return 0; }
|
if getcwd(@cwd_buf[0], 1024) == null { return 0; }
|
||||||
cwd : string = ---;
|
cwd : string = ---;
|
||||||
cwd.ptr = @cwd_buf[0];
|
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
|
// CFURLCreateFromFileSystemRepresentation takes an explicit byte length, so
|
||||||
// the formatted path needs no NUL terminator.
|
// 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_swap :: () { if g_audio != null { g_audio.play_swap(); } }
|
||||||
sfx_match :: () { if g_audio != null { g_audio.play_match(); } }
|
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_win :: () { if g_audio != null { g_audio.play_win(); } }
|
||||||
sfx_lose :: () { if g_audio != null { g_audio.play_lose(); } }
|
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 {
|
gem_char :: (g: Gem) -> u8 {
|
||||||
if g == .empty { return EMPTY_CHAR; }
|
if g == .empty { return EMPTY_CHAR; }
|
||||||
GEM_CHARS[cast(s64) g]
|
GEM_CHARS[cast(i64) g]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Deterministic RNG ─────────────────────────────────────────────────────
|
// ── Deterministic RNG ─────────────────────────────────────────────────────
|
||||||
// A 32-bit linear congruential generator (Numerical Recipes constants),
|
// 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
|
// 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.
|
// (including 0) yields a valid stream — an LCG has no forbidden state.
|
||||||
RNG_MASK32 :: 0xFFFFFFFF;
|
RNG_MASK32 :: 0xFFFFFFFF;
|
||||||
RNG_MUL :: 1664525;
|
RNG_MUL :: 1664525;
|
||||||
RNG_ADD :: 1013904223;
|
RNG_ADD :: 1013904223;
|
||||||
|
|
||||||
Rng :: struct {
|
Rng :: struct {
|
||||||
state: s64;
|
state: i64;
|
||||||
|
|
||||||
// Advance and return the next 32-bit value.
|
// 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 = (self.state * RNG_MUL + RNG_ADD) & RNG_MASK32;
|
||||||
self.state
|
self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uniform-ish value in [0, n). Uses the high bits, whose period is far
|
// Uniform-ish value in [0, n). Uses the high bits, whose period is far
|
||||||
// longer than the low bits of an LCG.
|
// 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
|
(self.next_u32() >> 16) % n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rng_seeded :: (seed: s64) -> Rng {
|
rng_seeded :: (seed: i64) -> Rng {
|
||||||
Rng.{ state = seed & RNG_MASK32 }
|
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 points (see `score_round`), and `resolve` adds each cascade
|
||||||
// round's base scaled by `combo_multiplier` (P3.2). The HUD (P4.4) reads this
|
// 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.
|
// 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 —
|
// Turn accounting (P3.3). `moves_made` counts the swaps actually COMMITTED —
|
||||||
// only a legal swap (one that resolved into >=1 match) via `commit_swap`
|
// 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
|
// 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
|
// counters can never drift apart. A hand-built board must set both before
|
||||||
// committing swaps.
|
// committing swaps.
|
||||||
moves_made: s64;
|
moves_made: i64;
|
||||||
move_limit: s64;
|
move_limit: i64;
|
||||||
|
|
||||||
// Per-level score goal (P7.1). `init` sets it to DEFAULT_TARGET_SCORE;
|
// 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
|
// `level_status` reads it to decide a win (`score >= target_score`). A
|
||||||
// hand-built board must set this before its status is read.
|
// 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
|
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
|
// 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
|
// committing past the budget — see DEFAULT_MOVE_LIMIT). The turn/goal loop
|
||||||
// (P7) reads this to decide when the game ends.
|
// (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
|
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)]
|
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;
|
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
|
// 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
|
// drawn from the remaining allowed types. At most two types are ever
|
||||||
// excluded, so a choice always remains.
|
// excluded, so a choice always remains.
|
||||||
init :: (self: *Board, seed: s64) {
|
init :: (self: *Board, seed: i64) {
|
||||||
self.rng = rng_seeded(seed);
|
self.rng = rng_seeded(seed);
|
||||||
self.score = 0;
|
self.score = 0;
|
||||||
self.moves_made = 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
|
// 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.
|
// 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 = ---;
|
forbidden : [GEM_COUNT]bool = ---;
|
||||||
for 0..GEM_COUNT (t) { forbidden[t] = false; }
|
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 {
|
if col >= 2 {
|
||||||
left := board.at(col - 1, row);
|
left := board.at(col - 1, row);
|
||||||
if left == board.at(col - 2, 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.
|
// Two same gems immediately above → a third of that type matches.
|
||||||
if row >= 2 {
|
if row >= 2 {
|
||||||
up := board.at(col, row - 1);
|
up := board.at(col, row - 1);
|
||||||
if up == board.at(col, row - 2) {
|
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 {
|
MatchMask :: struct {
|
||||||
cells: [BOARD_CELLS]bool;
|
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)]
|
self.cells[Board.idx(col, row)]
|
||||||
}
|
}
|
||||||
|
|
||||||
count :: (self: *MatchMask) -> s64 {
|
count :: (self: *MatchMask) -> i64 {
|
||||||
n : s64 = 0;
|
n : i64 = 0;
|
||||||
for 0..BOARD_CELLS (i) { if self.cells[i] { n += 1; } }
|
for 0..BOARD_CELLS (i) { if self.cells[i] { n += 1; } }
|
||||||
n
|
n
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ MatchMask :: struct {
|
|||||||
// Mark a closed span of cells along one axis. `vertical` picks the axis; `fixed`
|
// 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
|
// 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.
|
// 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) {
|
for start..end (i) {
|
||||||
if vertical {
|
if vertical {
|
||||||
m.cells[Board.idx(fixed, i)] = true;
|
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
|
// 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.
|
// and the move enumeration speak in (col, row) like the rest of the model.
|
||||||
Cell :: struct {
|
Cell :: struct {
|
||||||
col: s64;
|
col: i64;
|
||||||
row: s64;
|
row: i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange the gems of two cells, in place. `swap` is its own inverse: calling
|
// 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
|
// 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
|
// 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.
|
// `true` per shared cell) clear as one set with no double-counting.
|
||||||
clear_cells :: (board: *Board, mask: *MatchMask) -> s64 {
|
clear_cells :: (board: *Board, mask: *MatchMask) -> i64 {
|
||||||
cleared : s64 = 0;
|
cleared : i64 = 0;
|
||||||
for 0..BOARD_CELLS (i) {
|
for 0..BOARD_CELLS (i) {
|
||||||
if mask.cells[i] {
|
if mask.cells[i] {
|
||||||
board.cells[i] = .empty;
|
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
|
// 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
|
// unchanged. The count drives later cascade/scoring (P2.2+): a non-zero result
|
||||||
// means the board changed and the resolution loop should continue.
|
// means the board changed and the resolution loop should continue.
|
||||||
clear_matches :: (board: *Board) -> s64 {
|
clear_matches :: (board: *Board) -> i64 {
|
||||||
m := find_matches(board);
|
m := find_matches(board);
|
||||||
clear_cells(board, @m)
|
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
|
// 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
|
// `.empty`; afterwards the board has no holes left. Returns the number of cells
|
||||||
// filled (0 on a board that had none).
|
// filled (0 on a board that had none).
|
||||||
refill :: (board: *Board) -> s64 {
|
refill :: (board: *Board) -> i64 {
|
||||||
rng := @board.rng;
|
rng := @board.rng;
|
||||||
filled : s64 = 0;
|
filled : i64 = 0;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
for 0..BOARD_COLS (col) {
|
for 0..BOARD_COLS (col) {
|
||||||
if board.at(col, row) == .empty {
|
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` /
|
// make "did this settle clear a 4 / 5+ run" observable. `had_len4` /
|
||||||
// `had_len5_plus` are the boolean view of the same counts.
|
// `had_len5_plus` are the boolean view of the same counts.
|
||||||
Cascade :: struct {
|
Cascade :: struct {
|
||||||
depth: s64;
|
depth: i64;
|
||||||
cleared: List(s64);
|
cleared: List(i64);
|
||||||
awarded: s64;
|
awarded: i64;
|
||||||
len4: s64;
|
len4: i64;
|
||||||
len5_plus: s64;
|
len5_plus: i64;
|
||||||
|
|
||||||
had_len4 :: (self: *Cascade) -> bool {
|
had_len4 :: (self: *Cascade) -> bool {
|
||||||
self.len4 > 0
|
self.len4 > 0
|
||||||
@@ -546,7 +546,7 @@ Cascade :: struct {
|
|||||||
// number of cells cleared this round — 0 iff the board was already stable, in
|
// 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
|
// which case nothing moves and no gem is drawn. `resolve` repeats this until it
|
||||||
// returns 0.
|
// returns 0.
|
||||||
resolve_step :: (board: *Board) -> s64 {
|
resolve_step :: (board: *Board) -> i64 {
|
||||||
cleared := clear_matches(board);
|
cleared := clear_matches(board);
|
||||||
if cleared == 0 { return 0; }
|
if cleared == 0 { return 0; }
|
||||||
collapse(board);
|
collapse(board);
|
||||||
@@ -560,7 +560,7 @@ resolve_step :: (board: *Board) -> s64 {
|
|||||||
// Each round adds `score_round * combo_multiplier(round)` (round 1-based) to
|
// Each round adds `score_round * combo_multiplier(round)` (round 1-based) to
|
||||||
// `Board.score`; an already-stable board returns depth 0, awards 0, untouched.
|
// `Board.score`; an already-stable board returns depth 0, awards 0, untouched.
|
||||||
resolve :: (board: *Board) -> Cascade {
|
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 {
|
while true {
|
||||||
// Read the round's base points AND its special-match tally while the runs
|
// 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
|
// 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.
|
// vertical one) and the run covers `start..start+len` of the moving coordinate.
|
||||||
Run :: struct {
|
Run :: struct {
|
||||||
vertical: bool;
|
vertical: bool;
|
||||||
fixed: s64;
|
fixed: i64;
|
||||||
start: s64;
|
start: i64;
|
||||||
len: s64;
|
len: i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base points for a single maximal run, by length. Runs are always length >= 3
|
// 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
|
// (shorter spans are not enumerated), so 3 is the floor; 5 and longer all score
|
||||||
// the top tier.
|
// the top tier.
|
||||||
run_score :: (len: s64) -> s64 {
|
run_score :: (len: i64) -> i64 {
|
||||||
if len <= 3 { return SCORE_RUN_3; }
|
if len <= 3 { return SCORE_RUN_3; }
|
||||||
if len == 4 { return SCORE_RUN_4; }
|
if len == 4 { return SCORE_RUN_4; }
|
||||||
SCORE_RUN_5_PLUS
|
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
|
// 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
|
// BEFORE the round's clear, while the runs are still on the board. A board with
|
||||||
// no run scores 0.
|
// no run scores 0.
|
||||||
score_round :: (board: *Board) -> s64 {
|
score_round :: (board: *Board) -> i64 {
|
||||||
runs := find_runs(board);
|
runs := find_runs(board);
|
||||||
total : s64 = 0;
|
total : i64 = 0;
|
||||||
for 0..runs.len (i) {
|
for 0..runs.len (i) {
|
||||||
total += run_score(runs.items[i].len);
|
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
|
// `score` total and return them. The single-round accumulation primitive; the
|
||||||
// cascade loop (`resolve`) instead scales each round by `combo_multiplier`
|
// cascade loop (`resolve`) instead scales each round by `combo_multiplier`
|
||||||
// (P3.2). Neither path changes `score_round`.
|
// (P3.2). Neither path changes `score_round`.
|
||||||
add_round_score :: (board: *Board) -> s64 {
|
add_round_score :: (board: *Board) -> i64 {
|
||||||
points := score_round(board);
|
points := score_round(board);
|
||||||
board.score += points;
|
board.score += points;
|
||||||
points
|
points
|
||||||
@@ -694,7 +694,7 @@ add_round_score :: (board: *Board) -> s64 {
|
|||||||
// multi-round chain strictly beats the same clears scored flat. `resolve`
|
// multi-round chain strictly beats the same clears scored flat. `resolve`
|
||||||
// accumulates `score_round * combo_multiplier(round)` per round into `Board.score`
|
// accumulates `score_round * combo_multiplier(round)` per round into `Board.score`
|
||||||
// and reports the sum as `Cascade.awarded`.
|
// and reports the sum as `Cascade.awarded`.
|
||||||
combo_multiplier :: (round: s64) -> s64 {
|
combo_multiplier :: (round: i64) -> i64 {
|
||||||
round
|
round
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,8 +711,8 @@ combo_multiplier :: (round: s64) -> s64 {
|
|||||||
// "did any occur" lives on `Cascade` (`had_len4` / `had_len5_plus`) for the
|
// "did any occur" lives on `Cascade` (`had_len4` / `had_len5_plus`) for the
|
||||||
// whole settle; a single round reads these counts directly.
|
// whole settle; a single round reads these counts directly.
|
||||||
SpecialCounts :: struct {
|
SpecialCounts :: struct {
|
||||||
len4: s64;
|
len4: i64;
|
||||||
len5_plus: s64;
|
len5_plus: i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the board's currently-matched runs that hit a special length, by the
|
// 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 {
|
PlayerMove :: struct {
|
||||||
legal: bool;
|
legal: bool;
|
||||||
cascade: Cascade;
|
cascade: Cascade;
|
||||||
moves_remaining: s64;
|
moves_remaining: i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt the player's intended swap of two adjacent cells. If the swap is legal
|
// 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.
|
// spent (that is the P7 turn-loop's call) — see DEFAULT_MOVE_LIMIT.
|
||||||
commit_swap :: (board: *Board, a: Cell, b: Cell) -> PlayerMove {
|
commit_swap :: (board: *Board, a: Cell, b: Cell) -> PlayerMove {
|
||||||
if !swap_legal(board, a, b) {
|
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() };
|
return PlayerMove.{ legal = false, cascade = empty, moves_remaining = board.moves_remaining() };
|
||||||
}
|
}
|
||||||
swap(board, a, b);
|
swap(board, a, b);
|
||||||
@@ -900,7 +900,7 @@ reshuffle_if_deadlocked :: (board: *Board) -> bool {
|
|||||||
// seed → identical starting layout), zeroes `score` and `moves_made`, and
|
// seed → identical starting layout), zeroes `score` and `moves_made`, and
|
||||||
// restores the default move budget and score goal, so `level_status` reads
|
// restores the default move budget and score goal, so `level_status` reads
|
||||||
// `in_progress` again. The entry point P7.2's restart button calls.
|
// `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);
|
board.init(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,7 +930,7 @@ TurnResult :: struct {
|
|||||||
play_turn :: (board: *Board, a: Cell, b: Cell) -> TurnResult {
|
play_turn :: (board: *Board, a: Cell, b: Cell) -> TurnResult {
|
||||||
status := level_status(board);
|
status := level_status(board);
|
||||||
if status != .in_progress {
|
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() };
|
frozen := PlayerMove.{ legal = false, cascade = empty, moves_remaining = board.moves_remaining() };
|
||||||
return TurnResult.{ accepted = false, move = frozen, status = status, reshuffled = false };
|
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.
|
// 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.
|
// `tests/easing.sx` pins f(0)=0, f(1)=1, monotonicity, and the cascade ordering.
|
||||||
FALL_STAGGER_MAX :f32: 0.30;
|
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));
|
delay := FALL_STAGGER_MAX * (cast(f32) col / cast(f32) (BOARD_COLS - 1));
|
||||||
window := 1.0 - FALL_STAGGER_MAX;
|
window := 1.0 - FALL_STAGGER_MAX;
|
||||||
lt := (t - delay) / window;
|
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
|
// 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
|
// 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.
|
// 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))
|
(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
|
// 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`
|
// 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.
|
// 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
|
SWAP_ANIM_DUR + cast(f32) k * (CLEAR_ANIM_DUR + FALL_ANIM_DUR) + CLEAR_ANIM_DUR
|
||||||
+ fall_landing_frac(col) * FALL_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
|
// 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.
|
// 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 {
|
clear_diag_span :: (m: *MatchMask) -> ClearDiag {
|
||||||
lo : s64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1;
|
lo : i64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1;
|
||||||
hi : s64 = -1;
|
hi : i64 = -1;
|
||||||
for 0..BOARD_CELLS (i) {
|
for 0..BOARD_CELLS (i) {
|
||||||
if m.cells[i] {
|
if m.cells[i] {
|
||||||
d := (i % BOARD_COLS) + (i / BOARD_COLS);
|
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
|
// 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)
|
// 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.
|
// 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; }
|
if span.hi <= span.lo { return 0.0; }
|
||||||
cast(f32) ((col + row) - span.lo) / cast(f32) (span.hi - span.lo)
|
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 {
|
AnimRound :: struct {
|
||||||
before: [BOARD_CELLS]Gem;
|
before: [BOARD_CELLS]Gem;
|
||||||
matched: MatchMask;
|
matched: MatchMask;
|
||||||
src: [BOARD_CELLS]s64;
|
src: [BOARD_CELLS]i64;
|
||||||
after: [BOARD_CELLS]Gem;
|
after: [BOARD_CELLS]Gem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ AnimMove :: struct {
|
|||||||
pre: [BOARD_CELLS]Gem;
|
pre: [BOARD_CELLS]Gem;
|
||||||
rounds: List(AnimRound);
|
rounds: List(AnimRound);
|
||||||
final: [BOARD_CELLS]Gem;
|
final: [BOARD_CELLS]Gem;
|
||||||
awarded: s64;
|
awarded: i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The most recent round at or before `kmax` that dropped a MOVED gem onto
|
// 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
|
// 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
|
// per-round bounce (render_fall/clear) and the final-settle stamp share this so
|
||||||
// one envelope plays continuously across every seam.
|
// 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;
|
row := i / BOARD_COLS;
|
||||||
k := kmax;
|
k := kmax;
|
||||||
while k >= 0 {
|
while k >= 0 {
|
||||||
@@ -312,7 +312,7 @@ AnimPhaseKind :: enum { swap; clear; fall; done; }
|
|||||||
|
|
||||||
AnimPhase :: struct {
|
AnimPhase :: struct {
|
||||||
kind: AnimPhaseKind;
|
kind: AnimPhaseKind;
|
||||||
round: s64;
|
round: i64;
|
||||||
t: f32;
|
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,
|
// 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
|
// when its clear begins, never re-fired every frame. Reset whenever a move
|
||||||
// (re)starts; advanced by the frame loop as rounds clear.
|
// (re)starts; advanced by the frame loop as rounds clear.
|
||||||
cascade_fired: s64;
|
cascade_fired: i64;
|
||||||
|
|
||||||
init :: (self: *BoardAnim) {
|
init :: (self: *BoardAnim) {
|
||||||
self.active = false;
|
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
|
// 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 +
|
// against `BoardAnim.cascade_fired` to play one cue per newly-cleared round. Pure +
|
||||||
// headless so the per-round playback is snapshot-testable without audio.
|
// 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 num_rounds <= 0 { return 0; }
|
||||||
if elapsed < SWAP_ANIM_DUR { return 0; }
|
if elapsed < SWAP_ANIM_DUR { return 0; }
|
||||||
seg := CLEAR_ANIM_DUR + FALL_ANIM_DUR;
|
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; }
|
if started > num_rounds { return num_rounds; }
|
||||||
started
|
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
|
// 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 —
|
// pastel — the low channel is trimmed while the dominant/mid channel is lifted —
|
||||||
// so every burst pops as a punchier colour without losing luminance.
|
// 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 == 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 == 1 { return Color.{ r = 255, g = 164, b = 44, a = 255 }; }
|
||||||
if i == 2 { return Color.{ r = 255, g = 240, b = 72, 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
|
// lockstep with the cascade SFX cue. Pure arithmetic, OS-agnostic, and the
|
||||||
// equivalence to `cascade_cue_index` is locked headlessly (tests/fx_combo.sx).
|
// equivalence to `cascade_cue_index` is locked headlessly (tests/fx_combo.sx).
|
||||||
FX_COMBO_MAX_LEVEL :: 4; // == audio.sx COMBO_CLIPS - 1
|
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 <= 1 { return 0; }
|
||||||
if depth >= FX_COMBO_MAX_LEVEL + 1 { return FX_COMBO_MAX_LEVEL; }
|
if depth >= FX_COMBO_MAX_LEVEL + 1 { return FX_COMBO_MAX_LEVEL; }
|
||||||
depth - 1
|
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)
|
// 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
|
// 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.
|
// 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; }
|
if depth <= 1 { return FX_POPUP_FONT; }
|
||||||
FX_POPUP_COMBO_FONT + FX_POPUP_COMBO_STEP * cast(f32) (fx_combo_level(depth) - 1)
|
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
|
// 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.
|
// 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; }
|
if depth <= 1 { return FX_POPUP_COLOR; }
|
||||||
t := cast(f32) (fx_combo_level(depth) - 1) / cast(f32) (FX_COMBO_MAX_LEVEL - 1);
|
t := cast(f32) (fx_combo_level(depth) - 1) / cast(f32) (FX_COMBO_MAX_LEVEL - 1);
|
||||||
Color.{
|
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
|
// 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
|
// board_view.load_texture's upload half but takes an in-memory buffer (the
|
||||||
// per-colour tinted particle) instead of a file path.
|
// 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 {
|
if gpu != null {
|
||||||
return xx gpu.create_texture(w, h, .rgba8, xx pixels);
|
return xx gpu.create_texture(w, h, .rgba8, xx pixels);
|
||||||
}
|
}
|
||||||
@@ -128,22 +128,22 @@ BoardFxAssets :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load :: (self: *BoardFxAssets, gpu: ?GPU) {
|
load :: (self: *BoardFxAssets, gpu: ?GPU) {
|
||||||
w : s32 = 0;
|
w : i32 = 0;
|
||||||
h : s32 = 0;
|
h : i32 = 0;
|
||||||
ch : s32 = 0;
|
ch : i32 = 0;
|
||||||
src : [*]u8 = xx stbi_load("assets/fx/particle.png", @w, @h, @ch, 4);
|
src : [*]u8 = xx stbi_load("assets/fx/particle.png", @w, @h, @ch, 4);
|
||||||
if xx src == 0 {
|
if xx src == 0 {
|
||||||
out("WARNING: could not load assets/fx/particle.png\n");
|
out("WARNING: could not load assets/fx/particle.png\n");
|
||||||
self.loaded = false;
|
self.loaded = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
n := cast(s64) w * cast(s64) h;
|
n := cast(i64) w * cast(i64) h;
|
||||||
buf : [*]u8 = xx context.allocator.alloc_bytes(n * 4);
|
buf : [*]u8 = xx context.allocator.alloc_bytes(n * 4);
|
||||||
// Loop locals are hoisted: a block-scoped local declared inside a body
|
// Loop locals are hoisted: a block-scoped local declared inside a body
|
||||||
// that runs hundreds of thousands of times grows the stack per iteration
|
// 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.
|
// (sx codegen), so the per-pixel tint loop only ASSIGNS pre-declared vars.
|
||||||
i : s64 = 0;
|
i : i64 = 0;
|
||||||
o : s64 = 0;
|
o : i64 = 0;
|
||||||
for 0..GEM_COUNT (t) {
|
for 0..GEM_COUNT (t) {
|
||||||
col := fx_tint(t);
|
col := fx_tint(t);
|
||||||
i = 0;
|
i = 0;
|
||||||
@@ -168,7 +168,7 @@ BoardFxAssets :: struct {
|
|||||||
FxParticle :: struct {
|
FxParticle :: struct {
|
||||||
col: f32;
|
col: f32;
|
||||||
row: f32;
|
row: f32;
|
||||||
tint: s64;
|
tint: i64;
|
||||||
delay: f32;
|
delay: f32;
|
||||||
age: f32;
|
age: f32;
|
||||||
life: f32;
|
life: f32;
|
||||||
@@ -184,8 +184,8 @@ FxParticle :: struct {
|
|||||||
FxPopup :: struct {
|
FxPopup :: struct {
|
||||||
col: f32;
|
col: f32;
|
||||||
row: f32;
|
row: f32;
|
||||||
points: s64;
|
points: i64;
|
||||||
depth: s64;
|
depth: i64;
|
||||||
delay: f32;
|
delay: f32;
|
||||||
age: f32;
|
age: f32;
|
||||||
life: f32;
|
life: f32;
|
||||||
@@ -237,7 +237,7 @@ BoardFx :: struct {
|
|||||||
self.particles.append(FxParticle.{
|
self.particles.append(FxParticle.{
|
||||||
col = cast(f32) col + 0.5,
|
col = cast(f32) col + 0.5,
|
||||||
row = cast(f32) row + 0.5,
|
row = cast(f32) row + 0.5,
|
||||||
tint = cast(s64) g,
|
tint = cast(i64) g,
|
||||||
delay = t0 + rdelay,
|
delay = t0 + rdelay,
|
||||||
age = 0.0,
|
age = 0.0,
|
||||||
life = FX_BURST_LIFE,
|
life = FX_BURST_LIFE,
|
||||||
@@ -250,9 +250,9 @@ BoardFx :: struct {
|
|||||||
|
|
||||||
// One popup for the whole move at the first clear's centroid.
|
// One popup for the whole move at the first clear's centroid.
|
||||||
rd0 := @mv.rounds.items[0];
|
rd0 := @mv.rounds.items[0];
|
||||||
sc : s64 = 0;
|
sc : i64 = 0;
|
||||||
sr : s64 = 0;
|
sr : i64 = 0;
|
||||||
cnt : s64 = 0;
|
cnt : i64 = 0;
|
||||||
for 0..BOARD_CELLS (idx) {
|
for 0..BOARD_CELLS (idx) {
|
||||||
if rd0.matched.cells[idx] {
|
if rd0.matched.cells[idx] {
|
||||||
sc += idx % BOARD_COLS;
|
sc += idx % BOARD_COLS;
|
||||||
@@ -275,8 +275,8 @@ BoardFx :: struct {
|
|||||||
// Advance every live FX by `dt` and drop those past their lifetime. Kept
|
// Advance every live FX by `dt` and drop those past their lifetime. Kept
|
||||||
// simple: compact each list in place by overwriting dead entries.
|
// simple: compact each list in place by overwriting dead entries.
|
||||||
tick :: (self: *BoardFx, dt: f32) {
|
tick :: (self: *BoardFx, dt: f32) {
|
||||||
w : s64 = 0;
|
w : i64 = 0;
|
||||||
i : s64 = 0;
|
i : i64 = 0;
|
||||||
while i < self.particles.len {
|
while i < self.particles.len {
|
||||||
p := self.particles.items[i];
|
p := self.particles.items[i];
|
||||||
p.age += dt;
|
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(
|
Frame.make(
|
||||||
self.origin.x + xx col * self.cell_size,
|
self.origin.x + xx col * self.cell_size,
|
||||||
self.origin.y + xx row * 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;
|
fx := (p.x - self.origin.x) / self.cell_size;
|
||||||
fy := (p.y - self.origin.y) / self.cell_size;
|
fy := (p.y - self.origin.y) / self.cell_size;
|
||||||
if fx < 0.0 or fy < 0.0 { return null; }
|
if fx < 0.0 or fy < 0.0 { return null; }
|
||||||
col : s64 = xx fx;
|
col : i64 = xx fx;
|
||||||
row : s64 = xx fy;
|
row : i64 = xx fy;
|
||||||
if col >= BOARD_COLS or row >= BOARD_ROWS { return null; }
|
if col >= BOARD_COLS or row >= BOARD_ROWS { return null; }
|
||||||
Cell.{ col = col, row = row }
|
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;
|
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;
|
u0 : f32 = xx index * self.cell_u;
|
||||||
GemUV.{
|
GemUV.{
|
||||||
uv_min = Point.{ x = u0, y = 0.0 },
|
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
|
// 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.
|
// desktop GL path falls back to a plain GL_TEXTURE_2D.
|
||||||
load_texture :: (path: [:0]u8, gpu: ?GPU) -> u32 {
|
load_texture :: (path: [:0]u8, gpu: ?GPU) -> u32 {
|
||||||
w : s32 = 0;
|
w : i32 = 0;
|
||||||
h : s32 = 0;
|
h : i32 = 0;
|
||||||
ch : s32 = 0;
|
ch : i32 = 0;
|
||||||
pixels := stbi_load(path, @w, @h, @ch, 4);
|
pixels := stbi_load(path, @w, @h, @ch, 4);
|
||||||
if pixels == null {
|
if pixels == null {
|
||||||
out("WARNING: could not load texture: ");
|
out("WARNING: could not load texture: ");
|
||||||
@@ -237,7 +237,7 @@ BoardView :: struct {
|
|||||||
safe: EdgeInsets;
|
safe: EdgeInsets;
|
||||||
// Seed for `restart`: the same fixed seed main seeded the board with, so the
|
// Seed for `restart`: the same fixed seed main seeded the board with, so the
|
||||||
// restart button reproduces the identical starting level.
|
// 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,
|
// 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
|
// set only by the M3TE_FPS env pin); `fps` is the smoothed reciprocal frame
|
||||||
// rate computed in the frame loop. Purely a render overlay.
|
// 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 `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);
|
uv := self.assets.gem_uv(gem_index);
|
||||||
ctx.add_image_uv(gf, self.assets.gems_tex, uv.uv_min, uv.uv_max);
|
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
|
// 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).
|
// 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;
|
cs := self.layout.cell_size;
|
||||||
cx := self.layout.origin.x + cast(f32) col * cs + cs * 0.5;
|
cx := self.layout.origin.x + cast(f32) col * cs + cs * 0.5;
|
||||||
cy := self.layout.origin.y + cast(f32) row * 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
|
// 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
|
// in cell units). A resting pose reproduces gem_frame exactly, so the t==0
|
||||||
// idle pose draws identically to the static sprite.
|
// 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;
|
cs := self.layout.cell_size;
|
||||||
cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs + pose.dx * cs;
|
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;
|
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
|
// — 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
|
// 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.
|
// 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;
|
cs := self.layout.cell_size;
|
||||||
cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs;
|
cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs;
|
||||||
cy := self.layout.origin.y + (frow + 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,
|
// 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
|
// 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.
|
// 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);
|
pose := idle_pose(self.motion.clock, col, row);
|
||||||
|
|
||||||
sq := land_squash(self.motion.land_local(Board.idx(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.
|
// (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
|
// 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.
|
// 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);
|
m := delivering_round(@self.anim.move, i, kmax);
|
||||||
if m < 0 { return 0.0; }
|
if m < 0 { return 0.0; }
|
||||||
col := i % BOARD_COLS;
|
col := i % BOARD_COLS;
|
||||||
@@ -363,7 +363,7 @@ BoardView :: struct {
|
|||||||
if g != .empty {
|
if g != .empty {
|
||||||
pose := self.gem_pose_at(col, row);
|
pose := self.gem_pose_at(col, row);
|
||||||
gf := self.gem_pose_frame(col, row, dim, pose);
|
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 {
|
if g != .empty {
|
||||||
sq := self.rest_squash(i, last, e);
|
sq := self.rest_squash(i, last, e);
|
||||||
gf := self.gem_squash_frame(i % BOARD_COLS, cast(f32) (i / BOARD_COLS), dim, sq);
|
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];
|
g := mv.pre[i];
|
||||||
if g != .empty {
|
if g != .empty {
|
||||||
gf := self.gem_frame(cast(f32) (i % BOARD_COLS), cast(f32) (i / BOARD_COLS), inset, dim);
|
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];
|
ga := mv.pre[ai];
|
||||||
if ga != .empty {
|
if ga != .empty {
|
||||||
gf := self.gem_frame(afc + (bfc - afc) * p, afr + (bfr - afr) * p, inset, dim);
|
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];
|
gb := mv.pre[bi];
|
||||||
if gb != .empty {
|
if gb != .empty {
|
||||||
gf := self.gem_frame(bfc + (afc - bfc) * p, bfr + (afr - bfr) * p, inset, dim);
|
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
|
// Clear segment: matched gems pop outward then collapse to nothing (a
|
||||||
// satisfying pop, composing with the particle burst); the rest hold position.
|
// 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);
|
span := clear_diag_span(@rd.matched);
|
||||||
for 0..BOARD_CELLS (i) {
|
for 0..BOARD_CELLS (i) {
|
||||||
g := rd.before[i];
|
g := rd.before[i];
|
||||||
@@ -507,7 +507,7 @@ BoardView :: struct {
|
|||||||
// reaches scale 0 by t==1, keeping the seam to the fall clean.
|
// 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)));
|
pop := clear_pop_scale(clear_ripple_t(t, clear_rank(span, col, row)));
|
||||||
gf := self.gem_frame_scaled(col, row, dim, pop);
|
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 {
|
} else {
|
||||||
// before[k] is round k-1's settled board, so a survivor here still
|
// 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
|
// 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.
|
// (nothing has fallen yet), keeping that frame byte-identical.
|
||||||
sq := self.rest_squash(i, k - 1, e);
|
sq := self.rest_squash(i, k - 1, e);
|
||||||
gf := self.gem_squash_frame(col, cast(f32) row, dim, sq);
|
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
|
// 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
|
// 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.
|
// 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) {
|
for 0..BOARD_CELLS (i) {
|
||||||
g := rd.after[i];
|
g := rd.after[i];
|
||||||
if g == .empty { continue; }
|
if g == .empty { continue; }
|
||||||
@@ -608,7 +608,7 @@ BoardView :: struct {
|
|||||||
// has reached its cell flattens wide-and-short, then wobbles out.
|
// has reached its cell flattens wide-and-short, then wobbles out.
|
||||||
sq := self.rest_squash(i, k, e);
|
sq := self.rest_squash(i, k, e);
|
||||||
gf := self.gem_squash_frame(col, cur_row, dim, sq);
|
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
|
// 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.
|
// the dark text keeps the digits legible over the light background art.
|
||||||
render_fps_overlay :: (self: *BoardView, ctx: *RenderContext, frame: Frame) {
|
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);
|
txt := format("FPS {}", n);
|
||||||
sz := measure_text(txt, FPS_FONT);
|
sz := measure_text(txt, FPS_FONT);
|
||||||
x := frame.origin.x + self.safe.left + FPS_PAD;
|
x := frame.origin.x + self.safe.left + FPS_PAD;
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ the step asks for an honest accounting.
|
|||||||
|
|
||||||
## Category: Numerics / parsing
|
## Category: Numerics / parsing
|
||||||
|
|
||||||
### 1. String → number parsing (`parse_s64`, `parse_f32`)
|
### 1. String → number parsing (`parse_i64`, `parse_f32`)
|
||||||
- **m3te location:** `main.sx:156` (`parse_s64`), `main.sx:122` (`parse_f32`).
|
- **m3te location:** `main.sx:156` (`parse_i64`), `main.sx:122` (`parse_f32`).
|
||||||
- **What it does:** decimal ASCII `string` → `s64` / `f32` (sign + integer +
|
- **What it does:** decimal ASCII `string` → `i64` / `f32` (sign + integer +
|
||||||
optional fractional part).
|
optional fractional part).
|
||||||
- **Why general-purpose:** the exact inverse of the formatters everyone already
|
- **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
|
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`,
|
- **m3te location:** `board.sx:55` (`Rng` struct: `next_u32` `board.sx:59`,
|
||||||
`next_range` `board.sx:66`), `board.sx:71` (`rng_seeded`), constants
|
`next_range` `board.sx:66`), `board.sx:71` (`rng_seeded`), constants
|
||||||
`board.sx:51-53`.
|
`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)`),
|
and masked to 32 bits — `next_u32()`, `next_range(n)` (uniform-ish `[0,n)`),
|
||||||
`rng_seeded(seed)`.
|
`rng_seeded(seed)`.
|
||||||
- **Why general-purpose:** a seedable, reproducible RNG is a textbook stdlib
|
- **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)
|
## 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.
|
needed, the missing inverse of the existing formatters.
|
||||||
2. **Seedable PRNG** (`Rng` LCG) — textbook stdlib primitive, entirely absent.
|
2. **Seedable PRNG** (`Rng` LCG) — textbook stdlib primitive, entirely absent.
|
||||||
3. **`from_cstr` C-string bridge** (+ public `strlen`) — the most-repeated FFI
|
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
|
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.
|
// 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
|
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);
|
ramp := clamp(t / IDLE_RAMP, 0.0, 1.0);
|
||||||
w := t / IDLE_PERIOD * TAU + gem_idle_phase(col, row);
|
w := t / IDLE_PERIOD * TAU + gem_idle_phase(col, row);
|
||||||
s := IDLE_SCALE_A * sin(w) * ramp;
|
s := IDLE_SCALE_A * sin(w) * ramp;
|
||||||
@@ -135,7 +135,7 @@ GemMotion :: struct {
|
|||||||
for 0..BOARD_CELLS (i) { self.land_at[i] = -1000.0; }
|
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);
|
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
|
// 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
|
// 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.
|
// 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;
|
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]
|
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.
|
// be captured without injecting a tap. Absent → normal live behaviour.
|
||||||
read_env :: (name: [:0]u8) -> ?string {
|
read_env :: (name: [:0]u8) -> ?string {
|
||||||
p := getenv(name);
|
p := getenv(name);
|
||||||
addr : s64 = xx p;
|
addr : i64 = xx p;
|
||||||
if addr == 0 { return null; }
|
if addr == 0 { return null; }
|
||||||
n := cast(s64) strlen(p);
|
n := cast(i64) strlen(p);
|
||||||
if n == 0 { return ""; }
|
if n == 0 { return ""; }
|
||||||
buf := cstring(n);
|
buf := cstring(n);
|
||||||
memcpy(buf.ptr, xx p, n);
|
memcpy(buf.ptr, xx p, n);
|
||||||
buf
|
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/
|
// 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.
|
// 57) to f32 across the comparisons, which mis-types the byte compares.
|
||||||
parse_f32 :: (s: string) -> f32 {
|
parse_f32 :: (s: string) -> f32 {
|
||||||
i : s64 = 0;
|
i : i64 = 0;
|
||||||
neg : bool = false;
|
neg : bool = false;
|
||||||
if s.len > 0 {
|
if s.len > 0 {
|
||||||
c0 : s64 = xx s[0];
|
c0 : i64 = xx s[0];
|
||||||
if c0 == 45 { neg = true; i = 1; } // '-'
|
if c0 == 45 { neg = true; i = 1; } // '-'
|
||||||
}
|
}
|
||||||
intval : s64 = 0;
|
intval : i64 = 0;
|
||||||
while i < s.len {
|
while i < s.len {
|
||||||
c : s64 = xx s[i];
|
c : i64 = xx s[i];
|
||||||
if c < 48 or c > 57 { break; }
|
if c < 48 or c > 57 { break; }
|
||||||
intval = intval * 10 + (c - 48);
|
intval = intval * 10 + (c - 48);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
fracval : s64 = 0;
|
fracval : i64 = 0;
|
||||||
fracdiv : s64 = 1;
|
fracdiv : i64 = 1;
|
||||||
if i < s.len {
|
if i < s.len {
|
||||||
d : s64 = xx s[i];
|
d : i64 = xx s[i];
|
||||||
if d == 46 { // '.'
|
if d == 46 { // '.'
|
||||||
i += 1;
|
i += 1;
|
||||||
while i < s.len {
|
while i < s.len {
|
||||||
c : s64 = xx s[i];
|
c : i64 = xx s[i];
|
||||||
if c < 48 or c > 57 { break; }
|
if c < 48 or c > 57 { break; }
|
||||||
fracval = fracval * 10 + (c - 48);
|
fracval = fracval * 10 + (c - 48);
|
||||||
fracdiv = fracdiv * 10;
|
fracdiv = fracdiv * 10;
|
||||||
@@ -163,11 +163,11 @@ parse_f32 :: (s: string) -> f32 {
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_s64 :: (s: string) -> s64 {
|
parse_i64 :: (s: string) -> i64 {
|
||||||
i : s64 = 0;
|
i : i64 = 0;
|
||||||
v : s64 = 0;
|
v : i64 = 0;
|
||||||
while i < s.len {
|
while i < s.len {
|
||||||
c : s64 = xx s[i];
|
c : i64 = xx s[i];
|
||||||
if c < 48 or c > 57 { break; }
|
if c < 48 or c > 57 { break; }
|
||||||
v = v * 10 + (c - 48);
|
v = v * 10 + (c - 48);
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -405,7 +405,7 @@ main :: () -> void {
|
|||||||
g_motion.clock = parse_f32(t);
|
g_motion.clock = parse_f32(t);
|
||||||
}
|
}
|
||||||
if sc := read_env("M3TE_SELECT") {
|
if sc := read_env("M3TE_SELECT") {
|
||||||
idx := parse_s64(sc);
|
idx := parse_i64(sc);
|
||||||
if idx >= 0 and idx < BOARD_CELLS {
|
if idx >= 0 and idx < BOARD_CELLS {
|
||||||
g_sel.active = true;
|
g_sel.active = true;
|
||||||
g_sel.cell = Cell.{ col = idx % BOARD_COLS, row = idx / BOARD_COLS };
|
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 /
|
// committed golden stay byte-identical. Purely a render overlay — no board /
|
||||||
// score / move / animation state changes and it never gates input.
|
// score / move / animation state changes and it never gates input.
|
||||||
if fp := read_env("M3TE_FPS") {
|
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,
|
// 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") {
|
if fx := read_env("M3TE_FX") {
|
||||||
swaps := legal_swaps(g_board);
|
swaps := legal_swaps(g_board);
|
||||||
if swaps.len > 0 {
|
if swaps.len > 0 {
|
||||||
n := parse_s64(fx);
|
n := parse_i64(fx);
|
||||||
if n < 1 { n = 1; }
|
if n < 1 { n = 1; }
|
||||||
if n > swaps.len { n = swaps.len; }
|
if n > swaps.len { n = swaps.len; }
|
||||||
sw := swaps.items[n - 1];
|
sw := swaps.items[n - 1];
|
||||||
@@ -457,7 +457,7 @@ main :: () -> void {
|
|||||||
if bs := read_env("M3TE_BADSWAP") {
|
if bs := read_env("M3TE_BADSWAP") {
|
||||||
bad := illegal_swaps(g_board);
|
bad := illegal_swaps(g_board);
|
||||||
if bad.len > 0 {
|
if bad.len > 0 {
|
||||||
n := parse_s64(bs);
|
n := parse_i64(bs);
|
||||||
if n < 1 { n = 1; }
|
if n < 1 { n = 1; }
|
||||||
if n > bad.len { n = bad.len; }
|
if n > bad.len { n = bad.len; }
|
||||||
sw := bad.items[n - 1];
|
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_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
|
// M3TE_RESTART set non-zero the board is then restart()-ed, capturing the
|
||||||
// fresh in_progress board the restart button produces.
|
// fresh in_progress board the restart button produces.
|
||||||
if tg := read_env("M3TE_TARGET") { g_board.target_score = parse_s64(tg); }
|
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_s64(ml); }
|
if ml := read_env("M3TE_MOVE_LIMIT") { g_board.move_limit = parse_i64(ml); }
|
||||||
if rs := read_env("M3TE_RESTART") {
|
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));
|
g_pipeline.set_body(closure(build_ui));
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ boards_equal :: (x: *Board, y: *Board) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
fails : s64 = 0;
|
fails : i64 = 0;
|
||||||
|
|
||||||
// ── Legal swap: plan == model, timeline ends on the model ───────────────
|
// ── 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
|
// (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";
|
#import "modules/std.sx";
|
||||||
t :: #import "test.sx";
|
t :: #import "test.sx";
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
t.expect(2 + 2 == 4, "two plus two is four");
|
t.expect(2 + 2 == 4, "two plus two is four");
|
||||||
t.expect(7 % 3 == 1, "seven mod three is one");
|
t.expect(7 % 3 == 1, "seven mod three is one");
|
||||||
t.expect(10 - 4 == 6, "ten minus four is six");
|
t.expect(10 - 4 == 6, "ten minus four is six");
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
|
|
||||||
irect :: (f: Frame) -> string {
|
irect :: (f: Frame) -> string {
|
||||||
format("({},{},{},{})",
|
format("({},{},{},{})",
|
||||||
cast(s64) f.origin.x, cast(s64) f.origin.y,
|
cast(i64) f.origin.x, cast(i64) f.origin.y,
|
||||||
cast(s64) f.size.width, cast(s64) f.size.height)
|
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
|
// 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.
|
// same layout tests/hit_test.sx pins, so the numbers are checkable by hand.
|
||||||
lay : BoardLayout = ---;
|
lay : BoardLayout = ---;
|
||||||
@@ -36,18 +36,18 @@ main :: () -> s32 {
|
|||||||
print("title {}\n", irect(bl.title));
|
print("title {}\n", irect(bl.title));
|
||||||
print("button {}\n", irect(bl.button));
|
print("button {}\n", irect(bl.button));
|
||||||
|
|
||||||
fails : s64 = 0;
|
fails : i64 = 0;
|
||||||
|
|
||||||
// The button is horizontally centered on the grid (centred banner).
|
// The button is horizontally centered on the grid (centred banner).
|
||||||
bcx := bl.button.mid_x();
|
bcx := bl.button.mid_x();
|
||||||
if cast(s64) bcx != cast(s64) grid.mid_x() { fails += 1; }
|
if cast(i64) bcx != cast(i64) grid.mid_x() { fails += 1; }
|
||||||
print("button mid_x {} grid mid_x {}\n", cast(s64) bcx, cast(s64) grid.mid_x());
|
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,
|
// The whole button sits inside the panel — its four corners are contained,
|
||||||
// so it can never spill outside the drawn card.
|
// so it can never spill outside the drawn card.
|
||||||
bx0 := bl.button.origin.x; by0 := bl.button.origin.y;
|
bx0 := bl.button.origin.x; by0 := bl.button.origin.y;
|
||||||
bx1 := bl.button.max_x(); by1 := bl.button.max_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 = bx0, y = by0 }) { corners_in += 1; }
|
||||||
if bl.panel.contains(Point.{ x = bx1, 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; }
|
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.
|
// 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 };
|
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() };
|
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(corner_cell) { off_hits += 1; }
|
||||||
if bl.button.contains(outside) { off_hits += 1; }
|
if bl.button.contains(outside) { off_hits += 1; }
|
||||||
if off_hits != 0 { fails += 1; }
|
if off_hits != 0 { fails += 1; }
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ SEED :: 1337;
|
|||||||
// Count every horizontal or vertical window of three consecutive same-type
|
// Count every horizontal or vertical window of three consecutive same-type
|
||||||
// gems. A correctly initialized board has zero. This walks the finished board
|
// 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.
|
// independently of the placement logic, so it's a real check, not a tautology.
|
||||||
count_three_runs :: (b: *Board) -> s32 {
|
count_three_runs :: (b: *Board) -> i32 {
|
||||||
runs : s32 = 0;
|
runs : i32 = 0;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
for 0..(BOARD_COLS - 2) (col) {
|
for 0..(BOARD_COLS - 2) (col) {
|
||||||
g := b.at(col, row);
|
g := b.at(col, row);
|
||||||
@@ -27,7 +27,7 @@ count_three_runs :: (b: *Board) -> s32 {
|
|||||||
runs
|
runs
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
board : Board = ---;
|
board : Board = ---;
|
||||||
board.init(SEED);
|
board.init(SEED);
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ checker_board :: () -> Board {
|
|||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== cascade (resolution loop) ==\n");
|
print("== cascade (resolution loop) ==\n");
|
||||||
|
|
||||||
// Drive the loop one round at a time so each post-round board is visible in
|
// 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));
|
out(board_dump(@b));
|
||||||
|
|
||||||
depth := 0;
|
depth := 0;
|
||||||
counts := List(s64).{};
|
counts := List(i64).{};
|
||||||
while true {
|
while true {
|
||||||
n := resolve_step(@b);
|
n := resolve_step(@b);
|
||||||
if n == 0 { break; }
|
if n == 0 { break; }
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
#import "audio.sx";
|
#import "audio.sx";
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== cascade cue selection (depth -> combo cue) ==\n");
|
print("== cascade cue selection (depth -> combo cue) ==\n");
|
||||||
|
|
||||||
// Walk a representative depth range (0..9) so both clamps and the monotonic
|
// 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
|
// 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.
|
// the last; 2,3,4 step up one cue at a time.
|
||||||
prev : s64 = -1;
|
prev : i64 = -1;
|
||||||
for 0..10 (depth) {
|
for 0..10 (depth) {
|
||||||
idx := cascade_cue_index(depth);
|
idx := cascade_cue_index(depth);
|
||||||
print("depth {} -> idx {} ({})\n", depth, idx, cascade_cue_name(idx));
|
print("depth {} -> idx {} ({})\n", depth, idx, cascade_cue_name(idx));
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#import "board_anim.sx";
|
#import "board_anim.sx";
|
||||||
#import "audio.sx";
|
#import "audio.sx";
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== per-round cascade cue timing ==\n");
|
print("== per-round cascade cue timing ==\n");
|
||||||
|
|
||||||
// `cascade_rounds_started` = how many cascade rounds have BEGUN clearing by
|
// `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
|
// 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.
|
// each round window so the integer step is unambiguous. Locked for 5 rounds.
|
||||||
print("-- started-count across a 5-round chain --\n");
|
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.00 -> {}\n", cascade_rounds_started(0.00, rounds));
|
||||||
print("e=0.10 -> {}\n", cascade_rounds_started(0.10, rounds));
|
print("e=0.10 -> {}\n", cascade_rounds_started(0.10, rounds));
|
||||||
print("e=0.20 -> {}\n", cascade_rounds_started(0.20, 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
|
// ascending. This IS the loop main's frame loop runs; the emitted run is the
|
||||||
// locked acceptance ordering.
|
// locked acceptance ordering.
|
||||||
print("-- ascending per-round run --\n");
|
print("-- ascending per-round run --\n");
|
||||||
fired : s64 = 0;
|
fired : i64 = 0;
|
||||||
elapsed : f32 = 0.0;
|
elapsed : f32 = 0.0;
|
||||||
while fired < rounds {
|
while fired < rounds {
|
||||||
started := cascade_rounds_started(elapsed, 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
|
// 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,
|
// invariants against the matched-cell set: every flagged cell is now a hole,
|
||||||
// every unflagged cell is unchanged, and the returned count is exact.
|
// 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);
|
b := load_board(rows);
|
||||||
orig := load_board(rows); // pristine copy for the unchanged check
|
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"));
|
t.expect(cleared == want_cleared, concat(name, ": cleared count exact"));
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== clear (detect -> clear) ==\n");
|
print("== clear (detect -> clear) ==\n");
|
||||||
|
|
||||||
// Single horizontal 3-run (row 3, cols 2-4) → three holes there only.
|
// 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"));
|
t.expect(moved == want_moved, concat(name, ": moved flag exact"));
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== collapse (gravity) ==\n");
|
print("== collapse (gravity) ==\n");
|
||||||
|
|
||||||
// Eight independent columns, one case each (top-to-bottom):
|
// 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),
|
// 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.
|
// 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 = ---;
|
b : Board = ---;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
line := 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
|
// 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
|
// `Cascade.awarded` at the same depth. A depth-1 settle must equal the flat sum
|
||||||
// (no bonus); a deeper chain must strictly exceed it.
|
// (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);
|
print("== {} ==\n", name);
|
||||||
b := load_board(rows, seed);
|
b := load_board(rows, seed);
|
||||||
out(board_dump(@b));
|
out(board_dump(@b));
|
||||||
|
|
||||||
flat : s64 = 0;
|
flat : i64 = 0;
|
||||||
mult : s64 = 0;
|
mult : i64 = 0;
|
||||||
depth : s64 = 0;
|
depth : i64 = 0;
|
||||||
while true {
|
while true {
|
||||||
base := score_round(@b);
|
base := score_round(@b);
|
||||||
n := resolve_step(@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"));
|
t.expect(b2.score == want_mult, concat(name, ": resolve accumulates into board.score"));
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== combo (cascade multiplier) ==\n");
|
print("== combo (cascade multiplier) ==\n");
|
||||||
|
|
||||||
// Single-round clear (seed 0): one RRR clears and the refill makes no new
|
// 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 }
|
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 }
|
approx :: (a: f32, b: f32) -> bool { fabs(a - b) < 0.0001 }
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
fails : s64 = 0;
|
fails : i64 = 0;
|
||||||
|
|
||||||
// 1. Endpoints are locked: every curve starts/ends exactly on its rest value
|
// 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).
|
// (the in/out curves at 1, the spring at 1, the squash envelope at 0).
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
#import "board_fx.sx";
|
#import "board_fx.sx";
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== combo emphasis selection (depth -> fx level / popup font) ==\n");
|
print("== combo emphasis selection (depth -> fx level / popup font) ==\n");
|
||||||
|
|
||||||
// The cascade-cue index per depth 0..9, copied from cascade_cue.stdout. The
|
// 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.
|
// 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) {
|
for 0..10 (depth) {
|
||||||
lvl := fx_combo_level(depth);
|
lvl := fx_combo_level(depth);
|
||||||
font := fx_popup_font(depth);
|
font := fx_popup_font(depth);
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
fabs :: (x: f32) -> f32 { if x < 0.0 then 0.0 - x else x }
|
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 }
|
approx :: (a: f32, b: f32) -> bool { fabs(a - b) < 0.0001 }
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
fails : s64 = 0;
|
fails : i64 = 0;
|
||||||
|
|
||||||
// 1. t==0 idle pose is EXACTLY rest for every cell (the determinism invariant).
|
// 1. t==0 idle pose is EXACTLY rest for every cell (the determinism invariant).
|
||||||
print("== idle t=0 is rest for all cells ==\n");
|
print("== idle t=0 is rest for all cells ==\n");
|
||||||
|
|||||||
@@ -14,19 +14,19 @@
|
|||||||
#import "board.sx";
|
#import "board.sx";
|
||||||
#import "board_layout.sx";
|
#import "board_layout.sx";
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
// 800×600 with no safe inset → a 600px square grid, cell 75, centered: the
|
// 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.
|
// grid origin lands at (100, 0). Integer math keeps the dump deterministic.
|
||||||
lay : BoardLayout = ---;
|
lay : BoardLayout = ---;
|
||||||
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
||||||
|
|
||||||
print("grid origin ({},{}) cell {}\n",
|
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.
|
// Every cell center must map back to its own cell.
|
||||||
hits : s64 = 0;
|
hits : i64 = 0;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
for 0..BOARD_COLS (col) {
|
for 0..BOARD_COLS (col) {
|
||||||
cf := lay.cell_frame(col, row);
|
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
|
// A cell's top-left corner belongs to that cell (the leading edge is
|
||||||
// inclusive), so corner-of-(3,5) resolves to (3,5).
|
// 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 := Point.{ x = lay.origin.x + 3.0 * lay.cell_size, y = lay.origin.y + 5.0 * lay.cell_size };
|
||||||
corner_col : s64 = -1;
|
corner_col : i64 = -1;
|
||||||
corner_row : s64 = -1;
|
corner_row : i64 = -1;
|
||||||
if h := lay.point_to_cell(corner) { corner_col = h.col; corner_row = h.row; }
|
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; }
|
if corner_col != 3 or corner_row != 5 { fails += 1; }
|
||||||
print("corner maps to ({},{})\n", corner_col, corner_row);
|
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_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_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 };
|
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_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_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); }
|
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),
|
// 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
|
// seeded RNG, running score zeroed, the turn counters reset to a fresh game, and
|
||||||
// the per-level goal set.
|
// 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 = ---;
|
b : Board = ---;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
line := rows[row];
|
line := rows[row];
|
||||||
@@ -55,7 +55,7 @@ boards_equal :: (a: *Board, b: *Board) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== level (turn / goal state machine) ==\n");
|
print("== level (turn / goal state machine) ==\n");
|
||||||
|
|
||||||
// ── Start: a fresh seeded board reads in_progress with the default goal ──
|
// ── 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
|
// 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
|
// BOARD_COLS gem characters), detect matches, print board + matched dump, and
|
||||||
// assert the matched-cell count.
|
// assert the matched-cell count.
|
||||||
scene :: (name: string, rows: []string, want_count: s64) {
|
scene :: (name: string, rows: []string, want_count: i64) {
|
||||||
b : Board = ---;
|
b : Board = ---;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
line := rows[row];
|
line := rows[row];
|
||||||
@@ -38,7 +38,7 @@ scene :: (name: string, rows: []string, want_count: s64) {
|
|||||||
t.expect(m.count() == want_count, name);
|
t.expect(m.count() == want_count, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
// Single horizontal 3-run (row 3, cols 2-4).
|
// Single horizontal 3-run (row 3, cols 2-4).
|
||||||
scene("horizontal-3", .[
|
scene("horizontal-3", .[
|
||||||
"OGOGOGOG",
|
"OGOGOGOG",
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ load_board :: (rows: []string) -> Board {
|
|||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
count_empties :: (b: *Board) -> s64 {
|
count_empties :: (b: *Board) -> i64 {
|
||||||
n : s64 = 0;
|
n : i64 = 0;
|
||||||
for 0..BOARD_CELLS (i) { if b.cells[i] == .empty { n += 1; } }
|
for 0..BOARD_CELLS (i) { if b.cells[i] == .empty { n += 1; } }
|
||||||
n
|
n
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ fresh_board :: () -> Board {
|
|||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== refill (seeded) ==\n");
|
print("== refill (seeded) ==\n");
|
||||||
|
|
||||||
// Pipeline, snapshotting each stage.
|
// Pipeline, snapshotting each stage.
|
||||||
@@ -123,7 +123,7 @@ main :: () -> s32 {
|
|||||||
// filled, then refill again. The board's RNG has advanced past the first
|
// 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.
|
// fill, so the second fill draws new gems — proof it does NOT reseed per call.
|
||||||
holes_n := 0;
|
holes_n := 0;
|
||||||
hole_idx : [BOARD_CELLS]s64 = ---;
|
hole_idx : [BOARD_CELLS]i64 = ---;
|
||||||
fill1 : [BOARD_CELLS]Gem = ---;
|
fill1 : [BOARD_CELLS]Gem = ---;
|
||||||
for 0..BOARD_CELLS (i) {
|
for 0..BOARD_CELLS (i) {
|
||||||
if pre.cells[i] == .empty {
|
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 one scene: snapshot board + enumerated runs + points, then assert
|
||||||
// `score_round` is exact and `add_round_score` accumulates it into `board.score`.
|
// `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);
|
b := load_board(rows);
|
||||||
runs := find_runs(@b);
|
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"));
|
concat(name, ": add_round_score accumulates into board.score"));
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== score (base match scoring) ==\n");
|
print("== score (base match scoring) ==\n");
|
||||||
|
|
||||||
// Single length-3 horizontal run (row 3, cols 2-4) -> SCORE_RUN_3 = 30.
|
// 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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
cell :: (col: s64, row: s64) -> Cell {
|
cell :: (col: i64, row: i64) -> Cell {
|
||||||
Cell.{ col = col, row = row }
|
Cell.{ col = col, row = row }
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
print("== swap & legality ==\n");
|
print("== swap & legality ==\n");
|
||||||
|
|
||||||
// Board whose ONLY swap-formable match is the adjacent (2,3)<->(3,3)
|
// Board whose ONLY swap-formable match is the adjacent (2,3)<->(3,3)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
SEED :: 1337;
|
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);
|
cf := lay.cell_frame(col, row);
|
||||||
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
||||||
}
|
}
|
||||||
@@ -32,14 +32,14 @@ boards_equal :: (x: *Board, y: *Board) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
// 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A
|
// 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.
|
// 60px drag clears the cell*0.5 = 37.5px swipe threshold on the dominant axis.
|
||||||
lay : BoardLayout = ---;
|
lay : BoardLayout = ---;
|
||||||
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
||||||
D : f32 = 60.0;
|
D : f32 = 60.0;
|
||||||
|
|
||||||
fails : s64 = 0;
|
fails : i64 = 0;
|
||||||
|
|
||||||
// ── ILLEGAL swipe reverts ──────────────────────────────────────────────
|
// ── ILLEGAL swipe reverts ──────────────────────────────────────────────
|
||||||
// (0,0) and (1,0) are both red on the seed board, so swapping them forms no
|
// (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 "board_layout.sx";
|
||||||
#import "swipe.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);
|
cf := lay.cell_frame(col, row);
|
||||||
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the resolved intent (locked in the golden) and report whether it matches
|
// 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.
|
// 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 {
|
if s := got {
|
||||||
print("{}: ({},{})->({},{})\n", label, s.a.col, s.a.row, s.b.col, s.b.row);
|
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;
|
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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
lay : BoardLayout = ---;
|
lay : BoardLayout = ---;
|
||||||
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
|
||||||
print("grid origin ({},{}) cell {} threshold {}\n",
|
print("grid origin ({},{}) cell {} threshold {}\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,
|
||||||
cast(s64) (lay.cell_size * SWIPE_THRESHOLD_FRACTION));
|
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.
|
// A known interior cell; every cardinal swipe from it stays on the board.
|
||||||
start := cell_center(@lay, 3, 5);
|
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
|
// 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.
|
// 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 = ---;
|
b : Board = ---;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
line := rows[row];
|
line := rows[row];
|
||||||
@@ -52,13 +52,13 @@ load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) ->
|
|||||||
b
|
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);
|
cf := lay.cell_frame(col, row);
|
||||||
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
Point.{ x = cf.mid_x(), y = cf.mid_y() }
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> i32 {
|
||||||
fails : s64 = 0;
|
fails : i64 = 0;
|
||||||
|
|
||||||
// 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A 60px
|
// 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
|
// 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),
|
// 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
|
// seeded RNG, running score zeroed, and the turn counters reset to a fresh game
|
||||||
// (no moves made, the given move budget).
|
// (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 = ---;
|
b : Board = ---;
|
||||||
for 0..BOARD_ROWS (row) {
|
for 0..BOARD_ROWS (row) {
|
||||||
line := 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
|
// 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
|
// runs and assert the tallies (and the boolean flags derived from them) are
|
||||||
// exactly the documented values. No RNG, no clear — pure detection.
|
// 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);
|
print("== {} ==\n", name);
|
||||||
b := load_board(rows, 0, LIMIT);
|
b := load_board(rows, 0, LIMIT);
|
||||||
out(board_dump(@b));
|
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"));
|
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");
|
print("== turn (accounting + special-match flagging) ==\n");
|
||||||
|
|
||||||
// ── Special-match flagging (single round, no RNG) ──────────────────────
|
// ── 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
|
LUM_MARGIN :: 4; // lum headroom above the light checker shade
|
||||||
|
|
||||||
is_gray :: (r: u8, g: u8, b: u8) -> bool {
|
is_gray :: (r: u8, g: u8, b: u8) -> bool {
|
||||||
hi := max(max(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(s64) r, cast(s64) g), cast(s64) b);
|
lo := min(min(cast(i64) r, cast(i64) g), cast(i64) b);
|
||||||
hi - lo <= GRAY_TOL
|
hi - lo <= GRAY_TOL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark pixel `i` as removed background and queue it, if it is unvisited checker
|
// 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).
|
// (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; }
|
if bg[i] != 0 { return; }
|
||||||
p := i * 4;
|
p := i * 4;
|
||||||
if lum[i] <= lim and is_gray(src[p], src[p+1], src[p+2]) {
|
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 {
|
main :: () -> i32 {
|
||||||
w : s32 = 0;
|
w : i32 = 0;
|
||||||
h : s32 = 0;
|
h : i32 = 0;
|
||||||
ch : s32 = 0;
|
ch : i32 = 0;
|
||||||
src : [*]u8 = xx stbi_load(SRC_PATH, @w, @h, @ch, 4);
|
src : [*]u8 = xx stbi_load(SRC_PATH, @w, @h, @ch, 4);
|
||||||
if xx src == 0 {
|
if xx src == 0 {
|
||||||
print("FATAL: could not load {}\n", SRC_PATH);
|
print("FATAL: could not load {}\n", SRC_PATH);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
W := cast(s64) w;
|
W := cast(i64) w;
|
||||||
H := cast(s64) h;
|
H := cast(i64) h;
|
||||||
N := W * H;
|
N := W * H;
|
||||||
print("loaded {}x{} ({} src channels)\n", w, h, ch);
|
print("loaded {}x{} ({} src channels)\n", w, h, ch);
|
||||||
|
|
||||||
// Hoisted working locals (see codegen note above).
|
// Hoisted working locals (see codegen note above).
|
||||||
y : s64 = 0;
|
y : i64 = 0;
|
||||||
x : s64 = 0;
|
x : i64 = 0;
|
||||||
i : s64 = 0;
|
i : i64 = 0;
|
||||||
p : s64 = 0;
|
p : i64 = 0;
|
||||||
r : s64 = 0;
|
r : i64 = 0;
|
||||||
g : s64 = 0;
|
g : i64 = 0;
|
||||||
b : s64 = 0;
|
b : i64 = 0;
|
||||||
l : s64 = 0;
|
l : i64 = 0;
|
||||||
|
|
||||||
// Per-pixel luminance, plus the checker shades read off the border ring
|
// Per-pixel luminance, plus the checker shades read off the border ring
|
||||||
// (the border is pure checker — the glow never reaches the corners).
|
// (the border is pure checker — the glow never reaches the corners).
|
||||||
lum : [*]s64 = xx context.allocator.alloc_bytes(N * size_of(s64));
|
lum : [*]i64 = xx context.allocator.alloc_bytes(N * size_of(i64));
|
||||||
c_lo : s64 = 255;
|
c_lo : i64 = 255;
|
||||||
c_hi : s64 = 0;
|
c_hi : i64 = 0;
|
||||||
y = 0;
|
y = 0;
|
||||||
while y < H {
|
while y < H {
|
||||||
x = 0;
|
x = 0;
|
||||||
@@ -104,8 +104,8 @@ main :: () -> s32 {
|
|||||||
// border pixel. `bg[i]==1` marks a removed (transparent) background pixel.
|
// border pixel. `bg[i]==1` marks a removed (transparent) background pixel.
|
||||||
bg : [*]u8 = xx context.allocator.alloc_bytes(N);
|
bg : [*]u8 = xx context.allocator.alloc_bytes(N);
|
||||||
memset(xx bg, 0, N);
|
memset(xx bg, 0, N);
|
||||||
stack : [*]s64 = xx context.allocator.alloc_bytes(N * size_of(s64));
|
stack : [*]i64 = xx context.allocator.alloc_bytes(N * size_of(i64));
|
||||||
sp : s64 = 0;
|
sp : i64 = 0;
|
||||||
checker_lim := c_hi + LUM_MARGIN;
|
checker_lim := c_hi + LUM_MARGIN;
|
||||||
|
|
||||||
x = 0;
|
x = 0;
|
||||||
@@ -121,12 +121,12 @@ main :: () -> s32 {
|
|||||||
y += 1;
|
y += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cx : s64 = 0;
|
cx : i64 = 0;
|
||||||
cy : s64 = 0;
|
cy : i64 = 0;
|
||||||
dx : s64 = 0;
|
dx : i64 = 0;
|
||||||
dy : s64 = 0;
|
dy : i64 = 0;
|
||||||
nx : s64 = 0;
|
nx : i64 = 0;
|
||||||
ny : s64 = 0;
|
ny : i64 = 0;
|
||||||
while sp > 0 {
|
while sp > 0 {
|
||||||
sp -= 1;
|
sp -= 1;
|
||||||
i = stack[sp];
|
i = stack[sp];
|
||||||
@@ -154,8 +154,8 @@ main :: () -> s32 {
|
|||||||
denom := cast(f32) (255 - c_hi);
|
denom := cast(f32) (255 - c_hi);
|
||||||
if denom < 1.0 { denom = 1.0; }
|
if denom < 1.0 { denom = 1.0; }
|
||||||
alpha : [*]f32 = xx context.allocator.alloc_bytes(N * size_of(f32));
|
alpha : [*]f32 = xx context.allocator.alloc_bytes(N * size_of(f32));
|
||||||
kept : s64 = 0;
|
kept : i64 = 0;
|
||||||
n_bg : s64 = 0;
|
n_bg : i64 = 0;
|
||||||
a : f32 = 0.0;
|
a : f32 = 0.0;
|
||||||
i = 0;
|
i = 0;
|
||||||
while i < N {
|
while i < N {
|
||||||
@@ -178,26 +178,26 @@ main :: () -> s32 {
|
|||||||
sxf := cast(f32) W / cast(f32) OUT_DIM;
|
sxf := cast(f32) W / cast(f32) OUT_DIM;
|
||||||
syf := cast(f32) H / cast(f32) OUT_DIM;
|
syf := cast(f32) H / cast(f32) OUT_DIM;
|
||||||
max_a : f32 = 0.0;
|
max_a : f32 = 0.0;
|
||||||
ty : s64 = 0;
|
ty : i64 = 0;
|
||||||
tx : s64 = 0;
|
tx : i64 = 0;
|
||||||
x0 : s64 = 0;
|
x0 : i64 = 0;
|
||||||
x1 : s64 = 0;
|
x1 : i64 = 0;
|
||||||
y0 : s64 = 0;
|
y0 : i64 = 0;
|
||||||
y1 : s64 = 0;
|
y1 : i64 = 0;
|
||||||
sum : f32 = 0.0;
|
sum : f32 = 0.0;
|
||||||
cnt : s64 = 0;
|
cnt : i64 = 0;
|
||||||
sy : s64 = 0;
|
sy : i64 = 0;
|
||||||
sx : s64 = 0;
|
sx : i64 = 0;
|
||||||
av : f32 = 0.0;
|
av : f32 = 0.0;
|
||||||
o : s64 = 0;
|
o : i64 = 0;
|
||||||
ty = 0;
|
ty = 0;
|
||||||
while ty < OUT_DIM {
|
while ty < OUT_DIM {
|
||||||
tx = 0;
|
tx = 0;
|
||||||
while tx < OUT_DIM {
|
while tx < OUT_DIM {
|
||||||
x0 = cast(s64) (cast(f32) tx * sxf);
|
x0 = cast(i64) (cast(f32) tx * sxf);
|
||||||
x1 = cast(s64) (cast(f32) (tx + 1) * sxf);
|
x1 = cast(i64) (cast(f32) (tx + 1) * sxf);
|
||||||
y0 = cast(s64) (cast(f32) ty * syf);
|
y0 = cast(i64) (cast(f32) ty * syf);
|
||||||
y1 = cast(s64) (cast(f32) (ty + 1) * syf);
|
y1 = cast(i64) (cast(f32) (ty + 1) * syf);
|
||||||
if x1 <= x0 { x1 = x0 + 1; }
|
if x1 <= x0 { x1 = x0 + 1; }
|
||||||
if y1 <= y0 { y1 = y0 + 1; }
|
if y1 <= y0 { y1 = y0 + 1; }
|
||||||
sum = 0.0;
|
sum = 0.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user