import { todo } from './todo.js' import { pure } from './io.js' /** @import { Action, GameState, Session, Scene, IO, Stream, Input, Op, Program } from './types' */ /** Milliseconds between game steps. */ export const TICK_MS = 150 /** Milliseconds between animation frames (m10). */ export const FRAME_MS = 25 /** (m10) The longest step of time the game accepts at once, in milliseconds (see `clampTime` in stream.js). */ export const MAX_GAP_MS = 250 /** * Stream of everything that can happen to the game: `Action.Tick` every TICK_MS (from `interval`), * merged with the `Action` for each key press that `keyToAction` recognises (from `keyDowns`). * @type {Stream} */ export const classicActions = { step: () => todo('m09') } /** * (m10) Like `classicActions`, but the clock emits `Action.Frame` every FRAME_MS instead of `Tick` every TICK_MS; * key presses are translated by `keyToAction` as before (now including pause and restart). * @type {Stream} */ export const speedActions = { step: () => todo('m10') } /** * Fold a stream of actions into a stream of game states: start from `newGame(seed)`, apply `update` to each action * (scan), and emit the initial state first (startWith) so there is something to draw before anything happens. * @type {(seed: number, actions: Stream) => Stream} */ export const states = (seed, actions) => todo('m09') /** The drawing of one state, as an IO: `drawScene(view(state))`. * @type {(state: GameState) => IO} */ export const render = (state) => todo('m09') /** * (m10) Session = `{game, best, improved}`: the game, the best score so far, and whether the action that produced * this session raised the best. One action: `game' = update(game, action)`, `best' = max(best, game'.score)`, * `improved = best' > best` (strictly greater: equalling the record is not a new record). * @type {(session: Session, action: Action) => Session} */ export const sessionUpdate = (session, action) => todo('m10') /** * (m10) Like `states`, one level up: `startWith(scan(actions, sessionUpdate, s0), s0)` * with `s0 = {game: newGame(seed), best, improved: false}`. * @type {(seed: number, best: number, actions: Stream) => Stream} */ export const sessions = (seed, best, actions) => todo('m10') /** * (m10) `view(session.game)` followed by one more shape: the text `Best: ` at the top right, * `{tag:'Text', x: game.width * CELL - 6, y: 18, text: 'Best: 3', color: '#ffffff', size: 16, align: 'right'}`. * @type {(session: Session) => Scene} */ export const sessionView = (session) => todo('m10') /** * (m10) `drawScene(sessionView(session))`, followed by `saveBest(session.best)` when `session.improved` * (and only then). * @type {(session: Session) => IO} */ export const renderSession = (session) => todo('m10') /** * The whole program as a value, `IO>>`: ask for a random seed, then turn the stream of * states (built from `classicActions` in m09, from `speedActions` after m10) into a stream of drawings (`render`). * (m10) The whole stream is wrapped in `clampTime(..., MAX_GAP_MS)`. * (m10, Exercise 10.7) After the seed, ask for the stored best (`loadBest`), and draw the stream of * `sessions(seed, best, speedActions)` with `renderSession` instead of the states with `render`. * Nothing runs here. * @type {Program} */ export const main = pure({ step: () => todo('m09') }) /** * (m09) The whole program in one equality: for each drawing that `main` emits while it is fed `events` * (a list of inputs), the list of ops that drawing performs. Interpret `main` with `trace(main, seed)`, `run` the * stream it produces on `events`, and `trace` each emitted IO, keeping its `ops`. * (m10) Pass `best` to both traces as well, so that `LoadBest` is answered with it. * @type {(main: Program, seed: number, events: Input[], best?: number) => Op[][]} */ export const play = (main, seed, events, best = 0) => todo('m09')