Toranpuトランプ

@johnmorrisdotca/toranpu 2.13.2 · 26 entry points · 629 exports

@johnmorrisdotca/toranpu

allDifferent BIG_TWO_DEALS bigTwo CARD_GAME_KINDS CARD_GAME_LIST CARD_GAME_RULES CARD_GAME_TABLES cardGameCodec CardGameCodec CardGameKind CardGamePlays CardGameRules CardGameTable CardId cardOf cardOfId CardRank CardSeats cardShort cardsShort CardSuit cardText cardWords choices cliLanguage CliResult CliSurroundings computerSeats CRAZY_EIGHTS_SIZES crazyEights cribbage CRIBBAGE_SIZES CSV_COLUMNS dealRound euchre EUCHRE_SIZES fillIn fromCode fromJSON FULL_DECK gameName GameOf gameSays GIN_SIZES ginRummy GO_FISH_SIZES goFish hearts HEARTS_SIZES isCard isCardList KeptCardGame Language languageOf mixSeed MoveOf moveText MoveTextOptions namesList newGame NewGameOptions nextSeat OH_HELL_DEALS ohHell playComputers president PRESIDENT_ROUNDS Random randomSeed RANK_LETTERS rankName rankOf rankWords ReadGame RecordedMove recordOf reshuffled rulesFor runCli SAVE_FORMAT savedGame SavedGame seededRandom shuffled shuffledDeck spades SPADES_SIZES STRINGS suitName suitOf suitSymbol suitWords toCode toCSV toJSON ToranpuStrings toText VERSION without

function allDifferent

allDifferent(cards: readonly CardId[]): boolean

Whether a list names no card twice.

const BIG_TWO_DEALS

BIG_TWO_DEALS: readonly [1, 3, 5]

How many deals a game of Big Two may last.

namespace bigTwo

import { bigTwo } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/bigTwo"

Everything the @johnmorrisdotca/toranpu/bigTwo entry point exports, as one namespace.

const CARD_GAME_KINDS

CARD_GAME_KINDS: { readonly hearts: "hearts"; readonly bigTwo: "bigTwo"; readonly president: "president"; readonly goFish: "goFish"; readonly crazyEights: "crazyEights"; readonly spades: "spades"; readonly ginRummy: "ginRummy"; readonly euchre: "euchre"; readonly cribbage: "cribbage"; readonly ohHell: "ohHell"; }

The ten kinds by name, for code that would rather not type a string twice.

const CARD_GAME_LIST

CARD_GAME_LIST: readonly CardGameKind[]

Every card game.

const CARD_GAME_RULES

CARD_GAME_RULES: { hearts: CardGameRules<HeartsGame, HeartsMove>; bigTwo: CardGameRules<BigTwoGame, ClimbMove>; president: CardGameRules<PresidentGame, PresidentMove>; goFish: CardGameRules<GoFishGame, GoFishMove>; crazyEights: CardGameRules<CrazyEightsGame, CrazyEightsMove>; spades: CardGameRules<SpadesGame, SpadesMove>; ginRummy: CardGameRules<GinGame, GinMove>; euchre: CardGameRules<EuchreGame, EuchreMove>; cribb…

EVERY CARD GAME'S RULES, by kind: what a table plays and what the simulation plays out. A mapped type, so a new card game does not compile until its rules are here.

const CARD_GAME_TABLES

CARD_GAME_TABLES: Record<CardGameKind, CardGameTable>

Every game's table: the fewest, the most and the usual number of players, the lengths of game offered and the usual one.

function cardGameCodec

cardGameCodec<S extends KeptCardGame<M>, M>(game: string, start: (size: number, players: readonly string[], language: undefined, seed: number, computers: readonly boolean[]): S | null, play: (state: S, move: M) => S | null, isMove: (value: unknown) => value is M) => CardGameCodec<S>

The keeping for one game: game names it, so one game's text is never read as another's; start and play are its rules; isMove checks a move's shape before the rules are asked about it.

type CardGameCodec

type CardGameCodec<S> = { encode: (game: S) => string; decode: (text: string | null) => S | null; };

A game's keeping: encode writes it as text, decode reads it back or gives null.

type CardGameKind

type CardGameKind = "hearts" | "bigTwo" | "president" | "goFish" | "crazyEights" | "spades" | "ginRummy" | "euchre" | "cribbage" | "ohHell";

THE CARD GAMES, and the tables each offers. A card game's "size" is how long the game lasts, in its own terms:

- Hearts, three or four: the score that ends it, 50 or 100 (the usual game). - Big Two, two to four: how many deals, 1, 3 or 5, the fewest points winning. - President, three to eight: how many rounds, 3, 5 or 7, the most points winning. - Go Fish, two to six: one deal, played until every book is down. - Crazy Eights, two to seven: the score that wins, 50, 100 or 200. - Spades, four in two partnerships: the score that wins, 200, 300 or 500. - Gin Rummy, two: the score that wins, 50, 100 or 150. - Euchre, four in two partnerships: the score that wins, 5 or 10. - Cribbage, for two: the score that wins, 61 (once round the board) or 121. - Oh Hell, three or four each for themselves: how many deals, 7 (one card up to seven) or 13 (and back down).

Every seat may be a person's or a computer's, so a table of one person and three computers is a game of Hearts as much as four people round a phone.

type CardGamePlays

type CardGamePlays = { hearts: { game: HeartsGame; move: HeartsMove }; bigTwo: { game: BigTwoGame; move: ClimbMove }; president: { game: PresidentGame; move: PresidentMove }; goFish: { game: GoFishGame; move: GoFishMove }; crazyEights: { game: CrazyEightsGame; move: CrazyEightsMove }; spades: { game: SpadesGame; move: SpadesMove }; ginRummy: { game: GinGame; move: GinMove }; euchre: { game: EuchreGame; move: EuchreM…

Each card game's game and move, so its rules can be named with their own types.

type CardGameRules

type CardGameRules<S, M> = { /** * A new game for these players (one name a seat) at this size, or null for * a table the game is not offered for. `size` is how long the game lasts in * its own terms (`CARD_GAME_TABLES`). The deal is shuffled from `seed`, so a * seed and the moves make the same game again anywhere; `computers` says, * one a seat, which seats a computer plays. The third argument is unused and * reser…

WHAT EVERY CARD GAME'S RULES ANSWER, whatever the game. S is a game in progress and M one move in it; both are plain data, so a game can be kept, sent or compared as it is.

Every function is pure: it returns a new game and leaves the one it was given alone.

computer sees what the player in that seat could see and nothing more: their own hand, the cards on the table and everything said aloud (the moves so far). Each game's computer reads a view built for it (<game>View), so a player cannot be beaten by a program that looked at their hand; the tests beside each computer hold that.

type CardGameTable

type CardGameTable = { /** The fewest players a table may start with. */ fewestPlayers: number; /** The most. */ mostPlayers: number; /** The usual number. */ defaultPlayers: number; /** The lengths of game offered, in the game's own terms (below). */ sizes: readonly number[]; /** The usual length. */ defaultSize: number; };

What a game's table offers: how many may sit at it, and how long a game may last.

type CardId

type CardId = string;

A card by its short name: rank letter then suit letter, "QS", "TD", "AH" (cards.ts).

function cardOf

cardOf(rank: CardRank, suit: CardSuit): CardId

The card of this rank and suit.

function cardOfId

cardOfId(card: CardId): Card

The deck's card for an id, for the deck's components to draw.

type CardRank

type CardRank = Rank;

The deck's rank, ace low: 1 is the ace, 11 the jack, 12 the queen, 13 the king. Each game orders them its own way.

type CardSeats

type CardSeats = { players: readonly string[]; /** One a seat: true where a computer plays it. A seat nobody named a computer for is a person's. */ computers: readonly boolean[]; };

Who sits in each seat: the name the set-up gave, and whether a computer plays it.

function cardShort

cardShort(card: CardId): string

A card as a corner shows it: "Q♠", "10♦". The same in every language.

function cardsShort

cardsShort(cards: readonly CardId[]): string

Cards as corners show them, with a space between: "Q♠ 10♦ A♥".

type CardSuit

type CardSuit = "C" | "D" | "H" | "S";

Clubs, diamonds, hearts, spades.

function cardText

cardText(card: CardId, language?: Language): string

A card's name in full: "queen of spades", or "スペードのクイーン" in Japanese. What a screen reader should say.

function cardWords

cardWords(card: CardId): string

"queen of spades", as a sentence and a screen reader say it.

function choices

choices<T>(items: readonly T[], count: number): T[][]

Every way of choosing count of these cards, in the order they are given.

function cliLanguage

cliLanguage(flag: string | undefined, env?: Record<string, string | undefined>, locale?: string): Language

The language the command line speaks: --lang, or the environment's, or the system's; Japanese for ja…, English for anything else.

type CliResult

type CliResult = { /** 0 when all went well, 1 when what was asked for could not be done, 2 when the command itself was wrong. */ code: 0 | 1 | 2; /** For standard output. */ out: string; /** For standard error. */ err: string; };

What the command line came to.

type CliSurroundings

type CliSurroundings = { /** The environment, for the language (`LC_ALL`, `LC_MESSAGES`, `LANG`) and `NO_COLOR`. */ env?: Record<string, string | undefined>; /** Standard input, when `--stdin` asks for it: a saved game. */ stdin?: string; /** Reads a file named on the command line, or gives null when it cannot be read. `check` needs it; nothing else does. */ readFile?: (path: string) => string | null; /** Whether th…

What the command line is run in. All of it is optional.

function computerSeats

computerSeats(count: number, computers?: readonly boolean[]): boolean[]

The seats a computer plays, one a seat, from what the set-up said: a seat it said nothing about is a person's.

const CRAZY_EIGHTS_SIZES

CRAZY_EIGHTS_SIZES: readonly [50, 100, 200]

The scores a game of Crazy Eights may be played to.

namespace crazyEights

import { crazyEights } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/crazyEights"

Everything the @johnmorrisdotca/toranpu/crazyEights entry point exports, as one namespace.

namespace cribbage

import { cribbage } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/cribbage"

Everything the @johnmorrisdotca/toranpu/cribbage entry point exports, as one namespace.

const CRIBBAGE_SIZES

CRIBBAGE_SIZES: readonly [61, 121]

The scores a game of Cribbage may be played to: once round the board, or twice.

const CSV_COLUMNS

CSV_COLUMNS: readonly ["move", "seat", "player", "action", "cards", "detail", "text"]

The columns of the CSV, in order.

function dealRound

dealRound(deck: readonly CardId[], seats: number, each?: number): { hands: CardId[][]; stock: CardId[]; }

Deals the cards one at a time round the table, from the first seat, as a dealer does: each to a seat, or every card when each is absent (so some seats hold one more than others). What is not dealt is the stock, in order, its top card first.

namespace euchre

import { euchre } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/euchre"

Everything the @johnmorrisdotca/toranpu/euchre entry point exports, as one namespace.

const EUCHRE_SIZES

EUCHRE_SIZES: readonly [5, 10]

The scores a game of Euchre may be played to.

function fillIn

fillIn(template: string, values: Record<string, string | number>): string

Put values into a string's braces: fillIn("Bid {n}", { n: 3 }) is "Bid 3". A brace with no value is left as it is.

function fromCode

fromCode: (text: string) => ReadGame | null

A game from its code or its JSON: the same as fromJSON, under the name that pairs with toCode.

function fromJSON

fromJSON(text: string): ReadGame | null

A game from text that toJSON or toCode wrote, whichever game it is. Nothing in it is trusted: every move is played through the rules again, and a move the rules refuse means the text is not a game. Null for text that is not JSON, names no game, is of a later format than this version reads, or cannot be played out.

const FULL_DECK

FULL_DECK: readonly string[]

Every card, in the deck's own order.

function gameName

gameName(kind: CardGameKind, language?: Language): string

A game's name: "Hearts", or "ハーツ" in Japanese.

type GameOf

type GameOf<K extends CardGameKind> = CardGamePlays[K]["game"];

A game of that kind, in progress.

function gameSays

gameSays(kind: CardGameKind, language?: Language): string

A game in a line: what it is about, for a list of games.

const GIN_SIZES

GIN_SIZES: readonly [50, 100, 150]

The scores a game of Gin Rummy may be played to.

namespace ginRummy

import { ginRummy } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/ginRummy"

Everything the @johnmorrisdotca/toranpu/ginRummy entry point exports, as one namespace.

const GO_FISH_SIZES

GO_FISH_SIZES: readonly [1]

Go Fish is one deal: its only size is 1.

namespace goFish

import { goFish } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/goFish"

Everything the @johnmorrisdotca/toranpu/goFish entry point exports, as one namespace.

namespace hearts

import { hearts } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/hearts"

Everything the @johnmorrisdotca/toranpu/hearts entry point exports, as one namespace.

const HEARTS_SIZES

HEARTS_SIZES: { readonly short: 50; readonly full: 100; }

How long each game lasts, in its own terms (see above).

function isCard

isCard(value: unknown): value is CardId

Whether this is one of the fifty-two names.

function isCardList

isCardList(value: unknown): value is string[]

A list of card names, checked for shape only: whether they are cards the game holds is the rules' to say.

type KeptCardGame

type KeptCardGame<M> = { size: number; players: readonly string[]; computers: readonly boolean[]; seed: number; moves: readonly M[]; };

What every card game's state carries for its keeping.

type Language

type Language = "en" | "ja";

The languages Toranpu speaks.

function languageOf

languageOf(tag: string | null | undefined): Language

The language a tag such as ja-JP or en_US.UTF-8 names: Japanese for anything that starts ja, English otherwise.

function mixSeed

mixSeed(seed: number, salt: number): number

Two numbers folded into one seed, so each deal of a game (and each reshuffle within one) has its own order.

type MoveOf

type MoveOf<K extends CardGameKind> = CardGamePlays[K]["move"];

A move in a game of that kind.

function moveText

moveText<K extends CardGameKind>(kind: K, move: MoveOf<K>, options?: MoveTextOptions): string

A move in words, for any of the ten games: moveText("hearts", { play: "QS" }) is "Play Q♠", and with { form: "did" } it is "plays Q♠". kind is needed because a bid of nought is "nil" in Spades and "0" in Oh Hell.

type MoveTextOptions

type MoveTextOptions = { /** English unless said. */ language?: Language; /** * `"offer"` is a button's words for the player to move ("Play Q♠"), which is * what is given unless said. `"did"` is what a record says after a name * ("plays Q♠"). `"hidden"` is `"did"` as another player at the table saw * it: cards passed, given or laid away face down are counted, not named. */ form?: "offer" | "did" | "hidden"; /** The …

How a move is put into words.

function namesList

namesList(names: readonly string[], language?: Language): string

Names in a list: "Ann, Ben", or "Ann、Ben" in Japanese.

function newGame

newGame<K extends CardGameKind>(kind: K, options: NewGameOptions): GameOf<K> | null

A new game of that kind, or null when the game is not offered for that table (too many players, or a size it does not play to).

const game = newGame("hearts", { players: ["You", "Ann", "Ben", "Cy"], computers: [false, true, true, true] });

type NewGameOptions

type NewGameOptions = { /** One name a seat. The number of names is the number of players. */ players: readonly string[]; /** How long the game lasts, in its own terms (`CARD_GAME_TABLES`); the usual length when left out. */ size?: number; /** The seed every deal is shuffled from; a random one when left out. The same seed deals the same cards. */ seed?: number; /** One a seat: true where a computer plays. Seats not …

How to seat a new game. Everything but players has a sensible default.

function nextSeat

nextSeat(seat: number, seats: number): number

The next seat round the table, to the left: the order every game here plays in.

const OH_HELL_DEALS

OH_HELL_DEALS: readonly [7, 13]

How many deals a game of Oh Hell may last: up to seven cards, or up and back down.

namespace ohHell

import { ohHell } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/ohHell"

Everything the @johnmorrisdotca/toranpu/ohHell entry point exports, as one namespace.

function playComputers

playComputers<K extends CardGameKind>(kind: K, game: GameOf<K>): { game: GameOf<K>; moves: MoveOf<K>[]; }

Every computer move due, played in turn until a person is to move or the game is over. Returns the game and the moves the computers made, in order, so a table can show them one at a time.

namespace president

import { president } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/president"

Everything the @johnmorrisdotca/toranpu/president entry point exports, as one namespace.

const PRESIDENT_ROUNDS

PRESIDENT_ROUNDS: readonly [3, 5, 7]

How many rounds a game of President may last.

type Random

type Random = () => number;

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

function randomSeed

randomSeed(): number

A seed for a game nobody asked for by number: a whole number from 1 to 2³¹ − 1.

const RANK_LETTERS

RANK_LETTERS: "A23456789TJQK"

The rank letters, ace low: an index here plus one is the rank (A = 1, J = 11, Q = 12, K = 13).

function rankName

rankName(rank: CardRank, language?: Language): string

A rank as a sentence names one card of it: "queen", "seven"; in Japanese "クイーン", "7".

function rankOf

rankOf(card: CardId): CardRank

A card's rank, ace low: 1 to 13.

function rankWords

rankWords(rank: CardRank): string

A rank named in the plural, as it is asked for: "sevens", "sixes", "queens".

type ReadGame

type ReadGame = { [K in CardGameKind]: { kind: K; game: GameOf<K> } }[CardGameKind];

A game read back: which game it is, and the game itself.

type RecordedMove

type RecordedMove<K extends CardGameKind = CardGameKind> = { seat: number; move: MoveOf<K> };

One move of a record: which seat made it, and the move.

function recordOf

recordOf<K extends CardGameKind>(kind: K, game: GameOf<K>): RecordedMove<K>[] | null

Every move of a game with the seat that made it, in order, found by playing the game through again from its seed. Null if the moves do not play out, which a game the rules made never does.

function reshuffled

reshuffled(cards: readonly CardId[], seed: number, salt: number): CardId[]

These cards shuffled again, the same way for the same seed and salt: a pile turned over to make a new stock.

function rulesFor

rulesFor<K extends CardGameKind>(kind: K): CardGameRules<GameOf<K>, MoveOf<K>>

A game's rules by its kind, with its own game and move types.

function runCli

runCli(args: readonly string[], around?: CliSurroundings): CliResult

Run the command line. See toranpu --help for what it takes. Every deal comes from the seed, so the same command prints the same cards on every machine.

const SAVE_FORMAT

SAVE_FORMAT: 1

The shape of the JSON this package writes. It goes up only when a reader of the old shape would be wrong about the new one.

function savedGame

savedGame<K extends CardGameKind>(kind: K, game: GameOf<K>): SavedGame<K>

A game as the object toJSON writes: its table, its seed and its moves.

type SavedGame

type SavedGame<K extends CardGameKind = CardGameKind> = { /** The shape of this object: `SAVE_FORMAT`. */ format: typeof SAVE_FORMAT; /** What wrote it, such as `"toranpu 1.2.0"`. For people; nothing reads it back. */ generator: string; /** Which game: `"hearts"`, `"ginRummy"`… */ game: K; /** How long the game lasts, in its own terms (`CARD_GAME_TABLES`). */ size: number; /** One name a seat. */ players: string[]; …

A game as toJSON writes it.

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.

function shuffledDeck

shuffledDeck(seed: number, deal: number, leaveOut?: readonly CardId[]): CardId[]

The deck shuffled for one deal of one game: the same seed and deal always the same order, so a game kept as its seed and moves deals itself again exactly, and reloading can never re-deal a hand somebody has seen.

namespace spades

import { spades } from "@johnmorrisdotca/toranpu"; // or everything in it from "@johnmorrisdotca/toranpu/spades"

Everything the @johnmorrisdotca/toranpu/spades entry point exports, as one namespace.

const SPADES_SIZES

SPADES_SIZES: readonly [200, 300, 500]

The scores a game of Spades may be played to.

const STRINGS

STRINGS: Record<Language, ToranpuStrings>

Every string, in both languages.

function suitName

suitName(suit: CardSuit, language?: Language): string

A suit's name: "spades", or "スペード" in Japanese.

function suitOf

suitOf(card: CardId): CardSuit

A card's suit, as its letter: S, H, D or C.

function suitSymbol

suitSymbol(suit: CardSuit): string

A suit's symbol: ♠ ♥ ♦ ♣.

function suitWords

suitWords(suit: CardSuit): string

A suit's name: "spades".

function toCode

toCode<K extends CardGameKind>(kind: K, game: GameOf<K>): string

A game as its code: the one line its own rules' encode writes. Shorter than the JSON, and what Itsutsu keeps.

function toCSV

toCSV<K extends CardGameKind>(kind: K, game: GameOf<K>): string

A game's moves as CSV for a spreadsheet: a header, then a row a move, with its number from 1, the seat from 1 and its player, the kind of move (play, bid, pass…), the cards it names as two-letter ids with spaces between, the move itself as JSON, and the move in English. Lines end CRLF, as RFC 4180 has it. It is for reading, and is not read back.

function toJSON

toJSON<K extends CardGameKind>(kind: K, game: GameOf<K>): string

A game as JSON, two spaces deep, with the format's number first. fromJSON reads it back.

type ToranpuStrings

type ToranpuStrings = { gameHearts: string; gameSpades: string; gameEuchre: string; gameCribbage: string; gameOhHell: string; gameCrazyEights: string; gameGoFish: string; gameBigTwo: string; gamePresident: string; gameGinRummy: string; saysHearts: string; saysSpades: string; saysEuchre: string; saysCribbage: string; saysOhHell: string; saysCrazyEights: string; saysGoFish: string; saysBigTwo: string; saysPresident: s…

The names of the strings. Each is one line or one block of text.

function toText

toText<K extends CardGameKind>(kind: K, game: GameOf<K>, language?: Language): string

A game as a record a person can read: a heading, a numbered line a move with who made it, and how it stands. In English unless said.

It names every card, the ones passed face down too. It is a record for afterwards, not something to show a player while the game is on.

const VERSION

VERSION: "2.13.2"

The version of this package, as package.json has it. A test holds the two together.

function without

without(hand: readonly CardId[], cards: readonly CardId[]): CardId[] | null

The hand without these cards, or null when it does not hold every one of them.

@johnmorrisdotca/toranpu/deck

Card CARD_ALPHABET cardAt cardCode CardCode cardFromCode cardFromId cardId cardIndex cardName cardShortName colourOf dealAll dealRound DECK_SIZE freshDeck isRed isWholeDeck Random Rank RANK_DISPLAY RankDisplay RANKS readCards sameCard seededRandom shuffled shuffledDeck sortedHand Suit SUIT_DISPLAY SuitColour SuitDisplay SUITS take writeCards

type Card

type Card = { suit: Suit; rank: Rank };

A card as an object: its suit and its rank.

const CARD_ALPHABET

CARD_ALPHABET: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

The fifty-two characters a card is written as, in deck order: spades A–K are A–M, hearts N–Z, diamonds a–m, clubs n–z. Letters only, so a deal is safe in an address, a JSON body and a file name alike.

function cardAt

cardAt(index: number): Card

The card at a place in a fresh deck, 0 to 51. Throws for a place that is not one.

function cardCode

cardCode(card: Card): CardCode

One character for a card (CARD_ALPHABET).

type CardCode

type CardCode = string;

A card as ONE character, A–Z then a–z (cardCode): a whole deck is a fifty-two-character string, so a deal travels in a saved game, an address and a move list without any separator to parse.

function cardFromCode

cardFromCode(code: string): Card | null

The card a character names, or null for one that names none — a kept string is read, never trusted.

function cardFromId

cardFromId(id: string): Card | null

The card a two-character id names, or null. Lower case is read too.

function cardId

cardId(card: Card): string

"QS", "TH", "AC": the two-character id card players write, rank then suit, with T for the ten. For a game that keys its state by a readable id; a kept deal is written with cardCode, one character a card.

function cardIndex

cardIndex(card: Card): number

The card's place in a fresh deck, 0 to 51.

function cardName

cardName(card: Card): string

"queen of hearts": the name a screen reader says.

function cardShortName

cardShortName(card: Card): string

"Q♥": the short name a corner shows.

function colourOf

colourOf(card: Card): SuitColour

A card's colour: red for hearts and diamonds, black for spades and clubs.

function dealAll

dealAll(deck: readonly Card[], hands: number): Card[][]

Deal the whole deck round the table: the first hands get one more where it does not divide evenly.

function dealRound

dealRound(deck: readonly Card[], hands: number, each: number): { hands: Card[][]; rest: Card[]; }

Deal one card at a time round the table, as a dealer does: hands hands of each cards, dealt from the top (the start of the array), and what is left.

const DECK_SIZE

DECK_SIZE: number

How many cards a deck holds: fifty-two.

function freshDeck

freshDeck(): Card[]

A fresh deck in the order a new pack is sorted: spades ace to king, then hearts, diamonds, clubs.

function isRed

isRed(card: Card): boolean

Whether a card is a heart or a diamond.

function isWholeDeck

isWholeDeck(text: string): boolean

Whether a string is a whole deck: fifty-two cards, each exactly once.

type Random

type Random = () => number;

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

type Rank

type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;

Ace is 1 and King 13. A game that ranks the ace high (Hearts, Big Two) says so in its own rules.

const RANK_DISPLAY

RANK_DISPLAY: Record<Rank, RankDisplay>

How each rank is written: short (A, 10, K) and as a word (ace, ten, king).

type RankDisplay

type RankDisplay = { short: string; name: string };

How a rank is written: A, 2…10, J, Q, K, and in a sentence.

const RANKS

RANKS: readonly Rank[]

The ranks in order, ace low: 1 (the ace) to 13 (the king).

function readCards

readCards(text: string): Card[] | null

A string of card characters read back, or null if any character names no card.

function sameCard

sameCard(a: Card, b: Card): boolean

Whether two card objects are the same card.

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.

function shuffledDeck

shuffledDeck(seed: number): Card[]

A deck shuffled from a seed: the same seed, the same order, in every browser and every test.

function sortedHand

sortedHand(cards: readonly Card[], rankOrder?: (rank: Rank): number, suitOrder?: readonly Suit[]) => Card[]

A hand sorted by suit (the fresh-deck order) and then by rank: how a player arranges one.

type Suit

type Suit = "spades" | "hearts" | "diamonds" | "clubs";

One of the four suits, by name.

const SUIT_DISPLAY

SUIT_DISPLAY: Record<Suit, SuitDisplay>

How each suit is written and drawn: its symbol, its name and its colour.

type SuitColour

type SuitColour = "red" | "black";

A suit's colour, which Klondike's alternating runs and Crazy Eights' matches both read.

type SuitDisplay

type SuitDisplay = { /** The Unicode suit, for a sentence or an aria label: ♠ ♥ ♦ ♣. */ symbol: string; /** Its name in English, lower case, as a sentence uses it. */ name: string; colour: SuitColour; };

How a suit is written and drawn.

const SUITS

SUITS: readonly Suit[]

The suits in the order a fresh deck is sorted and a foundation row is laid out: spades, hearts, diamonds, clubs — black, red, red, black, so no two neighbours on a row of four look alike.

function take

take(deck: readonly Card[], count: number): { taken: Card[]; rest: Card[]; }

Take count from the top: what was taken, and what is left.

function writeCards

writeCards(cards: readonly Card[]): string

A run of cards as a string, one character each.

@johnmorrisdotca/toranpu/react

CardGameState useCardGame

type CardGameState

type CardGameState<K extends CardGameKind> = { /** The game now, or null when the table was one the game is not offered for. */ game: GameOf<K> | null; /** The seat to move, or null once the game is over. */ toPlay: number | null; /** Whether the seat to move is a computer's. */ computerToPlay: boolean; /** The moves the seat to move may make: none while a computer is thinking or once the game is over. */ moves: rea…

A card game in React state: the game, the moves the person to play may make, and play to make one. Computers take their turns by themselves, one move every computerDelay milliseconds, so a person can follow them. Drawing the table is yours: this holds the game and nothing else.

const table = useCardGame("crazyEights", { players: ["You", "Ann"], computers: [false, true] });
table.moves.map((move) => <button onClick={() => table.play(move)}>…</button>);

function useCardGame

useCardGame<K extends CardGameKind>(kind: K, options: NewGameOptions, computerDelay?: number): CardGameState<K>

A card game held in React state. See CardGameState for what it hands back; computerDelay is the milliseconds a computer waits before each move.

@johnmorrisdotca/toranpu/card-sounds

CARD_SOUND_KINDS CardSoundData CardSoundKind CardSounds CardSoundsOptions CardSoundWindow createCardSounds MOST_SOUNDS_AT_ONCE PlayCardSoundOptions soundTimes

const CARD_SOUND_KINDS

CARD_SOUND_KINDS: readonly ["shuffle", "deal", "flip", "play", "gather", "fan"]

Every kind of sound, in the order a game meets them.

type CardSoundData

type CardSoundData = Readonly<Record<string, string>>;

The recordings, by name (deal-2), as base64 AAC.

type CardSoundKind

type CardSoundKind = (typeof CARD_SOUND_KINDS)[number];

One kind of sound: deal a card sent to a hand, flip a card turned over, play a card laid on the table, shuffle the deck shuffled, gather a pile swept in, fan a hand spread open.

type CardSounds

type CardSounds = { /** Play a sound, or several of one kind in a row; nothing while muted or closed. */ play(kind: CardSoundKind, options?: PlayCardSoundOptions): void; /** Fetch and decode the recordings now, rather than at the first sound. True once they are ready; false where they cannot be had. */ load(): Promise<boolean>; /** Whether it is muted. */ readonly muted: boolean; /** Mute or unmute. Muting stops not…

A table's sounds: play one, mute and unmute, change the volume, close when the table goes.

type CardSoundsOptions

type CardSoundsOptions = { /** Start muted: nothing plays, and nothing is fetched, until `setMuted(false)`. Unless said, not muted. */ muted?: boolean; /** How loud, from 0 to 1. Unless said, 0.6. */ volume?: number; /** Where the recordings come from: the package's own module unless another is handed in. */ load?: () => Promise<{ CARD_SOUND_DATA: CardSoundData }>; /** The window to make sound in: the page's own unl…

How a table's sounds are made. Every field may be left out.

type CardSoundWindow

type CardSoundWindow = { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext; atob?: (text: string) => string; };

The parts of a window the sounds use: an audio context and atob. Any of them may be missing.

function createCardSounds

createCardSounds(options?: CardSoundsOptions): CardSounds

A table's card sounds. Nothing is fetched and no audio context is made until the first sound.

const MOST_SOUNDS_AT_ONCE

MOST_SOUNDS_AT_ONCE: 8

As many sounds as one call plays: thirteen cards dealt are eight slides, not a wall of noise.

type PlayCardSoundOptions

type PlayCardSoundOptions = { /** How many cards: `deal` with 13 is thirteen cards dealt one after another (heard as at most `MOST_SOUNDS_AT_ONCE`). Unless said, one. */ count?: number; /** Milliseconds between one card's sound and the next. Unless said, 85. */ gap?: number; /** Milliseconds to wait before the first. Unless said, none. */ delay?: number; };

How one sound is played.

function soundTimes

soundTimes(count: number, gap?: number): number[]

When each of count sounds starts, in milliseconds from the first: one every gap, and no more than MOST_SOUNDS_AT_ONCE, spread over the same time.

@johnmorrisdotca/toranpu/sounds

CARD_SOUND_DATA CardSoundFile

const CARD_SOUND_DATA

CARD_SOUND_DATA: Readonly<Record<CardSoundFile, string>>

The card sounds, recorded: cards dealt, turned over, played, shuffled, gathered and fanned, as base64 AAC (.m4a). From Kenney's Casino Audio pack, CC0; see docs/credits.md. Written by scripts/sounds.mjs from the files in ./sounds, never by hand. createCardSounds loads this module only when a sound is first played, so a page that stays silent never downloads it.

type CardSoundFile

type CardSoundFile = "deal-1" | "deal-2" | "deal-3" | "deal-4" | "fan-1" | "flip-1" | "flip-2" | "gather-1" | "gather-2" | "play-1" | "play-2" | "play-3" | "shuffle-1";

The name of one recording: its kind of sound and a number, such as deal-2.

@johnmorrisdotca/toranpu/card-backs

CARD_BACK_LOOK CARD_BACK_PROPERTIES CARD_BACKS CARD_BOX CardBackName CardBackOptions cardBackSvg cardBackUrl CardSuitLetter SUIT_PATHS

const CARD_BACK_LOOK

CARD_BACK_LOOK: Readonly<Record<"classic-red" | "classic-blue" | "ink-dots", { field: string; ink: string; paper: string; }>>

Each back's own colours: its field, the lines and dots drawn on the field, and the paper round it.

const CARD_BACK_PROPERTIES

CARD_BACK_PROPERTIES: { readonly field: "--toranpu-back"; readonly ink: "--toranpu-back-ink"; readonly paper: "--toranpu-back-paper"; }

The CSS custom properties a back drawn into a page takes its colours from, when no colour is given.

const CARD_BACKS

CARD_BACKS: readonly ["classic-red", "classic-blue", "ink-dots"]

The backs, by name.

const CARD_BOX

CARD_BOX: { readonly width: 100; readonly height: 140; readonly radius: 7; }

A card is drawn in a box 100 wide and 140 tall, the 5:7 of a poker-size card, and scales to any width.

type CardBackName

type CardBackName = (typeof CARD_BACKS)[number];

One back's name.

type CardBackOptions

type CardBackOptions = { /** The field's colour, in place of the back's own: `#8f2826`, `rebeccapurple`, `rgb(…)`. Anything else is ignored. */ colour?: string; /** The colour of the lines and dots on the field. */ ink?: string; /** The colour of the paper round the field. */ paper?: string; /** Words in the middle, such as a site's name or mark ("五つ", "一つ"), in place of the back's own ornament. */ mark?: string; /*…

How a back is drawn. Every field may be left out.

function cardBackSvg

cardBackSvg(name?: CardBackName | string, options?: CardBackOptions): string

A back as a whole SVG document, 100 by 140. name is one of CARD_BACKS (an unknown name draws the classic red); the options recolour it, put words in its middle, size it and name it.

Drawn into a page, a back takes its colours from the CSS custom properties --toranpu-back, --toranpu-back-ink and --toranpu-back-paper where they are set and no colour is given here; as an image, it keeps its own.

function cardBackUrl

cardBackUrl(name?: CardBackName | string, options?: CardBackOptions): string

A back as a data URL, for an <img src>, a CSS background or a canvas: the same drawing as cardBackSvg, with its own colours.

type CardSuitLetter

type CardSuitLetter = "S" | "H" | "D" | "C";

A suit as one letter, as a card's id writes it: spades, hearts, diamonds, clubs.

const SUIT_PATHS

SUIT_PATHS: Readonly<Record<CardSuitLetter, string>>

The four suits, each a path in a box of 100 filled in one colour. A club is three lobes and a stem, a spade a heart turned over with a stem, so the four are told apart by their outline alone.

@johnmorrisdotca/toranpu/card-faces

CARD_DESIGNS CARD_FACE_COLOURS CARD_FACE_PROPERTIES CardDesign CardDesignName CardFaceOptions cardFaceSvg cardFaceUrl EXTRA_CARDS EXTRA_LIMITS faceName isCardFace JOKERS loadCardDesign pipPlaces

const CARD_DESIGNS

CARD_DESIGNS: readonly ["plain", "four-colour", "english", "realistic"]

The designs drawn here, by name. The English pattern is ENGLISH_PATTERN from @johnmorrisdotca/toranpu/card-faces/english, or loadCardDesign("english").

const CARD_FACE_COLOURS

CARD_FACE_COLOURS: { readonly paper: "#fffdf8"; readonly black: "#1b1b1b"; readonly red: "#c2272d"; readonly blue: "#1f5fbf"; readonly green: "#1f7a3a"; readonly edge: "#000"; }

The faces' own colours.

const CARD_FACE_PROPERTIES

CARD_FACE_PROPERTIES: { readonly paper: "--toranpu-card"; readonly black: "--toranpu-card-ink"; readonly red: "--toranpu-card-red"; readonly blue: "--toranpu-card-blue"; readonly green: "--toranpu-card-green"; }

The CSS custom properties a plain or four-colour face drawn into a page takes its colours from.

type CardDesign

type CardDesign = { /** The design's name, as `cardFaceSvg` and an element's `design` attribute take it. */ name: string; /** The width and height each card is drawn in. */ box: readonly [number, number]; /** Each card's drawing, by its id. */ art: Readonly<Record<string, string>>; /** Where the set has no drawing for a card, the set that draws it instead; then plain. */ fallback?: CardDesign; };

A set of drawn cards: its name, the box each card is drawn in, and each card's drawing (what goes inside an <svg> of that box) by its id, KS for the king of spades and RJ and BJ for the red and black jokers. A card the set has no drawing for is drawn plain.

type CardDesignName

type CardDesignName = (typeof CARD_DESIGNS)[number];

A design's name.

type CardFaceOptions

type CardFaceOptions = { /** `"plain"` (unless said), `"four-colour"`, or a design handed in, such as `ENGLISH_PATTERN`. */ design?: "plain" | "four-colour" | CardDesign; /** The width to draw at, in pixels; the height is 1.4 times it. Unless said, the drawing fills what holds it. */ width?: number; /** What a screen reader says. Unless said, the card's name in `language`, "queen of spades". An empty string makes it…

How a face is drawn. Every field may be left out.

function cardFaceSvg

cardFaceSvg(card: string, options?: CardFaceOptions): string | null

A card's face as a whole SVG document, 100 by 140: QS, TD, a joker (RJ, BJ), a rules card (R1, R2) or the blank (BL). null for anything that is not a card. The plain design unless another is asked for; a design handed in that has no drawing for the card draws it plain.

Drawn into a page, the plain and four-colour faces take their colours from --toranpu-card, --toranpu-card-ink, --toranpu-card-red, --toranpu-card-blue and --toranpu-card-green where they are set; as an image they keep their own.

function cardFaceUrl

cardFaceUrl(card: string, options?: CardFaceOptions): string | null

A card's face as a data URL, for an <img src>, a CSS background or a canvas. null for anything that is not a card.

const EXTRA_CARDS

EXTRA_CARDS: readonly ["RJ", "BJ", "R1", "R2", "BL"]

THE EXTRAS OF A REAL DECK, which no game here deals but a hand on a page may hold, as a wild card or for the look of it: the two jokers, the two rules cards a pack is sold with (R1, the rules of Hearts; R2, of Spades), and a blank (BL). A hand holds at most EXTRA_LIMITS of each kind (readHand).

const EXTRA_LIMITS

EXTRA_LIMITS: { readonly jokers: 4; readonly rules: 2; readonly blanks: 2; }

The most of each kind of extra one hand holds: four jokers, as some packs have; two rules cards; two blanks.

function faceName

faceName(card: string, language?: Language): string

A face's name in words: "queen of spades", "red joker"; in Japanese "スペードのクイーン", "赤のジョーカー".

function isCardFace

isCardFace(text: unknown): text is string

Whether a text is a card a face can be drawn for: QS, TD, a joker (RJ, BJ), a rules card (R1, R2) or the blank (BL).

const JOKERS

JOKERS: readonly ["RJ", "BJ"]

The two jokers' ids: the red joker and the black. No game here plays with them; a table of your own may.

function loadCardDesign

loadCardDesign(name: CardDesignName | string): Promise<CardDesign | null>

A design by its name, loaded when it is first asked for: "english" fetches the English pattern (about 700 kB, 200 kB compressed) only then. "plain" and "four-colour" are drawn here and need no loading; they give null.

function pipPlaces

pipPlaces(count: number): { x: number; y: number; down: boolean; }[]

The middles of a number card's pips, in the 100 by 140 box, and whether each is drawn upside down.

@johnmorrisdotca/toranpu/card-faces/english

ENGLISH_PATTERN

const ENGLISH_PATTERN

ENGLISH_PATTERN: CardDesign

The English pattern, every card and two jokers, drawn in a box 360 by 540. About 700 kB, so it is its own entry point and is loaded only by a page that uses it.

@johnmorrisdotca/toranpu/card-faces/realistic

REALISTIC

const REALISTIC

REALISTIC: CardDesign

The realistic design: Knoll's 39 cards in a box 167.0869141 by 242.6669922, and Fomin's English pattern for every other card.

@johnmorrisdotca/toranpu/element

arrangeCards BUNDLE_BACKS CardLands CardOrder CardPlace defineToranpuElements ELEMENT_SIZES handLayout HandLayoutOptions HandTurnOptions mixCards partedHandLayout PartOptions pileLayout PileLayoutOptions readHand replaceCard SpinOptions TORANPU_TAGS ToranpuCard ToranpuHand ToranpuPile tossCard

function arrangeCards

arrangeCards(cards: readonly string[], by: CardOrder): string[]

A hand laid out another way, as a new list (the one given is left alone): "rank" sorts it low to high, the ace high, a rank's cards in suit order; "suit" groups it by suit, spades, hearts, clubs, diamonds, each in rank order; "face" groups it into the number cards, ace to ten, and then the face cards, jack, queen, king, each group in rank order and a rank's cards in suit order; "dealt" keeps the order given. The extras (jokers, rules cards, the blank) come last, in the order dealt. Cards of the same id keep their order, so a sort is stable.

arrangeCards(["QH", "2S", "AH", "2H"], "rank"); // ["2S", "2H", "QH", "AH"]
arrangeCards(["QH", "2S", "AH", "2H", "KS"], "suit"); // ["2S", "KS", "2H", "QH", "AH"]
arrangeCards(["QH", "2S", "AH", "KS", "9D"], "face"); // ["AH", "2S", "9D", "QH", "KS"]

const BUNDLE_BACKS

BUNDLE_BACKS: 3

How many backs a squared-up bundle shows, whatever the hand: a bundle never tells how many cards are in it.

type CardLands

type CardLands = "front" | "end";

Where a card a hand is given goes: to the front, or the end.

type CardOrder

type CardOrder = "dealt" | "rank" | "suit" | "face";

How a hand's cards are laid out: as they were dealt, by rank, grouped by suit, or the number cards apart from the face cards.

type CardPlace

type CardPlace = { x: number; y: number; rotate: number };

One card's place: across and down in card widths from where the first lies, and its turn in degrees.

function defineToranpuElements

defineToranpuElements(): void

Register the elements under their tags, once; a tag already taken is left as it is. Does nothing where there are no custom elements, as on a server.

const ELEMENT_SIZES

ELEMENT_SIZES: { readonly small: 46; readonly medium: 70; readonly large: 104; }

The sizes every element takes, as a card's width in pixels.

function handLayout

handLayout(count: number, options?: HandLayoutOptions): CardPlace[]

Where each card of a hand of count lies: a fan when open, a squared-up stack with a sliver of each card showing when closed, and every shape in between. The middle card turns least; the first lies at x 0.

type HandLayoutOptions

type HandLayoutOptions = { /** How far open, from 0 (squared up: only the top card shows) to 1 (a clear fan). Unless said, 1. */ open?: number; /** How far apart neighbours lie when the hand is open, in card widths. Unless said, 0.42. */ step?: number; /** How far the cards turn across the whole fan when it is open, in degrees. Unless said, 4 a card, at most 40. */ turn?: number; /** How much of each card under the …

How a hand is laid out.

type HandTurnOptions

type HandTurnOptions = { /** One card after another, from the first, rather than all at once. Unless said, all at once. */ oneByOne?: boolean; /** Milliseconds between one card and the next, one by one. Unless said, 110. */ gap?: number; };

How a hand's cards are turned face down or face up.

function mixCards

mixCards(cards: readonly string[], random?: (): number) => string[]

A hand mixed up, as a new list: the same cards in another order, never the order given (where a hand has two cards or more that differ), so a mix is always seen to change something. random is any source of numbers in [0, 1); unless given, Math.random.

function partedHandLayout

partedHandLayout(count: number, at: number, options?: HandLayoutOptions & PartOptions): CardPlace[]

A HAND PARTED AT ONE CARD: that card lifted a little, upright and wholly in view, and the cards to its left and right drawn apart from it into a group on each side (one group where it is the first or the last). The groups close up to make room, down to a sliver of each card, so the hand keeps the room its fan takes wherever it can; a hand too short to close up that far takes a little more. Built on the open fan of handLayout, its dip and turn kept for every card but the one parted. Where at is no card of the hand, the fan itself.

partedHandLayout(7, 3); // three cards squared up on the left, the fourth lifted clear, three on the right

type PartOptions

type PartOptions = { /** The room left on each side of the parted card, in card widths. Unless said, 0.12. */ gap?: number; /** How far the parted card is lifted above the rest, in card widths. Unless said, 0.2. */ lift?: number; /** The least the cards of a group may lie apart, squared up as they close in. Unless said, 0.04. */ peek?: number; };

How a parted hand is laid out.

function pileLayout

pileLayout(count: number, options?: PileLayoutOptions): CardPlace[]

Where each card of a pile of count lies, bottom first, the top card last: at 0 messiness a neat stack whose edge shows a card's thickness for each card under the top, and towards 1 a heap, each card nudged and turned a little more. Seeded, so the same pile always looks the same, and adding a card on top moves none of those under it. Only the top depth cards under the top one are given places; a pile is never drawn deeper than that.

type PileLayoutOptions

type PileLayoutOptions = { /** From 0 (squared up neatly) to 1 (very messy). Unless said, 0.3. */ messiness?: number; /** The pile's own seed: the same seed always lays the same pile the same way. Unless said, 1. */ seed?: number; /** How many cards under the top one are drawn at most, so a pile of fifty-two costs no more than one of ten. Unless said, 10. */ depth?: number; };

How a pile is laid out.

function readHand

readHand(text: string | null | undefined): string[] | null

A hand written as text, as its cards: the two-letter ids separated by spaces or commas ("AS KH 10D TC RJ", with 10 for ten as well as T), or the deck's one-letter codes run together ("pvOZ"). The extras by their ids or their words: JOKER (each the next of red and black), RULES, BLANK. null for anything else, or for more extras than one pack holds.

function replaceCard

replaceCard(cards: readonly string[], card: string, next: string, lands?: CardLands): string[]

A hand with one card tossed out and another given in its stead, at the front or the end: as a new list. Unchanged where it holds no such card.

type SpinOptions

type SpinOptions = { /** Which way it spins. Unless said, clockwise. */ direction?: "clockwise" | "anticlockwise"; /** How many whole turns before it comes to rest where it lay. Unless said, 3; at most 20. */ turns?: number; /** How long the spin takes, in milliseconds. Unless said, 700 and 420 a turn. */ ms?: number; };

How a card is spun.

const TORANPU_TAGS

TORANPU_TAGS: { readonly card: "toranpu-card"; readonly hand: "toranpu-hand"; readonly pile: "toranpu-pile"; }

The elements' tags.

const ToranpuCard

ToranpuCard: typeof ToranpuCard

ONE CARD ON ANY PAGE: <toranpu-card card="QS">, in any design and with any back, face up or face down, turned over by a tap when it has flip.

<toranpu-card card="KS" design="english" back="classic-blue" flip></toranpu-card>

Attributes, all optional but card: card the card: QS, TD, RJ, BJ design plain (unless said), four-colour or english, which is fetched the first time it is asked for back classic-red (unless said), classic-blue or ink-dots; back-colour and mark as cardBackSvg takes them face-down shows the back; the face is not in the page while it is down flip a tap, Enter or Space turns it over, with a turn that a device asking for less motion skips marked a mark on its corner, seen face up and face down, to follow it as it moves size small, medium (unless said) or large; or width in pixels; or the page's --toranpu-card-width sound the turn makes a sound lang ja for Japanese names; the page's language unless said

Each turn is a toranpu-flip event that bubbles, with { card, faceDown } as its detail. spin(options?) spins it where it lies, slowing to a stop as it was.

const ToranpuHand

ToranpuHand: typeof ToranpuHand

A HAND OF CARDS ON ANY PAGE: <toranpu-hand cards="AS KH QD">, fanned, in any design and with any back. It can be turned face down where it lies, all at once or one card after another, and back, or only the cards chosen; squared up into one bundle ("scrunched") that never says how many cards there are; parted at one card, which comes out from among the rest; held closed, squared up with only the top card showing, to open into a fan on a tap; and its cards marked, to be followed while face down, and spun.

<toranpu-hand cards="AS KH QD JC 10S" design="english" closed="0.9" reveal></toranpu-hand>

Attributes, all optional: cards the hand: ids separated by spaces or commas (AS KH 10D), or the deck's one-letter codes face-down every card shows its back; their faces are not in the page turned cards that show the other side from the rest: face up in a hand face down, face down in one face up scrunched squared up into one bundle that never says how many; face down, no faces in the page either; face up, its top card showing parted a card the hand is parted at: lifted out, wholly in view, the cards either side drawn away from it marked cards that carry a mark, a dot on the corner seen face up or face down closed how closed the hand lies, from 0 (a clear fan) to 1 (squared up, only the top card showing) reveal a tap, Enter or Space opens a closed hand into a fan, and closes it again order "rank" sorts the hand low to high, the ace high; "suit" groups it by suit, each in rank order; "face" puts the number cards before the face cards; left out, the cards lie as they were dealt (arrangeCards) receive where a card given by replace() lands: "front", or "end" (unless said) deal-after given new cards, how many milliseconds the hand waits before it gathers the old ones in and opens on the new; a table gives each seat a little more, so the hands are dealt in turn design, back, back-colour, mark, size, width, lang, sound as on <toranpu-card>

Methods: hide(options?) and show(options?) turn every card one way, one by one if asked, and toggle(cards?, options?) turns the cards named (or all) over; scrunch() and spread() square the hand up and lay it out again; partAt(card) and unpart(); open() and close() fan it and square it; sort(), group(by?), unsort(), mixUp(), toss(card) and replace(card, next); mark(cards) and unmark(cards?); and spin(cards?, options?). Each returns a promise that settles when the cards have finished moving. The motion is skipped on a device that asks for less. Each change is a toranpu-hand event that bubbles, its detail { faceDown, scrunched, open, turned, parted }.

const ToranpuPile

ToranpuPile: typeof ToranpuPile

A PILE OF CARDS ON ANY PAGE: <toranpu-pile>, a stock to draw from or a discard pile, its top card on top and the cards under it showing as a stack, from neatly squared to very messy. Seeded, so the same pile always looks the same, and a card put on top moves none of those under it.

<toranpu-pile count="24" face-down messiness="0.4" seed="7"></toranpu-pile>
<toranpu-pile cards="3C 9D QS 7H" messiness="0.6"></toranpu-pile>

Attributes, all optional: cards the pile from the bottom up, its top card last: ids separated by spaces or commas, or one-letter codes count for a face-down pile, how many cards, with no need to say which face-down every card shows its back, and no face is in the page messiness from 0 (squared up neatly) to 1 (very messy); unless said, 0.3 seed the pile's own seed, a whole number; unless said, 1 depth how many cards under the top are drawn at most; unless said, 10 design, back, back-colour, mark, size, width, lang as on <toranpu-card>

function tossCard

tossCard(cards: readonly string[], card: string): string[]

A hand without one card, as a new list: the first of that card taken out, or the hand unchanged where it holds none.

@johnmorrisdotca/toranpu/element/define

arrangeCards BUNDLE_BACKS CardLands CardOrder CardPlace defineToranpuElements ELEMENT_SIZES handLayout HandLayoutOptions HandTurnOptions mixCards partedHandLayout PartOptions pileLayout PileLayoutOptions readHand replaceCard SpinOptions TORANPU_TAGS ToranpuCard ToranpuHand ToranpuPile tossCard

function arrangeCards

arrangeCards(cards: readonly string[], by: CardOrder): string[]

A hand laid out another way, as a new list (the one given is left alone): "rank" sorts it low to high, the ace high, a rank's cards in suit order; "suit" groups it by suit, spades, hearts, clubs, diamonds, each in rank order; "face" groups it into the number cards, ace to ten, and then the face cards, jack, queen, king, each group in rank order and a rank's cards in suit order; "dealt" keeps the order given. The extras (jokers, rules cards, the blank) come last, in the order dealt. Cards of the same id keep their order, so a sort is stable.

arrangeCards(["QH", "2S", "AH", "2H"], "rank"); // ["2S", "2H", "QH", "AH"]
arrangeCards(["QH", "2S", "AH", "2H", "KS"], "suit"); // ["2S", "KS", "2H", "QH", "AH"]
arrangeCards(["QH", "2S", "AH", "KS", "9D"], "face"); // ["AH", "2S", "9D", "QH", "KS"]

const BUNDLE_BACKS

BUNDLE_BACKS: 3

How many backs a squared-up bundle shows, whatever the hand: a bundle never tells how many cards are in it.

type CardLands

type CardLands = "front" | "end";

Where a card a hand is given goes: to the front, or the end.

type CardOrder

type CardOrder = "dealt" | "rank" | "suit" | "face";

How a hand's cards are laid out: as they were dealt, by rank, grouped by suit, or the number cards apart from the face cards.

type CardPlace

type CardPlace = { x: number; y: number; rotate: number };

One card's place: across and down in card widths from where the first lies, and its turn in degrees.

function defineToranpuElements

defineToranpuElements(): void

Register the elements under their tags, once; a tag already taken is left as it is. Does nothing where there are no custom elements, as on a server.

const ELEMENT_SIZES

ELEMENT_SIZES: { readonly small: 46; readonly medium: 70; readonly large: 104; }

The sizes every element takes, as a card's width in pixels.

function handLayout

handLayout(count: number, options?: HandLayoutOptions): CardPlace[]

Where each card of a hand of count lies: a fan when open, a squared-up stack with a sliver of each card showing when closed, and every shape in between. The middle card turns least; the first lies at x 0.

type HandLayoutOptions

type HandLayoutOptions = { /** How far open, from 0 (squared up: only the top card shows) to 1 (a clear fan). Unless said, 1. */ open?: number; /** How far apart neighbours lie when the hand is open, in card widths. Unless said, 0.42. */ step?: number; /** How far the cards turn across the whole fan when it is open, in degrees. Unless said, 4 a card, at most 40. */ turn?: number; /** How much of each card under the …

How a hand is laid out.

type HandTurnOptions

type HandTurnOptions = { /** One card after another, from the first, rather than all at once. Unless said, all at once. */ oneByOne?: boolean; /** Milliseconds between one card and the next, one by one. Unless said, 110. */ gap?: number; };

How a hand's cards are turned face down or face up.

function mixCards

mixCards(cards: readonly string[], random?: (): number) => string[]

A hand mixed up, as a new list: the same cards in another order, never the order given (where a hand has two cards or more that differ), so a mix is always seen to change something. random is any source of numbers in [0, 1); unless given, Math.random.

function partedHandLayout

partedHandLayout(count: number, at: number, options?: HandLayoutOptions & PartOptions): CardPlace[]

A HAND PARTED AT ONE CARD: that card lifted a little, upright and wholly in view, and the cards to its left and right drawn apart from it into a group on each side (one group where it is the first or the last). The groups close up to make room, down to a sliver of each card, so the hand keeps the room its fan takes wherever it can; a hand too short to close up that far takes a little more. Built on the open fan of handLayout, its dip and turn kept for every card but the one parted. Where at is no card of the hand, the fan itself.

partedHandLayout(7, 3); // three cards squared up on the left, the fourth lifted clear, three on the right

type PartOptions

type PartOptions = { /** The room left on each side of the parted card, in card widths. Unless said, 0.12. */ gap?: number; /** How far the parted card is lifted above the rest, in card widths. Unless said, 0.2. */ lift?: number; /** The least the cards of a group may lie apart, squared up as they close in. Unless said, 0.04. */ peek?: number; };

How a parted hand is laid out.

function pileLayout

pileLayout(count: number, options?: PileLayoutOptions): CardPlace[]

Where each card of a pile of count lies, bottom first, the top card last: at 0 messiness a neat stack whose edge shows a card's thickness for each card under the top, and towards 1 a heap, each card nudged and turned a little more. Seeded, so the same pile always looks the same, and adding a card on top moves none of those under it. Only the top depth cards under the top one are given places; a pile is never drawn deeper than that.

type PileLayoutOptions

type PileLayoutOptions = { /** From 0 (squared up neatly) to 1 (very messy). Unless said, 0.3. */ messiness?: number; /** The pile's own seed: the same seed always lays the same pile the same way. Unless said, 1. */ seed?: number; /** How many cards under the top one are drawn at most, so a pile of fifty-two costs no more than one of ten. Unless said, 10. */ depth?: number; };

How a pile is laid out.

function readHand

readHand(text: string | null | undefined): string[] | null

A hand written as text, as its cards: the two-letter ids separated by spaces or commas ("AS KH 10D TC RJ", with 10 for ten as well as T), or the deck's one-letter codes run together ("pvOZ"). The extras by their ids or their words: JOKER (each the next of red and black), RULES, BLANK. null for anything else, or for more extras than one pack holds.

function replaceCard

replaceCard(cards: readonly string[], card: string, next: string, lands?: CardLands): string[]

A hand with one card tossed out and another given in its stead, at the front or the end: as a new list. Unchanged where it holds no such card.

type SpinOptions

type SpinOptions = { /** Which way it spins. Unless said, clockwise. */ direction?: "clockwise" | "anticlockwise"; /** How many whole turns before it comes to rest where it lay. Unless said, 3; at most 20. */ turns?: number; /** How long the spin takes, in milliseconds. Unless said, 700 and 420 a turn. */ ms?: number; };

How a card is spun.

const TORANPU_TAGS

TORANPU_TAGS: { readonly card: "toranpu-card"; readonly hand: "toranpu-hand"; readonly pile: "toranpu-pile"; }

The elements' tags.

const ToranpuCard

ToranpuCard: typeof ToranpuCard

ONE CARD ON ANY PAGE: <toranpu-card card="QS">, in any design and with any back, face up or face down, turned over by a tap when it has flip.

<toranpu-card card="KS" design="english" back="classic-blue" flip></toranpu-card>

Attributes, all optional but card: card the card: QS, TD, RJ, BJ design plain (unless said), four-colour or english, which is fetched the first time it is asked for back classic-red (unless said), classic-blue or ink-dots; back-colour and mark as cardBackSvg takes them face-down shows the back; the face is not in the page while it is down flip a tap, Enter or Space turns it over, with a turn that a device asking for less motion skips marked a mark on its corner, seen face up and face down, to follow it as it moves size small, medium (unless said) or large; or width in pixels; or the page's --toranpu-card-width sound the turn makes a sound lang ja for Japanese names; the page's language unless said

Each turn is a toranpu-flip event that bubbles, with { card, faceDown } as its detail. spin(options?) spins it where it lies, slowing to a stop as it was.

const ToranpuHand

ToranpuHand: typeof ToranpuHand

A HAND OF CARDS ON ANY PAGE: <toranpu-hand cards="AS KH QD">, fanned, in any design and with any back. It can be turned face down where it lies, all at once or one card after another, and back, or only the cards chosen; squared up into one bundle ("scrunched") that never says how many cards there are; parted at one card, which comes out from among the rest; held closed, squared up with only the top card showing, to open into a fan on a tap; and its cards marked, to be followed while face down, and spun.

<toranpu-hand cards="AS KH QD JC 10S" design="english" closed="0.9" reveal></toranpu-hand>

Attributes, all optional: cards the hand: ids separated by spaces or commas (AS KH 10D), or the deck's one-letter codes face-down every card shows its back; their faces are not in the page turned cards that show the other side from the rest: face up in a hand face down, face down in one face up scrunched squared up into one bundle that never says how many; face down, no faces in the page either; face up, its top card showing parted a card the hand is parted at: lifted out, wholly in view, the cards either side drawn away from it marked cards that carry a mark, a dot on the corner seen face up or face down closed how closed the hand lies, from 0 (a clear fan) to 1 (squared up, only the top card showing) reveal a tap, Enter or Space opens a closed hand into a fan, and closes it again order "rank" sorts the hand low to high, the ace high; "suit" groups it by suit, each in rank order; "face" puts the number cards before the face cards; left out, the cards lie as they were dealt (arrangeCards) receive where a card given by replace() lands: "front", or "end" (unless said) deal-after given new cards, how many milliseconds the hand waits before it gathers the old ones in and opens on the new; a table gives each seat a little more, so the hands are dealt in turn design, back, back-colour, mark, size, width, lang, sound as on <toranpu-card>

Methods: hide(options?) and show(options?) turn every card one way, one by one if asked, and toggle(cards?, options?) turns the cards named (or all) over; scrunch() and spread() square the hand up and lay it out again; partAt(card) and unpart(); open() and close() fan it and square it; sort(), group(by?), unsort(), mixUp(), toss(card) and replace(card, next); mark(cards) and unmark(cards?); and spin(cards?, options?). Each returns a promise that settles when the cards have finished moving. The motion is skipped on a device that asks for less. Each change is a toranpu-hand event that bubbles, its detail { faceDown, scrunched, open, turned, parted }.

const ToranpuPile

ToranpuPile: typeof ToranpuPile

A PILE OF CARDS ON ANY PAGE: <toranpu-pile>, a stock to draw from or a discard pile, its top card on top and the cards under it showing as a stack, from neatly squared to very messy. Seeded, so the same pile always looks the same, and a card put on top moves none of those under it.

<toranpu-pile count="24" face-down messiness="0.4" seed="7"></toranpu-pile>
<toranpu-pile cards="3C 9D QS 7H" messiness="0.6"></toranpu-pile>

Attributes, all optional: cards the pile from the bottom up, its top card last: ids separated by spaces or commas, or one-letter codes count for a face-down pile, how many cards, with no need to say which face-down every card shows its back, and no face is in the page messiness from 0 (squared up neatly) to 1 (very messy); unless said, 0.3 seed the pile's own seed, a whole number; unless said, 1 depth how many cards under the top are drawn at most; unless said, 10 design, back, back-colour, mark, size, width, lang as on <toranpu-card>

function tossCard

tossCard(cards: readonly string[], card: string): string[]

A hand without one card, as a new list: the first of that card taken out, or the hand unchanged where it holds none.

@johnmorrisdotca/toranpu/table

defineToranpuTable TABLE_CLOTHS TableCloth ToranpuTable

function defineToranpuTable

defineToranpuTable(): void

Registers <toranpu-table>, and the card, hand and pile elements, once. Nothing happens where there is no browser.

const TABLE_CLOTHS

TABLE_CLOTHS: { readonly green: { readonly felt: "#2f5d4a"; readonly deep: "#1f4135"; readonly ink: "#f3efe4"; }; readonly blue: { readonly felt: "#2865a6"; readonly deep: "#1a4677"; readonly ink: "#f3efe4"; }; readonly red: { readonly felt: "#a3342e"; readonly deep: "#7a231f"; readonly ink: "#f3efe4"; }; readonly black: { readonly felt: "#2f3236"; readonly deep: "#1b1d20"; readonly ink: "#ece8dc"; }; readonly wood:…

THE CLOTHS A TABLE MAY BE LAID IN: the same five the whole family offers, and itsutsu.com's boards. Each is the felt's colour, its deep edge and the ink written on it.

type TableCloth

type TableCloth = keyof typeof TABLE_CLOTHS;

A cloth's name.

const ToranpuTable

ToranpuTable: typeof ToranpuTable

<toranpu-table>: ANY OF THE TEN GAMES, READY TO PLAY. The seats round the felt, what lies on the table (the trick, the pile to beat, the stock and the discard as <toranpu-pile>), the hand of whoever is to play, the moves they may make, and computers in every other seat, playing their turns after a short pause.

<toranpu-table game="crazy-eights" players="3" cloth="blue" messiness="0.4"></toranpu-table>

Attributes: game any of the ten, by its key or in kebab case: hearts (unless said), spades, euchre, cribbage, oh-hell, crazy-eights, go-fish, big-two, president, gin-rummy players how many sit at the table, within the game's own range (its usual number unless said) people how many of the seats are people's, the first ones; the rest are computers (1 unless said) names the seats' names, separated by commas; the first is "You" unless said seed the deal: the same seed deals the same cards (a new one each game unless said) cloth the felt: green (unless said), blue, red, black or wood messiness how untidy the stock and the discard lie, 0 to 1 (0.3 unless said) delay how long a computer thinks before it plays, in milliseconds (550 unless said) design, back, back-colour, mark, size, width, lang, sound as on <toranpu-card>

deal() deals again, with a new seed unless one is given; the game property is the game as it stands. Each move is a toranpu-table event that bubbles, its detail { seat, move, over, winners }.

@johnmorrisdotca/toranpu/table/define

defineToranpuTable TABLE_CLOTHS TableCloth ToranpuTable

function defineToranpuTable

defineToranpuTable(): void

Registers <toranpu-table>, and the card, hand and pile elements, once. Nothing happens where there is no browser.

const TABLE_CLOTHS

TABLE_CLOTHS: { readonly green: { readonly felt: "#2f5d4a"; readonly deep: "#1f4135"; readonly ink: "#f3efe4"; }; readonly blue: { readonly felt: "#2865a6"; readonly deep: "#1a4677"; readonly ink: "#f3efe4"; }; readonly red: { readonly felt: "#a3342e"; readonly deep: "#7a231f"; readonly ink: "#f3efe4"; }; readonly black: { readonly felt: "#2f3236"; readonly deep: "#1b1d20"; readonly ink: "#ece8dc"; }; readonly wood:…

THE CLOTHS A TABLE MAY BE LAID IN: the same five the whole family offers, and itsutsu.com's boards. Each is the felt's colour, its deep edge and the ink written on it.

type TableCloth

type TableCloth = keyof typeof TABLE_CLOTHS;

A cloth's name.

const ToranpuTable

ToranpuTable: typeof ToranpuTable

<toranpu-table>: ANY OF THE TEN GAMES, READY TO PLAY. The seats round the felt, what lies on the table (the trick, the pile to beat, the stock and the discard as <toranpu-pile>), the hand of whoever is to play, the moves they may make, and computers in every other seat, playing their turns after a short pause.

<toranpu-table game="crazy-eights" players="3" cloth="blue" messiness="0.4"></toranpu-table>

Attributes: game any of the ten, by its key or in kebab case: hearts (unless said), spades, euchre, cribbage, oh-hell, crazy-eights, go-fish, big-two, president, gin-rummy players how many sit at the table, within the game's own range (its usual number unless said) people how many of the seats are people's, the first ones; the rest are computers (1 unless said) names the seats' names, separated by commas; the first is "You" unless said seed the deal: the same seed deals the same cards (a new one each game unless said) cloth the felt: green (unless said), blue, red, black or wood messiness how untidy the stock and the discard lie, 0 to 1 (0.3 unless said) delay how long a computer thinks before it plays, in milliseconds (550 unless said) design, back, back-colour, mark, size, width, lang, sound as on <toranpu-card>

deal() deals again, with a new seed unless one is given; the game property is the game as it stands. Each move is a toranpu-table event that bubbles, its detail { seat, move, over, winners }.

@johnmorrisdotca/toranpu/hearts

choosePass decodeHearts encodeHearts HEARTS_ALL_POINTS HEARTS_RULES heartsComputer HeartsGame heartsHeight HeartsMove heartsMoves HeartsPhase HeartsPlay heartsPlayable heartsPoints heartsView HeartsView heartsWinners passOffset playHearts QUEEN_OF_SPADES sortHearts startHearts trickWinner TWO_OF_CLUBS

function choosePass

choosePass(hand: readonly CardId[]): CardId[]

The three cards a computer passes from a hand of Hearts: the queen of spades and the high spades that catch her, high hearts, and cards from a short suit.

function decodeHearts

decodeHearts: (text: string | null) => HeartsGame | null

Text read back into the game of Hearts it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeHearts

encodeHearts: (game: HeartsGame) => string

A game of Hearts as text: its table, its seed and its moves, never a hand.

const HEARTS_ALL_POINTS

HEARTS_ALL_POINTS: 26

Every point in a deal: thirteen hearts and the queen.

const HEARTS_RULES

HEARTS_RULES: CardGameRules<HeartsGame, HeartsMove>

Hearts as one CardGameRules: everything a table asks of the game.

function heartsComputer

heartsComputer(game: HeartsGame): HeartsMove

The move a computer in the seat to play makes: always one the rules allow.

type HeartsGame

type HeartsGame = KeptCardGame<HeartsMove> & { /** Which deal this is, from 0: it decides the way the cards are passed. */ deal: number; phase: HeartsPhase; hands: CardId[][]; /** The three cards each seat has chosen to pass this deal, or null while it has not. */ passing: (CardId[] | null)[]; /** The three cards each seat was passed this deal, shown to that seat once the passing is done. */ received: CardId[][]; /*…

A GAME OF HEARTS: its table and moves (what is kept, KeptCardGame), and everything the moves make, read again from them whenever the game is. size is the score that ends the game: 50 or 100.

function heartsHeight

heartsHeight(card: CardId): number

A card's height in a trick: ace high.

type HeartsMove

type HeartsMove = { pass: CardId[] } | { play: CardId };

Three cards passed (all three at once, as a player hands them over), or one card played to the trick.

function heartsMoves

heartsMoves(game: HeartsGame): HeartsMove[]

Every move the seat to play may make now; none once the game is over.

type HeartsPhase

type HeartsPhase = "passing" | "playing" | "over";

Passing three cards before the deal is played, playing it out trick by trick, or the game over.

type HeartsPlay

type HeartsPlay = { seat: number; card: CardId };

One card laid on a trick, and whose it was.

function heartsPlayable

heartsPlayable(game: HeartsGame): CardId[]

The cards the seat to play may lay on the trick now.

function heartsPoints

heartsPoints(card: CardId): number

A card's points: a heart one, the queen of spades thirteen.

function heartsView

heartsView(game: HeartsGame): HeartsView

The part of the game the seat to play can see, which is all its computer is given.

type HeartsView

type HeartsView = { seat: number; seats: number; hand: readonly CardId[]; phase: HeartsGame["phase"]; trick: readonly HeartsPlay[]; /** Every card already played this deal, finished tricks and the one on the table. */ seen: ReadonlySet<CardId>; playable: readonly CardId[]; };

What the seat to move can see: its own hand and the table.

function heartsWinners

heartsWinners(game: HeartsGame): number[]

The lowest score wins; level on the lowest shares it.

function passOffset

passOffset(deal: number, seats: number): number

How far round the table the cards go this deal: 1 to the left, seats − 1 to the right, 2 across, 0 held.

function playHearts

playHearts(game: HeartsGame, move: HeartsMove): HeartsGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

const QUEEN_OF_SPADES

QUEEN_OF_SPADES: "QS"

The queen of spades, worth thirteen points to whoever takes her.

function sortHearts

sortHearts(hand: readonly CardId[]): CardId[]

A hand sorted as a player holds it: clubs, diamonds, spades, hearts, low to high within each.

function startHearts

startHearts(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): HeartsGame | null

A new game of Hearts for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Hearts is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

function trickWinner

trickWinner(trick: readonly HeartsPlay[]): number

Who takes a finished trick: the highest card of the suit led.

const TWO_OF_CLUBS

TWO_OF_CLUBS: "2C"

The two of clubs, which leads the first trick of every deal.

@johnmorrisdotca/toranpu/spades

chooseBid contractOf dealerOf decodeSpades encodeSpades handWorth NIL partnerOf partnershipScore playSpades sortSpades SPADES_BIDS SPADES_RULES SPADES_SEATS SPADES_TRICKS spadesComputer SpadesDealScore SpadesGame spadesHeight SpadesMove spadesMoves SpadesPhase SpadesPlay spadesPlayable spadesTrickWinner spadesView SpadesView spadesWinners startSpades teamOf

function chooseBid

chooseBid(hand: readonly CardId[], partnerBid: number | null): number

Nil for a hand of nothing (and never beside a partner's nil); otherwise what the hand is worth, at least one.

function contractOf

contractOf(game: SpadesGame, team: 0 | 1): number | null

A partnership's contract this deal: its bids added, a nil counting none; null while either has still to bid.

function dealerOf

dealerOf(deal: number): number

The seat that deals this deal: the first seat, then round to the left.

function decodeSpades

decodeSpades: (text: string | null) => SpadesGame | null

Text read back into the game of Spades it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeSpades

encodeSpades: (game: SpadesGame) => string

A game of Spades as text: its table, its seed and its moves, never a hand.

function handWorth

handWorth(hand: readonly CardId[]): number

The tricks a hand is worth, counted as a card player counts them before bidding.

const NIL

NIL: 0

The bid that means no tricks at all.

function partnerOf

partnerOf(seat: number): number

The seat across the table.

function partnershipScore

partnershipScore(bids: readonly number[], tricks: readonly number[], team: 0 | 1): { points: number; bags: number; }

What one partnership made of a deal: its contract (the two bids, a nil counting none) made or not, each nil made or not, and the overtricks — the tricks over the contract, and any trick a nil bidder took — as bags.

function playSpades

playSpades(game: SpadesGame, move: SpadesMove): SpadesGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function sortSpades

sortSpades(hand: readonly CardId[]): CardId[]

A hand sorted as a player holds it: diamonds, clubs, hearts, spades, low to high within each.

const SPADES_BIDS

SPADES_BIDS: readonly number[]

The bids open to a player: nil, or one to thirteen tricks.

const SPADES_RULES

SPADES_RULES: CardGameRules<SpadesGame, SpadesMove>

Spades as one CardGameRules: everything a table asks of the game.

const SPADES_SEATS

SPADES_SEATS: 4

How many play Spades: four, in two partnerships.

const SPADES_TRICKS

SPADES_TRICKS: 13

The tricks in a deal of Spades: thirteen.

function spadesComputer

spadesComputer(game: SpadesGame): SpadesMove

The move a computer in the seat to play makes: always one the rules allow.

type SpadesDealScore

type SpadesDealScore = { /** The points the deal made or cost each partnership: seats 0 and 2 first, then 1 and 3. */ points: [number, number]; /** The overtricks (bags) each partnership took this deal. */ bags: [number, number]; };

What one finished deal did to a partnership's score, as the scores table explains it.

type SpadesGame

type SpadesGame = KeptCardGame<SpadesMove> & { /** Which deal this is, from 0: the deal moves one seat round each time. */ deal: number; phase: SpadesPhase; hands: CardId[][]; /** Each seat's bid this deal, 0 for nil, or null while it has not bid. */ bids: (number | null)[]; /** The trick on the table, in the order it was played. */ trick: SpadesPlay[]; /** The trick before, and who took it. */ lastTrick: { plays: S…

A GAME OF SPADES: its table and moves (what is kept, KeptCardGame), and everything the moves make, read again from them whenever the game is. Four seats in two partnerships, sitting across from each other: seats 0 and 2 against 1 and 3. size is the score that ends the game: 200, 300 or 500.

function spadesHeight

spadesHeight(card: CardId): number

A card's height in a trick: ace high.

type SpadesMove

type SpadesMove = { bid: number } | { play: CardId };

A bid of tricks, 0 (nil) to 13, or one card played to the trick.

function spadesMoves

spadesMoves(game: SpadesGame): SpadesMove[]

Every move the seat to play may make now; none once the game is over.

type SpadesPhase

type SpadesPhase = "bidding" | "playing" | "over";

Bidding how many tricks each seat will take, playing the deal out trick by trick, or the game over.

type SpadesPlay

type SpadesPlay = { seat: number; card: CardId };

One card laid on a trick, and whose it was.

function spadesPlayable

spadesPlayable(game: SpadesGame): CardId[]

The cards the seat to play may lay on the trick now.

function spadesTrickWinner

spadesTrickWinner(trick: readonly SpadesPlay[]): number

Who takes a finished trick (or is winning one still being played): the highest spade, or else the highest card of the suit led.

function spadesView

spadesView(game: SpadesGame): SpadesView

The part of the game the seat to play can see, which is all its computer is given.

type SpadesView

type SpadesView = { seat: number; hand: readonly CardId[]; phase: SpadesGame["phase"]; bids: readonly (number | null)[]; tricks: readonly number[]; trick: readonly SpadesPlay[]; /** Every card already played this deal, finished tricks and the one on the table. */ seen: ReadonlySet<CardId>; playable: readonly CardId[]; };

What the seat to move can see: its own hand, every bid, the tricks taken and the cards played.

function spadesWinners

spadesWinners(game: SpadesGame): number[]

The partnership with the higher score wins, both its seats.

function startSpades

startSpades(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): SpadesGame | null

A new game of Spades for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Spades is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

function teamOf

teamOf(seat: number): 0 | 1

A seat's partnership: 0 for seats 0 and 2, 1 for seats 1 and 3.

@johnmorrisdotca/toranpu/euchre

dealerOf decodeEuchre encodeEuchre EUCHRE_DECK EUCHRE_RULES EUCHRE_SEATS euchreComputer EuchreGame EuchreHandScore euchreHeight EuchreMove euchreMoves EuchrePhase EuchrePlay euchrePlayable euchreTrickWinner euchreView EuchreView euchreWinners handPoints partnerOf playEuchre sameColour sortEuchre startEuchre suitIn teamOf throwAway trumpWorth

function dealerOf

dealerOf(deal: number): number

The seat that deals this hand: the first seat, then round to the left.

function decodeEuchre

decodeEuchre: (text: string | null) => EuchreGame | null

Text read back into the game of Euchre it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeEuchre

encodeEuchre: (game: EuchreGame) => string

A game of Euchre as text: its table, its seed and its moves, never a hand.

const EUCHRE_DECK

EUCHRE_DECK: readonly string[]

The cards Euchre is played with: nine to ace of each suit.

const EUCHRE_RULES

EUCHRE_RULES: CardGameRules<EuchreGame, EuchreMove>

Euchre as one CardGameRules: everything a table asks of the game.

const EUCHRE_SEATS

EUCHRE_SEATS: 4

How many play Euchre: four, in two partnerships.

function euchreComputer

euchreComputer(game: EuchreGame): EuchreMove

The move a computer in the seat to play makes: always one the rules allow.

type EuchreGame

type EuchreGame = KeptCardGame<EuchreMove> & { /** Which hand this is, from 0: the deal moves one seat round each time. */ deal: number; phase: EuchrePhase; hands: CardId[][]; /** The card turned up from the four left over. */ upcard: CardId; /** Trumps, once made; null while the making goes round. */ trump: CardSuit | null; /** The seat that made trumps. */ maker: number | null; /** How many seats have passed in th…

A GAME OF EUCHRE: its table and moves (what is kept, KeptCardGame), and everything the moves make. Four seats in two partnerships across the table, seats 0 and 2 against 1 and 3. size is the score that wins: 5 or 10.

type EuchreHandScore

type EuchreHandScore = { makers: 0 | 1; trump: CardSuit; tricks: number; points: [number, number] };

What one hand did: which partnership made trumps, the tricks it took, and the points each partnership scored.

function euchreHeight

euchreHeight(card: CardId, trump: CardSuit | null): number

A card's height within its suit as the hand is played: ace high; in trumps the right bower, then the left, above everything.

type EuchreMove

type EuchreMove = { order: true } | { pass: true } | { call: CardSuit } | { discard: CardId } | { play: CardId };

Ordering up the card turned up, or passing; naming another suit, or passing; the dealer throwing a card after picking the turned one up; or a card played to the trick.

function euchreMoves

euchreMoves(game: EuchreGame): EuchreMove[]

Every move the seat to play may make now; none once the game is over.

type EuchrePhase

type EuchrePhase = "order" | "call" | "discard" | "playing" | "over";

The first round of the making (take the turned card's suit, or pass), the second (name another suit, or pass — the dealer may not), the dealer's discard, the tricks, or the game over.

type EuchrePlay

type EuchrePlay = { seat: number; card: CardId };

One card laid, and whose it was.

function euchrePlayable

euchrePlayable(game: EuchreGame): CardId[]

The cards the seat to play may lay on the trick: the suit led (the left bower counting as a trump) if it holds any.

function euchreTrickWinner

euchreTrickWinner(trick: readonly EuchrePlay[], trump: CardSuit): number

Who is taking a trick: the highest trump, or else the highest card of the suit led.

function euchreView

euchreView(game: EuchreGame): EuchreView

The part of the game the seat to play can see, which is all its computer is given.

type EuchreView

type EuchreView = { seat: number; dealer: number; hand: readonly CardId[]; phase: EuchreGame["phase"]; upcard: CardId; trump: CardSuit | null; maker: number | null; trick: readonly EuchrePlay[]; tricks: readonly number[]; seen: ReadonlySet<CardId>; playable: readonly CardId[]; };

A COMPUTER AT THE EUCHRE TABLE. Making trumps, it weighs its hand in each suit — the bowers, the trump ace and king, the aces outside and the suits it is short in — and orders up or names a suit only with a hand that should take three tricks with its partner's help, counting the turned card for its own side when it or its partner deals. Stuck as dealer, it names its best. Playing, it leads its best trump when its side made them, cashes aces, leaves a trick its partner has won alone, and takes a trick as cheaply as it can.

It reads a view of the game (euchreView): its own hand, the card turned up, the making so far and the cards played — nothing of another hand.

function euchreWinners

euchreWinners(game: EuchreGame): number[]

The partnership with the higher score wins, both its seats.

function handPoints

handPoints(makers: 0 | 1, tricks: number): [number, number]

What a hand scores: one to the makers for three or four tricks, two for all five, and two to the other side when the makers take two or fewer.

function partnerOf

partnerOf(seat: number): number

The seat across the table.

function playEuchre

playEuchre(game: EuchreGame, move: EuchreMove): EuchreGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function sameColour

sameColour(suit: CardSuit): CardSuit

The suit of the same colour: clubs and spades, diamonds and hearts.

function sortEuchre

sortEuchre(hand: readonly CardId[], trump?: CardSuit | null): CardId[]

A hand sorted as a player holds it: by suit (trumps last once made), high cards to the right.

function startEuchre

startEuchre(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): EuchreGame | null

A new game of Euchre for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Euchre is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

function suitIn

suitIn(card: CardId, trump: CardSuit | null): CardSuit

The suit a card belongs to once trumps are made: the left bower is a trump.

function teamOf

teamOf(seat: number): 0 | 1

A seat's partnership: 0 for seats 0 and 2, 1 for seats 1 and 3.

function throwAway

throwAway(hand: readonly CardId[], trump: CardSuit): CardId

After picking the turned card up: the lowest card outside trumps, from the shortest suit, never an ace.

function trumpWorth

trumpWorth(hand: readonly CardId[], trump: CardSuit): number

How much a hand is worth with this suit as trumps, roughly in tricks.

@johnmorrisdotca/toranpu/cribbage

cardValue chooseCrib choosePeg CRIBBAGE_RULES CRIBBAGE_SEATS cribbageComputer CribbageCount CribbageGame CribbageHandScore CribbageMove cribbageMoves CribbagePeg CribbagePhase CribbagePlay cribbagePlayable cribbageView CribbageView cribbageWinners cribPairs dealerOf decodeCribbage encodeCribbage otherOf pegPoints playCribbage showCount sortCribbage startCribbage throwWorth

function cardValue

cardValue(card: CardId): number

A card's worth in the count: ace one, court cards ten.

function chooseCrib

chooseCrib(hand: readonly CardId[], ownCrib: boolean): [CardId, CardId]

The two cards to lay away: the four kept worth most on average with every starter, the crib counted for or against.

function choosePeg

choosePeg(view: CribbageView): CardId

The card to peg: most points now, then a count kept off five and twenty-one, a low lead that is not a five.

const CRIBBAGE_RULES

CRIBBAGE_RULES: CardGameRules<CribbageGame, CribbageMove>

Cribbage as one CardGameRules: everything a table asks of the game.

const CRIBBAGE_SEATS

CRIBBAGE_SEATS: 2

How many play Cribbage here: two.

function cribbageComputer

cribbageComputer(game: CribbageGame): CribbageMove

The move a computer in the seat to play makes: always one the rules allow.

type CribbageCount

type CribbageCount = { fifteens: number; pairs: number; runs: number; flush: number; nobs: number; total: number };

What a hand's show is worth, by kind.

type CribbageGame

type CribbageGame = KeptCardGame<CribbageMove> & { /** Which hand this is, from 0: the deal goes to the other player each time. */ deal: number; phase: CribbagePhase; /** The cards each seat still holds: six dealt, four kept, then played away in the pegging. */ hands: CardId[][]; /** The four cards each seat kept, as they will be shown. */ kept: CardId[][]; crib: CardId[]; /** The card to be cut once the crib is lai…

A GAME OF CRIBBAGE: its table and moves (what is kept, KeptCardGame), and everything the moves make, read again from them whenever the game is. Two seats; size is the score that wins, 61 or 121.

type CribbageHandScore

type CribbageHandScore = { dealer: number; starter: CardId; hands: [CardId[], CardId[]]; crib: CardId[]; counts: [CribbageCount, CribbageCount]; cribCount: CribbageCount; };

One hand as it was shown: the dealer, the starter, both four-card hands and the crib, and what each counted.

type CribbageMove

type CribbageMove = { crib: [CardId, CardId] } | { play: CardId };

Two cards laid away to the crib, or one card played in the pegging.

function cribbageMoves

cribbageMoves(game: CribbageGame): CribbageMove[]

Every move the seat to play may make now; none once the game is over.

type CribbagePeg

type CribbagePeg = { seat: number; points: number; why: string[] };

What one card played in the pegging scored, and why: "fifteen", "a pair", "a run of three", "go", "last card", "thirty-one".

type CribbagePhase

type CribbagePhase = "crib" | "pegging" | "over";

Both players laying two cards to the crib, the pegging, or the game over.

type CribbagePlay

type CribbagePlay = { seat: number; card: CardId };

One card laid, and whose it was.

function cribbagePlayable

cribbagePlayable(hand: readonly CardId[], count: number): CardId[]

The cards a seat may play on the count now: any that keeps it to thirty-one.

function cribbageView

cribbageView(game: CribbageGame): CribbageView

The part of the game the seat to play can see, which is all its computer is given.

type CribbageView

type CribbageView = { seat: number; dealer: number; phase: CribbageGame["phase"]; hand: readonly CardId[]; count: number; run: readonly CardId[]; };

A COMPUTER AT THE CRIBBAGE TABLE. Laying away, it tries every two cards it could throw and keeps the four whose show is worth most on average over every starter it might be cut, adding what the two thrown are worth to its own crib or taking it off the other player's. Pegging, it takes the most points a card can make now, and otherwise keeps the count off five and twenty-one (a ten makes fifteen or thirty-one of them), leads low, and never leads a five.

It reads a view of the game (cribbageView): its own hand and what is on the table — nothing of the other hand, the crib, or the card to be cut.

function cribbageWinners

cribbageWinners(game: CribbageGame): number[]

The first to the total.

function cribPairs

cribPairs(hand: readonly CardId[]): [CardId, CardId][]

Every way to lay two of a hand away.

function dealerOf

dealerOf(deal: number): number

The seat that deals this hand: the first seat, then the other, turn about.

function decodeCribbage

decodeCribbage: (text: string | null) => CribbageGame | null

Text read back into the game of Cribbage it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeCribbage

encodeCribbage: (game: CribbageGame) => string

A game of Cribbage as text: its table, its seed and its moves, never a hand.

function otherOf

otherOf(seat: number): number

The seat that is not this one.

function pegPoints

pegPoints(cards: readonly CardId[], count: number): { points: number; why: string[]; }

What laying the last card of cards scores, the count now count: fifteen, thirty-one, pairs and runs.

function playCribbage

playCribbage(game: CribbageGame, move: CribbageMove): CribbageGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function showCount

showCount(four: readonly CardId[], starter: CardId, crib?: boolean): CribbageCount

What four cards and the starter are worth in the show. A crib's flush counts only when all five are one suit.

function sortCribbage

sortCribbage(hand: readonly CardId[]): CardId[]

A hand sorted as a player holds it: ace low to king, by rank then suit.

function startCribbage

startCribbage(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): CribbageGame | null

A new game of Cribbage for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Cribbage is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

function throwWorth

throwWorth([a, b]: readonly [CardId, CardId]): number

What two cards thrown to a crib are worth to it, roughly: a pair, a fifteen, fives, and cards close enough to run.

@johnmorrisdotca/toranpu/oh-hell

chooseOhHellBid dealerOf dealPoints dealSizes decodeOhHell encodeOhHell OH_HELL_MOST_CARDS OH_HELL_RULES ohHellBids ohHellComputer OhHellDealScore OhHellGame ohHellHeight OhHellMove ohHellMoves OhHellPhase OhHellPlay ohHellPlayable ohHellTrickWinner ohHellView OhHellView ohHellWinners ohHellWorth playOhHell sortOhHell startOhHell

function chooseOhHellBid

chooseOhHellBid(worth: number, allowed: readonly number[]): number

The bid nearest what the hand is worth, among those allowed.

function dealerOf

dealerOf(deal: number, seats: number): number

The seat that deals this deal: the first seat, then round to the left.

function dealPoints

dealPoints(bid: number, tricks: number): number

What a seat scores for a deal: ten and its bid for exactly what it bid, nothing otherwise.

function dealSizes

dealSizes(size: number): number[]

The cards dealt to each seat in each deal of a game of this length: up to seven, and back down in the long game.

function decodeOhHell

decodeOhHell: (text: string | null) => OhHellGame | null

Text read back into the game of Oh Hell it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeOhHell

encodeOhHell: (game: OhHellGame) => string

A game of Oh Hell as text: its table, its seed and its moves, never a hand.

const OH_HELL_MOST_CARDS

OH_HELL_MOST_CARDS: 7

The most cards dealt to a hand of Oh Hell: seven.

const OH_HELL_RULES

OH_HELL_RULES: CardGameRules<OhHellGame, OhHellMove>

Oh Hell as one CardGameRules: everything a table asks of the game.

function ohHellBids

ohHellBids(game: OhHellGame): number[]

The bids open to the seat to bid: nought to the cards in hand, less the one a dealer may not make.

function ohHellComputer

ohHellComputer(game: OhHellGame): OhHellMove

The move a computer in the seat to play makes: always one the rules allow.

type OhHellDealScore

type OhHellDealScore = { cards: number; bids: number[]; tricks: number[]; points: number[] };

What one finished deal did: each seat's bid, the tricks it took, and what it scored.

type OhHellGame

type OhHellGame = KeptCardGame<OhHellMove> & { /** Which deal this is, from 0. */ deal: number; phase: OhHellPhase; /** The cards each seat holds this deal. */ cards: number; hands: CardId[][]; /** The card turned up after the deal: its suit is trumps. */ turned: CardId; trump: CardSuit; bids: (number | null)[]; trick: OhHellPlay[]; lastTrick: { plays: OhHellPlay[]; winner: number } | null; /** Every card played in …

A GAME OF OH HELL: its table and moves (what is kept, KeptCardGame), and everything the moves make. Three or four seats, each for itself. size is how many deals: 7 (one card, then two, up to seven) or 13 (up to seven and back down to one).

function ohHellHeight

ohHellHeight(card: CardId): number

A card's height in a trick: ace high.

type OhHellMove

type OhHellMove = { bid: number } | { play: CardId };

A bid of tricks, nought up to the cards in hand, or one card played to the trick.

function ohHellMoves

ohHellMoves(game: OhHellGame): OhHellMove[]

Every move the seat to play may make now; none once the game is over.

type OhHellPhase

type OhHellPhase = "bidding" | "playing" | "over";

Bidding the exact number of tricks each seat will take, playing the deal out, or the game over.

type OhHellPlay

type OhHellPlay = { seat: number; card: CardId };

One card laid, and whose it was.

function ohHellPlayable

ohHellPlayable(game: OhHellGame): CardId[]

The cards the seat to play may lay: the suit led if it holds any.

function ohHellTrickWinner

ohHellTrickWinner(trick: readonly OhHellPlay[], trump: string): number

Who takes a trick: the highest trump, or else the highest card of the suit led.

function ohHellView

ohHellView(game: OhHellGame): OhHellView

The part of the game the seat to play can see, which is all its computer is given.

type OhHellView

type OhHellView = { seat: number; hand: readonly CardId[]; trump: string; bid: number | null; took: number; trick: readonly OhHellPlay[]; seats: number; playable: readonly CardId[]; };

A COMPUTER AT THE OH HELL TABLE. Bidding, it counts the tricks its hand should take — high trumps, long trumps, aces outside and, in a hand of a few cards, kings — and bids the nearest it may. Playing, it tries to take a trick while it has taken fewer than it bid, as cheaply as it can, and to lose every trick once it has made its bid, throwing its most dangerous cards while it can do so safely.

It reads a view of the game (ohHellView): its own hand, the turned card, the bids and the cards played — nothing of another hand.

function ohHellWinners

ohHellWinners(game: OhHellGame): number[]

The highest score after the last deal: every seat that has it.

function ohHellWorth

ohHellWorth(hand: readonly CardId[], trump: string, seats: number): number

The tricks a hand should take with these trumps, roughly.

function playOhHell

playOhHell(game: OhHellGame, move: OhHellMove): OhHellGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function sortOhHell

sortOhHell(hand: readonly CardId[], trump: string): CardId[]

A hand as a player holds it: by suit, trumps last, high cards to the right.

function startOhHell

startOhHell(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): OhHellGame | null

A new game of Oh Hell for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Oh Hell is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

@johnmorrisdotca/toranpu/crazy-eights

CRAZY_EIGHTS_RULES crazyEightsComputer CrazyEightsGame CrazyEightsMove crazyEightsMoves CrazyEightsResult crazyEightsView CrazyEightsView crazyEightsWinners crazyHandSize crazyMatches crazyPlayable crazyPoints crazyTop decodeCrazyEights EIGHT encodeCrazyEights longestSuit playCrazyEights sortCrazy startCrazyEights

const CRAZY_EIGHTS_RULES

CRAZY_EIGHTS_RULES: CardGameRules<CrazyEightsGame, CrazyEightsMove>

Crazy Eights as one CardGameRules: everything a table asks of the game.

function crazyEightsComputer

crazyEightsComputer(game: CrazyEightsGame): CrazyEightsMove

The move a computer in the seat to play makes: always one the rules allow.

type CrazyEightsGame

type CrazyEightsGame = KeptCardGame<CrazyEightsMove> & { /** Which hand this is, from 0: it decides who plays first, round the table. */ hand: number; phase: "playing" | "over"; hands: CardId[][]; /** The stock, face down, top card first. */ stock: CardId[]; /** The discard pile, face up, the top card last. */ discard: CardId[]; /** The suit to follow: the top card's, or the one called when an eight was played. */ s…

A GAME OF CRAZY EIGHTS: its table and moves (what is kept), and the hand they have reached. size is the score that wins: 50, 100 or 200.

type CrazyEightsMove

type CrazyEightsMove = { play: CardId; suit?: CardSuit } | { draw: true } | { pass: true };

A card played (an eight names the suit to follow), a card drawn from the stock, or a turn passed.

function crazyEightsMoves

crazyEightsMoves(game: CrazyEightsGame): CrazyEightsMove[]

Every move the seat to play may make now; none once the game is over.

type CrazyEightsResult

type CrazyEightsResult = { winners: number[]; points: number; blocked: boolean };

How a hand ended: who won it, and the points each winner took.

function crazyEightsView

crazyEightsView(game: CrazyEightsGame): CrazyEightsView

The part of the game the seat to play can see, which is all its computer is given.

type CrazyEightsView

type CrazyEightsView = { seat: number; hand: readonly CardId[]; counts: readonly number[]; drawn: CardId | null; legal: readonly CrazyEightsMove[]; };

What one seat can see of a game of Crazy Eights: its own hand, the table, and what has been said and shown.

function crazyEightsWinners

crazyEightsWinners(game: CrazyEightsGame): number[]

The highest score wins, once somebody reaches the game's size; level on it shares the win.

function crazyHandSize

crazyHandSize(seats: number): number

Seven cards each for two players, five for more.

function crazyMatches

crazyMatches(game: CrazyEightsGame, card: CardId): boolean

Whether this card may go on the pile now: an eight always; otherwise the suit to follow or the top card's rank.

function crazyPlayable

crazyPlayable(game: CrazyEightsGame): CardId[]

The cards the player to move may play now: after drawing, only the card drawn.

function crazyPoints

crazyPoints(card: CardId): number

What a card left in hand is worth to the winner of the hand.

function crazyTop

crazyTop(game: CrazyEightsGame): CardId

The card on top of the discard pile.

function decodeCrazyEights

decodeCrazyEights: (text: string | null) => CrazyEightsGame | null

Text read back into the game of Crazy Eights it records, by playing every move again through the rules; null for anything they cannot play out.

const EIGHT

EIGHT: 8

The rank that is wild in Crazy Eights.

function encodeCrazyEights

encodeCrazyEights: (game: CrazyEightsGame) => string

A game of Crazy Eights as text: its table, its seed and its moves, never a hand.

function longestSuit

longestSuit(hand: readonly CardId[]): CardSuit

The suit this hand holds most of, eights aside; clubs first among equals so the choice is always the same.

function playCrazyEights

playCrazyEights(game: CrazyEightsGame, move: CrazyEightsMove): CrazyEightsGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function sortCrazy

sortCrazy(hand: readonly CardId[]): CardId[]

A hand as a player holds it: eights last, the rest by suit and rank.

function startCrazyEights

startCrazyEights(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): CrazyEightsGame | null

A new game of Crazy Eights for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Crazy Eights is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

@johnmorrisdotca/toranpu/go-fish

decodeGoFish encodeGoFish GO_FISH_RULES goFishComputer GoFishEvent GoFishGame goFishHandSize goFishMemory GoFishMemory GoFishMove goFishMoves goFishRanks goFishTargets goFishView GoFishView goFishWinners playGoFish sortGoFish startGoFish

function decodeGoFish

decodeGoFish: (text: string | null) => GoFishGame | null

Text read back into the game of Go Fish it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeGoFish

encodeGoFish: (game: GoFishGame) => string

A game of Go Fish as text: its table, its seed and its moves, never a hand.

const GO_FISH_RULES

GO_FISH_RULES: CardGameRules<GoFishGame, GoFishMove>

Go Fish as one CardGameRules: everything a table asks of the game.

function goFishComputer

goFishComputer(game: GoFishGame): GoFishMove

The move a computer in the seat to play makes: always one the rules allow.

type GoFishEvent

type GoFishEvent = | { kind: "ask"; seat: number; asked: number; rank: CardRank; got: number; fished: "caught" | "missed" | "dry" | null } | { kind: "draw"; seat: number } | { kind: "book"; seat: number; rank: CardRank };

What the whole table saw of a turn: who asked whom for what, how many cards were handed over, and — when none were — whether the card drawn from the pond was the rank asked for (which is shown, and earns another ask). A card drawn for any other reason is drawn face down: drew says only that it was.

type GoFishGame

type GoFishGame = KeptCardGame<GoFishMove> & { phase: "playing" | "over"; hands: CardId[][]; /** The pond: the cards not dealt, top card first. */ stock: CardId[]; /** Each seat's books, one rank each, in the order laid down. */ books: CardRank[][]; toPlay: number | null; /** Everything said and shown at the table, in order: what a player (or a computer) remembers from. */ log: GoFishEvent[]; };

A GAME OF GO FISH: its table and moves (what is kept), and what they make. One deal is the game; size is 1.

function goFishHandSize

goFishHandSize(seats: number): number

Seven cards each for two or three players, five for more.

function goFishMemory

goFishMemory(log: readonly GoFishEvent[], seats: number): GoFishMemory

What the table's log says about every seat's hand, worked out from the asks, the answers and the books.

type GoFishMemory

type GoFishMemory = { holds: Set<CardRank>[]; lacks: Set<CardRank>[] };

What the table has said about each seat's hand: for each seat, the ranks it is known to hold and known to be without.

type GoFishMove

type GoFishMove = { ask: number; rank: CardRank };

Asking one player for every card they hold of one rank.

function goFishMoves

goFishMoves(game: GoFishGame): GoFishMove[]

Every move the seat to play may make now; none once the game is over.

function goFishRanks

goFishRanks(game: GoFishGame): CardRank[]

The ranks the seat to move may ask for: those it holds.

function goFishTargets

goFishTargets(game: GoFishGame): number[]

The players the seat to move may ask: everybody else holding cards, or, when nobody else does, anybody else.

function goFishView

goFishView(game: GoFishGame): GoFishView

The part of the game the seat to play can see, which is all its computer is given.

type GoFishView

type GoFishView = { seat: number; hand: readonly CardId[]; targets: readonly number[]; ranks: readonly CardRank[]; counts: readonly number[]; log: readonly GoFishEvent[]; };

What one seat can see of a game of Go Fish: its own hand, the table, and what has been said and shown.

function goFishWinners

goFishWinners(game: GoFishGame): number[]

The most books wins; level on the most shares it.

function playGoFish

playGoFish(game: GoFishGame, move: GoFishMove): GoFishGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function sortGoFish

sortGoFish(hand: readonly CardId[]): CardId[]

A hand as a player holds it: grouped by rank, ace to king.

function startGoFish

startGoFish(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): GoFishGame | null

A new game of Go Fish for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Go Fish is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

@johnmorrisdotca/toranpu/big-two

afterTurn BIG_TWO_RULES bigTwoBeats bigTwoCard bigTwoCharge bigTwoComputer BigTwoGame BigTwoKind bigTwoMayPlay bigTwoMoves bigTwoPlays bigTwoRank bigTwoValue BigTwoValue bigTwoView BigTwoView bigTwoWinners ClimbMove ClimbPlay ClimbTrick decodeBigTwo encodeBigTwo freshTrick isClimbMove laid nextRound passedOn playBigTwo seatsIn sortBigTwo startBigTwo

function afterTurn

afterTurn<T extends ClimbTrick>(trick: T, from: number): T

Whose move it is after from played or passed: the next seat still in that has not passed, or — when nobody is left to answer — the trick cleared and led by whoever played last, or the next seat after them still in.

const BIG_TWO_RULES

BIG_TWO_RULES: CardGameRules<BigTwoGame, ClimbMove>

Big Two as one CardGameRules: everything a table asks of the game.

function bigTwoBeats

bigTwoBeats(play: BigTwoValue, table: BigTwoValue): boolean

Whether a play beats the one on the table: the same number of cards, and stronger.

function bigTwoCard

bigTwoCard(card: CardId): number

A card's height in the whole pack, 0 (three of diamonds) to 51 (two of spades).

function bigTwoCharge

bigTwoCharge(left: number): number

What a hand left in is charged when somebody goes out: a point a card, double for ten or more, treble for thirteen or more.

function bigTwoComputer

bigTwoComputer(game: BigTwoGame): ClimbMove

The move a computer in the seat to play makes: always one the rules allow.

type BigTwoGame

type BigTwoGame = KeptCardGame<ClimbMove> & ClimbTrick & { /** Which deal this is, from 0. */ deal: number; phase: "playing" | "over"; /** The lowest card dealt, which the first play of the deal must include; null once it has been played. */ opening: CardId | null; /** Each seat's penalty points so far: the lowest total wins. */ penalties: number[]; /** Each finished deal: who went out, and what every seat was charg…

A GAME OF BIG TWO: its table and moves (what is kept), and the deal they have reached. size is how many deals the game lasts: 1, 3 or 5.

type BigTwoKind

type BigTwoKind = "single" | "pair" | "triple" | "straight" | "flush" | "fullHouse" | "fourKind" | "straightFlush";

The kinds of play in Big Two, a single to a straight flush.

function bigTwoMayPlay

bigTwoMayPlay(game: BigTwoGame, cards: readonly CardId[]): boolean

Whether these cards may be played now by the seat to play.

function bigTwoMoves

bigTwoMoves(game: BigTwoGame): ClimbMove[]

Every move the seat to play may make now; none once the game is over.

function bigTwoPlays

bigTwoPlays(hand: readonly CardId[]): CardId[][]

Every play this hand could make, each once, lowest cards first within a kind.

function bigTwoRank

bigTwoRank(card: CardId): number

A rank's height, the three lowest (0) and the two highest (12).

function bigTwoValue

bigTwoValue(cards: readonly CardId[]): BigTwoValue | null

What these cards are worth played together, or null for cards that make no play.

type BigTwoValue

type BigTwoValue = { kind: BigTwoKind; size: number; strength: number };

A play's kind and how strong it is among plays of the same number of cards.

function bigTwoView

bigTwoView(game: BigTwoGame): BigTwoView

The part of the game the seat to play can see, which is all its computer is given.

type BigTwoView

type BigTwoView = { seat: number; hand: readonly CardId[]; pile: ClimbPlay | null; /** How many cards each seat holds: counted in the open at any table. */ counts: readonly number[]; legal: readonly ClimbMove[]; };

What one seat can see of a game of Big Two: its own hand, the table, and what has been said and shown.

function bigTwoWinners

bigTwoWinners(game: BigTwoGame): number[]

The fewest penalty points wins; level on the fewest shares it.

type ClimbMove

type ClimbMove = { play: CardId[] } | { pass: true };

Cards played to beat the table (or lead), or a pass.

type ClimbPlay

type ClimbPlay = { seat: number; cards: CardId[] };

What is on the table: the last cards played to this trick, and whose they were.

type ClimbTrick

type ClimbTrick = { hands: CardId[][]; /** The cards to beat, or null when the seat to play leads. */ pile: ClimbPlay | null; /** Who has passed on this trick: they wait until it is cleared. */ passed: boolean[]; /** Whose move it is; null once the game is over. */ toPlay: number | null; /** Every card played this deal, in order: what the whole table has seen. */ played: CardId[]; };

The part of a climbing game's state the trick machinery moves: every climbing game carries these.

function decodeBigTwo

decodeBigTwo: (text: string | null) => BigTwoGame | null

Text read back into the game of Big Two it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeBigTwo

encodeBigTwo: (game: BigTwoGame) => string

A game of Big Two as text: its table, its seed and its moves, never a hand.

function freshTrick

freshTrick(hands: CardId[][], leader: number): ClimbTrick

A fresh trick to lead, for a new deal.

function isClimbMove

isClimbMove(value: unknown): value is ClimbMove

A move read back from storage, checked for shape: cards to play, or a pass.

function laid

laid<T extends ClimbTrick>(trick: T, seat: number, cards: readonly CardId[]): T | null

The trick after seat lays these cards on it: out of their hand, on the table, and seen by all. Null if they do not hold them.

function nextRound

nextRound(from: number, seats: number, wanted: (seat: number): boolean) => number | null

The next seat round from from (not counting it) that wanted accepts, or null when none does.

function passedOn

passedOn<T extends ClimbTrick>(trick: T, seat: number): T

The trick after seat passes.

function playBigTwo

playBigTwo(game: BigTwoGame, move: ClimbMove): BigTwoGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function seatsIn

seatsIn(trick: Pick<ClimbTrick, "hands">): number[]

The seats still holding cards.

function sortBigTwo

sortBigTwo(cards: readonly CardId[]): CardId[]

Cards in Big Two's order, lowest first: how a hand is held.

function startBigTwo

startBigTwo(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): BigTwoGame | null

A new game of Big Two for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Big Two is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

@johnmorrisdotca/toranpu/president

afterTurn ClimbMove ClimbPlay ClimbTrick decodePresident encodePresident freshTrick isClimbMove laid nextRound passedOn playPresident PRESIDENT_RULES presidentComputer PresidentGame presidentMayPlay PresidentMove presidentMoves presidentRank PresidentSwap presidentSwapCount presidentTitle PresidentTitle presidentView PresidentView presidentWinners seatsIn sortPresident startPresident

function afterTurn

afterTurn<T extends ClimbTrick>(trick: T, from: number): T

Whose move it is after from played or passed: the next seat still in that has not passed, or — when nobody is left to answer — the trick cleared and led by whoever played last, or the next seat after them still in.

type ClimbMove

type ClimbMove = { play: CardId[] } | { pass: true };

Cards played to beat the table (or lead), or a pass.

type ClimbPlay

type ClimbPlay = { seat: number; cards: CardId[] };

What is on the table: the last cards played to this trick, and whose they were.

type ClimbTrick

type ClimbTrick = { hands: CardId[][]; /** The cards to beat, or null when the seat to play leads. */ pile: ClimbPlay | null; /** Who has passed on this trick: they wait until it is cleared. */ passed: boolean[]; /** Whose move it is; null once the game is over. */ toPlay: number | null; /** Every card played this deal, in order: what the whole table has seen. */ played: CardId[]; };

The part of a climbing game's state the trick machinery moves: every climbing game carries these.

function decodePresident

decodePresident: (text: string | null) => PresidentGame | null

Text read back into the game of President it records, by playing every move again through the rules; null for anything they cannot play out.

function encodePresident

encodePresident: (game: PresidentGame) => string

A game of President as text: its table, its seed and its moves, never a hand.

function freshTrick

freshTrick(hands: CardId[][], leader: number): ClimbTrick

A fresh trick to lead, for a new deal.

function isClimbMove

isClimbMove(value: unknown): value is ClimbMove

A move read back from storage, checked for shape: cards to play, or a pass.

function laid

laid<T extends ClimbTrick>(trick: T, seat: number, cards: readonly CardId[]): T | null

The trick after seat lays these cards on it: out of their hand, on the table, and seen by all. Null if they do not hold them.

function nextRound

nextRound(from: number, seats: number, wanted: (seat: number): boolean) => number | null

The next seat round from from (not counting it) that wanted accepts, or null when none does.

function passedOn

passedOn<T extends ClimbTrick>(trick: T, seat: number): T

The trick after seat passes.

function playPresident

playPresident(game: PresidentGame, move: PresidentMove): PresidentGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

const PRESIDENT_RULES

PRESIDENT_RULES: CardGameRules<PresidentGame, PresidentMove>

President as one CardGameRules: everything a table asks of the game.

function presidentComputer

presidentComputer(game: PresidentGame): PresidentMove

The move a computer in the seat to play makes: always one the rules allow.

type PresidentGame

type PresidentGame = KeptCardGame<PresidentMove> & ClimbTrick & { /** Which round this is, from 0. */ round: number; /** Handing cards over before the round, playing it, or the game over. */ phase: "exchange" | "playing" | "over"; /** The order players went out this round, first to last so far. */ out: number[]; /** The order they went out last round, which gives this round's titles; null in the first. */ titles: nu…

A GAME OF PRESIDENT: its table and moves (what is kept), and the round they have reached. size is how many rounds the game lasts: 3, 5 or 7.

function presidentMayPlay

presidentMayPlay(game: PresidentGame, cards: readonly CardId[]): boolean

Whether these cards may be played now by the seat to play: one rank, one to four of it, higher than the table's and as many.

type PresidentMove

type PresidentMove = ClimbMove | { give: CardId[] };

A climbing move, or the cards a President or Vice-President hands back down at the start of a round.

function presidentMoves

presidentMoves(game: PresidentGame): PresidentMove[]

Every move the seat to play may make now; none once the game is over.

function presidentRank

presidentRank(card: CardId): number

A rank's height: the three lowest (0), the two highest (12).

type PresidentSwap

type PresidentSwap = { from: number; to: number; cards: CardId[] };

Cards handed over at the start of a round: the best cards up, whatever the receiver chooses back down.

function presidentSwapCount

presidentSwapCount(seats: number): number

How many cards the President and the Beggar swap: two, or one when only three are at the table.

function presidentTitle

presidentTitle(order: readonly number[], seat: number): PresidentTitle

A seat's title from a round's finishing order.

type PresidentTitle

type PresidentTitle = "president" | "vicePresident" | "citizen" | "viceBeggar" | "beggar";

Each seat's title from a round's finishing order: 0 President, 1 Vice-President, the last two Vice-Beggar and Beggar, everybody else a Citizen.

function presidentView

presidentView(game: PresidentGame): PresidentView

The part of the game the seat to play can see, which is all its computer is given.

type PresidentView

type PresidentView = { seat: number; hand: readonly CardId[]; pile: ClimbPlay | null; counts: readonly number[]; legal: readonly PresidentMove[]; };

What one seat can see of a game of President: its own hand, the table, and what has been said and shown.

function presidentWinners

presidentWinners(game: PresidentGame): number[]

The most points wins; level on the most shares it.

function seatsIn

seatsIn(trick: Pick<ClimbTrick, "hands">): number[]

The seats still holding cards.

function sortPresident

sortPresident(cards: readonly CardId[]): CardId[]

Cards in President's order, lowest first, suits in a fixed order only so a hand always sorts the same way.

function startPresident

startPresident(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): PresidentGame | null

A new game of President for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table President is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

@johnmorrisdotca/toranpu/gin-rummy

bestLayout canKnockWith chooseThrow deadwoodIn deadwoodOf deadwoodValue decodeGin encodeGin GIN_BONUS GIN_RUMMY_RULES GIN_SEATS ginComputer GinGame GinLayout GinMeld GinMove ginMoves GinResult ginSensible ginView GinView ginWinners isMeld KNOCK_MOST layOff meldsIn playGin sortGin startGin UNDERCUT_BONUS wanted

function bestLayout

bestLayout(hand: readonly CardId[]): GinLayout

The hand laid out to leave the least deadwood: the melds chosen, none sharing a card, and what is left. Searched exhaustively over the cards as a bitmask, which for eleven cards at most is quick.

function canKnockWith

canKnockWith(hand: readonly CardId[], card: CardId): boolean

Whether throwing this card, from a hand of eleven, leaves little enough deadwood to knock with.

function chooseThrow

chooseThrow(view: GinView): CardId

The card to throw: the one leaving the least deadwood; of those, not a card the other player could use, and the highest.

function deadwoodIn

deadwoodIn(hand: readonly CardId[]): number

The deadwood left in a hand at its best.

function deadwoodOf

deadwoodOf(cards: readonly CardId[]): number

What a list of cards counts as deadwood, added up.

function deadwoodValue

deadwoodValue(card: CardId): number

A card's deadwood: an ace one, the numbers their number, a jack, queen or king ten.

function decodeGin

decodeGin: (text: string | null) => GinGame | null

Text read back into the game of Gin Rummy it records, by playing every move again through the rules; null for anything they cannot play out.

function encodeGin

encodeGin: (game: GinGame) => string

A game of Gin Rummy as text: its table, its seed and its moves, never a hand.

const GIN_BONUS

GIN_BONUS: 25

The bonus for gin, and for an undercut.

const GIN_RUMMY_RULES

GIN_RUMMY_RULES: CardGameRules<GinGame, GinMove>

Gin Rummy as one CardGameRules: everything a table asks of the game.

const GIN_SEATS

GIN_SEATS: 2

How many play Gin Rummy: two.

function ginComputer

ginComputer(game: GinGame): GinMove

The move a computer in the seat to play makes: always one the rules allow.

type GinGame

type GinGame = KeptCardGame<GinMove> & { /** Which hand this is, from 0: the first to play alternates. */ hand: number; /** Drawing a card, throwing one, or the game over. */ phase: "draw" | "discard" | "over"; hands: CardId[][]; /** The stock, face down, top card first. */ stock: CardId[]; /** The discard pile, face up, the top card last. */ discard: CardId[]; /** The card just taken from the discard pile, which ma…

A GAME OF GIN RUMMY: its table and moves (what is kept), and the hand they have reached. Two seats. size is the score that wins: 50, 100 or 150.

type GinLayout

type GinLayout = { melds: GinMeld[]; deadwood: CardId[] };

Each hand's player, laid out: their melds, and the cards left over.

type GinMeld

type GinMeld = CardId[];

A meld: three or four of a rank, or a run of three or more in one suit.

type GinMove

type GinMove = { draw: "stock" } | { draw: "discard" } | { discard: CardId } | { knock: CardId };

A card drawn from the stock or taken from the discard pile, or a card thrown: laid on the pile, or face down to knock.

function ginMoves

ginMoves(game: GinGame): GinMove[]

Every move the seat to play may make now; none once the game is over.

type GinResult

type GinResult = { /** The seat that knocked, or null for a hand drawn with the stock run down. */ knocker: number | null; /** The seat that scored the hand, or null for none. */ winner: number | null; points: number; /** "gin" with no deadwood, "knock" won, "undercut" when the other player had no more, "drawn" when the stock ran out. */ kind: "gin" | "knock" | "undercut" | "drawn"; /** Each seat's melds and deadwoo…

How a hand ended: who scored and how much, and how each hand was laid down.

function ginSensible

ginSensible(game: GinGame, random: (): number) => GinMove

A player a test can play at random who still ends a hand: a random draw and a random throw, but a knock whenever the card thrown allows one. A player choosing every move uniformly would almost never knock, and hand after hand would run the stock down to a draw with nobody scoring.

function ginView

ginView(game: GinGame): GinView

The part of the game the seat to play can see, which is all its computer is given.

type GinView

type GinView = { hand: readonly CardId[]; phase: GinGame["phase"]; /** The card on top of the discard pile, if any. */ top: CardId | null; /** The card just taken from the pile, which may not go straight back. */ taken: CardId | null; /** Every card the other player has taken from the discard pile this hand and still holds: the table watched them take it. */ theirPicks: readonly CardId[]; };

A COMPUTER AT THE GIN RUMMY TABLE: it takes the discard only when the card goes straight into a meld, throws the card that leaves the least deadwood (the highest of those that tie, and never one the other player has shown they are collecting when another will do), and knocks the moment it can.

It reads a view of the game (ginView): its own hand, the discard pile and what the other player took from it — and nothing of their hand or the stock.

function ginWinners

ginWinners(game: GinGame): number[]

The first to the game's total wins.

function isMeld

isMeld(cards: readonly CardId[]): boolean

Whether these cards are a meld: three or four of a rank, or three or more in a row in one suit.

const KNOCK_MOST

KNOCK_MOST: 10

The most deadwood a player may knock with.

function layOff

layOff(defender: readonly CardId[], knockerMelds: readonly GinMeld[]): { layout: GinLayout; laidOff: CardId[]; melds: GinMeld[]; }

The defender's side of a knock: their own melds first, then every card left that fits one of the knocker's melds laid off onto it — a run grown one card at a time, so a six can follow the five just laid.

function meldsIn

meldsIn(hand: readonly CardId[]): GinMeld[]

Every meld that can be made from these cards: each set of three or four, and each run of three or more.

function playGin

playGin(game: GinGame, move: GinMove): GinGame | null

The game after that move, with the move added to its record, or null for a move the rules refuse.

function sortGin

sortGin(hand: readonly CardId[]): CardId[]

A hand sorted as a player holds it: by suit (spades, hearts, clubs, diamonds, as the colours alternate), ace low.

function startGin

startGin(size: number, players: readonly string[], _language?: unknown, dealt?: number, computers?: readonly boolean[]): GinGame | null

A new game of Gin Rummy for these players (one name a seat) at this size, dealt from the seed dealt, or null for a table Gin Rummy is not offered for. computers says, one a seat, which seats a computer plays; the third argument is unused.

const UNDERCUT_BONUS

UNDERCUT_BONUS: 25

The bonus for an undercut: the defender has no more deadwood than the knocker.

function wanted

wanted(hand: readonly CardId[], card: CardId): boolean

Whether the discard would go straight into a meld: the hand with it and its best throw has less deadwood than now.

@johnmorrisdotca/toranpu/klondike

allFaceUp bestFirst BestFirstGame carriedFrom COLUMN_PILES columnAt COLUMNS dealKlondike dealOf dealOfSeed deckOf decodeMoves DRAW_CODE encodeMove encodeMoves finishingMoves fitsColumn fitsFoundation FOUNDATION_PILES foundationAt foundationPileOf homeMove isColumnPile isFoundationPile isSafeHome KlondikeColumn KlondikeMove KlondikePile KlondikeRules KlondikeTable klondikeWon liftable moveFor movesFrom pileCards playKlondike rankOf RANKS_A_SUIT RECYCLE_CODE recyclesLeft redCard replay solveKlondike stockMove stuck suitOf TableSpot

function allFaceUp

allFaceUp(table: KlondikeTable): boolean

Whether every card on the columns is face up: from here the game plays itself out (autoFinish).

function bestFirst

bestFirst<Table, Move>(game: BestFirstGame<Table, Move>, start: Table, budget: number): { moves: Move[] | null; tables: number; }

A winning line from this table — every move a replay of it makes, the settling ones included — or null when none was found within budget tables.

type BestFirstGame

type BestFirstGame<Table, Move> = { candidates: (table: Table) => Move[]; play: (table: Table, move: Move) => Table | null; settle?: (table: Table) => { table: Table; moves: Move[] }; won: (table: Table) => boolean; key: (table: Table) => string; distance: (table: Table) => number; };

A BEST-FIRST SEARCH for a card game played alone, shared by FreeCell's solver and Spider's (freecell/solve.ts, spider/solve.ts).

The table that looks nearest to won is opened next (distance, the lower the nearer), ties in the order they were found, and every table is opened once (key). It gives up after a fixed number of tables, never after a fixed time, so the same deal gets the same answer on a phone, a desk and the server — which is what lets a seed name "the first winnable deal from here" in every browser alike.

settle is what a game does by itself after every move (FreeCell's safe cards sent home): its moves are part of the line, so a replay of the line makes exactly the tables the search saw.

function carriedFrom

carriedFrom(table: KlondikeTable, column: number, to: KlondikePile): number | null

Which card of a column a carry to to would take: the one face-up card whose run fits there, or null. A column's face-up cards are a single run, so only one card in it can be one below the target's top, and only its foot can be a King.

const COLUMN_PILES

COLUMN_PILES: readonly ["1", "2", "3", "4", "5", "6", "7"]

The columns, as a move names them.

function columnAt

columnAt(pile: KlondikePile): number

Which column, from 0, a pile a move names is.

const COLUMNS

COLUMNS: 7

How many columns the tableau has.

function dealKlondike

dealKlondike(deck: readonly number[], rules: KlondikeRules): KlondikeTable

Deal a deck (card numbers, the first dealt first) into a table: column by column along each row, as a dealer does.

function dealOf

dealOf(deck: readonly number[]): string

A deal string from card numbers.

function dealOfSeed

dealOfSeed(seed: number): string

The deal of a seed: the site's one shuffle (shuffledDeck), written down.

function deckOf

deckOf(deal: string): number[] | null

The deck a deal string names, as card numbers, or null for one that is not a whole deck.

function decodeMoves

decodeMoves(code: string): KlondikeMove[] | null

A move list read back, or null for one with a character that is no move.

const DRAW_CODE

DRAW_CODE: "d"

The letter a draw from the stock is written as, in a move list.

function encodeMove

encodeMove(move: KlondikeMove): string

One move written as text: one or two letters.

function encodeMoves

encodeMoves(moves: readonly KlondikeMove[]): string

A list of moves written as text, one after another with nothing between.

function finishingMoves

finishingMoves(table: KlondikeTable): KlondikeMove[] | null

THE GAME PLAYS ITSELF OUT once every card on the columns is face up: the moves that bring the rest home, found by the solver on a small budget (a table with nothing hidden is solved in a handful of tables), or null while a card is still face down or no way home is found — then the player goes on.

function fitsColumn

fitsColumn(card: number, column: KlondikeColumn): boolean

Whether a card may go onto a column: onto a card one higher of the other colour, or a King onto an empty one.

function fitsFoundation

fitsFoundation(card: number, foundation: readonly number[]): boolean

Whether a card may go onto its suit's foundation now.

const FOUNDATION_PILES

FOUNDATION_PILES: readonly ["S", "H", "D", "C"]

The foundations' letters, in SUITS order (spades, hearts, diamonds, clubs), as a card id writes a suit.

function foundationAt

foundationAt(pile: KlondikePile): number

Which foundation, from 0 in SUITS order, a pile a move names is.

function foundationPileOf

foundationPileOf(card: number): KlondikePile

The foundation a card goes to.

function homeMove

homeMove(table: KlondikeTable, spot: TableSpot): KlondikeMove | null

The move that sends a top card home, if it can go: what a double tap does.

function isColumnPile

isColumnPile(pile: KlondikePile): boolean

Whether a pile a move names is a column.

function isFoundationPile

isFoundationPile(pile: KlondikePile): boolean

Whether a pile a move names is a suit's foundation.

function isSafeHome

isSafeHome: (card: number, foundation: readonly number[]) => boolean

Whether a card is one the solver treats as home safely: exported for its test.

type KlondikeColumn

type KlondikeColumn = { down: number; cards: readonly number[] };

One of the seven columns: its cards from the bottom up, of which the first down lie face down.

type KlondikeMove

type KlondikeMove = { kind: "draw" } | { kind: "recycle" } | { kind: "carry"; from: KlondikePile; to: KlondikePile };

One move: turn cards from the stock (draw), turn the waste back over (recycle), or carry cards from one pile to another. Which cards a carry takes is never written, because the rules decide it: one card from the waste or a foundation or to a foundation, and from column to column the one run whose foot fits where it lands — a column's face-up cards are always a single run, so there is only ever one.

type KlondikePile

type KlondikePile = "s" | "w" | "S" | "H" | "D" | "C" | "1" | "2" | "3" | "4" | "5" | "6" | "7";

A place on the table a move names: the stock s, the waste w, a suit's foundation by its letter (S H D C, as a card id writes a suit), or a column 1–7.

type KlondikeRules

type KlondikeRules = { draw: 1 | 3; passes: number };

How the game was set up: cards turned from the stock at a time, and how many times through the stock (Infinity for no limit).

type KlondikeTable

type KlondikeTable = { rules: KlondikeRules; /** Seven columns. */ tableau: readonly KlondikeColumn[]; /** Face down; the top card, the next to turn, is the LAST. */ stock: readonly number[]; /** Face up beside the stock; the top card, the one that can be played, is the LAST. */ waste: readonly number[]; /** How many cards are home on each suit's foundation, in `SUITS` order: its top card is that rank. */ foundation…

A game of Klondike as it stands: every pile, and the rules it is played under.

function klondikeWon

klondikeWon(table: KlondikeTable): boolean

Whether every card is home.

function liftable

liftable(table: KlondikeTable, spot: TableSpot): readonly number[]

The cards a person can take hold of from a spot, the one held and every one on it, or none: the waste's top card, a foundation's top card, or any face-up card of a column with the run on top of it.

function moveFor

moveFor(table: KlondikeTable, from: TableSpot, to: KlondikePile): KlondikeMove | null

The move that carries the cards held at from to to, if the rules allow exactly those cards there. A column gives up the one run that fits, so the card held must be that run's foot: holding the 7 of a 9-8-7 and letting it go on an 8 carries the 7, never the 9 that happens to fit somewhere.

function movesFrom

movesFrom(table: KlondikeTable): KlondikeMove[]

Every move the rules allow from this table, in a fixed order: to the foundations first, then onto the columns, then the stock. The table's own list, for a solver and a stuck-game check; a person's move is checked by playKlondike alone.

function pileCards

pileCards(table: KlondikeTable, pile: KlondikePile): readonly number[]

The cards of a pile, bottom first, as the table holds them.

function playKlondike

playKlondike(table: KlondikeTable, move: KlondikeMove): KlondikeTable | null

The table after a move, or null where the rules refuse it.

function rankOf

rankOf: (card: number) => number

A card's rank, 1 for an ace to 13 for a king.

const RANKS_A_SUIT

RANKS_A_SUIT: 13

How many ranks a suit has, ace to king.

const RECYCLE_CODE

RECYCLE_CODE: "r"

The letter turning the waste back over is written as, in a move list.

function recyclesLeft

recyclesLeft(table: KlondikeTable): number

How many times the waste may still be turned back: passes less one, less those already made.

function redCard

redCard: (card: number) => boolean

Hearts and diamonds, the second and third suits.

function replay

replay(deal: string, rules: KlondikeRules, moves: string): KlondikeTable[] | null

A game played out from its deal: every table it passed through, the first the deal, or null where the deal is not a deck or a move is one the rules refuse. The one reader of a move list, for the table, a kept run, the check and a finished game's page alike.

function solveKlondike

solveKlondike(start: KlondikeTable, budget?: number): { moves: KlondikeMove[] | null; tables: number; }

A winning line from this table — every move, the safe ones home included, that a replay of it makes — or null when none was found within budget tables. budget is a count, never a time, so every browser agrees.

function stockMove

stockMove(table: KlondikeTable): KlondikeMove | null

What a press on the stock does: turn it, or turn the waste back over, or nothing once the passes are spent.

function stuck

stuck(table: KlondikeTable): boolean

Whether nothing can be done but give up: no card can move anywhere, and the stock can be neither turned nor turned back. With passes left a person may still turn the stock for a card they need, so this is only ever said of the one table where it is certainly true.

function suitOf

suitOf: (card: number) => number

A card's suit, 0 to 3 in SUITS order.

type TableSpot

type TableSpot = { pile: KlondikePile; index: number };

A card on the table, as a person points at it: the pile, and its place from the bottom (-1 for the pile's empty place).

@johnmorrisdotca/toranpu/freecell

bestFirst BestFirstGame CELL_PILES cellAt COLUMN_PILES columnAt COLUMNS countOnto dealFreeCell dealOfSeed deckOf decodeMoves encodeMove encodeMoves fitsFoundation follows FOUNDATION_PILES foundationAt foundationPileOf FREECELL_PILES freeCellFinishingMoves freeCellHomeMove freeCellLiftable FreeCellMove freeCellMoveFor FreeCellPile freeCellPileCards FreeCellSpot freeCellStuck FreeCellTable freeCellWon isCellPile isColumnPile isFoundationPile mostCarried movesFrom playFreeCell rankOf RANKS_A_SUIT redCard replayFreeCell runLength solveFreeCell suitOf topOf

function bestFirst

bestFirst<Table, Move>(game: BestFirstGame<Table, Move>, start: Table, budget: number): { moves: Move[] | null; tables: number; }

A winning line from this table — every move a replay of it makes, the settling ones included — or null when none was found within budget tables.

type BestFirstGame

type BestFirstGame<Table, Move> = { candidates: (table: Table) => Move[]; play: (table: Table, move: Move) => Table | null; settle?: (table: Table) => { table: Table; moves: Move[] }; won: (table: Table) => boolean; key: (table: Table) => string; distance: (table: Table) => number; };

A BEST-FIRST SEARCH for a card game played alone, shared by FreeCell's solver and Spider's (freecell/solve.ts, spider/solve.ts).

The table that looks nearest to won is opened next (distance, the lower the nearer), ties in the order they were found, and every table is opened once (key). It gives up after a fixed number of tables, never after a fixed time, so the same deal gets the same answer on a phone, a desk and the server — which is what lets a seed name "the first winnable deal from here" in every browser alike.

settle is what a game does by itself after every move (FreeCell's safe cards sent home): its moves are part of the line, so a replay of the line makes exactly the tables the search saw.

const CELL_PILES

CELL_PILES: readonly ["a", "b", "c", "d"]

The free cells, as a move names them: as many as the game is played with, from the first.

function cellAt

cellAt(pile: FreeCellPile): number

Which free cell, from 0, a pile a move names is.

const COLUMN_PILES

COLUMN_PILES: readonly ["1", "2", "3", "4", "5", "6", "7", "8"]

The columns, as a move names them.

function columnAt

columnAt(pile: FreeCellPile): number

Which column, from 0, a pile a move names is.

const COLUMNS

COLUMNS: 8

How many columns the tableau has.

function countOnto

countOnto(table: FreeCellTable, from: FreeCellPile, to: FreeCellPile): number | null

How many cards a carry from one column to another takes, where it is not the player's to say: onto a card, the one count whose foot fits it. Into an empty column any count up to the most would do, so none is chosen here.

function dealFreeCell

dealFreeCell(deck: readonly number[], cells: number): FreeCellTable

Deal a deck (card numbers, the first dealt first) into a table with so many free cells: one card to each column in turn.

function dealOfSeed

dealOfSeed(seed: number): string

The deal of a seed: the site's one shuffle (shuffledDeck), written down.

function deckOf

deckOf(deal: string): number[] | null

The deck a deal string names, as card numbers, or null for one that is not a whole deck.

function decodeMoves

decodeMoves(code: string): FreeCellMove[] | null

A move list read back, or null for one with a character that is no move.

function encodeMove

encodeMove(move: FreeCellMove): string

One move written as text: one or two letters.

function encodeMoves

encodeMoves(moves: readonly FreeCellMove[]): string

A list of moves written as text, one after another with nothing between.

function fitsFoundation

fitsFoundation(card: number, foundation: readonly number[]): boolean

Whether a card may go onto its suit's foundation now.

function follows

follows(lower: number, upper: number): boolean

Whether upper may lie on lower in a column: one lower, of the other colour.

const FOUNDATION_PILES

FOUNDATION_PILES: readonly ["S", "H", "D", "C"]

The foundations' letters, in SUITS order (spades, hearts, diamonds, clubs), as a card id writes a suit.

function foundationAt

foundationAt(pile: FreeCellPile): number

Which foundation, from 0 in SUITS order, a pile a move names is.

function foundationPileOf

foundationPileOf(card: number): FreeCellPile

The foundation a card goes to.

const FREECELL_PILES

FREECELL_PILES: ReadonlySet<string>

Every pile's letter, for a move written down to be read back.

function freeCellFinishingMoves

freeCellFinishingMoves(table: FreeCellTable): FreeCellMove[] | null

THE GAME PLAYS ITSELF OUT once nothing is left to decide: when every card still out can go home in turn, the lowest first, the moves that take them there — or null while one cannot, and the player goes on.

function freeCellHomeMove

freeCellHomeMove(table: FreeCellTable, spot: FreeCellSpot): FreeCellMove | null

The move that sends a top card home, if it can go: what a double tap does.

function freeCellLiftable

freeCellLiftable(table: FreeCellTable, spot: FreeCellSpot): readonly number[]

The cards a person can take hold of from a spot, the one held and every one on it, or none: a free cell's card, or a column's card with the run in order on top of it. A foundation's cards stay home.

type FreeCellMove

type FreeCellMove = { from: FreeCellPile; to: FreeCellPile; count: number };

One move: count cards carried from one pile to another. Only a column to a column ever carries more than one, and then the cards are the top of the column, in order, and as many as the free cells and empty columns could carry one at a time (mostCarried).

function freeCellMoveFor

freeCellMoveFor(table: FreeCellTable, from: FreeCellSpot, to: FreeCellPile): FreeCellMove | null

The move that carries the cards held at from to to, if the rules allow exactly those cards there.

type FreeCellPile

type FreeCellPile = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "a" | "b" | "c" | "d" | "S" | "H" | "D" | "C";

A place on the table a move names: a column 1–8, a free cell a–d, or a suit's foundation by its letter (S H D C, as a card id writes a suit).

function freeCellPileCards

freeCellPileCards(table: FreeCellTable, pile: FreeCellPile): readonly number[]

The cards of a pile, bottom first, as the table holds them.

type FreeCellSpot

type FreeCellSpot = { pile: FreeCellPile; index: number };

A card on the table, as a person points at it: the pile, and its place from the bottom (-1 for the pile's empty place).

function freeCellStuck

freeCellStuck(table: FreeCellTable): boolean

Whether nothing can be done but give up: no card can move anywhere.

type FreeCellTable

type FreeCellTable = { /** Eight columns, each its cards from the bottom up. Every card is face up. */ tableau: readonly (readonly number[])[]; /** The free cells, one card or none each: four in the classic game, fewer to make it harder. */ cells: readonly (number | null)[]; /** How many cards are home on each suit's foundation, in `SUITS` order: its top card is that rank. */ foundation: readonly number[]; };

A game of FreeCell as it stands: the columns, the free cells and the foundations.

function freeCellWon

freeCellWon(table: FreeCellTable): boolean

Whether every card is home.

function isCellPile

isCellPile(pile: FreeCellPile): boolean

Whether a pile a move names is a free cell.

function isColumnPile

isColumnPile(pile: FreeCellPile): boolean

Whether a pile a move names is a column.

function isFoundationPile

isFoundationPile(pile: FreeCellPile): boolean

Whether a pile a move names is a suit's foundation.

function mostCarried

mostCarried(table: FreeCellTable, toEmptyColumn: boolean): number

The most cards a carry may take, as though moved one at a time: one more than the empty free cells, doubled for every empty column but the one the cards are going to.

function movesFrom

movesFrom(table: FreeCellTable): FreeCellMove[]

Every move the rules allow from a table, one of each (the longest run into an empty column), for a stuck-game check.

function playFreeCell

playFreeCell(table: FreeCellTable, move: FreeCellMove): FreeCellTable | null

The table after a move, or null where the rules refuse it.

function rankOf

rankOf: (card: number) => number

A card's rank, 1 for an ace to 13 for a king.

const RANKS_A_SUIT

RANKS_A_SUIT: 13

How many ranks a suit has, ace to king.

function redCard

redCard: (card: number) => boolean

Hearts and diamonds, the second and third suits.

function replayFreeCell

replayFreeCell(deal: string, cells: number, moves: string): FreeCellTable[] | null

A game played out from its deal: every table it passed through, the first the deal, or null where the deal is not a deck or a move is one the rules refuse. The one reader of a move list, for the table, a kept run, the check and a finished game's page alike.

function runLength

runLength(column: readonly number[]): number

How many cards on top of a column are in order, each on the one under it: the most a single carry could ever take.

function solveFreeCell

solveFreeCell(start: FreeCellTable, budget?: number): { moves: FreeCellMove[] | null; tables: number; }

A winning line from this table — every move a replay of it makes, the safe ones home included — or null when none was found within budget tables. Best first (bestFirst): the table that looks nearest to won is opened next.

function suitOf

suitOf: (card: number) => number

A card's suit, 0 to 3 in SUITS order.

function topOf

topOf(table: FreeCellTable, pile: FreeCellPile): number | undefined

The top card of a pile, or undefined where it holds none (a foundation's top is its suit at its count).

@johnmorrisdotca/toranpu/spider

allShowing bestFirst BestFirstGame canDeal COLUMNS countOnto DEAL_CODE dealSpider DEALT DECK_SIZE decodeMoves encodeMove encodeMoves movesFrom playSpider rankOf RANKS_A_SUIT replaySpider runLength RUNS_TO_WIN solveSpider spiderBestMove SpiderColumn spiderColumnOf spiderDealMove spiderDealOfSeed spiderDeck spiderDeckOf spiderFinishingMoves spiderLiftable SpiderMove spiderMoveFor SpiderSpot spiderStuck SpiderTable spiderWon suitOf suitsFor

function allShowing

allShowing(table: SpiderTable): boolean

Whether every card is dealt and face up: from here the game plays itself out, where it can (finishingMoves).

function bestFirst

bestFirst<Table, Move>(game: BestFirstGame<Table, Move>, start: Table, budget: number): { moves: Move[] | null; tables: number; }

A winning line from this table — every move a replay of it makes, the settling ones included — or null when none was found within budget tables.

type BestFirstGame

type BestFirstGame<Table, Move> = { candidates: (table: Table) => Move[]; play: (table: Table, move: Move) => Table | null; settle?: (table: Table) => { table: Table; moves: Move[] }; won: (table: Table) => boolean; key: (table: Table) => string; distance: (table: Table) => number; };

A BEST-FIRST SEARCH for a card game played alone, shared by FreeCell's solver and Spider's (freecell/solve.ts, spider/solve.ts).

The table that looks nearest to won is opened next (distance, the lower the nearer), ties in the order they were found, and every table is opened once (key). It gives up after a fixed number of tables, never after a fixed time, so the same deal gets the same answer on a phone, a desk and the server — which is what lets a seed name "the first winnable deal from here" in every browser alike.

settle is what a game does by itself after every move (FreeCell's safe cards sent home): its moves are part of the line, so a replay of the line makes exactly the tables the search saw.

function canDeal

canDeal(table: SpiderTable): boolean

Whether the stock can deal now: cards left, and no column empty.

const COLUMNS

COLUMNS: 10

How many columns the tableau has.

function countOnto

countOnto(table: SpiderTable, from: number, to: number): number | null

How many cards a carry from one column onto another takes, where it is not the player's to say: onto a card, the one count whose foot is one lower. Into an empty column any count up to the run would do, so none is chosen here.

const DEAL_CODE

DEAL_CODE: "d"

The letter dealing a card to every column is written as, in a move list.

function dealSpider

dealSpider(deck: readonly number[]): SpiderTable

Deal a shuffled deck (card numbers, the first dealt first) into a table.

const DEALT

DEALT: 54

Cards dealt to the columns before play; the rest are the stock.

const DECK_SIZE

DECK_SIZE: 104

How many cards two decks hold, which Spider is played with at any number of suits.

function decodeMoves

decodeMoves(code: string): SpiderMove[] | null

A move list read back, or null for one with a character that is no move.

function encodeMove

encodeMove(move: SpiderMove): string

One move written as text: one or two letters.

function encodeMoves

encodeMoves(moves: readonly SpiderMove[]): string

A list of moves written as text, one after another with nothing between.

function movesFrom

movesFrom(table: SpiderTable): SpiderMove[]

Every move the rules allow from a table, one of each (the whole run into an empty column), for a stuck-game check.

function playSpider

playSpider(table: SpiderTable, move: SpiderMove): SpiderTable | null

The table after a move, or null where the rules refuse it.

function rankOf

rankOf: (card: number) => number

A card's rank, 1 for an ace to 13 for a king.

const RANKS_A_SUIT

RANKS_A_SUIT: 13

How many ranks a suit has, ace to king.

function replaySpider

replaySpider(deal: string, suits: number, moves: string): SpiderTable[] | null

A game played out from its deal: every table it passed through, the first the deal, or null where the deal is not two decks or a move is one the rules refuse. The one reader of a move list, for the table, a kept run, the check and a finished game's page alike.

function runLength

runLength(column: SpiderColumn): number

How many cards on top of a column are one suit and in order, all face up: the most a carry can take.

const RUNS_TO_WIN

RUNS_TO_WIN: 8

Full runs to make: two decks of four suits.

function solveSpider

solveSpider(start: SpiderTable, budget?: number): { moves: SpiderMove[] | null; tables: number; }

A winning line from this table, or null when none was found within budget tables.

function spiderBestMove

spiderBestMove(table: SpiderTable, spot: SpiderSpot): SpiderMove | null

What a double tap does in Spider, where no card goes home by itself: the run carried to the best column that takes it — one whose top is its own suit, else any card one higher, else an empty column.

type SpiderColumn

type SpiderColumn = { down: number; cards: readonly number[] };

One of the ten columns: its cards from the bottom up, of which the first down lie face down.

function spiderColumnOf

spiderColumnOf(pile: string): number | null

The column a pile names, or null for the stock and the made runs.

function spiderDealMove

spiderDealMove(table: SpiderTable): SpiderMove | null

What a press on the stock does: deal, or nothing while a column is empty or the stock is spent.

function spiderDealOfSeed

spiderDealOfSeed(seed: number, suits: number): string

The deal of a seed at so many suits: the site's one shuffle (shuffled) of both decks, written down.

function spiderDeck

spiderDeck(suits: number): number[]

The hundred and four cards of a game of so many suits, sorted: each suit's thirteen, as many times as fill two decks.

function spiderDeckOf

spiderDeckOf(deal: string, suits: number): number[] | null

The cards a deal string names, or null for one that is not both decks of a game of so many suits.

function spiderFinishingMoves

spiderFinishingMoves(table: SpiderTable): SpiderMove[] | null

THE GAME PLAYS ITSELF OUT once every card is dealt and face up: the moves that make the runs left, found by the solver on a small budget, or null while a card is hidden or no way is found — then the player goes on.

function spiderLiftable

spiderLiftable(table: SpiderTable, spot: SpiderSpot): readonly number[]

The cards a person can take hold of from a spot: a face-up card with the run of its own suit, in order, on top of it.

type SpiderMove

type SpiderMove = { kind: "deal" } | { kind: "carry"; from: number; to: number; count: number };

One move: deal a card from the stock onto every column (deal), or carry count cards from the top of one column onto another. Columns are named 0 to 9, from the left.

function spiderMoveFor

spiderMoveFor(table: SpiderTable, from: SpiderSpot, to: string): SpiderMove | null

The move that carries the cards held at from onto the column to, if the rules allow exactly those cards there.

type SpiderSpot

type SpiderSpot = { pile: string; index: number };

A place a tap lands on the table: the pile, and which card in it from the bottom.

function spiderStuck

spiderStuck(table: SpiderTable): boolean

Whether nothing can be done but give up: no card can move and the stock cannot deal.

type SpiderTable

type SpiderTable = { /** Ten columns. */ tableau: readonly SpiderColumn[]; /** The cards still to be dealt, ten at a time, in the order they will be dealt. */ stock: readonly number[]; /** The suit of each full run taken off the table, King down to Ace, in the order they were made: eight is a won game. */ done: readonly number[]; };

A game of Spider as it stands: the columns, the cards still to deal, and the runs sent home.

function spiderWon

spiderWon(table: SpiderTable): boolean

Whether all eight runs are made.

function suitOf

suitOf: (card: number) => number

A card's suit, 0 to 3 in SUITS order.

function suitsFor

suitsFor(suits: number): number[]

The suits a game of so many suits is played with, in SUITS order: spades; spades and hearts; all four.