Files
functional-programming-snake/js/solutions/types.d.ts
T
Lucas WintherandClaude Fable 5.2 edd57f0148 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>
2026-11-11 12:05:29 +01:00

93 lines
3.8 KiB
TypeScript

// Given, not an exercise: the course's data types, written down for an editor that understands types (optional,
// see Module 0, "If your editor understands types"). Nothing imports this file at run time; the JS files refer to it
// from their doc comments (`@import`, `@type`). Every sum type is a union of plain objects with a `tag`, so a
// `switch (x.tag)` narrows `x` to one case inside each `case`.
// ---- m02 model, m10 replay viewer, m11 Pong, m13 Life
// The model types are written where the model is: as `@typedef` comments in model.js (Module 2 shows them there),
// pong/model.js, life/model.js and replay.js. Each action union sits next to the object of the same name that
// builds its values, so `import { Action } from './model.js'` gives both. They are re-exported here so that every
// type can be found in one place.
import type { Point, Direction, Status, Action, GameState } from './model.js'
import type { Viewer, ViewerAction } from './replay.js'
import type { PongState, PongAction } from './pong/model.js'
import type { LifeState, LifeAction } from './life/model.js'
export type { Point, Direction, Status, Action, GameState, Viewer, ViewerAction, PongState, PongAction, LifeState, LifeAction }
// ---- m04 random and state
/** A random value: a function from a seed to the value and the next seed. */
export type Random<A> = (seed: number) => [A, number]
/** A value that reads and replaces a state of type S. `Random<A>` is `State<number, A>`. */
export type State<S, A> = (s: S) => [A, S]
// ---- m06 view
export type Align = 'left' | 'center' | 'right'
export type Shape =
| { tag: 'Rect'; x: number; y: number; w: number; h: number; color: string }
| { tag: 'Text'; x: number; y: number; text: string; color: string; size: number; align: Align }
export type Scene = Shape[]
// ---- m07 IO
export type Op =
| { tag: 'Draw'; shape: Shape }
| { tag: 'RandomSeed' }
| { tag: 'LoadBest' }
| { tag: 'SaveBest'; score: number }
/** A program that produces an A. The answer given to `k` depends on the op (a number, or undefined), hence `any`. */
export type IO<A> =
| { tag: 'Pure'; value: A }
| { tag: 'Effect'; op: Op; k: (answer: any) => IO<A> }
/** What the pure interpreters return: the program's result and the ops it performed, in order. */
export type Trace<A> = { result: A; ops: Op[] }
// ---- m07b stack-safe IO
export type SafeIO<A> =
| { tag: 'Pure'; value: A }
| { tag: 'Effect'; op: Op; k: (answer: any) => SafeIO<A> }
| { tag: 'FlatMap'; io: SafeIO<any>; f: (result: any) => SafeIO<A> }
// ---- m08 streams
/** What the world feeds a stream. `Click` arrives in Module 13. */
export type Input =
| { tag: 'Time'; t: number }
| { tag: 'KeyDown'; key: string }
| { tag: 'KeyUp'; key: string }
| { tag: 'Click'; x: number; y: number }
export type Stream<A> = { step: (input: Input) => [A[], Stream<A>] }
/** The whole program: ask the world for what it needs, then a stream of drawings. */
export type Program = IO<Stream<IO<unknown>>>
// ---- m09 runtime
export type Platform = {
fillRect: (x: number, y: number, w: number, h: number, color: string) => void
fillText: (x: number, y: number, text: string, color: string, size: number, align: Align) => void
randomSeed: () => number
loadBest: () => number
saveBest: (score: number) => void
startClock: (onTime: (t: number) => void) => void
onKeyDown: (cb: (key: string) => void) => void
onKeyUp: (cb: (key: string) => void) => void
onClick: (cb: (x: number, y: number) => void) => void
}
// ---- m10 high score and replay viewer
export type Session = { game: GameState; best: number; improved: boolean }
/** A recorded game (`fixtures/replay-sample.json`). */
export type Recording = { seed: number; actions: Action[] }
// ---- m12 property-based testing
export type CheckResult<A> = { ok: true; runs: number } | { ok: false; run: number; seed: number; value: A }