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 }