// m02 model: the data the whole game is built from. Plain objects, never mutated. /** @typedef {{x: number, y: number}} Point */ /** @typedef {'Up'|'Down'|'Left'|'Right'} Direction */ /** @typedef {'Playing'|'Paused'|'GameOver'|'Won'} Status */ /** @typedef {{width: number, height: number, snake: Point[], dir: Direction, nextDir: Direction, * apple: Point|null, seed: number, score: number, status: Status, frame: number}} GameState */ /** A grid cell. */ export const point = (x, y) => ({ x, y }) /** Structural equality: same x and same y. */ export const pointEquals = (a, b) => a.x === b.x && a.y === b.y const deltas = { Up: point(0, -1), Down: point(0, 1), Left: point(-1, 0), Right: point(1, 0), } /** The unit step for a direction (y grows downward). */ export const delta = (dir) => deltas[dir] const opposites = { Up: 'Down', Down: 'Up', Left: 'Right', Right: 'Left' } /** The direction pointing the other way. */ export const opposite = (dir) => opposites[dir] /** Everything that can happen to the game, as data. `Turn` is a function because it carries a direction. */ export const Action = { Tick: { tag: 'Tick' }, Turn: (dir) => ({ tag: 'Turn', dir }), TogglePause: { tag: 'TogglePause' }, Restart: { tag: 'Restart' }, Frame: { tag: 'Frame' }, } /** A fresh 20x20 game: 3-segment snake heading Right, no apple yet. */ export const initialState = (seed) => ({ width: 20, height: 20, snake: [point(10, 10), point(9, 10), point(8, 10)], dir: 'Right', nextDir: 'Right', apple: null, seed, score: 0, status: 'Playing', frame: 0, })