Meikyuu迷宮

@johnmorrisdotca/meikyuu 1.0.0 · 6 entry points · 173 exports

@johnmorrisdotca/meikyuu

Arrow ARROW_HEARTS ARROW_SHAPES ARROW_STEPS ArrowBoard ArrowDirection ArrowGame ArrowMeasure ArrowRecipe arrowRecipeCode ArrowShape arrowsLeft ArrowTap below blockedBy blockersOf boundaryCells Box buildMaze buildMixed carveMaze centreCell circleGrid clearArrows Door dragMaze EFFORT_LEAST EFFORT_MOST Grid gridOf headOf hexGrid hintArrow hintMaze isFree isPerfect layoutCells liftMaze Links makeArrows Mask maskOf Maze MazeGame MazeMeasure mazeProgress MazeRecipe measureArrows measureMaze measureMixed MEIKYUU_ALGORITHMS MEIKYUU_MODES MEIKYUU_MOST_ARROW_CELLS MEIKYUU_MOST_CELLS MEIKYUU_MOST_KEYS MEIKYUU_SHAPES MeikyuuAlgorithm MeikyuuMode MeikyuuShape MixedBoard MixedGame MixedRecipe mixedRecipeCode mixedSolved newArrowGame newMazeGame newMixedGame parseArrowRecipe parseMixedRecipe parseRecipe passageCount peelRounds pick playSolution Point pressMaze Random ratingOf rayOf recipeCode restartArrows restartMaze ringCounts seededRandom shuffled Side solutionOf squareGrid tapArrow tapMaze triangleGrid undoArrow undoMaze unlockArrows VERSION walk Wall withArrows withMaze

type Arrow

type Arrow = { readonly cells: readonly number[]; readonly dir: ArrowDirection };

One arrow: its cells from tail to head (indexes into the board, row by row), and the way its head points.

const ARROW_HEARTS

ARROW_HEARTS: 3

The hearts a puzzle starts with.

const ARROW_SHAPES

ARROW_SHAPES: readonly ["square", "heart", "leaf", "star", "ring", "diamond", "cross", "moon"]

The pictures an arrow board can be: the whole square, or one of the shapes cut from it.

const ARROW_STEPS

ARROW_STEPS: readonly (readonly [number, number])[]

type ArrowBoard

type ArrowBoard = { readonly recipe: ArrowRecipe; readonly w: number; readonly h: number; /** Which cells are part of the picture, whether an arrow is on them or not. */ readonly inShape: readonly boolean[]; readonly arrows: readonly Arrow[]; /** Which arrows are locked: the ones the mixed puzzles hide behind a button. */ readonly locked: readonly boolean[]; };

type ArrowDirection

type ArrowDirection = 0 | 1 | 2 | 3;

Which way an arrow's head points: up, right, down, left.

type ArrowGame

type ArrowGame = { readonly board: ArrowBoard; /** Whether each arrow is still on the board. */ readonly present: readonly boolean[]; readonly hearts: number; readonly heartsAtStart: number; /** Whether the locked arrows have been unlocked. */ readonly unlocked: boolean; readonly status: "playing" | "cleared" | "lost"; /** The arrows taken, in order, for Undo and for the page's own record. */ readonly taken: readonl…

AN ARROW PUZZLE BEING PLAYED, as pure functions. tapArrow is the one move: a free arrow is taken off the board; a blocked one costs a heart (and says which arrow is in its way); a locked one that has not been unlocked does nothing at all, and costs nothing, because the player cannot know yet. Clear every arrow to win; lose the last heart and the puzzle is lost until it is restarted.

The mixed puzzles lock some arrows until a button deep in a maze is reached. unlockArrows is what reaching it does.

type ArrowMeasure

type ArrowMeasure = { arrows: number; cells: number; rounds: number; free: number; blocked: number; longest: number; effort: number };

How hard an arrow puzzle is: the arrows to clear, the rounds of unblocking they take, and how many are blocked at the start.

type ArrowRecipe

type ArrowRecipe = { readonly shape: ArrowShape; /** Columns and rows of the board: the same for every shape but `square`. */ readonly w: number; readonly h: number; /** The most cells an arrow may have. */ readonly longest: number; readonly seed: number; /** How many arrows are locked until they are unlocked (the mixed puzzles). */ readonly locks?: number; };

Everything that makes an arrow puzzle.

function arrowRecipeCode

arrowRecipeCode(recipe: ArrowRecipe): string

A recipe as a short word, such as heart:15:6:77 or square:9x9:5:123:2 (the last number: how many are locked), and back.

type ArrowShape

type ArrowShape = (typeof ARROW_SHAPES)[number];

function arrowsLeft

arrowsLeft(game: ArrowGame): number

The count of arrows still on the board.

type ArrowTap

type ArrowTap = { readonly game: ArrowGame; readonly result: "removed" | "blocked" | "locked" | "ignored"; /** The arrow in the way, for `blocked`. */ readonly by?: number };

What a tap did.

function below

below(random: Random, n: number): number

A whole number in [0, n) from the stream. n must be at least 1.

function blockedBy

blockedBy(game: ArrowGame, id: number): number[]

The arrows on the way out of id that are still on the board.

function blockersOf

blockersOf(board: Pick<ArrowBoard, "w" | "h" | "arrows">): number[][]

Which arrows block others: for each arrow, the arrows whose cells lie on its way out.

function boundaryCells

boundaryCells(grid: Grid): number[]

The cells with a side on the edge of the shape: where a door in the outer wall can be.

type Box

type Box = { readonly x: number; readonly y: number; readonly w: number; readonly h: number };

A rectangle, in cells.

function buildMaze

buildMaze(recipe: MazeRecipe): Maze

Make the maze a recipe names.

function buildMixed

buildMixed(recipe: MixedRecipe): MixedBoard

function carveMaze

carveMaze(grid: Grid, algorithm: MeikyuuAlgorithm, random: Random): Links

The open neighbours of every cell, for a perfect maze of the grid made by this algorithm from this stream.

function centreCell

centreCell(grid: Grid): number

The cell whose middle is nearest the middle of the shape.

function circleGrid

circleGrid(rings: number): Grid

A circle maze: rings rings round a middle cell, cells running round each ring and out between the rings.

function clearArrows

clearArrows(game: ArrowGame): ArrowGame

Take every arrow off by tapping only free ones, the way a test or a demo plays. The same game when it cannot (a locked arrow in the way of the rest).

type Door

type Door = { readonly cell: number; readonly side: number };

A door in the outer wall: the cell it is beside, and which of that cell's sides.

function dragMaze

dragMaze(game: MazeGame, cell: number): MazeGame

The finger moved into a cell. A cell that is not next to the line's end is ignored: a page that moves faster than cells go calls this for each cell between.

const EFFORT_LEAST

EFFORT_LEAST: 9

The easiest effort a level is given a rating for, and the hardest: 1 and 100 are these.

const EFFORT_MOST

EFFORT_MOST: 5400

type Grid

type Grid = { readonly shape: MeikyuuShape; /** The two numbers the shape was asked for (columns and rows; rings; a radius). */ readonly w: number; readonly h: number; readonly cells: number; /** Where each cell's middle is. */ readonly centres: readonly Point[]; /** Each cell's sides, one per wall round it, in the order the shape draws them. */ readonly sides: readonly (readonly Side[])[]; /** Each cell's neighbour…

function gridOf

gridOf(shape: MeikyuuShape, w: number, h?: number): Grid

A grid by name. w and h are columns and rows for squares, hexagons and triangles, and the size of the raster a shape is cut from for the rest (h is ignored there); for a circle w is the number of rings, and for a hexagon the radius, and for a pyramid the rows.

function headOf

headOf(game: MazeGame): number | null

The end of the line, or null while there is none.

function hexGrid

hexGrid(w: number, h: number): Grid

Columns by rows of hexagons, pointy side up, every odd row shifted half a cell right.

function hintArrow

hintArrow(game: ArrowGame): number | null

The arrow to tap next: one that is free now and keeps the most arrows waiting behind it, so it also frees the most others. None when every arrow still on the board is blocked or locked (the player must unlock).

function hintMaze

hintMaze(game: MazeGame, ahead?: number): { back: number; cells: number[]; }

Where to look next: how many cells of the line to draw back (the part that has strayed from the way to what is still needed), and the next stretch of the right way from there, up to ahead cells, not counting the one the line is on. The right way goes to the nearest key not yet picked up, and to the goal when there are none. Empty ahead when the maze is solved.

function isFree

isFree(game: ArrowGame, id: number): boolean

Whether an arrow can be tapped now and slide off: it is on the board, not locked, and nothing is in front of it.

function isPerfect

isPerfect(grid: Grid, links: Links): boolean

Whether the passages are a spanning tree of the grid: every cell reached, and exactly one way to each.

function layoutCells

layoutCells(shape: MeikyuuShape, w: number, h: number): number

How many cells laying out a recipe's grid takes, counted before anything is built: the grid's own cells for squares, hexagons, triangles and circles, and the whole raster for a shape cut out of one (a cut-out shape keeps fewer, but is built from all of them). A hexagon of radius w is cut from a hex grid 2w + 1 across, and a pyramid of w rows from a triangle grid twice as wide as it is tall.

function liftMaze

liftMaze(game: MazeGame): MazeGame

The finger lifted. A stroke that changed nothing is forgotten, so Undo never has a step that did nothing.

function makeArrows

makeArrows(recipe: ArrowRecipe): ArrowBoard

The arrows of a recipe, in the order they were added: the last one added is free from the start.

type Mask

type Mask = (u: number, v: number) => boolean;

THE OUTLINES cut out of a square raster: a function from a point of the square (both numbers from -1 to 1, y down) to whether that point is inside the shape. A maze takes the cells whose middles are inside, and the biggest piece of them that touch.

function maskOf

maskOf(shape: MeikyuuShape): Mask

The outline a raster shape is cut with.

type Maze

type Maze = { readonly recipe: MazeRecipe; readonly grid: Grid; /** Each cell's open neighbours. */ readonly links: Links; /** The cell the line starts in. */ readonly start: number; /** The cell the line must reach. */ readonly goal: number; /** The door the line comes in by, for `enter-leave`. */ readonly entrance: Door | null; /** The door the line goes out by, for `enter-leave`, `centre-out` and `keys`. */ reado…

type MazeGame

type MazeGame = { readonly maze: Maze; /** The line, from the start; empty until it is begun. */ readonly path: readonly number[]; /** The keys picked up, in the order they were. */ readonly collected: readonly number[]; readonly solved: boolean; /** Whether a stroke is being drawn. */ readonly drawing: boolean; /** How many strokes have been made, to count moves. */ readonly strokes: number; /** What each finished …

A MAZE BEING DRAWN, as pure functions: each takes a game and returns a new one (or the same one, when nothing changed), so a page, a server and a test play by the same rules.

A line starts at the start cell and runs through open passages, one cell to the next. A cell the line is on again cuts the line back to it, so drawing back shortens it and a line never crosses itself. A key is picked up by passing over it and stays picked up when the line is drawn back. The maze is solved when the line's end is on the goal and every key is picked up; after that the line is fixed until Undo or Restart.

A finger is a stroke: pressMaze begins one (on the start, on the line's end, or on any cell of the line, which cuts it back there), dragMaze follows it into each cell, liftMaze ends it and is what Undo undoes. A tap, where the page offers it, is tapMaze.

type MazeMeasure

type MazeMeasure = { cells: number; /** Cells with one passage. */ deadEnds: number; deadEndShare: number; /** Cells with three or more passages. */ junctions: number; /** The share of cells with exactly two passages: a high `river` is a maze of long winding corridors. */ river: number; /** Cells on the way from the start to the goal, both included. */ solution: number; /** Places on the way where the line could hav…

HOW HARD A MAZE IS, measured from the maze alone (docs/MAZES.md says why each number is here). Everything is a whole number counted off the passages, so the same maze measures the same in every engine, and a level list ordered by effort stays ordered.

effort is an estimate, in cells drawn, of what a person does to solve it: the length of the way through, plus the length of the wrong turns taken (a person who meets a fork takes the wrong side half the time and walks to the end of it before turning back, so half of two times the depth of each wrong branch), plus two for every fork on the way (looking, and deciding), plus what collecting the keys adds. A big maze with a long way through costs more than a small one with many dead ends, as it takes longer to draw; two mazes of one size cost what their branching makes them.

function mazeProgress

mazeProgress(game: MazeGame): { cells: number; keys: number; keysOf: number; solved: boolean; }

How far the line has got, for a progress line: cells drawn, keys, and whether it is solved.

type MazeRecipe

type MazeRecipe = { readonly shape: MeikyuuShape; /** Columns, rings, a radius or the size of the square a shape is cut from: see `gridOf`. */ readonly w: number; /** Rows, for the shapes that have them; the same as `w` for the others. */ readonly h: number; readonly algorithm: MeikyuuAlgorithm; readonly mode: MeikyuuMode; readonly seed: number; /** How many keys, for the `keys` way to play. */ readonly keys?: numbe…

Everything that makes a maze.

function measureArrows

measureArrows(board: ArrowBoard): ArrowMeasure

function measureMaze

measureMaze(maze: Maze): MazeMeasure

Measure a maze.

function measureMixed

measureMixed(board: MixedBoard): number

How hard it is: the arrows' effort and half the labyrinth's.

const MEIKYUU_ALGORITHMS

MEIKYUU_ALGORITHMS: readonly ["backtracker", "hunt", "growing", "prim", "kruskal", "wilson", "eller"]

THE SEVEN WAYS TO MAKE A PERFECT MAZE, each over any grid: a perfect maze is a spanning tree of the cells, so there is exactly one way between any two of them. They differ in the texture of what they make, which is written up in docs/MAZES.md:

- backtracker: walk till stuck, then back up. Long winding passages, few dead ends. - hunt: hunt-and-kill. Much the same, a little less winding. - growing: growing tree, taking the newest cell half the time and a random one the rest: in between. - prim: randomized Prim, taking a random edge from the frontier. Very many short dead ends. - kruskal: joins random edges that join different pieces. Many short dead ends, evenly spread. - wilson: loop-erased random walks. Every maze equally likely, so no texture of its own. - eller: one row at a time. Squares only: it needs rows.

They return each cell's open neighbours. Nothing here reads a cell's position.

const MEIKYUU_MODES

MEIKYUU_MODES: readonly ["enter-leave", "to-goal", "centre-out", "keys"]

A MAZE: a grid, the passages carved through it (always a spanning tree, so exactly one way between any two cells), and how it is played. The way to play is a declared field of the level, never inferred from the shape:

- enter-leave: in at one door in the outer wall, out at another. - to-goal: from a cell inside to a dot hidden deep in the maze. - centre-out: from the middle of the shape to a door in the outer wall. - keys: from a cell inside, collecting every key, to a door in the outer wall. A key is picked up by passing over it, and stays picked up if the line is drawn back.

A maze is a recipe (MazeRecipe) and nothing else: the same recipe makes the same maze in every browser and every Node, because everything that decides it is integer arithmetic on a seeded stream.

const MEIKYUU_MOST_ARROW_CELLS

MEIKYUU_MOST_ARROW_CELLS: 4000

The most cells an arrow board may have: a little over twice the biggest level's (1,849).

const MEIKYUU_MOST_CELLS

MEIKYUU_MOST_CELLS: 40000

The most cells a recipe may lay out: a little over twice what the biggest level does (19,321, a heart, leaf, star or moon cut from a raster 139 across). A recipe names its own size, and a server that takes recipes from other people must not be asked to build a maze of millions of cells, so parseRecipe refuses one that would.

const MEIKYUU_MOST_KEYS

MEIKYUU_MOST_KEYS: 10

The most keys a recipe may ask for: twice the most any level uses (five).

const MEIKYUU_SHAPES

MEIKYUU_SHAPES: readonly ["square", "hex", "triangle", "circle", "heart", "leaf", "star", "ring", "diamond", "cross", "moon", "hexagon", "pyramid"]

Every shape a maze can be made on.

type MeikyuuAlgorithm

type MeikyuuAlgorithm = (typeof MEIKYUU_ALGORITHMS)[number];

type MeikyuuMode

type MeikyuuMode = (typeof MEIKYUU_MODES)[number];

type MeikyuuShape

type MeikyuuShape = (typeof MEIKYUU_SHAPES)[number];

type MixedBoard

type MixedBoard = { readonly recipe: MixedRecipe; readonly arrows: ArrowBoard; readonly maze: Maze };

type MixedGame

type MixedGame = { readonly arrows: ArrowGame; readonly maze: MazeGame };

type MixedRecipe

type MixedRecipe = { readonly arrows: ArrowRecipe; readonly maze: MazeRecipe };

THE MIXED PUZZLE: an arrow board with some arrows locked, and a labyrinth beside it with an unlock button hidden deep inside. The labyrinth is an ordinary maze played to its goal (the button); reaching the button unlocks every locked arrow at once. The arrows are the puzzle (clear them all, with the hearts you have); the labyrinth is the way to the arrows that are in the way.

A locked arrow can be in the way of others, so the puzzle cannot be finished without the button, and it can always be: the arrows can all be cleared once they are unlocked, and the labyrinth can always be solved. A level is its two recipes, written <arrows>|<maze>.

function mixedRecipeCode

mixedRecipeCode(recipe: MixedRecipe): string

function mixedSolved

mixedSolved(game: MixedGame): boolean

Whether the puzzle is won: every arrow cleared.

function newArrowGame

newArrowGame(board: ArrowBoard, hearts?: number): ArrowGame

function newMazeGame

newMazeGame(maze: Maze): MazeGame

A game of the maze, with nothing drawn.

function newMixedGame

newMixedGame(board: MixedBoard): MixedGame

function parseArrowRecipe

parseArrowRecipe(code: string): ArrowRecipe | null

A recipe's arrow board from its code, or null when it is not one, or names a board bigger than MEIKYUU_MOST_ARROW_CELLS.

function parseMixedRecipe

parseMixedRecipe(code: string): MixedRecipe | null

function parseRecipe

parseRecipe(code: string): MazeRecipe | null

A recipe from its code, or null when it is not one, or names a maze bigger than MEIKYUU_MOST_CELLS or more keys than MEIKYUU_MOST_KEYS.

function passageCount

passageCount(links: Links): number

How many passages the maze has: one fewer than its cells, if it is perfect.

function peelRounds

peelRounds(board: Pick<ArrowBoard, "w" | "h" | "arrows">): number[][] | null

The order the arrows come off in if every free one is taken each round, as rounds: the length of this is how many times the board has to be looked at again.

function pick

pick<T>(items: readonly T[], random: Random): T

One of the items, chosen from the stream.

function playSolution

playSolution(game: MazeGame): MazeGame

Draw the maze's whole solution through the game, a cell at a time, as a finger would: the way a test or a demo plays. Keys are collected on the way.

type Point

type Point = readonly [number, number];

A point, [x, y], in cells.

function pressMaze

pressMaze(game: MazeGame, cell: number): MazeGame

A finger down on a cell: it begins a stroke if the cell is the start, the line's end, or on the line.

type Random

type Random = () => number;

A number in [0, 1), like Math.random, from a stream a seed fixes.

function ratingOf

ratingOf(effort: number): number

An effort as a rating from 1 to 100, on a scale where doubling the effort adds the same each time.

function rayOf

rayOf(board: Pick<ArrowBoard, "w" | "h">, arrow: Arrow): number[]

The cells in front of an arrow's head, out to the edge of the board, nearest first.

function recipeCode

recipeCode(recipe: MazeRecipe): string

A recipe as one short word, such as square:12x9:wilson:to-goal:48213 or heart:25:prim:keys-3:7, and the other way (parseRecipe).

function restartArrows

restartArrows(game: ArrowGame): ArrowGame

function restartMaze

restartMaze(game: MazeGame): MazeGame

Everything off the maze again.

function ringCounts

ringCounts(rings: number): number[]

How many cells each ring of a circle maze holds: one in the middle, six round it, and a ring doubles its cells when its cells would otherwise be wider than tall.

function seededRandom

seededRandom(seed: number): Random

A stream of numbers in [0, 1) fixed by a seed.

function shuffled

shuffled<T>(items: readonly T[], random: Random): T[]

A copy of the list in a random order (Fisher–Yates); the list given is left alone.

type Side

type Side = { readonly to: number; readonly wall: Wall };

One side of a cell: the cell across it (-1 for the edge of the shape) and the wall it is drawn with.

function solutionOf

solutionOf(maze: Maze): number[]

The one way from the start to the goal, cell by cell, both ends included.

function squareGrid

squareGrid(w: number, h: number): Grid

Columns by rows of square cells.

function tapArrow

tapArrow(game: ArrowGame, id: number): ArrowTap

function tapMaze

tapMaze(game: MazeGame, cell: number): MazeGame

A tap on a cell: the line runs along the passage toward it as far as the next place it could have gone another way, or to the cell itself if that comes first. A tap on a cell of the line cuts the line back to it. It begins the line when the tap is on the start. It never decides a fork for the player.

function triangleGrid

triangleGrid(w: number, h: number): Grid

Rows of triangles, each pointing up or down in turn, w to a row; a cell is up when its column and row make an even number.

function undoArrow

undoArrow(game: ArrowGame): ArrowGame

Put the last arrow taken back, and give back no heart.

function undoMaze

undoMaze(game: MazeGame): MazeGame

Put the whole line back as it was before the last stroke.

function unlockArrows

unlockArrows(game: ArrowGame): ArrowGame

Let the locked arrows be tapped.

const VERSION

VERSION: "1.0.0"

The package's version.

function walk

walk(links: Links, from: number): { dist: Int32Array; before: Int32Array; order: Int32Array; }

How far every cell is from from along the passages, and the cell before it on the way: -1 where unreachable.

type Wall

type Wall = { readonly a: Point; readonly b: Point; readonly r?: number };

The line a wall is drawn along, from a to b. With r, an arc of the circle of radius r about the origin, drawn clockwise on the screen from a to b (circle mazes only).

function withArrows

withArrows(game: MixedGame, arrows: ArrowGame): MixedGame

The arrows changed.

function withMaze

withMaze(game: MixedGame, maze: MazeGame): MixedGame

The labyrinth changed: when its button is reached, every locked arrow is unlocked.

@johnmorrisdotca/meikyuu/draw

arrowColour arrowMarkup boardLookOf doorPlace drawArrows DrawArrowsOptions drawMaze DrawMazeOptions fixed framed linePath lookAttributes lookStyle marksOf MazeLook MEIKYUU_ARROW_COLOURS MEIKYUU_BOARD_NAMES MEIKYUU_BOARDS MEIKYUU_STRINGS MEIKYUU_STYLE MEIKYUU_TRAIL_NAMES MEIKYUU_TRAILS MeikyuuBoardLook MeikyuuBoardName MeikyuuLanguage meikyuuLanguageOf meikyuuSay MeikyuuTrailName standingWalls wallPath

function arrowColour

arrowColour: (id: number) => string

The colour of arrow number id.

function arrowMarkup

arrowMarkup(board: Pick<ArrowBoard, "w">, arrow: Arrow, id: number, extra: { locked: boolean; gone?: boolean; hint?: boolean; }): string

The arrow's picture: its stem, its head, and a padlock if it is locked.

function boardLookOf

boardLookOf(board: MazeLook["board"]): MeikyuuBoardLook

The look a board name or board object means.

function doorPlace

doorPlace(grid: Grid, door: Door): { x: number; y: number; dx: number; dy: number; }

Where a door is: the middle of its gap in the wall, and the way out through it as a unit vector.

function drawArrows

drawArrows(board: ArrowBoard, options?: DrawArrowsOptions): string

type DrawArrowsOptions

type DrawArrowsOptions = MazeLook & { /** Which arrows are still on the board; all of them when left out. */ present?: readonly boolean[]; /** Whether the locked arrows have been unlocked. */ unlocked?: boolean; /** An arrow to light as the hint. */ hint?: number | null; language?: MeikyuuLanguage; label?: string; standalone?: boolean; };

AN ARROW BOARD AS SVG TEXT: a faint dot on every cell of the picture, and each arrow on it as a line through its cells with a head at the end it points from. One unit is one cell. Each arrow is a group with data-id, data-dir and data-locked, so a page can find the one under a finger; the style (MEIKYUU_STYLE) gives it its colour and its motion. A locked arrow is grey, dashed, and has a padlock at its tail.

function drawMaze

drawMaze(maze: Maze, options?: DrawMazeOptions): string

The maze as SVG text.

type DrawMazeOptions

type DrawMazeOptions = MazeLook & { /** The line drawn so far, as cells from the start. */ path?: readonly number[]; /** The keys picked up. */ collected?: readonly number[]; /** Draw the one way from the start to the goal. */ solution?: boolean; /** Cells to light as a hint, and how many cells of the line to draw back first. */ hint?: { back?: number; cells: readonly number[] }; /** Whether the maze is solved, whic…

function fixed

fixed: (n: number) => string

A number as short text: two decimals at most, so a drawing is small.

function framed

framed(grid: Grid, pad: number): { x: number; y: number; w: number; h: number; }

The rectangle to look at to see the whole of a shape with a margin of pad cells.

function linePath

linePath(grid: Grid, cells: readonly number[]): string

A line through the middles of the cells, in order.

function lookAttributes

lookAttributes(look: MazeLook): string

The attributes (as text) that make an element a Meikyuu drawing: its class, its look, and its custom properties.

function lookStyle

lookStyle(look: MazeLook): string

The inline custom properties a look needs: none for plain paper with the default line, which the style gives.

function marksOf

marksOf(maze: Maze, collected?: readonly number[]): string

The marks on a maze: the start, the goal or exit, the doors and the keys, as SVG.

type MazeLook

type MazeLook = { /** A board by name, or its colours. */ board?: MeikyuuBoardName | MeikyuuBoardLook; /** The line's colour: one of the named ones, or any CSS colour. */ trail?: MeikyuuTrailName | (string & {}); /** The thickness of a wall, in cells. Default 0.12. */ wall?: number; /** The thickness of the line, in cells. Default 0.34. */ line?: number; };

What a maze looks like: every option that does not depend on where the line is.

const MEIKYUU_ARROW_COLOURS

MEIKYUU_ARROW_COLOURS: readonly string[]

The colours of each arrow on an arrow board, in turn.

const MEIKYUU_BOARD_NAMES

MEIKYUU_BOARD_NAMES: ("paper" | "wood" | "green" | "blue" | "red" | "black")[]

const MEIKYUU_BOARDS

MEIKYUU_BOARDS: { readonly paper: { readonly paper: "#fbf8f1"; readonly wall: "#1f2320"; readonly frame: "#a98954"; readonly dark: false; readonly trail: "#2e8b57"; }; readonly wood: { readonly paper: "#e2ba7a"; readonly wall: "#3a2410"; readonly frame: "#8a5a2b"; readonly dark: false; readonly trail: "#1d6b46"; }; readonly green: { readonly paper: "#2f5d4a"; readonly wall: "#f3efe4"; readonly frame: "#1f4135"; read…

const MEIKYUU_STRINGS

MEIKYUU_STRINGS: Record<MeikyuuLanguage, Record<string, string>>

const MEIKYUU_STYLE

MEIKYUU_STYLE: string

const MEIKYUU_TRAIL_NAMES

MEIKYUU_TRAIL_NAMES: ("green" | "blue" | "red" | "violet" | "orange")[]

const MEIKYUU_TRAILS

MEIKYUU_TRAILS: { readonly green: { readonly light: "#2e8b57"; readonly dark: "#6fcf97"; }; readonly blue: { readonly light: "#2865a6"; readonly dark: "#7fb4ee"; }; readonly red: { readonly light: "#d9381e"; readonly dark: "#ff8a6b"; }; readonly violet: { readonly light: "#7b4fb0"; readonly dark: "#c3a1f0"; }; readonly orange: { readonly light: "#d97a1e"; readonly dark: "#ffb061"; }; }

type MeikyuuBoardLook

type MeikyuuBoardLook = { paper: string; wall: string; frame: string; dark: boolean; trail: string };

A board's colours: the paper, the walls, the frame, whether it is a dark board, and the line that shows on it when none is chosen.

type MeikyuuBoardName

type MeikyuuBoardName = keyof typeof MEIKYUU_BOARDS;

type MeikyuuLanguage

type MeikyuuLanguage = "en" | "ja";

THE WORDS MEIKYUU SAYS, in English and Japanese: what a screen reader hears of a drawing, what the buttons and the lines under a playable board say, and the names of the shapes and the ways to play. Plain data, so a page can read them, replace a few, or add a language of its own beside these two.

{name} in a line is a value filled in; a line foo that has a fooOne beside it is said as fooOne when its {n} is 1.

function meikyuuLanguageOf

meikyuuLanguageOf(tag: string | null | undefined): MeikyuuLanguage

The language a lang attribute asks for: Japanese for any ja, English for everything else.

function meikyuuSay

meikyuuSay(language: MeikyuuLanguage, key: string, values?: Record<string, string | number>): string

A line in a language, with its {name} values filled in, and its One form when n is 1.

type MeikyuuTrailName

type MeikyuuTrailName = keyof typeof MEIKYUU_TRAILS;

function standingWalls

standingWalls(maze: Maze): Wall[]

Every wall of the maze that is standing: between cells that are not joined, and round the outside, except the doors. Each is listed once.

function wallPath

wallPath(wall: Wall): string

The path data for a wall: a line, or an arc round the middle of a circle maze.

@johnmorrisdotca/meikyuu/play

createMeikyuuSounds EDGE EDGE_STEP edgeNudge ensureMeikyuuPlayStyle fitView isFitted keptView MEIKYUU_PLAY_STYLE MEIKYUU_SOUND_KINDS MeikyuuEventDetail MeikyuuMount MeikyuuMountOptions MeikyuuPuzzle MeikyuuSoundKind MeikyuuSounds MOST_CELL_PIXELS mountMeikyuu pannedBy pointAt puzzleOf scaleLimits View ViewBox viewBoxOf visibleArea zoomedAbout

function createMeikyuuSounds

createMeikyuuSounds(): MeikyuuSounds

const EDGE

EDGE: 44

How near an edge of the box a line's end must be dragged to move the view, and how far each frame moves it, in pixels.

const EDGE_STEP

EDGE_STEP: 7

function edgeNudge

edgeNudge(px: number, py: number, width: number, height: number): { dx: number; dy: number; }

How far to move the view in a frame for a finger at (px, py) of a box: the board moves the other way, toward the finger.

function ensureMeikyuuPlayStyle

ensureMeikyuuPlayStyle(host: Element): void

Put the style in the page once: in the document's head, or in the shadow root the host is in.

function fitView

fitView(box: ViewBox, pad?: number): View

The view that shows the whole of the area, centred, with a margin of pad cells.

function isFitted

isFitted(view: View, box: ViewBox, pad?: number): boolean

Whether the view is the whole board fitted.

function keptView

keptView(view: View, box: ViewBox, pad?: number): View

A view kept where some of the board can be seen: its middle never leaves the area (with a margin).

const MEIKYUU_PLAY_STYLE

MEIKYUU_PLAY_STYLE: string

THE STYLE a playable Meikyuu board wears (mountMeikyuu, <meikyuu-board>): the drawing's own (MEIKYUU_STYLE) and the board's box, its buttons, its lines of words and its tabs. Colours are custom properties on .meikyuu-play (--mkp-ink, --mkp-muted, --mkp-rule, --mkp-surface, --mkp-accent, --mkp-good) so a page sets only what it wants different.

Nothing moves when something is chosen: the board is one square box, the lines of words keep the room their longest wording takes, and the buttons are one size. Nothing the player touches can be selected.

const MEIKYUU_SOUND_KINDS

MEIKYUU_SOUND_KINDS: readonly ["step", "back", "key", "bump", "fly", "unlock", "solve", "lose", "tap"]

THE SOUNDS, made in the browser: short tones from the Web Audio API, one for each thing that happens (a step, drawing back, a key, an arrow bumping, an arrow flying off, an unlock, a solve, a loss). There are no recordings, so there is nothing to fetch and nothing to credit. Nothing is made until the first sound is asked for, a browser with no audio, or one that has not been touched yet, is silent without an error, and a sound that comes too soon after the last of its kind is dropped.

type MeikyuuEventDetail

type MeikyuuEventDetail = { kind: MeikyuuKind; /** The level's number in its list, when it is one of the package's. */ level: number | null; /** Strokes drawn (mazes) or arrows tapped (arrow puzzles). */ moves: number; /** Cells in the line now. */ cells: number; keys: number; keysOf: number; /** Hearts left, for arrow puzzles. */ hearts: number | null; arrowsLeft: number | null; solved: boolean; };

What every event tells of the board.

type MeikyuuMount

type MeikyuuMount = { readonly host: HTMLElement; kind: () => MeikyuuKind; puzzle: () => MeikyuuPuzzle; /** The maze game as it stands (a maze, or the labyrinth of a mixed puzzle), or null for arrows. */ mazeGame: () => MazeGame | null; /** The arrow game as it stands, or null for a maze. */ arrowGame: () => ArrowGame | null; /** Play another puzzle: a level or a recipe. Returns false when there is no such puzzle. *…

type MeikyuuMountOptions

type MeikyuuMountOptions = MazeLook & { /** Which list the level is from. Default `maze`. */ kind?: MeikyuuKind; /** A level of the package's own lists, from 1. */ level?: number; /** A recipe of your own, as its code or as an object, instead of a level. */ recipe?: string | MazeRecipe | ArrowRecipe | MixedRecipe; /** A tap runs the line along the corridor to the next fork. Default false. */ tap?: boolean; /** Offer…

type MeikyuuPuzzle

type MeikyuuPuzzle = | { kind: "maze"; recipe: MazeRecipe; level?: number } | { kind: "arrows"; recipe: ArrowRecipe; level?: number } | { kind: "mixed"; recipe: MixedRecipe; level?: number };

What is being played.

type MeikyuuSoundKind

type MeikyuuSoundKind = (typeof MEIKYUU_SOUND_KINDS)[number];

type MeikyuuSounds

type MeikyuuSounds = { play(kind: MeikyuuSoundKind): void; destroy(): void; };

const MOST_CELL_PIXELS

MOST_CELL_PIXELS: 72

The scales a view may have: the whole board fitted, to a cell this many pixels wide (at least a bit past the fit, so a small maze can still zoom).

function mountMeikyuu

mountMeikyuu(host: HTMLElement, options?: MeikyuuMountOptions): MeikyuuMount | null

Draw a puzzle into host and play it. Returns the handle that drives it, or null for a level or recipe that is none.

function pannedBy

pannedBy(view: View, dx: number, dy: number, box: ViewBox, pad?: number): View

The view moved so that the board slides by (dx, dy) pixels under the box.

function pointAt

pointAt(view: View, px: number, py: number): [number, number]

The point of the board under a pixel of the box.

function puzzleOf

puzzleOf(options: { kind?: MeikyuuKind; level?: number; recipe?: MeikyuuMountOptions["recipe"]; }): MeikyuuPuzzle | null

The puzzle a set of options names: a level of the package's lists, or a recipe given, or null.

function scaleLimits

scaleLimits(box: ViewBox, pad?: number): { least: number; most: number; }

type View

type View = { x: number; y: number; scale: number };

A BOARD LOOKED AT THROUGH A BOX, which may be zoomed in until a cell is as big as a thumb and moved about: the arithmetic of the view, without a page. A view is the point of the board at the box's top left corner and how many pixels a cell takes (scale); the board is drawn by setting an SVG's viewBox to what viewBoxOf says. Everything here is a pure function of numbers.

type ViewBox

type ViewBox = { width: number; height: number; area: Box };

How a box of width by height pixels shows a board: the margin round it in cells, and how far in a view may go.

function viewBoxOf

viewBoxOf(view: View, box: ViewBox): string

The text of the viewBox attribute that shows the view.

function visibleArea

visibleArea(view: View, box: ViewBox): Box

The part of the board the view shows.

function zoomedAbout

zoomedAbout(view: View, factor: number, px: number, py: number, box: ViewBox, pad?: number): View

The view zoomed by factor about the pixel (px, py) of the box, which stays over the same spot of the board.

@johnmorrisdotca/meikyuu/element

MeikyuuBoard

const MeikyuuBoard

MeikyuuBoard: typeof MeikyuuBoard

@johnmorrisdotca/meikyuu/element/define

@johnmorrisdotca/meikyuu/levels

findMazeLevels levelCount levelOf levelsOf MEIKYUU_ARROW_LEVELS MEIKYUU_KINDS MEIKYUU_MAZE_LEVELS MEIKYUU_MIXED_LEVELS MEIKYUU_SIZES MeikyuuArrowLevel MeikyuuKind MeikyuuLevel MeikyuuMazeLevel MeikyuuMixedLevel MeikyuuSize sizeOf

function findMazeLevels

findMazeLevels(filter?: { shape?: MazeRecipe["shape"]; mode?: MazeRecipe["mode"]; size?: MeikyuuSize; }): MeikyuuMazeLevel[]

The levels of a kind that pass a filter, with their numbers: by shape, by way to play and by size, each when given.

function levelCount

levelCount(kind: MeikyuuKind): number

How many levels a kind has.

function levelOf

levelOf(kind: "maze", n: number): MeikyuuMazeLevel | null levelOf(kind: "arrows", n: number): MeikyuuArrowLevel | null levelOf(kind: "mixed", n: number): MeikyuuMixedLevel | null levelOf(kind: MeikyuuKind, n: number): MeikyuuLevel | null

Level number n (from 1) of a kind, or null when there is none.

function levelsOf

levelsOf(kind: "maze"): readonly MeikyuuMazeLevel[] levelsOf(kind: "arrows"): readonly MeikyuuArrowLevel[] levelsOf(kind: "mixed"): readonly MeikyuuMixedLevel[] levelsOf(kind: MeikyuuKind): readonly MeikyuuLevel[]

Every level of a kind.

const MEIKYUU_ARROW_LEVELS

MEIKYUU_ARROW_LEVELS: readonly MeikyuuArrowLevel[]

const MEIKYUU_KINDS

MEIKYUU_KINDS: readonly ["maze", "arrows", "mixed"]

THE LEVELS: numbered lists of recipes, easiest first, never drawings. Each list is ordered by the effort its levels measure, so every level is at least as hard as the one before. A recipe rebuilds the same puzzle on every browser, and a level once published keeps its number.

There are three lists, each numbered from 1: the mazes (about a thousand, from a few cells to thousands, in every shape and every way to play), the arrow puzzles, and the mixed ones, an arrow puzzle with locked arrows and a labyrinth to find the unlock button in. In the order of play the mixed come after the plain ones: MEIKYUU_KINDS is that order.

const MEIKYUU_MAZE_LEVELS

MEIKYUU_MAZE_LEVELS: readonly MeikyuuMazeLevel[]

const MEIKYUU_MIXED_LEVELS

MEIKYUU_MIXED_LEVELS: readonly MeikyuuMixedLevel[]

const MEIKYUU_SIZES

MEIKYUU_SIZES: readonly ["small", "medium", "large", "huge"]

How big a maze is, in words a person picks by: under 150 cells is small, under 800 medium, under 4,000 large, and the rest huge.

type MeikyuuArrowLevel

type MeikyuuArrowLevel = Base & { readonly kind: "arrows"; readonly recipe: ArrowRecipe };

type MeikyuuKind

type MeikyuuKind = (typeof MEIKYUU_KINDS)[number];

type MeikyuuLevel

type MeikyuuLevel = MeikyuuMazeLevel | MeikyuuArrowLevel | MeikyuuMixedLevel;

type MeikyuuMazeLevel

type MeikyuuMazeLevel = Base & { readonly kind: "maze"; readonly recipe: MazeRecipe; readonly cells: number };

type MeikyuuMixedLevel

type MeikyuuMixedLevel = Base & { readonly kind: "mixed"; readonly recipe: MixedRecipe };

type MeikyuuSize

type MeikyuuSize = (typeof MEIKYUU_SIZES)[number];

function sizeOf

sizeOf(cells: number): MeikyuuSize