feat: overlay manifests for the two reference courses
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c78c7ec8d8
commit
b8c65626f9
@@ -0,0 +1,558 @@
|
||||
schema: 1
|
||||
id: pure-snake
|
||||
title: Pure Snake
|
||||
pitch: Build Snake as pure functions, then wire it to the world with IO and event streams, in JavaScript, Scala 3 or F#.
|
||||
visibility: public
|
||||
root: /home/user/functional-programming-course
|
||||
tracks:
|
||||
- { id: js, label: JavaScript }
|
||||
- { id: scala, label: Scala 3 }
|
||||
- { id: fsharp, label: F# }
|
||||
mentor: MENTOR.md
|
||||
source: { kind: authored }
|
||||
modules:
|
||||
- id: m00
|
||||
title: "Setup and how to use this course"
|
||||
lessons:
|
||||
- { id: m00-text, title: "Setup and how to use this course", type: text, file: "course/00-setup.md" }
|
||||
- id: m00-ex-green-lights
|
||||
title: "Exercise 0.1 — Green lights"
|
||||
type: exercise
|
||||
file: "course/00-setup.md"
|
||||
anchor: exercise-01--green-lights
|
||||
hints: { file: "course/00-setup.md", match: "Exercise 0.1" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m00-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M00"
|
||||
fsharp: "cd fsharp && ./test.sh M00"
|
||||
- id: m00-ex-read-a-red-test
|
||||
title: "Exercise 0.2 — Read a red test"
|
||||
type: exercise
|
||||
file: "course/00-setup.md"
|
||||
anchor: exercise-02--read-a-red-test
|
||||
hints: { file: "course/00-setup.md", match: "Exercise 0.2" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m00-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M00"
|
||||
fsharp: "cd fsharp && ./test.sh M00"
|
||||
- id: m00-ex-break-the-purity-audit-on-purpose
|
||||
title: "Exercise 0.3 — Break the purity audit on purpose"
|
||||
type: exercise
|
||||
file: "course/00-setup.md"
|
||||
anchor: exercise-03--break-the-purity-audit-on-purpose
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m00-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M00"
|
||||
fsharp: "cd fsharp && ./test.sh M00"
|
||||
- id: m01
|
||||
title: "Purity and immutability"
|
||||
lessons:
|
||||
- { id: m01-text, title: "Purity and immutability", type: text, file: "course/01-purity-and-immutability.md" }
|
||||
- id: m01-ex-range
|
||||
title: "Exercise 1.1 — `range`"
|
||||
type: exercise
|
||||
file: "course/01-purity-and-immutability.md"
|
||||
anchor: exercise-11--range
|
||||
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.1 (range)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m01-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M01"
|
||||
fsharp: "cd fsharp && ./test.sh M01"
|
||||
- id: m01-ex-droplast
|
||||
title: "Exercise 1.2 — `dropLast`"
|
||||
type: exercise
|
||||
file: "course/01-purity-and-immutability.md"
|
||||
anchor: exercise-12--droplast
|
||||
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.2 (dropLast)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m01-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M01"
|
||||
fsharp: "cd fsharp && ./test.sh M01"
|
||||
- id: m01-ex-contains
|
||||
title: "Exercise 1.3 — `contains`"
|
||||
type: exercise
|
||||
file: "course/01-purity-and-immutability.md"
|
||||
anchor: exercise-13--contains
|
||||
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.3 (contains)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m01-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M01"
|
||||
fsharp: "cd fsharp && ./test.sh M01"
|
||||
- id: m01-ex-pipe
|
||||
title: "Exercise 1.4 — `pipe` (JS only)"
|
||||
type: exercise
|
||||
file: "course/01-purity-and-immutability.md"
|
||||
anchor: exercise-14--pipe-js-only
|
||||
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.4 (pipe)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m01-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M01"
|
||||
fsharp: "cd fsharp && ./test.sh M01"
|
||||
- id: m01-ex-compose
|
||||
title: "Exercise 1.5 — `compose` (JS only)"
|
||||
type: exercise
|
||||
file: "course/01-purity-and-immutability.md"
|
||||
anchor: exercise-15--compose-js-only
|
||||
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.5 (compose)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m01-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M01"
|
||||
fsharp: "cd fsharp && ./test.sh M01"
|
||||
- id: m01-ex-deepfreeze
|
||||
title: "Exercise 1.6 — `deepFreeze` (JS only)"
|
||||
type: exercise
|
||||
file: "course/01-purity-and-immutability.md"
|
||||
anchor: exercise-16--deepfreeze-js-only
|
||||
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.6 (deepFreeze)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m01-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M01"
|
||||
fsharp: "cd fsharp && ./test.sh M01"
|
||||
- id: m02
|
||||
title: "The game as data"
|
||||
lessons:
|
||||
- { id: m02-text, title: "The game as data", type: text, file: "course/02-the-game-as-data.md" }
|
||||
- id: m02-ex-your-own-design
|
||||
title: "Exercise 2.0 — Your own design (paper)"
|
||||
type: exercise
|
||||
file: "course/02-the-game-as-data.md"
|
||||
anchor: exercise-20--your-own-design-paper
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m02-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M02"
|
||||
fsharp: "cd fsharp && ./test.sh M02"
|
||||
- id: m02-ex-point
|
||||
title: "Exercise 2.1 — `point` and `pointEquals` (JS)"
|
||||
type: exercise
|
||||
file: "course/02-the-game-as-data.md"
|
||||
anchor: exercise-21--point-and-pointequals-js
|
||||
hints: { file: "course/02-the-game-as-data.md", match: "Exercise 2.1 (pointEquals)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m02-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M02"
|
||||
fsharp: "cd fsharp && ./test.sh M02"
|
||||
- id: m02-ex-delta
|
||||
title: "Exercise 2.2 — `delta`"
|
||||
type: exercise
|
||||
file: "course/02-the-game-as-data.md"
|
||||
anchor: exercise-22--delta
|
||||
hints: { file: "course/02-the-game-as-data.md", match: "Exercises 2.2 and 2.3 (delta, opposite)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m02-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M02"
|
||||
fsharp: "cd fsharp && ./test.sh M02"
|
||||
- id: m02-ex-opposite
|
||||
title: "Exercise 2.3 — `opposite`"
|
||||
type: exercise
|
||||
file: "course/02-the-game-as-data.md"
|
||||
anchor: exercise-23--opposite
|
||||
hints: { file: "course/02-the-game-as-data.md", match: "Exercises 2.2 and 2.3 (delta, opposite)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m02-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M02"
|
||||
fsharp: "cd fsharp && ./test.sh M02"
|
||||
- id: m02-ex-action
|
||||
title: "Exercise 2.4 — `Action` (JS)"
|
||||
type: exercise
|
||||
file: "course/02-the-game-as-data.md"
|
||||
anchor: exercise-24--action-js
|
||||
hints: { file: "course/02-the-game-as-data.md", match: "Exercise 2.4 (Action)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m02-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M02"
|
||||
fsharp: "cd fsharp && ./test.sh M02"
|
||||
- id: m02-ex-initialstate
|
||||
title: "Exercise 2.5 — `initialState`"
|
||||
type: exercise
|
||||
file: "course/02-the-game-as-data.md"
|
||||
anchor: exercise-25--initialstate
|
||||
hints: { file: "course/02-the-game-as-data.md", match: "Exercise 2.5 (initialState)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m02-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M02"
|
||||
fsharp: "cd fsharp && ./test.sh M02"
|
||||
- id: m03
|
||||
title: "Pure game rules"
|
||||
lessons:
|
||||
- { id: m03-text, title: "Pure game rules", type: text, file: "course/03-pure-game-rules.md" }
|
||||
- id: m03-ex-move
|
||||
title: "Exercise 3.1 — `move`"
|
||||
type: exercise
|
||||
file: "course/03-pure-game-rules.md"
|
||||
anchor: exercise-31--move
|
||||
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.1 (move)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m03-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M03"
|
||||
fsharp: "cd fsharp && ./test.sh M03"
|
||||
- id: m03-ex-inbounds
|
||||
title: "Exercise 3.2 — `inBounds`"
|
||||
type: exercise
|
||||
file: "course/03-pure-game-rules.md"
|
||||
anchor: exercise-32--inbounds
|
||||
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.2 (inBounds)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m03-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M03"
|
||||
fsharp: "cd fsharp && ./test.sh M03"
|
||||
- id: m03-ex-turn
|
||||
title: "Exercise 3.3 — `turn`"
|
||||
type: exercise
|
||||
file: "course/03-pure-game-rules.md"
|
||||
anchor: exercise-33--turn
|
||||
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.3 (turn)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m03-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M03"
|
||||
fsharp: "cd fsharp && ./test.sh M03"
|
||||
- id: m03-ex-advance
|
||||
title: "Exercise 3.4 — `advance`"
|
||||
type: exercise
|
||||
file: "course/03-pure-game-rules.md"
|
||||
anchor: exercise-34--advance
|
||||
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.4 (advance)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m03-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M03"
|
||||
fsharp: "cd fsharp && ./test.sh M03"
|
||||
- id: m04
|
||||
title: "Randomness without side effects"
|
||||
lessons:
|
||||
- { id: m04-text, title: "Randomness without side effects", type: text, file: "course/04-randomness-without-side-effects.md" }
|
||||
- id: m04-ex-nextuint32
|
||||
title: "Exercise 4.1 — `nextUint32`"
|
||||
type: exercise
|
||||
file: "course/04-randomness-without-side-effects.md"
|
||||
anchor: exercise-41--nextuint32
|
||||
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.1 (nextUint32)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m04-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M04"
|
||||
fsharp: "cd fsharp && ./test.sh M04"
|
||||
- id: m04-ex-pure
|
||||
title: "Exercise 4.2 — `pure`, `map`, `flatMap`, `run`"
|
||||
type: exercise
|
||||
file: "course/04-randomness-without-side-effects.md"
|
||||
anchor: exercise-42--pure-map-flatmap-run
|
||||
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.2 (pure, map, flatMap, run)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m04-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M04"
|
||||
fsharp: "cd fsharp && ./test.sh M04"
|
||||
- id: m04-ex-intbelow
|
||||
title: "Exercise 4.3 — `intBelow`"
|
||||
type: exercise
|
||||
file: "course/04-randomness-without-side-effects.md"
|
||||
anchor: exercise-43--intbelow
|
||||
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.3 (intBelow)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m04-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M04"
|
||||
fsharp: "cd fsharp && ./test.sh M04"
|
||||
- id: m04-ex-freecells
|
||||
title: "Exercise 4.4 — `freeCells`"
|
||||
type: exercise
|
||||
file: "course/04-randomness-without-side-effects.md"
|
||||
anchor: exercise-44--freecells
|
||||
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.4 (freeCells)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m04-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M04"
|
||||
fsharp: "cd fsharp && ./test.sh M04"
|
||||
- id: m04-ex-spawnapple
|
||||
title: "Exercise 4.5 — `spawnApple`"
|
||||
type: exercise
|
||||
file: "course/04-randomness-without-side-effects.md"
|
||||
anchor: exercise-45--spawnapple
|
||||
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.5 (spawnApple)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m04-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M04"
|
||||
fsharp: "cd fsharp && ./test.sh M04"
|
||||
- id: m04-ex-tick
|
||||
title: "Exercise 4.6 — `tick` and `newGame`"
|
||||
type: exercise
|
||||
file: "course/04-randomness-without-side-effects.md"
|
||||
anchor: exercise-46--tick-and-newgame
|
||||
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.6 (tick, newGame)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m04-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M04"
|
||||
fsharp: "cd fsharp && ./test.sh M04"
|
||||
- id: m05
|
||||
title: "Folding time"
|
||||
lessons:
|
||||
- { id: m05-text, title: "Folding time", type: text, file: "course/05-folding-time.md" }
|
||||
- id: m05-ex-update
|
||||
title: "Exercise 5.1 — `update`"
|
||||
type: exercise
|
||||
file: "course/05-folding-time.md"
|
||||
anchor: exercise-51--update
|
||||
hints: { file: "course/05-folding-time.md", match: "Exercise 5.1 (update)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m05-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M05"
|
||||
fsharp: "cd fsharp && ./test.sh M05"
|
||||
- id: m05-ex-replay
|
||||
title: "Exercise 5.2 — `replay`"
|
||||
type: exercise
|
||||
file: "course/05-folding-time.md"
|
||||
anchor: exercise-52--replay
|
||||
hints: { file: "course/05-folding-time.md", match: "Exercise 5.2 (replay)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m05-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M05"
|
||||
fsharp: "cd fsharp && ./test.sh M05"
|
||||
- id: m05-ex-history
|
||||
title: "Exercise 5.3 — `history`"
|
||||
type: exercise
|
||||
file: "course/05-folding-time.md"
|
||||
anchor: exercise-53--history
|
||||
hints: { file: "course/05-folding-time.md", match: "Exercise 5.3 (history)" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m05-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M05"
|
||||
fsharp: "cd fsharp && ./test.sh M05"
|
||||
- id: m06
|
||||
title: "Rendering as data"
|
||||
lessons:
|
||||
- { id: m06-text, title: "Rendering as data", type: text, file: "course/06-rendering-as-data.md" }
|
||||
- id: m06-ex-view
|
||||
title: "Exercise 6.1 — `view`"
|
||||
type: exercise
|
||||
file: "course/06-rendering-as-data.md"
|
||||
anchor: exercise-61--view
|
||||
hints: { file: "course/06-rendering-as-data.md", match: "Exercise 6.1" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m06-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M06"
|
||||
fsharp: "cd fsharp && ./test.sh M06"
|
||||
- id: m06-ex-a-second-interpreter
|
||||
title: "Exercise 6.2 (optional, untested) — a second interpreter"
|
||||
type: exercise
|
||||
file: "course/06-rendering-as-data.md"
|
||||
anchor: exercise-62-optional-untested--a-second-interpreter
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m06-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M06"
|
||||
fsharp: "cd fsharp && ./test.sh M06"
|
||||
- id: m07
|
||||
title: "The IO type"
|
||||
lessons:
|
||||
- { id: m07-text, title: "The IO type", type: text, file: "course/07-the-io-type.md" }
|
||||
- id: m07-ex-pure
|
||||
title: "Exercise 7.1 — `pure` and `effect`"
|
||||
type: exercise
|
||||
file: "course/07-the-io-type.md"
|
||||
anchor: exercise-71--pure-and-effect
|
||||
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.1" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m07-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M07"
|
||||
fsharp: "cd fsharp && ./test.sh M07"
|
||||
- id: m07-ex-map
|
||||
title: "Exercise 7.2 — `map` and `flatMap`"
|
||||
type: exercise
|
||||
file: "course/07-the-io-type.md"
|
||||
anchor: exercise-72--map-and-flatmap
|
||||
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.2" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m07-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M07"
|
||||
fsharp: "cd fsharp && ./test.sh M07"
|
||||
- id: m07-ex-sequence
|
||||
title: "Exercise 7.3 — `sequence`, `draw`, `randomSeed`, `drawScene`"
|
||||
type: exercise
|
||||
file: "course/07-the-io-type.md"
|
||||
anchor: exercise-73--sequence-draw-randomseed-drawscene
|
||||
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.3" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m07-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M07"
|
||||
fsharp: "cd fsharp && ./test.sh M07"
|
||||
- id: m07-ex-trace
|
||||
title: "Exercise 7.4 — `trace`, the pure interpreter"
|
||||
type: exercise
|
||||
file: "course/07-the-io-type.md"
|
||||
anchor: exercise-74--trace-the-pure-interpreter
|
||||
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.4" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m07-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M07"
|
||||
fsharp: "cd fsharp && ./test.sh M07"
|
||||
- id: m08
|
||||
title: "Event streams"
|
||||
lessons:
|
||||
- { id: m08-text, title: "Event streams", type: text, file: "course/08-event-streams.md" }
|
||||
- id: m08-ex-run
|
||||
title: "Exercise 8.1 — `run`"
|
||||
type: exercise
|
||||
file: "course/08-event-streams.md"
|
||||
anchor: exercise-81--run
|
||||
hints: { file: "course/08-event-streams.md", match: "Exercise 8.1" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m08-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M08"
|
||||
fsharp: "cd fsharp && ./test.sh M08"
|
||||
- id: m08-ex-keydowns
|
||||
title: "Exercise 8.2 — sources: `keyDowns`, `keyUps`, `interval`"
|
||||
type: exercise
|
||||
file: "course/08-event-streams.md"
|
||||
anchor: exercise-82--sources-keydowns-keyups-interval
|
||||
hints: { file: "course/08-event-streams.md", match: "Exercise 8.2" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m08-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M08"
|
||||
fsharp: "cd fsharp && ./test.sh M08"
|
||||
- id: m08-ex-map
|
||||
title: "Exercise 8.3 — `map`, `filter`, `filterMap`"
|
||||
type: exercise
|
||||
file: "course/08-event-streams.md"
|
||||
anchor: exercise-83--map-filter-filtermap
|
||||
hints: { file: "course/08-event-streams.md", match: "Exercise 8.3 / 8.4" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m08-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M08"
|
||||
fsharp: "cd fsharp && ./test.sh M08"
|
||||
- id: m08-ex-merge
|
||||
title: "Exercise 8.4 — `merge`, `scan`, `startWith`"
|
||||
type: exercise
|
||||
file: "course/08-event-streams.md"
|
||||
anchor: exercise-84--merge-scan-startwith
|
||||
hints: { file: "course/08-event-streams.md", match: "Exercise 8.4" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m08-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M08"
|
||||
fsharp: "cd fsharp && ./test.sh M08"
|
||||
- id: m09
|
||||
title: "Wiring it together"
|
||||
lessons:
|
||||
- { id: m09-text, title: "Wiring it together", type: text, file: "course/09-wiring-it-together.md" }
|
||||
- id: m09-ex-keytoaction
|
||||
title: "Exercise 9.1 — `keyToAction`"
|
||||
type: exercise
|
||||
file: "course/09-wiring-it-together.md"
|
||||
anchor: exercise-91--keytoaction
|
||||
hints: { file: "course/09-wiring-it-together.md", match: "Exercise 9.1" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m09-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M09"
|
||||
fsharp: "cd fsharp && ./test.sh M09"
|
||||
- id: m09-ex-classicactions
|
||||
title: "Exercise 9.2 — the app: `classicActions`, `states`, `render`, `main`"
|
||||
type: exercise
|
||||
file: "course/09-wiring-it-together.md"
|
||||
anchor: exercise-92--the-app-classicactions-states-render-main
|
||||
hints: { file: "course/09-wiring-it-together.md", match: "Exercise 9.2" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m09-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M09"
|
||||
fsharp: "cd fsharp && ./test.sh M09"
|
||||
- id: m09-ex-runio
|
||||
title: "Exercise 9.3 — the runtime: `runIO` and `runApp`"
|
||||
type: exercise
|
||||
file: "course/09-wiring-it-together.md"
|
||||
anchor: exercise-93--the-runtime-runio-and-runapp
|
||||
hints: { file: "course/09-wiring-it-together.md", match: "Exercise 9.3" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m09-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M09"
|
||||
fsharp: "cd fsharp && ./test.sh M09"
|
||||
- id: m09-ex-browserplatform
|
||||
title: "Exercise 9.4 (JS only) — `browserPlatform`"
|
||||
type: exercise
|
||||
file: "course/09-wiring-it-together.md"
|
||||
anchor: exercise-94-js-only--browserplatform
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m09-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M09"
|
||||
fsharp: "cd fsharp && ./test.sh M09"
|
||||
- id: m10
|
||||
title: "Extensions, all pure"
|
||||
lessons:
|
||||
- { id: m10-text, title: "Extensions, all pure", type: text, file: "course/10-extensions.md" }
|
||||
- id: m10-ex-movesevery
|
||||
title: "Exercise 10.1 — `movesEvery`"
|
||||
type: exercise
|
||||
file: "course/10-extensions.md"
|
||||
anchor: exercise-101--movesevery
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m10-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M10"
|
||||
fsharp: "cd fsharp && ./test.sh M10"
|
||||
- id: m10-ex-update
|
||||
title: "Exercise 10.2 — `update`: `TogglePause`, `Restart`, `Frame`"
|
||||
type: exercise
|
||||
file: "course/10-extensions.md"
|
||||
anchor: exercise-102--update-togglepause-restart-frame
|
||||
hints: { file: "course/10-extensions.md", match: "Exercise 10.2" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m10-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M10"
|
||||
fsharp: "cd fsharp && ./test.sh M10"
|
||||
- id: m10-ex-two-more-keys
|
||||
title: "Exercise 10.3 — two more keys"
|
||||
type: exercise
|
||||
file: "course/10-extensions.md"
|
||||
anchor: exercise-103--two-more-keys
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m10-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M10"
|
||||
fsharp: "cd fsharp && ./test.sh M10"
|
||||
- id: m10-ex-the-frame-clock
|
||||
title: "Exercise 10.4 — the frame clock"
|
||||
type: exercise
|
||||
file: "course/10-extensions.md"
|
||||
anchor: exercise-104--the-frame-clock
|
||||
hints: { file: "course/10-extensions.md", match: "Exercise 10.4" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m10-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M10"
|
||||
fsharp: "cd fsharp && ./test.sh M10"
|
||||
- id: m11
|
||||
title: "Capstone: Pong, by the method"
|
||||
lessons:
|
||||
- { id: m11-text, title: "Capstone: Pong, by the method", type: text, file: "course/11-capstone-pong.md" }
|
||||
- id: m11-ex-model
|
||||
title: "Exercise 11.1 — model"
|
||||
type: exercise
|
||||
file: "course/11-capstone-pong.md"
|
||||
anchor: exercise-111--model
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m11-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M11"
|
||||
fsharp: "cd fsharp && ./test.sh M11"
|
||||
- id: m11-ex-rules
|
||||
title: "Exercise 11.2 — rules"
|
||||
type: exercise
|
||||
file: "course/11-capstone-pong.md"
|
||||
anchor: exercise-112--rules
|
||||
hints: { file: "course/11-capstone-pong.md", match: "Exercise 11.2" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m11-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M11"
|
||||
fsharp: "cd fsharp && ./test.sh M11"
|
||||
- id: m11-ex-view
|
||||
title: "Exercise 11.3 — view"
|
||||
type: exercise
|
||||
file: "course/11-capstone-pong.md"
|
||||
anchor: exercise-113--view
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m11-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M11"
|
||||
fsharp: "cd fsharp && ./test.sh M11"
|
||||
- id: m11-ex-keys-and-wiring
|
||||
title: "Exercise 11.4 — keys and wiring"
|
||||
type: exercise
|
||||
file: "course/11-capstone-pong.md"
|
||||
anchor: exercise-114--keys-and-wiring
|
||||
hints: { file: "course/11-capstone-pong.md", match: "Exercise 11.4" }
|
||||
localCommand:
|
||||
js: "cd js && node --test \"test/m11-*.test.js\""
|
||||
scala: "cd scala && ./test.sh M11"
|
||||
fsharp: "cd fsharp && ./test.sh M11"
|
||||
- id: ref
|
||||
title: "Method and reference"
|
||||
lessons:
|
||||
- { id: ref-method, title: "The method", type: text, file: course/method.md }
|
||||
- { id: ref-reference, title: "Reference", type: text, file: course/reference.md }
|
||||
@@ -0,0 +1,110 @@
|
||||
schema: 1
|
||||
id: systemic-design
|
||||
title: Systemic Design
|
||||
pitch: Learn to build a rule-driven game engine by writing the systems that make its rules compose.
|
||||
visibility: public
|
||||
gating: sequential
|
||||
root: /home/user/systemic-design-course
|
||||
tracks:
|
||||
- { id: rust, label: Rust }
|
||||
- { id: ts, label: TypeScript }
|
||||
mentor: mentor/SKILL.md
|
||||
source: { kind: authored }
|
||||
modules:
|
||||
- id: m00
|
||||
title: "The Rewrite Trap"
|
||||
lessons:
|
||||
- id: m00-lesson
|
||||
title: "The Rewrite Trap"
|
||||
type: text
|
||||
file: "course/00-rewrite-trap/lesson.md"
|
||||
- id: m01
|
||||
title: "Handles and order of play"
|
||||
lessons:
|
||||
- id: m01-lesson
|
||||
title: "Handles and order of play"
|
||||
type: text
|
||||
file: "course/01-handles/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 01 <your-workspace>"
|
||||
- { id: m01-scenarios, title: Scenarios, type: text, file: "course/01-handles/scenarios.md" }
|
||||
- id: m02
|
||||
title: "The action queue, depth-first"
|
||||
lessons:
|
||||
- id: m02-lesson
|
||||
title: "The action queue, depth-first"
|
||||
type: text
|
||||
file: "course/02-action-queue/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 02 <your-workspace>"
|
||||
- { id: m02-scenarios, title: Scenarios, type: text, file: "course/02-action-queue/scenarios.md" }
|
||||
- id: m03
|
||||
title: "Deferred death"
|
||||
lessons:
|
||||
- id: m03-lesson
|
||||
title: "Deferred death"
|
||||
type: text
|
||||
file: "course/03-death-step/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 03 <your-workspace>"
|
||||
- { id: m03-scenarios, title: Scenarios, type: text, file: "course/03-death-step/scenarios.md" }
|
||||
- id: m04
|
||||
title: "Interceptors and \"trigger twice\""
|
||||
lessons:
|
||||
- id: m04-lesson
|
||||
title: "Interceptors and \"trigger twice\""
|
||||
type: text
|
||||
file: "course/04-interceptors/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 04 <your-workspace>"
|
||||
- { id: m04-scenarios, title: Scenarios, type: text, file: "course/04-interceptors/scenarios.md" }
|
||||
- id: m05
|
||||
title: "Behavior as data and Assemble"
|
||||
lessons:
|
||||
- id: m05-lesson
|
||||
title: "Behavior as data and Assemble"
|
||||
type: text
|
||||
file: "course/05-assemble/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 05 <your-workspace>"
|
||||
- { id: m05-scenarios, title: Scenarios, type: text, file: "course/05-assemble/scenarios.md" }
|
||||
- id: m06
|
||||
title: "Grid, lexer and parser"
|
||||
lessons:
|
||||
- id: m06-lesson
|
||||
title: "Grid, lexer and parser"
|
||||
type: text
|
||||
file: "course/06-parser/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 06 <your-workspace>"
|
||||
- { id: m06-scenarios, title: Scenarios, type: text, file: "course/06-parser/scenarios.md" }
|
||||
- id: m07
|
||||
title: "Resolving rules: NOT, identity, meta-nouns"
|
||||
lessons:
|
||||
- id: m07-lesson
|
||||
title: "Resolving rules: NOT, identity, meta-nouns"
|
||||
type: text
|
||||
file: "course/07-rule-resolution/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 07 <your-workspace>"
|
||||
- { id: m07-scenarios, title: Scenarios, type: text, file: "course/07-rule-resolution/scenarios.md" }
|
||||
- id: m08
|
||||
title: "Push chains: validate, then commit"
|
||||
lessons:
|
||||
- id: m08-lesson
|
||||
title: "Push chains: validate, then commit"
|
||||
type: text
|
||||
file: "course/08-push-chains/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 08 <your-workspace>"
|
||||
- { id: m08-scenarios, title: Scenarios, type: text, file: "course/08-push-chains/scenarios.md" }
|
||||
- id: m09
|
||||
title: "The turn pipeline"
|
||||
lessons:
|
||||
- id: m09-lesson
|
||||
title: "The turn pipeline"
|
||||
type: text
|
||||
file: "course/09-turn-pipeline/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 09 <your-workspace>"
|
||||
- { id: m09-scenarios, title: Scenarios, type: text, file: "course/09-turn-pipeline/scenarios.md" }
|
||||
- id: m10
|
||||
title: "SpellGrid"
|
||||
lessons:
|
||||
- id: m10-lesson
|
||||
title: "SpellGrid"
|
||||
type: text
|
||||
file: "course/10-spellgrid/lesson.md"
|
||||
localCommand: "node tools/start-module.mjs 10 <your-workspace>"
|
||||
- { id: m10-scenarios, title: Scenarios, type: text, file: "course/10-spellgrid/scenarios.md" }
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { createCourseRepository, extractHints } from "../src/index";
|
||||
|
||||
const overlays = resolve(import.meta.dir, "../../../overlays");
|
||||
const snakeRoot = "/home/user/functional-programming-course";
|
||||
const systemicRoot = "/home/user/systemic-design-course";
|
||||
|
||||
describe("reference courses", () => {
|
||||
test.skipIf(!existsSync(snakeRoot))("pure-snake loads cleanly", async () => {
|
||||
const repo = await createCourseRepository([overlays]);
|
||||
const c = repo.get("pure-snake")!;
|
||||
expect(c.diagnostics.filter((d) => d.level === "error")).toEqual([]);
|
||||
expect(c.status).toBe("ok");
|
||||
expect(c.manifest!.modules).toHaveLength(13);
|
||||
expect(c.lessonIndex.has("m03-ex-move")).toBe(true);
|
||||
const hints = c.lessonIndex.get("m03-ex-move")!.lesson.hints!;
|
||||
const md = await Bun.file(repo.resolvePath("pure-snake", hints.file)!).text();
|
||||
expect(extractHints(md, hints.match)).toHaveLength(3);
|
||||
expect(await repo.readLessonMarkdown("pure-snake", "m03-ex-move")).toStartWith("### Exercise 3.1");
|
||||
});
|
||||
|
||||
test.skipIf(!existsSync(systemicRoot))("systemic-design loads cleanly", async () => {
|
||||
const repo = await createCourseRepository([overlays]);
|
||||
const c = repo.get("systemic-design")!;
|
||||
expect(c.diagnostics.filter((d) => d.level === "error")).toEqual([]);
|
||||
expect(c.status).toBe("ok");
|
||||
expect(c.manifest!.modules).toHaveLength(11);
|
||||
expect(c.manifest!.gating).toBe("sequential");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user