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.
@johnmorrisdotca/meikyuu 1.0.0 · 6 entry points · 173 exports
@johnmorrisdotca/meikyuuArrow 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 = { 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.
ARROW_HEARTS: 3
The hearts a puzzle starts with.
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.
ARROW_STEPS: readonly (readonly [number, number])[]
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 = 0 | 1 | 2 | 3;
Which way an arrow's head points: up, right, down, left.
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 = { 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 = { 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.
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 = (typeof ARROW_SHAPES)[number];
arrowsLeft(game: ArrowGame): number
The count of arrows still on the board.
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.
below(random: Random, n: number): number
A whole number in [0, n) from the stream. n must be at least 1.
blockedBy(game: ArrowGame, id: number): number[]
The arrows on the way out of id that are still on the board.
blockersOf(board: Pick<ArrowBoard, "w" | "h" | "arrows">): number[][]
Which arrows block others: for each arrow, the arrows whose cells lie on its way out.
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 = { readonly x: number; readonly y: number; readonly w: number; readonly h: number };
A rectangle, in cells.
buildMaze(recipe: MazeRecipe): Maze
Make the maze a recipe names.
buildMixed(recipe: MixedRecipe): MixedBoard
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.
centreCell(grid: Grid): number
The cell whose middle is nearest the middle of the shape.
circleGrid(rings: number): Grid
A circle maze: rings rings round a middle cell, cells running round each ring and out between the rings.
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 = { readonly cell: number; readonly side: number };
A door in the outer wall: the cell it is beside, and which of that cell's sides.
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.
EFFORT_LEAST: 9
The easiest effort a level is given a rating for, and the hardest: 1 and 100 are these.
EFFORT_MOST: 5400
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…
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.
headOf(game: MazeGame): number | null
The end of the line, or null while there is none.
hexGrid(w: number, h: number): Grid
Columns by rows of hexagons, pointy side up, every odd row shifted half a cell right.
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).
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.
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.
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.
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.
liftMaze(game: MazeGame): MazeGame
The finger lifted. A stroke that changed nothing is forgotten, so Undo never has a step that did nothing.
type Links = number[][];
Each cell's open neighbours.
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 = (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.
maskOf(shape: MeikyuuShape): Mask
The outline a raster shape is cut with.
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 = { 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 = { 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.
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 = { 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.
measureArrows(board: ArrowBoard): ArrowMeasure
measureMaze(maze: Maze): MazeMeasure
Measure a maze.
measureMixed(board: MixedBoard): number
How hard it is: the arrows' effort and half the labyrinth's.
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.
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.
MEIKYUU_MOST_ARROW_CELLS: 4000
The most cells an arrow board may have: a little over twice the biggest level's (1,849).
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.
MEIKYUU_MOST_KEYS: 10
The most keys a recipe may ask for: twice the most any level uses (five).
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 = (typeof MEIKYUU_ALGORITHMS)[number];
type MeikyuuMode = (typeof MEIKYUU_MODES)[number];
type MeikyuuShape = (typeof MEIKYUU_SHAPES)[number];
type MixedBoard = { readonly recipe: MixedRecipe; readonly arrows: ArrowBoard; readonly maze: Maze };
type MixedGame = { readonly arrows: ArrowGame; readonly maze: MazeGame };
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>.
mixedRecipeCode(recipe: MixedRecipe): string
mixedSolved(game: MixedGame): boolean
Whether the puzzle is won: every arrow cleared.
newArrowGame(board: ArrowBoard, hearts?: number): ArrowGame
newMazeGame(maze: Maze): MazeGame
A game of the maze, with nothing drawn.
newMixedGame(board: MixedBoard): MixedGame
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.
parseMixedRecipe(code: string): MixedRecipe | null
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.
passageCount(links: Links): number
How many passages the maze has: one fewer than its cells, if it is perfect.
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.
pick<T>(items: readonly T[], random: Random): T
One of the items, chosen from the stream.
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 = readonly [number, number];
A point, [x, y], in cells.
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 = () => number;
A number in [0, 1), like Math.random, from a stream a seed fixes.
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.
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.
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).
restartArrows(game: ArrowGame): ArrowGame
restartMaze(game: MazeGame): MazeGame
Everything off the maze again.
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.
seededRandom(seed: number): Random
A stream of numbers in [0, 1) fixed by a seed.
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 = { 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.
solutionOf(maze: Maze): number[]
The one way from the start to the goal, cell by cell, both ends included.
squareGrid(w: number, h: number): Grid
Columns by rows of square cells.
tapArrow(game: ArrowGame, id: number): ArrowTap
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.
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.
undoArrow(game: ArrowGame): ArrowGame
Put the last arrow taken back, and give back no heart.
undoMaze(game: MazeGame): MazeGame
Put the whole line back as it was before the last stroke.
unlockArrows(game: ArrowGame): ArrowGame
Let the locked arrows be tapped.
VERSION: "1.0.0"
The package's version.
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 = { 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).
withArrows(game: MixedGame, arrows: ArrowGame): MixedGame
The arrows changed.
withMaze(game: MixedGame, maze: MazeGame): MixedGame
The labyrinth changed: when its button is reached, every locked arrow is unlocked.
@johnmorrisdotca/meikyuu/drawarrowColour 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
arrowColour: (id: number) => string
The colour of arrow number id.
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.
boardLookOf(board: MazeLook["board"]): MeikyuuBoardLook
The look a board name or board object means.
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.
drawArrows(board: ArrowBoard, options?: DrawArrowsOptions): string
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.
drawMaze(maze: Maze, options?: DrawMazeOptions): string
The maze as SVG text.
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…
fixed: (n: number) => string
A number as short text: two decimals at most, so a drawing is small.
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.
linePath(grid: Grid, cells: readonly number[]): string
A line through the middles of the cells, in order.
lookAttributes(look: MazeLook): string
The attributes (as text) that make an element a Meikyuu drawing: its class, its look, and its custom properties.
lookStyle(look: MazeLook): string
The inline custom properties a look needs: none for plain paper with the default line, which the style gives.
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 = { /** 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.
MEIKYUU_ARROW_COLOURS: readonly string[]
The colours of each arrow on an arrow board, in turn.
MEIKYUU_BOARD_NAMES: ("paper" | "wood" | "green" | "blue" | "red" | "black")[]
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…
MEIKYUU_STRINGS: Record<MeikyuuLanguage, Record<string, string>>
MEIKYUU_STYLE: string
MEIKYUU_TRAIL_NAMES: ("green" | "blue" | "red" | "violet" | "orange")[]
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 = { 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 = keyof typeof MEIKYUU_BOARDS;
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.
meikyuuLanguageOf(tag: string | null | undefined): MeikyuuLanguage
The language a lang attribute asks for: Japanese for any ja, English for everything else.
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 = keyof typeof MEIKYUU_TRAILS;
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.
wallPath(wall: Wall): string
The path data for a wall: a line, or an arc round the middle of a circle maze.
@johnmorrisdotca/meikyuu/playcreateMeikyuuSounds 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
createMeikyuuSounds(): MeikyuuSounds
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.
EDGE_STEP: 7
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.
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.
fitView(box: ViewBox, pad?: number): View
The view that shows the whole of the area, centred, with a margin of pad cells.
isFitted(view: View, box: ViewBox, pad?: number): boolean
Whether the view is the whole board fitted.
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).
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.
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 = { 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 = { 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 = 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 = | { kind: "maze"; recipe: MazeRecipe; level?: number } | { kind: "arrows"; recipe: ArrowRecipe; level?: number } | { kind: "mixed"; recipe: MixedRecipe; level?: number };
What is being played.
type MeikyuuSoundKind = (typeof MEIKYUU_SOUND_KINDS)[number];
type MeikyuuSounds = { play(kind: MeikyuuSoundKind): void; destroy(): void; };
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).
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.
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.
pointAt(view: View, px: number, py: number): [number, number]
The point of the board under a pixel of the box.
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.
scaleLimits(box: ViewBox, pad?: number): { least: number; most: number; }
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 = { 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.
viewBoxOf(view: View, box: ViewBox): string
The text of the viewBox attribute that shows the view.
visibleArea(view: View, box: ViewBox): Box
The part of the board the view shows.
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/elementMeikyuuBoard: typeof MeikyuuBoard
@johnmorrisdotca/meikyuu/element/define@johnmorrisdotca/meikyuu/levelsfindMazeLevels levelCount levelOf levelsOf MEIKYUU_ARROW_LEVELS MEIKYUU_KINDS MEIKYUU_MAZE_LEVELS MEIKYUU_MIXED_LEVELS MEIKYUU_SIZES MeikyuuArrowLevel MeikyuuKind MeikyuuLevel MeikyuuMazeLevel MeikyuuMixedLevel MeikyuuSize sizeOf
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.
levelCount(kind: MeikyuuKind): number
How many levels a kind has.
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.
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.
MEIKYUU_ARROW_LEVELS: readonly MeikyuuArrowLevel[]
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.
MEIKYUU_MAZE_LEVELS: readonly MeikyuuMazeLevel[]
MEIKYUU_MIXED_LEVELS: readonly MeikyuuMixedLevel[]
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 = Base & { readonly kind: "arrows"; readonly recipe: ArrowRecipe };
type MeikyuuKind = (typeof MEIKYUU_KINDS)[number];
type MeikyuuLevel = MeikyuuMazeLevel | MeikyuuArrowLevel | MeikyuuMixedLevel;
type MeikyuuMazeLevel = Base & { readonly kind: "maze"; readonly recipe: MazeRecipe; readonly cells: number };
type MeikyuuMixedLevel = Base & { readonly kind: "mixed"; readonly recipe: MixedRecipe };
type MeikyuuSize = (typeof MEIKYUU_SIZES)[number];
sizeOf(cells: number): MeikyuuSize
この日本語は、まだ日本語を母語とする方の確認を受けていません。訂正を歓迎します。