feat(course): audit fixes and twelve extensions (Modules 7b, 12, 13)
Audit pass: - Frame rule counts frames since the last move, so every step lasts exactly movesEvery(score) frames, also right after a speed-up - clampTime + MAX_GAP_MS: a hidden tab no longer replays the whole gap - Pong Restart keeps held keys - Fact corrections (mulberry32 output coverage, Haskell IO, uniform) - New fixture cases from a mutation analysis Extensions (all three tracks, shared fixtures, course text): - Turn queue (Exercise 10.6), persisted high score (10.7), replay viewer (10.8), play() end-to-end helper (9.5) - Optional State (4.7) and optional Module 7b, stack-safe IO - Module 12, property-based testing with generators, shrinking, invariants and a model-based oracle - Module 13, Conway's Life with a Click event - JS type checking via jsconfig, JSDoc and types.d.ts - Maintainer tools: scripts/mutate.mjs, scripts/typecheck-js.sh, Playwright end-to-end suite (e2e/, scripts/e2e.sh) Docs: docs/contract.md is the canonical contract; specs and build log under docs/superpowers. Verified: JS 728, Scala 689, F# 691 unit tests; stubs and progression M01-M13 and M07b; 176 end-to-end tests on Chromium and WebKit. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
876f5aabb1
commit
edd57f0148
253 files changed
+21753
-503
No files matched your search
@@ -163,7 +163,7 @@ The check compared against a direction the snake *had not moved in yet*. The sna
|
||||
|
||||
That is why the model has two directions (Module 2, question 3): `dir` is a fact about the body (where the neck is), `nextDir` is a wish. Turns are validated against the fact. The fixture `turn-against-last-moved-dir` is exactly this situation: `dir = Right`, `nextDir = Up`, turn `Left`, expected result: unchanged.
|
||||
|
||||
Notice the consequences of the rule: several allowed turns within one tick overwrite each other, and "the last allowed one wins" (fixture `turn-replaces-pending-turn`: with `dir = Right` and `nextDir = Up`, pressing `Down` gives `nextDir = Down`, which is allowed because Down is not the opposite of Right). And the player's U-turn intention (Up, then Left) is partly lost: only Up happens. Some games keep a small queue of pending turns to honour both; see the questions at the end.
|
||||
Notice the consequences of the rule: several allowed turns within one tick overwrite each other, and "the last allowed one wins" (fixture `turn-replaces-pending-turn`: with `dir = Right` and `nextDir = Up`, pressing `Down` gives `nextDir = Down`, which is allowed because Down is not the opposite of Right). And the player's U-turn intention (Up, then Left) is partly lost: only Up happens. Some games keep a small queue of pending turns to honour both; see question 4 at the end, and Module 10, where you build one (Exercise 10.6) without changing `turn`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -441,11 +441,12 @@ let advance (state: GameState) : GameState =
|
||||
1. Why does `turn` return the state unchanged when paused, instead of throwing? Who would have to deal with the exception, and how would that change the rest of the program?
|
||||
2. Without looking: on a 5 x 5 board, the snake is `(1,1) (1,2) (2,2) (2,1)` heading Up with `nextDir = Right` and an apple at (2,1). What does `advance` return? Now the same with no apple.
|
||||
3. Explain to a friend, with a drawing, why `turn` must compare against `dir` and not `nextDir`. What sequence of key presses kills the snake in the wrong version?
|
||||
4. With the correct `turn`, the player presses Up then Left within one tick, intending a quick U-turn, and only Up happens. Sketch a different model that would honour both presses. What would you add to `GameState`, and how would `advance` change?
|
||||
4. With the correct `turn`, the player presses Up then Left within one tick, intending a quick U-turn, and only Up happens. Sketch a different model that would honour both presses. What would you add to `GameState`, and how would `advance` change? (Keep your sketch: Module 10, Exercise 10.6 builds the course's answer, and you can compare. Note that the course's version leaves `turn` and `advance` exactly as they are here.)
|
||||
5. After `advance` crashes into a wall, the snake is unchanged but `dir` is updated. What invariant does that keep? Would anything visible break if `dir` were left unchanged?
|
||||
6. `advance` does not place a new apple after eating. Why is it a good idea to keep that out of `advance`? (Think about what `advance` would need as input.)
|
||||
7. Predict: `advance(advance(s))` where `s` is a `GameOver` state. And `turn(advance(s), 'Up')` for the same `s`.
|
||||
8. Why are the fixture boards 5 x 5 rather than the real 20 x 20? What does that tell you about the decision to store `width` and `height` in the state?
|
||||
9. Build this state by hand: a two-cell snake `(1,1) (0,1)` with `dir = Right` and `nextDir = Left`, no apple. What does `advance` return, by the rules of this module? Is that a bug in `advance`? Explain why no sequence of `turn` and `advance` calls from a new game can ever produce such a state. (A statement like "`nextDir` is never the opposite of `dir`" is called an *invariant*; Module 12 checks invariants on thousands of random games.)
|
||||
|
||||
## In production
|
||||
|
||||
|
||||
Reference in new issue
Block a user