P3.2: cascade combo multiplier (pure sx)
Scale each cascade round's base points by combo_multiplier(round) = the 1-based round index (round 1 x1, round 2 x2, ...), so deeper chains pay out more. resolve now reads score_round before each clear, accumulates score_round * combo_multiplier(round) into Board.score, and reports the settle's payout as the new Cascade.awarded field. A depth-1 settle scores exactly its base (x1, no bonus); any multi-round chain strictly exceeds the same clears scored flat. resolve_step keeps its signature (no scoring), so cascade.sx and its golden are unchanged; score_round/add_round_score are untouched, so score.sx is unchanged. New tests/combo.sx golden locks exact cumulative scores for a single-round clear (30), the P2.4 cascade board (flat 60 -> mult 90), and a controlled 3-round chain (flat 90 -> mult 180), printing per-round base/multiplier/points so the golden self-explains.
This commit is contained in:
57
board.sx
57
board.sx
@@ -88,10 +88,10 @@ Board :: struct {
|
||||
// must seed this before any draw.
|
||||
rng: Rng;
|
||||
|
||||
// Running score total (P3.1). `init` zeroes it; `add_round_score` accumulates
|
||||
// a round's base points (see `score_round`). The cascade-wide combo MULTIPLIER
|
||||
// off `Cascade.depth` lands in P3.2, and the HUD (P4.4) reads this field. A
|
||||
// hand-built board must zero this before accumulating.
|
||||
// Running score total. `init` zeroes it; `add_round_score` adds a single
|
||||
// round's base points (see `score_round`), and `resolve` adds each cascade
|
||||
// round's base scaled by `combo_multiplier` (P3.2). The HUD (P4.4) reads this
|
||||
// field. A hand-built board must zero this before accumulating.
|
||||
score: s64;
|
||||
|
||||
idx :: (col: s64, row: s64) -> s64 {
|
||||
@@ -474,11 +474,14 @@ refill :: (board: *Board) -> s64 {
|
||||
// Outcome of resolving a board to a stable state. `depth` is the number of
|
||||
// rounds that found and cleared at least one match (0 for an already-stable
|
||||
// board). `cleared` holds those rounds' cleared-cell counts in round order, so
|
||||
// `cleared.len == depth`; P3 scores each round off this list and reads the
|
||||
// combo multiplier from the depth.
|
||||
// `cleared.len == depth`. `awarded` is the total points this settle added to
|
||||
// `Board.score`: the sum over rounds of `score_round * combo_multiplier(round)`
|
||||
// (P3.2), so the HUD (P4.4) and turn accounting (P3.3) can read a swap's payout
|
||||
// without re-deriving it. A depth-0 (already-stable) board awards 0.
|
||||
Cascade :: struct {
|
||||
depth: s64;
|
||||
cleared: List(s64);
|
||||
awarded: s64;
|
||||
}
|
||||
|
||||
// One resolution round: detect matches and, if any, clear them, collapse under
|
||||
@@ -494,16 +497,24 @@ resolve_step :: (board: *Board) -> s64 {
|
||||
cleared
|
||||
}
|
||||
|
||||
// Resolve the board to a stable state, running rounds until one finds no match.
|
||||
// Returns the cascade: its depth and per-round cleared-cell counts. An
|
||||
// already-stable board returns depth 0 with an empty `cleared` list, untouched.
|
||||
// Resolve the board to a stable state, running rounds until one finds no match,
|
||||
// scoring each round with the cascade combo multiplier (P3.2). Returns the
|
||||
// cascade: its depth, per-round cleared-cell counts, and total `awarded` points.
|
||||
// Each round adds `score_round * combo_multiplier(round)` (round 1-based) to
|
||||
// `Board.score`; an already-stable board returns depth 0, awards 0, untouched.
|
||||
resolve :: (board: *Board) -> Cascade {
|
||||
result := Cascade.{ depth = 0, cleared = List(s64).{} };
|
||||
result := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0 };
|
||||
while true {
|
||||
// Read the round's base points while its runs are still on the board:
|
||||
// `resolve_step` clears them, so the score has to be taken first.
|
||||
base := score_round(board);
|
||||
n := resolve_step(board);
|
||||
if n == 0 { break; }
|
||||
result.cleared.append(n);
|
||||
result.depth += 1;
|
||||
points := base * combo_multiplier(result.depth);
|
||||
board.score += points;
|
||||
result.awarded += points;
|
||||
result.cleared.append(n);
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -521,7 +532,8 @@ resolve :: (board: *Board) -> Cascade {
|
||||
// corner) scores horizontal + vertical — the corner counts toward both runs'
|
||||
// lengths, unlike the cleared-cell set which unions it once.
|
||||
//
|
||||
// One round only: the cross-round combo MULTIPLIER (off `Cascade.depth`) is P3.2.
|
||||
// One round only: the cross-round combo MULTIPLIER is `combo_multiplier` (P3.2),
|
||||
// applied by `resolve`; this base scheme is unscaled.
|
||||
SCORE_RUN_3 :: 30;
|
||||
SCORE_RUN_4 :: 60;
|
||||
SCORE_RUN_5_PLUS :: 100;
|
||||
@@ -602,16 +614,29 @@ score_round :: (board: *Board) -> s64 {
|
||||
total
|
||||
}
|
||||
|
||||
// Add this round's base points to the board's running `score` total and return
|
||||
// them. The accumulation primitive the HUD (P4.4) reads and that P3.2 wraps with
|
||||
// the cascade combo multiplier — P3.2 layers the per-round multiplier here / in
|
||||
// the resolve loop without changing `score_round`.
|
||||
// Add this round's base points (×1, no combo multiplier) to the board's running
|
||||
// `score` total and return them. The single-round accumulation primitive; the
|
||||
// cascade loop (`resolve`) instead scales each round by `combo_multiplier`
|
||||
// (P3.2). Neither path changes `score_round`.
|
||||
add_round_score :: (board: *Board) -> s64 {
|
||||
points := score_round(board);
|
||||
board.score += points;
|
||||
points
|
||||
}
|
||||
|
||||
// ── Combo multiplier (P3.2) ────────────────────────────────────────────────
|
||||
// Across one swap's cascade, each resolution round's base points (`score_round`)
|
||||
// are scaled by a multiplier that grows with chain depth, so deeper chains pay
|
||||
// out more. The scheme: the 1-based round index IS the multiplier — round 1 ×1,
|
||||
// round 2 ×2, round 3 ×3, … A single-round settle (depth 1) therefore scores
|
||||
// exactly its base (×1, no bonus); every round past the first is amplified, so a
|
||||
// multi-round chain strictly beats the same clears scored flat. `resolve`
|
||||
// accumulates `score_round * combo_multiplier(round)` per round into `Board.score`
|
||||
// and reports the sum as `Cascade.awarded`.
|
||||
combo_multiplier :: (round: s64) -> s64 {
|
||||
round
|
||||
}
|
||||
|
||||
// Deterministic textual dump of an enumerated run list, in `find_runs` order: a
|
||||
// count header, then one run per line as `<axis> len <n> at fixed <f> start <s>`
|
||||
// where axis is H (horizontal) or V (vertical). An empty list dumps as just
|
||||
|
||||
Reference in New Issue
Block a user