fix: final review findings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-29 01:43:48 +01:00
co-authored by Claude Opus 5.5
parent 82f47fb3ee
commit b46a3611af
7 changed files with 23 additions and 11 deletions
+4
View File
@@ -5,6 +5,10 @@ obj/
.scala-build/
.bsp/
*/web/dist/
.bloop/
.metals/
.idea/
.vscode/
# build outputs
.fable/
+3 -3
View File
@@ -44,9 +44,9 @@ To play the game in any track you also need a modern browser. [Module 0](course/
Run the tests (from the repository root):
```sh
cd js && node --test "test/m03-*.test.js" # JavaScript, one module (all: ./test.sh)
cd scala && ./test.sh M03 # Scala, one module (all: ./test.sh)
cd fsharp && ./test.sh M03 # F#, one module (all: ./test.sh)
(cd js && node --test "test/m03-*.test.js") # JavaScript, one module (all: ./test.sh)
(cd scala && ./test.sh M03) # Scala, one module (all: ./test.sh)
(cd fsharp && ./test.sh M03) # F#, one module (all: ./test.sh)
```
In a fresh checkout, Module 0's tests pass and every other module's tests fail: the files in `src/` are stubs for you to fill in.
+2
View File
@@ -189,6 +189,8 @@ cd fsharp
./test.sh # everything
```
If `scala-cli` prints a hint that the munit dependency is outdated, ignore it: the course pins munit on purpose, for reproducibility.
Try it now with Module 0, which has a few sanity tests that already pass. They check that your toolchain works and that the fixtures folder can be read. If you see three green tests (Scala and F#: a short summary with no failures), you are set up.
### What a red test means
+1 -1
View File
@@ -195,7 +195,7 @@ Our `view : GameState → Scene` is exactly this, for a canvas instead of HTML.
## Exercises
> **About test names.** The test titles quoted under "Proved by" are the JavaScript ones. The Scala and F# suites test the same things one-to-one, but their titles use each track's vocabulary: "events" instead of "inputs", `None` instead of `null`, `()` instead of `undefined`, `bind`/`choose` instead of `flatMap`/`filterMap`, `IO.map throwingF io` instead of `map(io, throwingF)`, and so on. Failing tests are reported by name when you run the module's tests; to read all titles, open `scala/test/M06View.test.scala` or `fsharp/tests/M06View.fs`. Fixture-based tests have the same case names in every track.
> **About test names.** The test titles quoted under "Proved by" are the JavaScript ones, which carry an `m06` prefix (e.g. `m06 CELL is 20 pixels`). The Scala and F# suites test the same things one-to-one, without that prefix: `CELL is 20 pixels` is identical, and the fixture-driven cases appear as `view: <case name>` with the same case names in every track. Failing tests are reported by name when you run the module's tests; to read all titles, open `scala/test/M06View.test.scala` or `fsharp/tests/M06View.fs`.
### Exercise 6.1 — `view`
+5 -5
View File
@@ -229,11 +229,11 @@ Remember section 12 of Module 7: the loop only helps if each continuation does a
That's the whole runtime. Go back to the stateful code at the start of Module 8, with its `let state` and logic in callbacks, and compare. There, the mutable variable held the *game state*, and game logic lived in the callbacks. Here, the variable holds the *current stream* — the runtime doesn't even know there's a game. It has no idea what a snake, a tick or a score is. All the game logic is in pure values; the runtime is a few lines of plumbing. That is what "pushing effects to the edge" means in practice.
```
┌──────────────────────── runtime (impure, ~20 lines) ─────────────────────────┐
browser ──key──▶│ feed(KeyDown key): [ios, next] = stream.step(ev); stream = next; ios.forEach(runIO) │──▶ canvas
──time─▶│ feed(Time t) │
└───────────────────────────────────────────────────────────────────────────────┘
▲ the one `let` / `var` / ref cell: stream
┌────────────────────────────── runtime (impure, ~20 lines) ─────────────────────────────────┐
browser ──key──▶│ feed(KeyDown key): [ios, next] = stream.step(ev); stream = next; ios.forEach(runIO) │
──time─▶│ feed(Time t) │
└────────────────────────────────────────────────────────────────────────────────────────────┘
▲ the one `let` / `var` / ref cell: stream
```
The tests describe the behaviour precisely; read them before writing the code:
+7 -1
View File
@@ -29,7 +29,13 @@ let private violations (code: string) =
let private fsFiles () =
if Directory.Exists srcDir then
Directory.GetFiles(srcDir, "*.fs", SearchOption.AllDirectories) |> Array.sort |> List.ofArray
Directory.GetFiles(srcDir, "*.fs", SearchOption.AllDirectories)
|> Array.filter (fun f ->
// skip generated output: any path component named bin or obj
let rel = Path.GetRelativePath(srcDir, f)
not (rel.Split([| '/'; '\\' |]) |> Array.exists (fun d -> d = "bin" || d = "obj")))
|> Array.sort
|> List.ofArray
else
[]
+1 -1
View File
@@ -27,4 +27,4 @@ createServer(async (req, res) => {
res.writeHead(e.code === 'EACCES' ? 403 : 404, { 'content-type': 'text/plain' })
res.end(e.code === 'EACCES' ? 'forbidden' : 'not found')
}
}).listen(port, () => console.log(`Serving ${root} on http://localhost:${port}`))
}).listen(port, '127.0.0.1', () => console.log(`Serving ${root} on http://localhost:${port}`))