@johnmorrisdotca/karakuri
choiceStory Controller GameInfo gridEscape Info KARAKURI_STRINGS KarakuriLanguage nutsAndBolts pinRescue Point ropeCut saveTheCharacter say Say Status stretchGrabber Theme tubeSort VERSION wordKey
namespace choiceStory
import { choiceStory } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/choiceStory"
Everything the @johnmorrisdotca/karakuri/choiceStory entry point exports, as one namespace.
type Controller
interface Controller { /** The size of the play area, in the game's units. The player scales it to the box it is given. */ readonly width: number; readonly height: number; /** `drag` games read a finger's whole path, so the player stops the page scrolling under it; `tap` games leave the page its gestures. */ readonly gesture: "drag" | "tap"; readonly status: Status; /** Why a level was won or lost, in words, once it…
One level of one game, played.
type GameInfo
interface GameInfo { id: string; /** How many levels it has. */ levels: number; gesture: "drag" | "tap"; /** Whether it runs a physics simulation, stepped sixty times a second. */ physics: boolean; /** Builds a controller for a level (from 1). */ create(level: number): Controller; }
What a game of the package is, for a chooser.
namespace gridEscape
import { gridEscape } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/gridEscape"
Everything the @johnmorrisdotca/karakuri/gridEscape entry point exports, as one namespace.
type Info
type Info = Say;
What the player reads in the bar above the board.
const KARAKURI_STRINGS
KARAKURI_STRINGS: Record<KarakuriLanguage, Record<string, string>>
Every word the package says: its own, and the stories'.
type KarakuriLanguage
type KarakuriLanguage = "en" | "ja";
namespace nutsAndBolts
import { nutsAndBolts } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/nutsAndBolts"
Everything the @johnmorrisdotca/karakuri/nutsAndBolts entry point exports, as one namespace.
namespace pinRescue
import { pinRescue } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/pinRescue"
Everything the @johnmorrisdotca/karakuri/pinRescue entry point exports, as one namespace.
type Point
interface Point { x: number; y: number; }
A place in a game's own world, in its own units (not pixels).
namespace ropeCut
import { ropeCut } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/ropeCut"
Everything the @johnmorrisdotca/karakuri/ropeCut entry point exports, as one namespace.
namespace saveTheCharacter
import { saveTheCharacter } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/saveTheCharacter"
Everything the @johnmorrisdotca/karakuri/saveTheCharacter entry point exports, as one namespace.
function say
say(lang: KarakuriLanguage, key: string, values?: Record<string, string | number>): string
The words for key in lang, with each {name} filled from values; English when the language has none; the key itself when nobody has.
type Say
interface Say { key: string; values?: Record<string, string | number>; }
A line of words to be said in the player's language: a key of KARAKURI_STRINGS and the values for its {name}s.
type Status
type Status = "playing" | "won" | "lost";
How a level stands: still being played, won, or lost.
namespace stretchGrabber
import { stretchGrabber } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/stretchGrabber"
Everything the @johnmorrisdotca/karakuri/stretchGrabber entry point exports, as one namespace.
type Theme
interface Theme { /** The play area's floor. */ board: string; /** The darker ground, for grooves and shadows. */ deep: string; ink: string; muted: string; /** The line the player draws, and highlights. */ accent: string; good: string; bad: string; gold: string; water: string; lava: string; stone: string; /** A light colour that reads on `accent`, `bad` and the game colours. */ paper: string; dark: boolean; font: st…
The colours and sizes a game draws with, read from the page's custom properties (--kk-*) so that it follows light, dark and the family's cloths.
namespace tubeSort
import { tubeSort } from "@johnmorrisdotca/karakuri"; // or everything in it from "@johnmorrisdotca/karakuri/tubeSort"
Everything the @johnmorrisdotca/karakuri/tubeSort entry point exports, as one namespace.
const VERSION
VERSION: "0.1.1"
The package's version, kept equal to package.json's by a test.
function wordKey
wordKey: (game: string) => string
The key a game's words are kept under: its id with underscores for hyphens (game_tube_sort, rules_tube_sort).
@johnmorrisdotca/karakuri/physics
addBody addLink addNode addRope Body BodySpec BodyType capsulesAlong CapsuleShape CircleShape clamp closestBetweenSegments closestOnSegment Contact contactsBetween countOf createBody createFluid createRig createWorld Disc distance distanceSquared distanceToSegment fillRect Fluid FluidKind fluidNumbers gap hashNumbers isSettled length linkCrossed nearestTo nodeSpeed Particle pointInPolygon refreshShapes Rig RigLink RigNode rigNumbers segmentsIntersect Shape STEP stepFluid stepRig stepWorld unit Vec World worldNumbers WorldShape
function addBody
addBody<T extends Body>(world: World, body: T): T
Adds a body to the world and gives it back.
function addLink
addLink(rig: Rig, a: number, b: number, length_?: number): number
Links two nodes at their present distance (or at length) and gives back the link's index.
function addNode
addNode(rig: Rig, x: number, y: number, invMass: number, r?: number): number
Adds a node and gives back its index.
function addRope
addRope(rig: Rig, ax: number, ay: number, bx: number, by: number, pieces: number, end?: number): { nodes: number[]; links: number[]; }
Lays a rope of pieces links from (ax, ay) to (bx, by): the first node is pinned and the last is the node end if one is given (its place is moved to the rope's end), and the nodes between are light. Gives back the indices of the rope's nodes from the pinned end to the other and of its links.
type Body
interface Body { id: string; /** The game's own word for what the body is: "hero", "rock", "stroke". The physics never reads it. */ tag: string; type: BodyType; x: number; y: number; vx: number; vy: number; /** The angle as its cosine and sine, turned by arithmetic alone, never by a trigonometric call. */ c: number; s: number; /** The angular velocity, in radians a second. */ w: number; mass: number; invMass: number…
A rigid body. A game reads and, for a kinematic one, writes x, y, vx and vy; the rest is the solver's.
type BodySpec
interface BodySpec { id: string; tag?: string; type?: BodyType; x?: number; y?: number; shapes: Shape[]; density?: number; restitution?: number; friction?: number; /** A body that does not turn (a character that stays upright). */ fixedRotation?: boolean; sensor?: boolean; }
What createBody is told. A dynamic body's mass and inertia are worked out from its shapes and density unless given.
type BodyType
type BodyType = "dynamic" | "static" | "kinematic";
How a body moves: dynamic by forces and contacts, kinematic only as the game sets it (it pushes, and is not pushed), static not at all.
function capsulesAlong
capsulesAlong(points: readonly { x: number; y: number; }[], radius: number): CapsuleShape[]
A capsule chain for a drawn line: one capsule between each pair of neighbouring points.
type CapsuleShape
interface CapsuleShape { kind: "capsule"; ax: number; ay: number; bx: number; by: number; r: number; }
A thick segment, from (ax, ay) to (bx, by) with a radius, placed in the body's own frame.
type CircleShape
interface CircleShape { kind: "circle"; x: number; y: number; r: number; }
A disc, placed in the body's own frame (its origin is the body's centre of mass for a dynamic body).
function clamp
clamp: (value: number, low: number, high: number) => number
value held between low and high.
function closestBetweenSegments
closestBetweenSegments(a1x: number, a1y: number, a2x: number, a2y: number, b1x: number, b1y: number, b2x: number, b2y: number): { ax: number; ay: number; bx: number; by: number; distance: number; }
The nearest points of two segments: where on each, and how far apart they are. Crossing segments are 0 apart.
function closestOnSegment
closestOnSegment(px: number, py: number, ax: number, ay: number, bx: number, by: number): { x: number; y: number; t: number; }
The point on the segment a to b nearest to (px, py), and how far along the segment it is (0 to 1).
type Contact
interface Contact { a: Body; b: Body; nx: number; ny: number; depth: number; x: number; y: number; }
One contact between two shapes: the normal points from a towards b.
function contactsBetween
contactsBetween(a: Body, b: Body, margin: number): Contact[]
The contacts between two bodies whose shapes are within margin of each other.
function countOf
countOf(fluid: Fluid, kind: FluidKind): number
How many particles of a kind.
function createBody
createBody(spec: BodySpec): Body
Makes a body from a spec. A dynamic body's origin is moved to its centre of mass, and its shapes are re-placed round it.
function createFluid
createFluid(options?: { radius?: number; gravity?: number; damping?: number; iterations?: number; reacts?: boolean; }): Fluid
Makes a liquid with no particles.
function createRig
createRig(options?: { gravity?: number; damping?: number; iterations?: number; grip?: number; bounce?: number; }): Rig
Makes an empty rig.
function createWorld
createWorld(options?: { gravity?: number; substeps?: number; iterations?: number; }): World
Makes a world.
type Disc
interface Disc { x: number; y: number; r: number; }
The round bodies the particles must not enter (a character, a coin): centre and radius.
function distance
distance: (ax: number, ay: number, bx: number, by: number) => number
The distance between two points.
function distanceSquared
distanceSquared: (ax: number, ay: number, bx: number, by: number) => number
The squared distance between two points, which needs no square root.
function distanceToSegment
distanceToSegment(px: number, py: number, ax: number, ay: number, bx: number, by: number): number
How far (px, py) is from the segment a to b.
function fillRect
fillRect(fluid: Fluid, kind: FluidKind, x: number, y: number, w: number, h: number): number
Fills the rectangle from (x, y) to (x + w, y + h) with particles of one kind on a grid a little looser than they settle.
type Fluid
interface Fluid { particles: Particle[]; /** A particle's radius, which is also how far apart two settle (twice this). */ radius: number; gravity: number; /** How much of its speed a particle keeps each step. */ damping: number; iterations: number; /** Whether water and lava turn each other to stone where they touch. */ reacts: boolean; }
The liquid: its particles and how it behaves.
type FluidKind
type FluidKind = "water" | "lava" | "stone";
What a particle is made of. Stone is lava and water that met: it does not move again.
function fluidNumbers
fluidNumbers(fluid: Fluid): number[]
Every number of the liquid's particles, for hashing it (kinds as 0, 1, 2).
function gap
gap(a: Body, b: Body): number
The gap between two bodies: the smallest gap of any pair of their shapes (negative when they overlap).
function hashNumbers
hashNumbers(values: ArrayLike<number>): string
A 32-bit FNV-1a hash of a list of numbers, taken over the bits of each as a 64-bit float.
function isSettled
isSettled(world: World, speed?: number): boolean
Whether every dynamic body in the world is slower than speed (units a second) and is hardly turning.
function length
length: (x: number, y: number) => number
The length of (x, y).
function linkCrossed
linkCrossed(rig: Rig, x1: number, y1: number, x2: number, y2: number, among?: readonly number[]): number
The index of the first live link that the swipe from (x1, y1) to (x2, y2) crosses, or -1. A swipe cuts one link: the nearest to its start.
function nearestTo
nearestTo(fluid: Fluid, kind: FluidKind, disc: Disc): number
The nearest distance from a particle of a kind to a disc's surface (negative when it is inside), or Infinity when there is none.
function nodeSpeed
nodeSpeed(n: RigNode, dt: number): number
The speed of a node in units a second at a step of dt.
type Particle
interface Particle { x: number; y: number; px: number; py: number; kind: FluidKind; }
One particle.
function pointInPolygon
pointInPolygon(px: number, py: number, polygon: readonly Vec[]): boolean
Whether (px, py) is inside the polygon (an even-odd test over its vertices, in order).
function refreshShapes
refreshShapes(body: Body): void
Recomputes a body's shapes in world places from its position and angle.
type Rig
interface Rig { nodes: RigNode[]; links: RigLink[]; statics: WorldShape[]; gravity: number; /** How much of its speed a node keeps each step (1 keeps all). */ damping: number; iterations: number; /** How much of a round node's speed along a surface it keeps when it touches one. */ grip: number; bounce: number; }
A set of nodes and links, with the fixed shapes the round nodes rest on.
type RigLink
interface RigLink { a: number; b: number; length: number; alive: boolean; }
A link that keeps two nodes length apart (a rope pulls but does not push: slack links only pull).
type RigNode
interface RigNode { x: number; y: number; px: number; py: number; invMass: number; r: number; }
A point of a chain. invMass 0 pins it. r above 0 makes it a disc that rests on the rig's statics.
function rigNumbers
rigNumbers(rig: Rig): number[]
Every number of the rig's nodes, for hashing it.
function segmentsIntersect
segmentsIntersect(a1x: number, a1y: number, a2x: number, a2y: number, b1x: number, b1y: number, b2x: number, b2y: number): boolean
Whether the segments a1 to a2 and b1 to b2 cross or touch.
type Shape
type Shape = CircleShape | CapsuleShape;
A shape of a body.
const STEP
STEP: number
The length of the fixed step every game runs at, in seconds.
function stepFluid
stepFluid(fluid: Fluid, dt: number, statics: readonly WorldShape[], discs?: readonly Disc[]): void
Advances the liquid by one step of dt seconds.
function stepRig
stepRig(rig: Rig, dt: number): void
Advances the rig by one step of dt seconds.
function stepWorld
stepWorld(world: World, dt?: number): void
Advances the world by one fixed step (STEP seconds) in substeps smaller ones.
function unit
unit(x: number, y: number): Vec
The unit vector of (x, y), or (0, 0) for a zero vector.
type Vec
interface Vec { x: number; y: number; }
A point or a vector in the plane.
type World
interface World { gravity: number; bodies: Body[]; /** Smaller steps within one fixed step. A fast, small thing needs more. */ substeps: number; /** How many passes the contact solver makes. */ iterations: number; }
The world: its gravity (down is +y, in units a second squared), its bodies in the order they were added, and how hard it works.
function worldNumbers
worldNumbers(world: World): number[]
Every number of a world's bodies' state, for hashing it: a determinism test compares these bit for bit.
type WorldShape
interface WorldShape { ax: number; ay: number; bx: number; by: number; r: number; }
A shape as the world sees it now: a circle as (ax, ay, r) with a = b, a capsule with its two ends.
@johnmorrisdotca/karakuri/levels
GRID_ESCAPE_LEVELS GridEscapeLevel gridPuzzleOf NUTS_AND_BOLTS_LEVELS PIN_RESCUE_LEVELS PinRescueEntry ROPE_CUT_LEVELS RopeCutEntry SAVE_THE_CHARACTER_LEVELS SaveEntry STORIES STRETCH_GRABBER_LEVELS StretchGrabberEntry TUBE_SORT_LEVELS
const GRID_ESCAPE_LEVELS
GRID_ESCAPE_LEVELS: readonly GridEscapeLevel[]
The levels, from easy to hard by their fewest moves.
type GridEscapeLevel
interface GridEscapeLevel { rows: readonly string[]; fewest: number; }
One level: its board and its proved fewest moves.
function gridPuzzleOf
gridPuzzleOf(level: GridEscapeLevel): GridPuzzle
The board of a level, read.
const NUTS_AND_BOLTS_LEVELS
NUTS_AND_BOLTS_LEVELS: readonly NutPuzzle[]
The levels, with more plates and more screws each time.
const PIN_RESCUE_LEVELS
PIN_RESCUE_LEVELS: readonly PinRescueEntry[]
The levels, each with more pins to choose between and more ways to lose.
type PinRescueEntry
interface PinRescueEntry extends PinLevel { solution: readonly number[]; loss: readonly number[]; }
A level, the pins that win it in order (by index in pins), and pins that lose it in order.
const ROPE_CUT_LEVELS
ROPE_CUT_LEVELS: readonly RopeCutEntry[]
The levels.
type RopeCutEntry
interface RopeCutEntry extends RopeLevel { solution: readonly { at: number; rope: number; link: number }[]; loss: readonly { at: number; rope: number; link: number }[]; }
A level and the cuts that win it, in order: the step to cut at, which rope, which link of it.
const SAVE_THE_CHARACTER_LEVELS
SAVE_THE_CHARACTER_LEVELS: readonly SaveEntry[]
The levels.
type SaveEntry
interface SaveEntry extends SaveLevel { solution: readonly { x: number; y: number }[]; loss: readonly { x: number; y: number }[]; }
A level and the stroke that wins it (points, drawn in order) and a stroke that loses it.
const STORIES
STORIES: readonly Story[]
The stories, one to a level.
const STRETCH_GRABBER_LEVELS
STRETCH_GRABBER_LEVELS: readonly StretchGrabberEntry[]
The levels.
type StretchGrabberEntry
interface StretchGrabberEntry extends GrabLevel { solution: readonly Pt[]; loss: readonly Pt[]; }
A level, the waypoints of one way to win it, and of one way to lose it.
const TUBE_SORT_LEVELS
TUBE_SORT_LEVELS: readonly TubePuzzle[]
The levels, each with more colours and more tubes than the one before.
@johnmorrisdotca/karakuri/play
Controller createController defaultTheme GameInfo Info KARAKURI_GAME_IDS KARAKURI_GAMES KARAKURI_STYLE KARAKURI_THEMES KarakuriGame KarakuriMount mountKarakuri MountOptions Point readTheme Say Status StatusEvent Theme
type Controller
interface Controller { /** The size of the play area, in the game's units. The player scales it to the box it is given. */ readonly width: number; readonly height: number; /** `drag` games read a finger's whole path, so the player stops the page scrolling under it; `tap` games leave the page its gestures. */ readonly gesture: "drag" | "tap"; readonly status: Status; /** Why a level was won or lost, in words, once it…
One level of one game, played.
function createController
createController(game: KarakuriGame, level: number): Controller
Makes a controller for level level (from 1) of a game; a level out of range is held to the nearest.
function defaultTheme
defaultTheme(dark?: boolean): Theme
The theme without a page: the light one, for a drawing made where there is no style to read (a test, a server).
type GameInfo
interface GameInfo { id: string; /** How many levels it has. */ levels: number; gesture: "drag" | "tap"; /** Whether it runs a physics simulation, stepped sixty times a second. */ physics: boolean; /** Builds a controller for a level (from 1). */ create(level: number): Controller; }
What a game of the package is, for a chooser.
type Info
type Info = Say;
What the player reads in the bar above the board.
const KARAKURI_GAME_IDS
KARAKURI_GAME_IDS: readonly ["save-the-character", "pin-rescue", "nuts-and-bolts", "stretch-grabber", "grid-escape", "rope-cut", "tube-sort", "choice-story"]
The ids of the games, in the order the package lists them (kebab case, the same in every address and attribute).
const KARAKURI_GAMES
KARAKURI_GAMES: Record<"save-the-character" | "pin-rescue" | "nuts-and-bolts" | "stretch-grabber" | "grid-escape" | "rope-cut" | "tube-sort" | "choice-story", GameInfo>
THE GAMES of the package, by id. Each is a GameInfo: how many levels it has, how it is played (drag or tap), whether it runs physics, and how to make a controller for a level.
const KARAKURI_STYLE
KARAKURI_STYLE: string
THE STYLE the player wears: the colours as custom properties on .karakuri (light, and dark when the page asks for it by prefers-color-scheme or by data-theme="dark" on the root), the layout of the bar, the play area and the buttons, and the one rule that matters for games played with fingers: nothing on the board can be selected, dragged or double-tapped, and the play area stops the page scrolling under a finger only for the games that read a finger's whole path.
const KARAKURI_THEMES
KARAKURI_THEMES: { readonly light: { readonly board: "#f6f1e6"; readonly deep: "#e4dcc9"; readonly ink: "#2b2a28"; readonly muted: "#8a8473"; readonly accent: "#2f6f55"; readonly good: "#2f8f5b"; readonly bad: "#c2453a"; readonly gold: "#e0a82e"; readonly water: "#3b8fd9"; readonly lava: "#e8561f"; readonly stone: "#8d8a82"; readonly paper: "#fffdf7"; }; readonly dark: { readonly board: "#263029"; readonly deep: "#1…
The look the games draw with, as custom properties, so a page can change any of them. Light and dark follow the page.
type KarakuriGame
type KarakuriGame = (typeof KARAKURI_GAME_IDS)[number];
The id of a game of the package.
type KarakuriMount
interface KarakuriMount { readonly game: KarakuriGame; readonly level: number; readonly status: Status; /** The controller of the level being played, for reading its snapshot. */ readonly controller: Controller; readonly canvas: HTMLCanvasElement; restart(): void; /** Goes to another level (from 1) of the same game; a number out of range is held to the nearest. */ setLevel(level: number): void; setLang(lang: Karakur…
The handle mountKarakuri gives back.
function mountKarakuri
mountKarakuri(host: HTMLElement, options: MountOptions): KarakuriMount
Plays one game of the package in host: a canvas that scales to the box it is given, the pointer (finger or mouse) wired to the game, a fixed-step loop that runs only while something moves, and (with ui: "full") the bar, the buttons and the card that says how a level ended. Safe to call again on the same host: the old game is taken down first.
type MountOptions
interface MountOptions { game: KarakuriGame; /** The level to start on, from 1. */ level?: number; /** `en` or `ja`; the page's own language when left out. */ lang?: KarakuriLanguage; /** `full` (default) draws the bar above the board, the result card on it and the buttons below; `board` draws the board alone, for a page that has its own buttons. */ ui?: "full" | "board"; /** * The most height, in pixels, the board …
What mountKarakuri is told.
type Point
interface Point { x: number; y: number; }
A place in a game's own world, in its own units (not pixels).
function readTheme
readTheme(element: Element): Theme
Reads the theme a game draws with from an element's computed style: each colour is --kk-<name>, with the light or the dark default beneath.
type Say
interface Say { key: string; values?: Record<string, string | number>; }
A line of words to be said in the player's language: a key of KARAKURI_STRINGS and the values for its {name}s.
type Status
type Status = "playing" | "won" | "lost";
How a level stands: still being played, won, or lost.
type StatusEvent
interface StatusEvent { game: KarakuriGame; level: number; status: Status; result: Say | null; /** The words of `result` in the player's language ("" while playing), for a page that draws its own result. */ text: string; /** The line the bar would show about the level, in the player's language (moves made, ink left, and so on). */ info: string; }
What a status callback is given.
type Theme
interface Theme { /** The play area's floor. */ board: string; /** The darker ground, for grooves and shadows. */ deep: string; ink: string; muted: string; /** The line the player draws, and highlights. */ accent: string; good: string; bad: string; gold: string; water: string; lava: string; stone: string; /** A light colour that reads on `accent`, `bad` and the game colours. */ paper: string; dark: boolean; font: st…
The colours and sizes a game draws with, read from the page's custom properties (--kk-*) so that it follows light, dark and the family's cloths.