Jirai地雷

@johnmorrisdotca/jirai 0.4.1 · 7 entry points · 90 exports

@johnmorrisdotca/jirai

activeCell activeCells Board Constraint dailySeed decodeOrthogonalGame deduce Deduction DEFAULT_SETTINGS encodeOrthogonalGame ENUMERATION_CELLS ENUMERATION_NODES Game gameFromProgress gameProgress GENERATION_ATTEMPTS GenerationError GenerationOptions Grid GRID_SPECS GRIDS hintFor HUGE_SIZES hugeSettings HugeSize isSolvable Language Level LEVEL_ALIASES LEVEL_SIZES LevelAlias levelNamed LEVELS levelSettings LevelSize makeBoard makeOrthogonalBoard Mark MARKS Material MAX_CELLS MAX_SIDE Measure measureBoard Move MOVES neighbours neighboursOf newGame newOrthogonalGame ORTHOGONAL_VARIANT orthogonalHint orthogonalNeighbours OrthogonalSettings Pieces play PRESETS ProofKind ProofTally seededRandom Settings SHAPES Status STATUSES validCell validSettings VERSION visibleGame VisibleGame withBoard

function activeCell

activeCell(settings: Settings, cell: number): boolean

Row-major addresses stay stable; omitted cells have no clue and no neighbours.

function activeCells

activeCells(settings: Settings): number[]

Returns all active cells in stable row-major order.

type Board

type Board = { settings: Settings; mines: readonly boolean[]; clues: readonly number[]; first: number; /** Which candidate this deal was: below the `attempts` limit it is a random layout, at or above it a repaired one. */ attempt: number; };

A dealt board including its answer; keep it off public clients.

type Constraint

type Constraint = { cells: readonly number[]; mines: number; sources: readonly number[] };

An exact count over unknown cells, with the clues that supplied it.

function dailySeed

dailySeed(day: string, grid?: string): number

UTC by definition, so two people sharing a daily seed get the same board.

function decodeOrthogonalGame

decodeOrthogonalGame(code: string): Game | null

Restores only version 2 orthogonal progress records.

function deduce

deduce(game: VisibleGame, knownMines?: ReadonlySet<number>, enumerate?: boolean, adjacent?: readonly (readonly number[])[]): Deduction

What the clues prove, without reading the answer or trusting a player's flags. A flag is a note; treating it as evidence makes a bad note a bad hint.

type Deduction

type Deduction = { safe: readonly number[]; mines: readonly number[]; reason: "count" | "overlap" | "total" | "enumeration" | "none"; sources: readonly number[]; /** A conflicting clue, or a board with no consistent mine placement. */ contradiction: boolean; };

Certain safe cells and mines proved from visible clues.

const DEFAULT_SETTINGS

DEFAULT_SETTINGS: Settings

Default easy game, with a verified no-guess board and clear opening.

function encodeOrthogonalGame

encodeOrthogonalGame(game: Game): string

Encodes an orthogonal game with an explicit variant marker and version 2.

const ENUMERATION_CELLS

ENUMERATION_CELLS: 18

Largest frontier enumerated for exact deductions.

const ENUMERATION_NODES

ENUMERATION_NODES: 100000

Maximum partial assignments checked in one exact deduction search.

type Game

type Game = { settings: Settings; board: Board | null; marks: readonly Mark[]; status: Status; exploded: number | null; moves: readonly Move[]; /** A proved hint has been shown during this run. */ helped: boolean; };

Rules data includes the answer; never send it to a competitive client. Use visibleGame for hints and drawing.

function gameFromProgress

gameFromProgress(progress: string): Game | null

A saved game is rebuilt under the rules. Malformed or unavailable boards return null.

function gameProgress

gameProgress(game: Game): string

Keep settings and moves, never an unchecked answer array. Version the deal before changing it.

const GENERATION_ATTEMPTS

GENERATION_ATTEMPTS: 128

Default number of random layouts tried before a no-guess deal falls back to repairing the closest one.

const GenerationError

GenerationError: typeof GenerationError

A bounded generator must say it failed, never quietly return a guessing board.

type GenerationOptions

type GenerationOptions = { attempts?: number; repairs?: number; repair?: boolean; enumerate?: boolean };

Work limits for seeded board generation and its no-guess check. attempts is the number of random layouts tried first; repairs is how many single-mine moves the repair stage may try on the closest of them (0, or repair: false, keeps the 0.2 behaviour of giving up after the attempts); enumerate switches the exact small-frontier check.

type Grid

type Grid = "square" | "orthogonal" | "hex" | "wrap";

Neighbour topology used to count the clues around each cell.

const GRID_SPECS

GRID_SPECS: Record<Grid, { offsets: readonly (readonly [number, number])[]; wrap: boolean; }>

Neighbour offsets and edge behaviour for each topology. Hex coordinates are axial: each row is displaced half a cell to the right.

const GRIDS

GRIDS: { readonly square: "square"; readonly orthogonal: "orthogonal"; readonly hex: "hex"; readonly wrap: "wrap"; }

Names for the supported square, four-neighbour, hex, and wrap grids.

function hintFor

hintFor(game: VisibleGame): Deduction

Keep discovering certain mines until there is a safe cell or nothing more is proved.

const HUGE_SIZES

HUGE_SIZES: readonly [{ readonly width: 32; readonly height: 32; }, { readonly width: 48; readonly height: 24; }, { readonly width: 24; readonly height: 48; }]

The huge fields: four times the area of a 16×16 (the medium level), 1,024 to 1,152 squares, for a long solve. The same share of mines as a level, see hugeSettings. They are dealt, proved and drawn like any other field: a no-guess deal takes a few milliseconds to a tenth of a second here, and 2,400 squares is the most validSettings accepts.

function hugeSettings

hugeSettings(level: Level | LevelAlias, board?: { grid?: Grid; shape?: Settings["shape"]; size?: HugeSize; }): LevelSize

The size and mine count of a level on a huge field: a field of HUGE_SIZES (32 × 32 unless size says another of them), with the level's own share of mines over its squares. Easy is 12% mines, medium 16%, hard 21% and extra-hard 25%, as on the level's own field, and a heart, star or hexagon outline keeps that share over the squares it has left. Every field is still dealt and proved to need no guess, on every grid. Throws RangeError for a name that is no level or a size that is not huge.

type HugeSize

type HugeSize = { width: number; height: number };

The outline of a huge field: one of HUGE_SIZES' width and height.

function isSolvable

isSolvable(board: Board, enumerate?: boolean): boolean

Verify a board from one opening, using only clues that have been uncovered.

type Language

type Language = "en" | "ja";

Supported interface copy locales.

type Level

type Level = "easy" | "medium" | "hard" | "extra-hard";

A difficulty level: easy, medium, hard or extra-hard.

const LEVEL_ALIASES

LEVEL_ALIASES: { readonly beginner: "easy"; readonly intermediate: "medium"; readonly expert: "hard"; }

The 0.1 and 0.2 names for the first three levels. They are still accepted wherever a level is.

const LEVEL_SIZES

LEVEL_SIZES: Record<"easy" | "medium" | "hard" | "extra-hard", { width: number; height: number; mines: number; }>

Size and mine count of each level on a rectangular board. Shaped boards keep the size and the density.

type LevelAlias

type LevelAlias = "beginner" | "intermediate" | "expert";

The 0.1 and 0.2 names for easy, medium and hard, still accepted wherever a level is.

function levelNamed

levelNamed(name: unknown): Level | null

The level a name means, or null. Accepts easy, medium, hard and extra-hard (also written extra hard, extra_hard or extraHard, in any case), and the older beginner, intermediate and expert.

const LEVELS

LEVELS: readonly ["easy", "medium", "hard", "extra-hard"]

The four difficulty levels, easiest first. Each is a larger and denser field than the one before.

function levelSettings

levelSettings(level: Level | LevelAlias, board?: { grid?: Grid; shape?: Settings["shape"]; }): LevelSize

The size and mine count of a level. A rectangle gets the level's own numbers; a shaped board keeps the level's width and height and its density of mines over the cells that are left. Throws RangeError for a name that is no level.

type LevelSize

type LevelSize = Pick<Settings, "width" | "height" | "mines">;

Board size and mine count a level asks for.

function makeBoard

makeBoard(settings: Settings, first: number, options?: GenerationOptions): Board

Deal after the first reveal. A seed fixes the whole candidate stream and the accepted board.

A no-guess deal first draws random layouts, as 0.1 and 0.2 did, so every board those versions dealt is still dealt. If none of them can be finished by deduction it takes the closest and repairs it: single mines are moved near the place the solver stopped until the solver finishes the whole field. The result is proved the same way either route.

function makeOrthogonalBoard

makeOrthogonalBoard(settings: OrthogonalSettings, first: number, options?: GenerationOptions): Board

Deals a board whose clues count only up, down, left and right.

type Mark

type Mark = "covered" | "flag" | "question" | "open";

Player-facing state of one cell; the answer is never a mark.

const MARKS

MARKS: { readonly covered: "covered"; readonly flag: "flag"; readonly question: "question"; readonly open: "open"; }

Names for the cell mark cycle.

type Material

type Material = "ivory" | "wood" | "slate";

Board colours for drawing and mounted play.

const MAX_CELLS

MAX_CELLS: 2400

Largest total cell count accepted by settings validation.

const MAX_SIDE

MAX_SIDE: 60

Largest width or height accepted by settings validation.

type Measure

type Measure = { /** Mines per playable cell. */ density: number; /** Playable cells. */ cells: number; /** Share of the safe cells the first reveal opens before any reasoning. */ opening: number; /** Cells proved by each kind of reasoning: one clue, two clues together, the mine counter, or every arrangement of a small frontier. */ proofs: ProofTally; /** Share of the playable cells proved by anything beyond one clu…

How demanding a dealt board is to solve by deduction, measured by solving it.

function measureBoard

measureBoard(board: Board): Measure

Solve a dealt board from its opening and report what it took. The score is 100 × density + 100 × multiStep + depth ÷ 4, so it rises with the crowding of the mines, with the share of the field that needs more than a single clue, and with how long the longest chain of reasoning runs. It is a way to put boards in order, not a rating of how long a person will take.

type Move

type Move = { kind: "reveal" | "mark" | "chord"; cell: number };

A reveal, mark-cycle, or numbered-cell chord applied to a game.

const MOVES

MOVES: { readonly reveal: "reveal"; readonly mark: "mark"; readonly chord: "chord"; }

Names for the moves accepted by the engine.

function neighbours

neighbours(settings: Settings, cell: number): number[]

Every neighbour once. Wrap joins both pairs of opposite edges.

function neighboursOf

neighboursOf(settings: Settings): number[][]

Precomputes the neighbour list for each row-major cell.

function newGame

newGame(settings?: Settings): Game

Creates an unstarted game with covered cells and no dealt answer.

function newOrthogonalGame

newOrthogonalGame(settings: OrthogonalSettings): Game

Starts a game under four-neighbour rules.

const ORTHOGONAL_VARIANT

ORTHOGONAL_VARIANT: "orthogonal"

Identifies the four-neighbour rules and progress-code format.

function orthogonalHint

orthogonalHint(game: Game): import("/home/runner/work/jirai/jirai/src/jirai.types").Deduction

Gives a clue-only deduction; flags remain notes and cannot influence the result.

function orthogonalNeighbours

orthogonalNeighbours(settings: OrthogonalSettings, cell: number): number[]

Returns the adjacent cells under the orthogonal rules.

type OrthogonalSettings

type OrthogonalSettings = Omit<Settings, "grid">;

Settings accepted by the four-neighbour-specific helpers.

type Pieces

type Pieces = "flags" | "stones" | "flowers";

Symbols used to mark mines.

function play

play(game: Game, move: Move): Game

A legal move returns a new game; an unavailable move returns the same one.

const PRESETS

PRESETS: { readonly beginner: { width: number; height: number; mines: number; }; readonly intermediate: { width: number; height: number; mines: number; }; readonly expert: { width: number; height: number; mines: number; }; readonly wide: { readonly width: 21; readonly height: 9; readonly mines: 24; }; readonly tall: { readonly width: 9; readonly height: 21; readonly mines: 24; }; readonly easy: { width: number; heig…

Common minefield dimensions and mine counts. The levels, their older names, and two shapes of field.

type ProofKind

type ProofKind = "count" | "overlap" | "total" | "enumeration";

Why a cell was proved: one clue, two overlapping clues, the mine counter, or every arrangement of a small frontier.

type ProofTally

type ProofTally = Record<ProofKind, number>;

Cells proved by each kind of reasoning.

function seededRandom

seededRandom(seed: number): () => number

Mulberry32, pinned by the replay tests: changing it would change every kept board.

type Settings

type Settings = { /** Shape omits cells; rectangle is the compatible default. Wrap requires rectangle. */ shape?: "rectangle" | "heart" | "star" | "hexagon"; width: number; height: number; mines: number; grid: Grid; /** Only accept boards the deduction solver finishes from the opening. */ noGuess: boolean; /** The opening and every neighbour are safe, not only the first cell. */ opening: "safe" | "clear"; seed: numb…

Board dimensions and rules used to deal and validate a game.

const SHAPES

SHAPES: readonly ["rectangle", "heart", "star", "hexagon"]

Board silhouettes supported by settings.

type Status

type Status = "ready" | "playing" | "won" | "lost";

Current game state, including whether a mine has been hit.

const STATUSES

STATUSES: { readonly ready: "ready"; readonly playing: "playing"; readonly won: "won"; readonly lost: "lost"; }

Names for the game lifecycle states.

function validCell

validCell(settings: Settings, cell: number): boolean

Reports whether a row-major cell is inside the board's active shape.

function validSettings

validSettings(value: unknown): value is Settings

Reject a setting before it reaches a board allocation or a shuffle.

const VERSION

VERSION: "0.4.1"

Package version, kept in step with the release metadata.

function visibleGame

visibleGame(game: Game): VisibleGame

The clue-only view used by the solver. No hidden mine, including under a flag, survives it.

type VisibleGame

type VisibleGame = { settings: Settings; /** Null is hidden, including flags. Only opened cells give a clue. */ clues: readonly (number | null)[]; marks: readonly Mark[]; status: Status; };

Answer-free state suitable for deductions, hints, and public display.

function withBoard

withBoard(game: Game, board: Board): Game

Attaches a worker-dealt board that matches the game's settings, preserving existing moves.

@johnmorrisdotca/jirai/orthogonal

decodeOrthogonalGame encodeOrthogonalGame makeOrthogonalBoard newOrthogonalGame ORTHOGONAL_VARIANT orthogonalHint orthogonalNeighbours OrthogonalSettings

function decodeOrthogonalGame

decodeOrthogonalGame(code: string): Game | null

Restores only version 2 orthogonal progress records.

function encodeOrthogonalGame

encodeOrthogonalGame(game: Game): string

Encodes an orthogonal game with an explicit variant marker and version 2.

function makeOrthogonalBoard

makeOrthogonalBoard(settings: OrthogonalSettings, first: number, options?: GenerationOptions): Board

Deals a board whose clues count only up, down, left and right.

function newOrthogonalGame

newOrthogonalGame(settings: OrthogonalSettings): Game

Starts a game under four-neighbour rules.

const ORTHOGONAL_VARIANT

ORTHOGONAL_VARIANT: "orthogonal"

Identifies the four-neighbour rules and progress-code format.

function orthogonalHint

orthogonalHint(game: Game): import("/home/runner/work/jirai/jirai/src/jirai.types").Deduction

Gives a clue-only deduction; flags remain notes and cannot influence the result.

function orthogonalNeighbours

orthogonalNeighbours(settings: OrthogonalSettings, cell: number): number[]

Returns the adjacent cells under the orthogonal rules.

type OrthogonalSettings

type OrthogonalSettings = Omit<Settings, "grid">;

Settings accepted by the four-neighbour-specific helpers.

@johnmorrisdotca/jirai/play

JiraiMount mountJirai MountOptions STRINGS

type JiraiMount

type JiraiMount = { game: () => Game; progress: () => string; play: (cell: number, mark?: boolean) => void; hint: () => void; restart: () => void; load: (settings: Settings, progress?: string) => void; set: (options: DrawOptions) => void; destroy: () => void; };

Controls for reading and operating a mounted board.

function mountJirai

mountJirai(host: HTMLElement, options?: MountOptions): JiraiMount

One playable board in any element. The engine owns the rules, this module owns presses, focus, the clock and the worker. Destroy removes only its own root, so two mounted boards never take each other's listeners or children.

type MountOptions

type MountOptions = DrawOptions & { settings?: Settings; progress?: string; controls?: boolean; onChange?: (game: Game) => void; onFinish?: (game: Game) => void; onError?: (error: Error) => void; };

Initial game, appearance, control, and lifecycle callbacks for a mounted board.

const STRINGS

STRINGS: { readonly en: { readonly board: "Minesweeper board"; readonly covered: "covered"; readonly flag: "flagged"; readonly question: "uncertain"; readonly empty: "empty"; readonly mine: "mine"; readonly wrong: "incorrect flag"; readonly ready: "Open a cell to begin. Your first cell is safe."; readonly playing: "Read the numbers. Open every safe cell."; readonly won: "Every safe cell is open. Well played."; reado…

Built-in English and Japanese strings used by the player and renderer.

@johnmorrisdotca/jirai/draw

boardModel BoardModel CellModel DrawOptions JIRAI_STYLE

function boardModel

boardModel(game: Game, options?: DrawOptions): BoardModel

Where the cells sit, and what may be shown. The drawing never exposes a covered clue.

type BoardModel

type BoardModel = { width: number; height: number; cells: CellModel[] };

Layout and cell presentation returned by boardModel.

type CellModel

type CellModel = { cell: number; x: number; y: number; width: number; height: number; label: string; text: string; kind: string; hint: boolean };

Accessible presentation data for one active board cell.

type DrawOptions

type DrawOptions = { material?: Material; pieces?: Pieces; language?: Language; hint?: number | null };

Appearance and optional hint marker used by drawing and play.

const JIRAI_STYLE

JIRAI_STYLE: "\n.jr-root{--jr-ground:#a98954;--jr-cover:#fbf8f1;--jr-open:#efe8d8;--jr-line:#cfc6b2;--jr-ink:#1f2320;--jr-accent:#2f7a4f;color:var(--jr-ui-ink,var(--jr-ink));font:inherit;position:relative}\n.jr-root[data-material=wood]{--jr-ground:#ae804a;--jr-cover:#e0bb7e;--jr-open:#c69d63;--jr-line:#936e40;--jr-ink:#352c20;background-image:repeating-linear-gradient(4deg,transparent 0 8px,#4b2b0c08 9px 10px)}\n.jr…

Scoped to one mounted board. Its host supplies the font and may override every colour.

@johnmorrisdotca/jirai/element

defineJirai JiraiElement

function defineJirai

defineJirai(registry?: CustomElementRegistry): void

Registers the <jirai-board> custom element once in a registry.

const JiraiElement

JiraiElement: typeof JiraiElement

Configurable <jirai-board> element that owns its mounted game.

@johnmorrisdotca/jirai/element/define

@johnmorrisdotca/jirai/react

JiraiBoard

function JiraiBoard

JiraiBoard({ settings, progress, material, pieces, language, controls, onChange, onFinish, onError, ...element }: JiraiProps): import("/home/runner/work/jirai/jirai/node_modules/.pnpm/@types+react@19.3.0/node_modules/@types/react/index").JSX.Element

Renders the DOM player inside a React-owned host. Options are read on mount; use a new key to load a new board.