P7.1: turn / goal state machine (pure sx)

Add a thin, deterministic level loop over the headless model in board.sx:

- target_score: per-level score goal (DEFAULT_TARGET_SCORE=1500), seeded by init.
- Status (in_progress/won/lost) derived purely from score/target/move budget via
  level_status; won is checked before lost so meeting the goal on the final move
  wins. status_name for the HUD/snapshots.
- has_legal_swap: allocation-free deadlock probe (first legal pair short-circuits).
- reshuffle: Fisher-Yates over existing gems via the board's seeded RNG until the
  arrangement has no immediate match and at least one legal move; consumes no
  move, terminates via MAX_RESHUFFLE_TRIES.
- restart: reseed a fresh, reproducible level (resets cells/score/moves/goal).
- play_turn / TurnResult: commit_swap then reshuffle on deadlock while in
  progress, reporting the resulting status.

tests/level.sx (golden tests/expected/level.{stdout,exit}) asserts: start
in_progress; one legal swap crossing a low goal -> won transition; budget
exhausted below an unreachable goal -> lost transition; a provably deadlocked
diagonal Latin-square board ((col-row) mod 6) reshuffles to >=1 legal move with
no immediate match and no move spent; restart resets progress and a fixed seed
reproduces the same starting board.

Gate: sx build --target ios-sim main.sx (exit 0); bash tools/run_tests.sh
(17 passed, 0 failed).
This commit is contained in:
swipelab
2026-06-05 08:25:08 +03:00
parent 7e82c34a1f
commit a40a994ae1
4 changed files with 411 additions and 0 deletions

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,74 @@
== level (turn / goal state machine) ==
== start-in-progress ==
RRPPOGRG
PGPOPRRO
YYBBYRYB
GBYYRGGP
OGBRRORY
BYRRPRBG
YOYYROBB
OROBPPRB
score 0 target 1500 moves_remaining 30 status in_progress
== win-transition ==
RROGOGOG
GGROGOGO
OGOGOGOG
GOGOGOGO
OGOGOGOG
GOGOGOGO
OGOGOGOG
GOGOGOGO
before: score 0 target 30 moves_remaining 5 status in_progress
after: legal true awarded 30 reshuffled false
after: score 30 moves_remaining 4 status won
RBRGOGOG
GGOOGOGO
OGOGOGOG
GOGOGOGO
OGOGOGOG
GOGOGOGO
OGOGOGOG
GOGOGOGO
== lose-transition ==
RROGOGOG
GGROGOGO
OGOGOGOG
GOGOGOGO
OGOGOGOG
GOGOGOGO
OGOGOGOG
GOGOGOGO
before: score 0 target 1000000 moves_remaining 1 status in_progress
after: legal true score 30 moves_remaining 0 status lost
== deadlock-reshuffle ==
ROYGBPRO
PROYGBPR
BPROYGBP
GBPROYGB
YGBPROYG
OYGBPROY
ROYGBPRO
PROYGBPR
before: matches 0 legal_swaps 0 has_legal_swap false
reshuffled true matches 0 legal_swaps 9
after: score 0 moves_made 0
BGGYORYR
RRYGOPBY
YRYBPRGB
OOBGBPRG
RPRPYRPO
OBBPOOPG
OBGGOPGY
YPRYBORP
== restart ==
dirty: score 500 moves_made 7 status in_progress
after restart: score 0 moves_made 0 moves_remaining 30 status in_progress
RRPPOGRG
PGPOPRRO
YYBBYRYB
GBYYRGGP
OGBRRORY
BYRRPRBG
YOYYROBB
OROBPPRB
ok: level / turn-goal state machine