The pack
letterCascade
function letterCascade(): Pack<{ letterCascade: LetterCascadeApi }>
The letter-cascade pack: falling letter tile rules over the english-words lists. Pure logic, so it runs headless.
Helpers
CASCADE_LENGTH_MULTIPLIERS
const CASCADE_LENGTH_MULTIPLIERS: ReadonlyArray<number>
A word's multiplier by its length in letters: 3 letters ×1, 4 ×1.5, 5 ×2, rising to ×6 at 10 letters and beyond.
CASCADE_LEVELS
const CASCADE_LEVELS: ReadonlyArray<number>
The scores that reach levels 2, 3, 4 and on.
CASCADE_VALUES
const CASCADE_VALUES: Readonly<Record<string, number>>
Points for each letter, by rarity: common letters 1, the rarest 7. A QU tile counts as q.
cascadeNeighbours
function cascadeNeighbours(cols: number, rows: number, adjacency: CascadeAdjacency, pos: number): number[]
Every position next to pos (col * rows + row, row 0 at the top) on a board of cols × rows.
cascadeScore
function cascadeScore(tiles: ReadonlyArray<{ readonly letter: string; readonly kind?: CascadeKind }>, options: { values?: Readonly<Record<string, number>>; lengthMultipliers?: ReadonlyArray<number> } = {}): number
A word's points from its tiles, in order: the tiles' letter values added up, times the multiplier for the word's length in letters, times 2 for each gold tile and 3 for each gem in it, rounded. Burning tiles score as letters.
createLetterCascade
function createLetterCascade(options: LetterCascadeOptions): LetterCascade
Start a game. Throws a RangeError for options out of range: a board of no tiles, an adjacency other than 'hex',
8 or 4, a set board with uneven rows or characters other than letters, a special outside the board.
Types
CascadeAdjacency
type CascadeAdjacency = 'hex' | 8 | 4
Which tiles touch: 'hex' (columns offset by half a tile, six neighbours), 8 (sides and corners) or 4 (sides).
CascadeEvent
type CascadeEvent = /** The tiles of the word (or burnt) taken away. */ | { readonly kind: 'remove'; readonly ids: ReadonlyArray<number> } /** A tile falling down its column to close a gap. */ | { readonly kind: 'fall'; readonly id: number; readonly col: number; readonly fromRow: number; readonly toRow: number } /** A new tile at the top of a column, already at the row it lands in. */ | { readonly kind: 'spawn'; readonly id: number; readonly col: number; readonly row: number; readonly letter: string; readonly tileKind: CascadeKind } /** A burning tile burning the tile below it. */ | { readonly kind: 'burn'; readonly fireId: number; readonly burnedId: number } /** A scramble giving a tile a new letter. */ | { readonly kind: 'reletter'; readonly id: number; readonly letter: string } /** A scramble's cost: a tile set alight. */ | { readonly kind: 'ignite'; readonly id: number } /** A new level reached. */ | { readonly kind: 'level'; readonly level: number } /** A burning tile reached the bottom row: the game is over. */ | { readonly kind: 'over' }
What happened in a move, in the order it happened, for a game to animate.
CascadeFireOptions
interface CascadeFireOptions
How burning tiles come and go.
chance?: number— Chance that a word brings a burning tile, 0..1, before level and weak words add to it. Default 0.06.perLevel?: number— Added to the chance for each level after the first. Default 0.03.perWeak?: number— Added for each weak word in a row (a strong word starts the count again). Default 0.18.weakBelow?: number— A word scoring fewer points than this is weak. Default 8.max?: number— Most burning tiles on the board at once. Default the number of columns.
CascadeKind
type CascadeKind = 'normal' | 'fire' | 'gold' | 'gem'
What a tile is: an ordinary letter, a burning tile, or a bonus tile that multiplies a word's points.
CascadeMove
type CascadeMove = { readonly kind: 'word'; readonly path: ReadonlyArray<number> } | { readonly kind: 'scramble' }
A move: a word traced through tiles (positions col * rows + row), or a scramble.
CascadeResult
interface CascadeResult
What a move did.
readonly kind: 'word' | 'scramble'—'word'or'scramble'.readonly word: string | null— The word played, lower case; null for a scramble.readonly points: number— Points the word scored; 0 for a scramble.readonly events: ReadonlyArray<CascadeEvent>— Everything that happened, in order.readonly level: number— The level after the move.readonly over: boolean— Did the move end the game?
CascadeTile
interface CascadeTile
One tile. Its id stays with it as it falls, so a game can animate it.
readonly id: number— Stable for the tile's life; never reused.readonly letter: string— Its letters, lower case (qufor a QU tile).readonly kind: CascadeKind— What kind of tile it is.
CascadeView
interface CascadeView
The game as it stands.
readonly phase: 'playing' | 'over'—'playing', or'over'once a burning tile has reached the bottom row.readonly cols: number— Columns.readonly rows: number— Rows.readonly adjacency: CascadeAdjacency— Which tiles touch.readonly tiles: ReadonlyArray<CascadeTile>— Every tile, at positioncol * rows + row, row 0 at the top.readonly score: number— Points scored.readonly level: number— The level, from 1.readonly nextLevel: number | null— The score that reaches the next level, or null past the last.readonly words: ReadonlyArray<{ readonly word: string; readonly points: number }>— Every word played, oldest first.readonly best: { readonly word: string; readonly points: number } | null— The best-scoring word so far, or null.readonly fires: number— Burning tiles on the board.readonly fireChance: number— The chance the next word brings a burning tile, 0..1.readonly turns: number— Moves made.readonly minLength: number— Shortest word, in letters.
LetterCascade
interface LetterCascade
A game of falling letter tiles. Make one with createLetterCascade.
observation(): CascadeView— The game as it stands.at(col: number, row: number): CascadeTile | null— The tile at a column and row (row 0 at the top), or null outside the board.positionOf(id: number): number— Where the tile with this id is now (col * rows + row), or -1 when it is gone.neighbours(pos: number): number[]— The positions that touchpos.canExtend(path: ReadonlyArray<number>, pos: number): boolean— Can a trace atpathgo on topos: touching its last tile and not used yet? True for any tile when the path is empty.spell(path: ReadonlyArray<number>): string | null— What a path spells, lower case, or null when it is not a chain of touching tiles used once each.check(path: ReadonlyArray<number>): { readonly ok: true; readonly word: string; readonly points: number } | Refusal— Would this word score now?{ ok: true, word, points }, or the refusal with a reason a player can be shown.play(path: ReadonlyArray<number>): { readonly ok: true; readonly result: CascadeResult } | Refusal— Play a word. Refused, changing nothing, whencheckwould refuse it.scramble(): { readonly ok: true; readonly result: CascadeResult } | Refusal— Give every ordinary tile a new letter, at the cost ofscrambleFireburning tiles; a turn passes. Refused when the game is over.undo(): boolean— Take back the last move.hint(budget?: number): { readonly word: string; readonly points: number; readonly path: ReadonlyArray<number> } | null— The best-scoring word on the board and its path, searching at mostbudgetsteps (default 60000); null when there is none.snapshot(): HistorySave<CascadeMove>— The game as a save: the seed and the moves. Restore it into a game made with the same options.restore(save: HistorySave<CascadeMove> | string): void— Replace this game with a save. Throws, changing nothing, when it does not replay under these options.
LetterCascadeApi
type LetterCascadeApi = typeof rules
game.letterCascade: every function and type this pack carries, as one namespace.
LetterCascadeOptions
interface LetterCascadeOptions
How a game is set up. Only seed is required.
seed: number— Deals the letters and decides every chance, as an unsigned 32-bit integer.cols?: number— Columns. Default 7, or the board's width.rows?: number— Rows. Default 8, or the board's height.adjacency?: CascadeAdjacency— Which tiles touch. Default'hex'.minLength?: number— Shortest word, in letters. Default 3.words?: WordList— The words that count. Default the English lexicon.qu?: boolean— Show Q as a QU tile. Default true.values?: Readonly<Record<string, number>>— Points for each letter (a QU tile counts asq). DefaultCASCADE_VALUES, by rarity.lengthMultipliers?: ReadonlyArray<number>— The multiplier for a word of each length in letters, by index; the last entry is used for anything longer. DefaultCASCADE_LENGTH_MULTIPLIERS.fire?: CascadeFireOptions | false— Burning tiles, or false for none.lengthForBonus?: number— A word this long or longer brings a gold tile (×2); two letters longer, a gem (×3). Default 6.levels?: ReadonlyArray<number>— The score that reaches each next level: level 2 at the first, level 3 at the second, and so on. DefaultCASCADE_LEVELS.scrambleFire?: number— Burning tiles a scramble costs, set on the top row. Default 1 (none when fire is off).board?: ReadonlyArray<string>— A set board instead of a dealt one: one string a row, top first, one letter a tile (qis a QU tile whenquis on).specials?: ReadonlyArray<{ readonly col: number; readonly row: number; readonly kind: Exclude<CascadeKind, 'normal'> }>— Special tiles on a set board.


