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
+39
-3
@@ -49,6 +49,10 @@ First **Snake**: a 20 x 20 board, a snake that moves one cell per tick, apples t
|
||||
|
||||
Then, in the capstone (Module 11), **Pong**: you convert a second game on your own, applying a recipe you have practised through the whole course.
|
||||
|
||||
A bonus module (Module 12) comes back to testing: you build a small property-based testing tool out of your own random generator and use it to find bugs that example tests miss.
|
||||
|
||||
A second capstone (Module 13) converts **Conway's Game of Life**, played with the mouse: a new kind of input, and the first change to the edge of the program since Module 9. Along the way, Module 10 also gives you a **replay viewer** that plays back a recorded game of Snake, forwards and backwards.
|
||||
|
||||
### The big picture
|
||||
|
||||
Here is the whole program you are going to build. Keep coming back to this picture; every module fills in one box.
|
||||
@@ -126,16 +130,43 @@ dotnet --version # 10.x
|
||||
|
||||
An editor with language support helps a lot: VS Code with the Metals extension (Scala) or Ionide (F#), or IntelliJ / Rider. For JavaScript any editor is fine.
|
||||
|
||||
#### If your editor understands types (JavaScript, optional)
|
||||
|
||||
Scala and F# check the shape of your data before the program runs: pass a direction where a point is expected and the compiler refuses. Plain JavaScript only finds out when that line runs, often far from the mistake. You can get much of the compiler's help in JavaScript without installing anything and without changing how the course works.
|
||||
|
||||
The `js/` folder contains two given files for this: `js/jsconfig.json` switches on type checking for the files in `js/src/`, and `js/src/types.d.ts` writes down the course's data types (`GameState`, `Action`, `IO`, `Stream` and the rest; the model types themselves are the `@typedef` comments at the top of `model.js`). Every function in `src/` carries one extra line in its doc comment that names its type, for example:
|
||||
|
||||
```js
|
||||
/** One step of the game. ...
|
||||
* @type {(state: GameState) => GameState} */
|
||||
export const advance = (state) => todo('m03')
|
||||
```
|
||||
|
||||
The `@type` line is a comment: Node ignores it, and the tests do not care whether it is there. An editor with TypeScript support reads it. **VS Code** does this out of the box: open the repository folder and the JS files in `js/src/` are checked as you type. A misspelled field (`state.nextdir`), a forgotten `null` check on `state.apple`, or a `Turn` built without its direction gets a red underline, and hovering over a function shows its type. The untouched stubs show no errors; red only appears where your code disagrees with the types.
|
||||
|
||||
From the command line, the same check is one command (it needs the network the first time, because `npx` downloads TypeScript into npm's cache, not into the repository):
|
||||
|
||||
```sh
|
||||
npx -y -p typescript tsc -p js/jsconfig.json # no output means no type errors
|
||||
```
|
||||
|
||||
Helpers you add yourself have no `@type` line, so the checker guesses their types from their bodies, and sometimes it guesses too wide. The usual case: a helper that returns the pair `[outputs, next]` is read as "an array of outputs-or-streams", not as a pair, and the export that uses it gets a red line although the code is right. Give such a helper a `@type` line of its own, written like the ones on the exports, for example `/** @type {(ms: number, last: number) => Stream<number>} */`; the names it uses come from the `@import` line at the top of the file.
|
||||
|
||||
This is a convenience, not a requirement. The tests remain the judge of whether your code is right; the type checker only catches a class of mistakes earlier. If you do not use VS Code or do not want the help, ignore both files. Module 2 shows the one trick worth knowing: how to make the checker tell you about a forgotten `case`.
|
||||
|
||||
> **From everyday programming:** this is the "linter in the editor" experience: problems are underlined while you type instead of being reported by a failing run. TypeScript projects get the same checks from `.ts` files; here the types live in comments, so the files stay plain JavaScript that Node runs as they are.
|
||||
|
||||
### A tour of the repository
|
||||
|
||||
```
|
||||
README.md front page
|
||||
MENTOR.md instructions for an AI mentor (see "Solo or with a mentor")
|
||||
course/ this text: 00-setup.md ... 11-capstone-pong.md, reference.md, method.md
|
||||
course/ this text: 00-setup.md ... 13-capstone-life.md (plus the optional 07b), reference.md, method.md
|
||||
fixtures/ shared test cases as JSON, used by all three tracks
|
||||
js/ src/ YOUR code: stubs to fill in, one file per topic
|
||||
solutions/ reference solutions, same file names (try first!)
|
||||
test/ tests, one file per module: m03-rules.test.js, ...
|
||||
jsconfig.json, src/types.d.ts optional type checking (see "If your editor understands types")
|
||||
scala/ src/ solutions/ test/ (same idea; files are PascalCase: Rules.scala)
|
||||
fsharp/ src/ solutions/ tests/ (same idea: Rules.fs)
|
||||
scripts/serve.mjs a tiny web server for playing the game in the browser
|
||||
@@ -155,14 +186,17 @@ Each module tells you, in its header, which files you will edit. The map is the
|
||||
| 01 | `util.js` | `Util.scala` | `Util.fs` |
|
||||
| 02 | `model.js` | `Model.scala` | `Model.fs` |
|
||||
| 03 | `rules.js` | `Rules.scala` | `Rules.fs` |
|
||||
| 04 | `random.js`, `spawn.js` | `Random.scala`, `Spawn.scala` | `Random.fs`, `Spawn.fs` |
|
||||
| 04 | `random.js`, `spawn.js` (optional: `state.js`) | `Random.scala`, `Spawn.scala` (optional: `State.scala`) | `Random.fs`, `Spawn.fs` (optional: `State.fs`) |
|
||||
| 05 | `game.js` | `Game.scala` | `Game.fs` |
|
||||
| 06 | `view.js` | `View.scala` | `View.fs` |
|
||||
| 07 | `io.js` | `IO.scala` | `IO.fs` |
|
||||
| 07b (optional) | `safeio.js` | `SafeIO.scala` | `SafeIO.fs` |
|
||||
| 08 | `stream.js` | `Stream.scala` | `Stream.fs` |
|
||||
| 09 | `input.js`, `app.js`, `runtime.js`, `browser.js` (`main.js` is given) | `Input.scala`, `App.scala`, `Runtime.scala` | `Input.fs`, `App.fs`, `Runtime.fs` |
|
||||
| 10 | edits `game`, `input`, `app` | same | same |
|
||||
| 10 | edits `game`, `input`, `stream`, `app`, `io`, `runtime` (JS: also `browser.js`); new: `replay.js` (`replay-main.js` is given) | same, without the browser file; new: `Replay.scala` | same, without the browser file; new: `Replay.fs` |
|
||||
| 11 | `pong/` | `pong/` | `Pong/` |
|
||||
| 12 (bonus) | `check.js` | `Check.scala` | `Check.fs` |
|
||||
| 13 | `life/` (`life/main.js` is given); edits `runtime.js`, `browser.js` | `life/`; edits `Runtime.scala` | `Life/`; edits `Runtime.fs` |
|
||||
|
||||
### Running the tests
|
||||
|
||||
@@ -284,6 +318,8 @@ You can play the game once Module 9 is done. For reference, this is how:
|
||||
- **Scala:** `cd scala && ./build-web.sh` (the first run downloads the Scala.js linker), then serve as above and open http://localhost:8000/scala/
|
||||
- **F#:** `cd fsharp && ./build-web.sh` (the first run restores the Fable compiler), serve, and open http://localhost:8000/fsharp/
|
||||
|
||||
The other pages sit next to `index.html` in each track's folder: `pong.html` (after Module 11), `replay.html` (the replay viewer, after Exercise 10.8) and `life.html` (after Module 13).
|
||||
|
||||
## Exercises
|
||||
|
||||
### Exercise 0.1 — Green lights
|
||||
|
||||
Reference in new issue
Block a user