Tenka天下

@johnmorrisdotca/tenka 1.2.1 · 4 entry points · 152 exports

@johnmorrisdotca/tenka

areNeighbours armiesHeld attacksOpen battleLosses beginTurn boardOf cardKind cardTerritory choiceNow cleanTenkaName connectedOwn continentNameIn continentsHeld counted decodeTenka defendDice encodeTenka endOfTurn finished isSet isTenkaSeed isTenkaTable isTerritory isWild marksFor mostAttackDice mustTrade nextRandom nextSeatIn NO_CHOICE playTenka randomBelow readTenkaMove reinforcementFor replayTenka sensibleTenkaMove setsIn shuffled startTenka tapTerritory TENKA_ATTACK_DICE TENKA_CARD_KINDS TENKA_CONTINENTS TENKA_CSV_COLUMNS TENKA_DECK TENKA_DEFEND_DICE TENKA_EXPORT_FORMAT TENKA_FEWEST_PLAYERS TENKA_LEAST_REINFORCEMENT TENKA_LENGTHS TENKA_MAP_LIST TENKA_MAPS TENKA_MEDIUM_ROUNDS TENKA_MOST_PLAYERS TENKA_MOVES TENKA_MUST_TRADE_AT TENKA_NAME_MOST TENKA_NEUTRAL TENKA_PHASES TENKA_PLACING TENKA_SEED_MOST TENKA_SHORT_ROUNDS TENKA_STARTING_ARMIES TENKA_STRINGS TENKA_TERRITORIES TENKA_TERRITORIES_PER_ARMY TENKA_TERRITORY_CARD_BONUS TENKA_TERRITORY_COUNT TENKA_TRADE_STEP TENKA_TRADE_VALUES TENKA_VERSION TENKA_WILD TENKA_WILD_CARDS TENKA_WORLD_ROUNDS tenkaAgain TenkaCard TenkaCardKind TenkaChoice tenkaContinent TenkaContinent TenkaContinentKey tenkaDeckFor TenkaEuropeRegionKey tenkaExported TenkaExported tenkaFromJSON TenkaGame TenkaLocale TenkaMap TenkaMapKey TenkaMapMarks tenkaMapOf TenkaMove TenkaMoveKind tenkaMoves tenkaNeighbours tenkaOver TenkaOwner TenkaPhase TenkaPlacing tenkaPlayerName tenkaRecord TenkaRecordEntry TenkaRoll tenkaSay TenkaSeat TenkaShapes tenkaStrings TenkaStrings TenkaTerritoryData tenkaToCSV tenkaToJSON tenkaToText TenkaTrade TenkaWorldContinentKey territoriesHeld territoryNameIn throwDice tradeValue writeTenkaMove

function areNeighbours

areNeighbours(a: number, b: number, map?: TenkaMap): boolean

Whether two territories are neighbours, by land or by sea.

function armiesHeld

armiesHeld(game: Pick<TenkaGame, "owners" | "armies">, owner: TenkaOwner): number

How many armies an owner has on the map.

function attacksOpen

attacksOpen(game: TenkaGame): TenkaMove[]

Every attack open now: from each territory of theirs with armies to spare, on each neighbour somebody else holds, with each number of dice — and the same attack again and again until it is decided.

function battleLosses

battleLosses(attack: readonly number[], defend: readonly number[]): { attackerLost: number; defenderLost: number; }

WHO LOSES WHAT: the highest die of each side compared, then the next highest, as many pairs as the side with fewer dice threw. The higher die wins its pair; a tie goes to the defender. Each lost pair is one army. Both lists are highest first.

function beginTurn

beginTurn(game: TenkaGame, seat: TenkaSeat, round: number): TenkaGame

A turn starting for seat: its armies worked out, nothing yet taken, nothing rolled.

function boardOf

boardOf(map: unknown): TenkaMap

The map an argument names, and the world for anything that is not one: these functions are handed to an array's map and filter (deck.map(cardKind)), which pass an index where the map would go.

function cardKind

cardKind(card: TenkaCard, map?: TenkaMap): TenkaCardKind

What a card shows: land, sea or air for a territory's card, in turn round the map; wild for a wild card.

function cardTerritory

cardTerritory(card: TenkaCard, map?: TenkaMap): number | null

The territory a card shows, or null for a wild card.

function choiceNow

choiceNow(game: TenkaGame, choice: TenkaChoice): TenkaChoice

A choice made on an earlier map, read against this one: only what still stands, and a number of armies the move can take.

function cleanTenkaName

cleanTenkaName(name: string): string

A name as the table typed it, tidied: spaces run together, trimmed, cut to TENKA_NAME_MOST.

function connectedOwn

connectedOwn(owners: readonly TenkaOwner[], from: number, map?: TenkaMap): number[]

Every territory of the same owner reachable from this one through that owner's own territories, in order; never the territory itself.

function continentNameIn

continentNameIn(strings: TenkaStrings, key: string): string

A continent's name in a table of strings, by its key ("northAmerica", "asia"…).

function continentsHeld

continentsHeld(owners: readonly TenkaOwner[], seat: TenkaOwner, map?: TenkaMap): TenkaContinent[]

The continents a seat holds every territory of.

function counted

counted(game: TenkaGame): TenkaGame

THE COUNT, when the last round is over: whoever holds the most territories wins; level on territories, whoever has more armies on the map; level on both, they share the win.

function decodeTenka

decodeTenka(text: string | null): TenkaGame | null

A kept game read back, or null for nothing kept, or for text that is not a game these rules can play out again: a browser's storage is somebody's to edit, and a half-understood game is worse than none.

function defendDice

defendDice(armies: number): number

The dice a defender holding armies throws: as many as allowed, since more never hurts the defence.

function encodeTenka

encodeTenka(game: TenkaGame): string

A game as text to keep: its table and its moves.

function endOfTurn

endOfTurn(game: TenkaGame): TenkaGame

THE END OF A TURN: a card for a player who took a territory in it, then the next player still in. Passing the first player's place starts a new round, and past the last round the game is counted.

function finished

finished(game: TenkaGame, winners: readonly TenkaSeat[]): TenkaGame

The game ended, with these seats winning.

function isSet

isSet(cards: readonly TenkaCard[], map?: TenkaMap): boolean

Whether three cards make a set: three of one kind, one of each kind, or any two with a wild card (a wild card stands for whatever the set needs).

function isTenkaSeed

isTenkaSeed(seed: number): boolean

Whether a number is a seed a game can be dealt from: a whole number from 0 to TENKA_SEED_MOST.

function isTenkaTable

isTenkaTable(rounds: number, count: number): boolean

Whether a game this long for this many players is one Tenka is offered for.

function isTerritory

isTerritory(territory: number, map?: TenkaMap): boolean

Whether a number is a territory of the map.

function isWild

isWild(card: TenkaCard, map?: TenkaMap): boolean

Whether a card is one of the two wild cards.

function marksFor

marksFor(game: TenkaGame, choice: TenkaChoice): TenkaMapMarks

What the map lights up for a choice.

function mostAttackDice

mostAttackDice(armies: number): number

The most dice an attacker may throw from a territory holding armies: one army always stays behind.

function mustTrade

mustTrade(game: TenkaGame): boolean

Whether the player to move must trade cards before anything else: five or more in hand while reinforcing.

function nextRandom

nextRandom(state: number): { value: number; state: number; }

The next number from the random at state, in [0, 1), and the state after it.

function nextSeatIn

nextSeatIn(game: TenkaGame, seat: TenkaSeat): TenkaSeat

The next player still in after seat, round the table; the neutral army never takes a turn.

const NO_CHOICE

NO_CHOICE: TenkaChoice

Nothing chosen: where a turn, and a table, start.

function playTenka

playTenka(game: TenkaGame, move: TenkaMove): TenkaGame | null

The game after the player to move makes move, or null when they may not: a move for another phase, a territory not theirs, a neighbour that is not one, dice they have not the armies for, a set that is not a set.

function randomBelow

randomBelow(state: number, below: number): { value: number; state: number; }

A whole number from 0 to below - 1, and the state after it.

function readTenkaMove

readTenkaMove(kept: unknown): TenkaMove | null

A kept move read back, or null for anything that is not one.

function reinforcementFor

reinforcementFor(owners: readonly TenkaOwner[], seat: TenkaSeat, map?: TenkaMap): number

THE ARMIES A TURN BRINGS: one for every three territories held, never fewer than three, and each continent held whole adds its bonus. Cards traded in come on top (tenka.ts).

function replayTenka

replayTenka(table: { rounds: number; players: readonly string[]; seed: number; placing: TenkaPlacing; map?: TenkaMapKey; }, moves: readonly TenkaMove[]): TenkaGame | null

A game made again from its table and its moves, or null if any move could not have been made when it was.

function sensibleTenkaMove

sensibleTenkaMove(game: TenkaGame, random: (): number) => TenkaMove

The computer player's move: one the rules accept, chosen as a person might. random is any function returning a number from 0 up to 1, such as Math.random; give it a seeded one and the computer plays the same game every time.

function setsIn

setsIn(hand: readonly TenkaCard[], map?: TenkaMap): TenkaCard[][]

Every set a hand holds, each as its three cards smallest first, in order.

function shuffled

shuffled<T>(state: number, items: readonly T[]): { value: T[]; state: number; }

A list in a random order (Fisher and Yates), and the state after it; the list given is left alone.

function startTenka

startTenka(rounds: number, players: readonly string[], seed: number, placing?: TenkaPlacing, map?: TenkaMapKey): TenkaGame | null

A NEW GAME, all of it drawn from seed: who goes first; the territories shuffled and dealt round the table one at a time from that player, each with one army on it (at a table of two, dealt three ways, the neutral army taking every third); the cards shuffled; and each player's starting armies (TENKA_STARTING_ARMIES) less the ones already on their territories — scattered at random when placing is auto, or left to be placed by hand, one at a time round the table. The neutral army's are always scattered.

Null for a table the game is not offered for, or a seed that is not one.

function tapTerritory

tapTerritory(game: TenkaGame, choice: TenkaChoice, territory: number): { choice: TenkaChoice; move: TenkaMove | null; }

What tapping territory does now: a new choice, and the move to make at once, if the tap is one.

const TENKA_ATTACK_DICE

TENKA_ATTACK_DICE: 3

The most dice an attacker throws.

const TENKA_CARD_KINDS

TENKA_CARD_KINDS: readonly TenkaCardKind[]

The three kinds a territory's card shows, in turn round the map, and the wild card that is any of them.

const TENKA_CONTINENTS

TENKA_CONTINENTS: readonly TenkaContinent[]

The world's six continents, each with the territories in it.

const TENKA_CSV_COLUMNS

TENKA_CSV_COLUMNS: readonly string[]

The columns of the CSV export, in order.

const TENKA_DECK

TENKA_DECK: readonly number[]

Every card in the game: the territories' cards, then the wild cards.

const TENKA_DEFEND_DICE

TENKA_DEFEND_DICE: 2

The most dice a defender throws.

const TENKA_EXPORT_FORMAT

TENKA_EXPORT_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.

const TENKA_FEWEST_PLAYERS

TENKA_FEWEST_PLAYERS: 2

The fewest at a table: two, joined by the neutral army.

const TENKA_LEAST_REINFORCEMENT

TENKA_LEAST_REINFORCEMENT: 3

The fewest armies a turn brings, however little is held.

const TENKA_LENGTHS

TENKA_LENGTHS: readonly number[]

The lengths of game startTenka takes, in rounds: 10, 20 and 60.

const TENKA_MAP_LIST

TENKA_MAP_LIST: readonly TenkaMapKey[]

The maps in the order a set-up offers them: the world first, the usual one.

const TENKA_MAPS

TENKA_MAPS: Readonly<Record<TenkaMapKey, TenkaMap>>

Every map a game may be played on, by key.

const TENKA_MEDIUM_ROUNDS

TENKA_MEDIUM_ROUNDS: 20

The medium game: twenty rounds, then the count.

const TENKA_MOST_PLAYERS

TENKA_MOST_PLAYERS: 6

The most at a table: six.

const TENKA_MOVES

TENKA_MOVES: { readonly place: "place"; readonly trade: "trade"; readonly attack: "attack"; readonly blitz: "blitz"; readonly occupy: "occupy"; readonly endAttack: "endAttack"; readonly fortify: "fortify"; readonly shift: "shift"; readonly endTurn: "endTurn"; }

The kinds of move, by name: what goes in a move's kind.

const TENKA_MUST_TRADE_AT

TENKA_MUST_TRADE_AT: 5

With this many cards in hand a player must trade before placing; after knocking somebody out, down to fewer than this.

const TENKA_NAME_MOST

TENKA_NAME_MOST: 20

The longest name a seat keeps.

const TENKA_NEUTRAL

TENKA_NEUTRAL: -1

The owner of the territories nobody at a table of two holds: the neutral army, which never takes a turn.

const TENKA_PHASES

TENKA_PHASES: { readonly setUp: "setUp"; readonly reinforce: "reinforce"; readonly attack: "attack"; readonly occupy: "occupy"; readonly fortify: "fortify"; readonly shift: "shift"; readonly over: "over"; }

The phases of a turn, by name: compare game.phase with these rather than with text typed out.

const TENKA_PLACING

TENKA_PLACING: { readonly auto: "auto"; readonly hand: "hand"; }

How the starting armies are placed: scattered at random (auto), or by the players in turn (hand).

const TENKA_SEED_MOST

TENKA_SEED_MOST: 4294967295

The largest seed a game keeps: the random's state is one 32-bit number.

const TENKA_SHORT_ROUNDS

TENKA_SHORT_ROUNDS: 10

The short game: ten rounds, then the count.

const TENKA_STARTING_ARMIES

TENKA_STARTING_ARMIES: Readonly<Record<number, number>>

THE ARMIES EACH PLAYER STARTS WITH, by how many are playing: the standard table. Two players are joined by a neutral army with forty of its own, holding a third of the world, which never moves and only defends.

const TENKA_STRINGS

TENKA_STRINGS: Readonly<Record<TenkaLocale, TenkaStrings>>

The package's words in each language it speaks: TENKA_STRINGS.en, TENKA_STRINGS.ja.

const TENKA_TERRITORIES

TENKA_TERRITORIES: readonly TenkaTerritoryData[]

The world's territories: the map a game is played on unless it says otherwise.

const TENKA_TERRITORIES_PER_ARMY

TENKA_TERRITORIES_PER_ARMY: 3

How many territories held bring one army each turn: three.

const TENKA_TERRITORY_CARD_BONUS

TENKA_TERRITORY_CARD_BONUS: 2

The armies placed straight onto a territory the trader holds, when a card of the set shows it.

const TENKA_TERRITORY_COUNT

TENKA_TERRITORY_COUNT: number

How many territories the world has: forty-two.

const TENKA_TRADE_STEP

TENKA_TRADE_STEP: 5

How much more each set is worth than the last, once TENKA_TRADE_VALUES runs out: 20, 25, 30…

const TENKA_TRADE_VALUES

TENKA_TRADE_VALUES: readonly number[]

WHAT A SET OF CARDS IS WORTH, by how many sets anybody has traded in before it: 4, 6, 8, 10, 12, 15, and five more for every set after that — the escalating standard schedule. And two armies more, placed straight onto it, when a card in the set shows a territory the trader holds.

const TENKA_VERSION

TENKA_VERSION: "1.2.1"

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

const TENKA_WILD

TENKA_WILD: TenkaCardKind

The kind of a wild card, which stands for whatever a set needs.

const TENKA_WILD_CARDS

TENKA_WILD_CARDS: 2

How many wild cards are in the deck.

const TENKA_WORLD_ROUNDS

TENKA_WORLD_ROUNDS: 60

The whole world: played to the last player standing, and counted after sixty rounds if nobody is.

function tenkaAgain

tenkaAgain(game: TenkaGame, seed: number): TenkaGame | null

The same table again, from nothing, with a new seed: a new deal, a new first player, new dice.

type TenkaCard

type TenkaCard = number;

A territory's card (0–41) or a wild card (42, 43).

type TenkaCardKind

type TenkaCardKind = "land" | "sea" | "air" | "wild";

What a card shows: one of three kinds, or a wild card that stands for any of them.

type TenkaChoice

type TenkaChoice = { from: number | null; to: number | null; /** How many armies move, when there is a number to choose. */ armies: number; /** The last territory a reinforcing army was placed on, for "all the rest here". */ placedOn: number | null; };

A choice made on the map and not yet a move: where from, where to, and how many armies (tapTerritory).

function tenkaContinent

tenkaContinent(key: TenkaContinentKey, map?: TenkaMap): TenkaContinent

A continent of a map by its key.

type TenkaContinent

type TenkaContinent = { key: TenkaContinentKey; name: string; kanji: string; bonus: number; territories: readonly number[]; };

A continent: its name, the armies holding all of it is worth each turn, and its territories.

type TenkaContinentKey

type TenkaContinentKey = TenkaWorldContinentKey | TenkaEuropeRegionKey;

A continent of any map, by key: the world's continents and Europe's regions.

function tenkaDeckFor

tenkaDeckFor(map?: TenkaMap): TenkaCard[]

Every card of a game on this map: a card for each territory, numbered as the territories are, then the wild cards.

type TenkaEuropeRegionKey

type TenkaEuropeRegionKey = | "britishIsles" | "scandinavia" | "iberia" | "maghreb" | "france" | "centralEurope" | "italyBalkans" | "danube" | "easternEurope" | "russia" | "anatolia";

Europe's eleven regions, by key: the continents of the Europe map.

function tenkaExported

tenkaExported(game: TenkaGame): TenkaExported

A game as the JSON export's object: its table, its moves, and where it stands.

type TenkaExported

type TenkaExported = { /** The shape's number: `TENKA_EXPORT_FORMAT`. */ format: typeof TENKA_EXPORT_FORMAT; /** Always `"tenka"`, so a file of this shape is not mistaken for another game's. */ game: "tenka"; /** The package and version that wrote it, such as `"tenka 1.1.0"`. For people; never read back. */ generator: string; /** The seed every deal, shuffle and die is drawn from. */ seed: number; /** The names roun…

A whole JSON export of one game: what tenkaToJSON writes and tenkaFromJSON reads.

function tenkaFromJSON

tenkaFromJSON(text: string): TenkaGame | null

A game from JSON that tenkaToJSON wrote, or text that encodeTenka did. Nothing in it is trusted: the game is dealt again from the seed and every move is played through the rules, so what comes back is a game these rules made. Null when the text is not JSON, is of a later format than this version reads, is another game's, or holds a move that could not have been made when it was.

type TenkaGame

type TenkaGame = { /** The map it is played on; left out for the world, so a game saved before there was a choice reads as it did. */ map?: TenkaMapKey; /** The seed every shuffle, deal and die of this game is drawn from. */ seed: number; /** The names given at the table, in seat order: "" for one left blank. */ players: readonly string[]; /** How many rounds before the count: `TENKA_WORLD_ROUNDS` for the whole worl…

A game, as its moves make it. Only the table — seed, players, rounds, placing — and moves are ever kept (encodeTenka); everything else is read again from them, dice and all, since every die is drawn from the game's own seeded random (rng). A kept game can never hold a world its moves do not make.

type TenkaLocale

type TenkaLocale = "en" | "ja";

The languages the package speaks: English and Japanese.

type TenkaMap

type TenkaMap = { key: TenkaMapKey; name: string; kanji: string; territories: readonly TenkaTerritoryData[]; continents: readonly TenkaContinent[]; neighbours: readonly (readonly number[])[]; };

A map as the rules read it: its territories, its continents and each territory's neighbours by land and by sea.

type TenkaMapKey

type TenkaMapKey = "world" | "europe";

The maps a game may be played on.

type TenkaMapMarks

type TenkaMapMarks = { chosen: number | null; reach: readonly number[]; target: number | null; };

What the map lights up for a choice (marksFor): the territory chosen, what it can reach, and the target.

function tenkaMapOf

tenkaMapOf(game: Pick<TenkaGame, "map"> | TenkaMapKey | undefined): TenkaMap

The map a game is played on: the one it was started on, and the world for a game saved before there was a choice.

type TenkaMove

type TenkaMove = | { kind: "place"; territory: number; armies: number } | { kind: "trade"; cards: readonly TenkaCard[] } | { kind: "attack"; from: number; to: number; dice: number } /** Attack again and again with every die allowed, until the territory falls or only one army is left to attack with. */ | { kind: "blitz"; from: number; to: number } | { kind: "occupy"; armies: number } | { kind: "endAttack" } | { kind:…

A move, as playTenka takes it. Which kinds may be made depends on the phase: place and trade while reinforcing (place alone while setting up), attack, blitz and endAttack while attacking, occupy after taking a territory, fortify and endTurn while fortifying, and shift once a fortifying move is chosen. tenkaMoves(game) lists the ones open.

type TenkaMoveKind

type TenkaMoveKind = TenkaMove["kind"];

The kinds of move, by name.

function tenkaMoves

tenkaMoves(game: TenkaGame): TenkaMove[]

Every move the player to move may make now, each one playTenka accepts. Placing is listed as one army or all still waiting, on each territory held, since any spread is a run of those. None once the game is over.

function tenkaNeighbours

tenkaNeighbours(territory: number, map?: TenkaMap): readonly number[]

A territory's neighbours, by land and by sea.

function tenkaOver

tenkaOver(game: TenkaGame): boolean

Whether the game is over.

type TenkaOwner

type TenkaOwner = number;

Who holds a territory: a seat, or TENKA_NEUTRAL.

type TenkaPhase

type TenkaPhase = "setUp" | "reinforce" | "attack" | "occupy" | "fortify" | "shift" | "over";

Where a turn is. setUp is the placing of the starting armies by hand, one each round the table; reinforce the placing of a turn's new armies (and trading cards for more); attack rolling against a neighbour; occupy choosing how many move into a territory just taken; fortify choosing one move between two of your own connected territories, and shift how many armies it moves; over the end.

type TenkaPlacing

type TenkaPlacing = "auto" | "hand";

Starting armies placed at random, or by hand in turn.

function tenkaPlayerName

tenkaPlayerName(game: Pick<TenkaGame, "players">, seat: TenkaSeat): string

A seat's name as the table reads it: the one given, or "Player 3".

function tenkaRecord

tenkaRecord(game: TenkaGame): TenkaRecordEntry[]

THE RECORD OF A GAME, move by move, with the dice and everything else each move brought: made by playing the game again from its seed. A game whose moves do not replay from its table (one put together by hand, in a test) is recorded as far as they do.

type TenkaRecordEntry

type TenkaRecordEntry = { /** The move's number, from 1. */ n: number; /** The round it was made in. */ round: number; /** The seat that made it. */ seat: TenkaSeat; /** The move itself. */ move: TenkaMove; /** The dice of an attack: the one throw, or the last of a blitz with the losses of all of them. */ roll?: TenkaRoll; /** The set traded in, what it was worth, and the territory that got two more. */ trade?: Tenk…

One move of a game's record, with what came of it: what the text and the CSV are written from.

type TenkaRoll

type TenkaRoll = { from: number; to: number; attacker: TenkaSeat; defender: TenkaOwner; attackDice: readonly number[]; defendDice: readonly number[]; /** Armies lost, over every throw of the run. */ attackerLost: number; defenderLost: number; /** How many times the dice were thrown: one for a single attack. */ throws: number; /** Whether this roll took the territory. */ took: boolean; };

One roll of the dice — or the last of a run of them (blitz) — what each side threw, highest first, and what each lost in all.

function tenkaSay

tenkaSay(line: string, values?: Readonly<Record<string, string | number>>): string

A line from a table of strings with its braces filled in: tenkaSay(strings.moveIn, { n: 3 }) is "Move 3 in". A brace with no value is left as it is.

type TenkaSeat

type TenkaSeat = number;

A player's place round the table: 0 for the first name given, up to 5.

type TenkaShapes

type TenkaShapes = { width: number; height: number; /** Where each territory's army counter stands. */ labels: readonly (readonly number[])[]; /** Each territory's extent, left, top, right, bottom: what a view frames to show it. */ boxes: readonly (readonly number[])[]; /** Each sea link's dashed line, x1 y1 x2 y2; the crossing of the Bering Strait is two, one off each edge. */ seaLines: readonly (readonly number[])…

How the world is drawn, in map units (tenkaShapes.data.ts, written by scripts/map.mjs).

function tenkaStrings

tenkaStrings(locale: TenkaLocale | string | undefined, own?: Partial<TenkaStrings>): TenkaStrings

The table of strings for a locale with a page's own words laid over it. An unknown locale is English.

type TenkaStrings

type TenkaStrings = { [Name in keyof typeof EN]: string };

Every word the package shows, by name: one table of these for each language.

type TenkaTerritoryData

type TenkaTerritoryData = { key: string; name: string; continent: TenkaContinentKey; land: readonly number[]; sea: readonly number[]; };

One territory as the map script writes it: neighbours by land and by sea are indices into the same list.

function tenkaToCSV

tenkaToCSV(game: TenkaGame): string

A game as CSV, a row to a move, for a spreadsheet: TENKA_CSV_COLUMNS. Territories are written by key (alaska, westernCanada), seats from 1, dice as they fell, highest first and spaced. Lines end CRLF, as RFC 4180 has them, and a player's name that a spreadsheet would run as a formula is made safe with a leading apostrophe.

function tenkaToJSON

tenkaToJSON(game: TenkaGame): string

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

function tenkaToText

tenkaToText(game: TenkaGame, strings?: TenkaStrings): string

A game as plain text, a line to a move, for a chat or a log: who placed what where, every throw of the dice, every territory taken, and how it ended. In English unless given another table of strings (TENKA_STRINGS.ja). Lines end with a line feed.

type TenkaTrade

type TenkaTrade = { seat: TenkaSeat; cards: readonly TenkaCard[]; armies: number; bonusTerritory: number | null };

The last set of cards traded in, for the table to see: by whom, for how many, and the territory that got two more.

type TenkaWorldContinentKey

type TenkaWorldContinentKey = "northAmerica" | "southAmerica" | "europe" | "africa" | "asia" | "oceania";

The world's six continents, by key.

function territoriesHeld

territoriesHeld(owners: readonly TenkaOwner[], owner: TenkaOwner): number

How many territories an owner holds.

function territoryNameIn

territoryNameIn(strings: TenkaStrings, key: string): string

A territory's name in a table of strings, by its key (TENKA_TERRITORIES[n].key, such as "alaska"); "" for a key that is no territory's.

function throwDice

throwDice(state: number, count: number): { value: number[]; state: number; }

count dice, highest first, and the state after them.

function tradeValue

tradeValue(trades: number): number

What the next set traded in is worth, when trades sets have been traded before it: 4, 6, 8, 10, 12, 15, 20, 25…

function writeTenkaMove

writeTenkaMove(move: TenkaMove): Kept

A move as the short list it is kept as — and sent as, to the other devices at a table played on several.

@johnmorrisdotca/tenka/shapes

TENKA_EUROPE_SHAPES TENKA_SHAPES TenkaShapes

const TENKA_EUROPE_SHAPES

TENKA_EUROPE_SHAPES: TenkaShapes

How Tenka's Europe is drawn: Miller's projection from 25°W to 60°E and 33.5°N to 71.5°N, 2000 by 1242 units. One outline per territory, in the order of TENKA_EUROPE_TERRITORY_DATA; where its army counter stands; its extent; the sea links' dashed lines; the links that go off one edge and on at the other; and the borders between continents, drawn heavier. Read only by the board in the browser, so none of it is carried by a page the server renders for the rules.

const TENKA_SHAPES

TENKA_SHAPES: TenkaShapes

How Tenka's world is drawn: Miller's projection from 170°W round to 192°E, 2000 by 984 units. One outline per territory, in the order of TENKA_TERRITORY_DATA; where its army counter stands; its extent; the sea links' dashed lines; the links that go off one edge and on at the other; and the borders between continents, drawn heavier. Read only by the board in the browser, so none of it is carried by a page the server renders for the rules.

type TenkaShapes

type TenkaShapes = { width: number; height: number; /** Where each territory's army counter stands. */ labels: readonly (readonly number[])[]; /** Each territory's extent, left, top, right, bottom: what a view frames to show it. */ boxes: readonly (readonly number[])[]; /** Each sea link's dashed line, x1 y1 x2 y2; the crossing of the Bering Strait is two, one off each edge. */ seaLines: readonly (readonly number[])…

How the world is drawn, in map units (tenkaShapes.data.ts, written by scripts/map.mjs).

@johnmorrisdotca/tenka/ui

continentNameIn continentView mountTenka nearestLand NO_MARKS ownerColour TENKA_MAP_SHAPES TENKA_NEUTRAL_COLOUR TENKA_SEAT_COLOURS TENKA_STRINGS TENKA_STYLE TenkaLand TenkaLocale tenkaMapModel TenkaMapModel tenkaMapSvg TenkaRing tenkaSay tenkaShapesOf tenkaStrings TenkaStrings TenkaTableHandle TenkaTableOptions TenkaView territoryNameIn

function continentNameIn

continentNameIn(strings: TenkaStrings, key: string): string

A continent's name in a table of strings, by its key ("northAmerica", "asia"…).

function continentView

continentView(key: TenkaContinentKey | null, map?: TenkaMapKey): TenkaView

The part of the map that frames a continent, with a margin, widened to the map's own shape so it fills the same box; the whole world for null.

function mountTenka

mountTenka(target: HTMLElement, options?: TenkaTableOptions): TenkaTableHandle

A WHOLE TABLE OF TENKA IN PLAIN DOM: the map, whose turn it is and what to do, the players, your cards and the last dice, played against the computer (or by people taking turns on one device, with computers all false). No framework and no server; everything is in the element given.

Tap your own territory to place an army; to attack or move, tap where from, then where to, and choose from the buttons under the map. Under the players is the record of the game, which saves as JSON, text or CSV and loads a saved game back.

function nearestLand

nearestLand(x: number, y: number, reach: number, map?: TenkaMapKey): number | null

The territory whose counter is nearest a point of the map, within reach map units, or null.

const NO_MARKS

NO_MARKS: TenkaMapMarks

Nothing lit up.

function ownerColour

ownerColour(owner: number, colours?: readonly string[]): string

The colour a territory's owner is drawn in.

const TENKA_MAP_SHAPES

TENKA_MAP_SHAPES: Readonly<Record<TenkaMapKey, TenkaShapes>>

How each map is drawn.

const TENKA_NEUTRAL_COLOUR

TENKA_NEUTRAL_COLOUR: "#9a9a92"

The grey of the neutral army.

const TENKA_SEAT_COLOURS

TENKA_SEAT_COLOURS: readonly string[]

THE COLOURS A TABLE IS DRAWN IN: one for each of six seats, and a grey for the neutral army that holds a third of the world in a game for two. Any list of CSS colours may be given instead; a seat past its end wraps round.

const TENKA_STRINGS

TENKA_STRINGS: Readonly<Record<TenkaLocale, TenkaStrings>>

The package's words in each language it speaks: TENKA_STRINGS.en, TENKA_STRINGS.ja.

const TENKA_STYLE

TENKA_STYLE: "\n.tk-root {\n --tk-sea: #b9d3dc; --tk-ink: #1f2320; --tk-panel: #f7f3ea; --tk-line: rgba(20,20,20,.55);\n --tk-border: rgba(10,10,10,.8); --tk-link: #1d3440; --tk-accent: #2f5d4a; --tk-accent-ink: #fff;\n --tk-ring: #111; --tk-ring-target: #fff; --tk-counter-edge: #111; --tk-counter-ink: #fff;\n --tk-attack: #c8463d; --tk-attack-ink: #fff; --tk-defend: #f4efe4; --tk-defend-ink: #1f2320;\n --tk-radius:…

The table's own styles, scoped to .tk-root, with every colour a CSS variable so a page can wear it in its own colours (set them on the element or any ancestor, or pass them as theme). Light and dark follow the reader's system. Everything to be tapped is at least 44px.

type TenkaLand

type TenkaLand = { /** Its number. */ territory: number; /** Its key, such as `"alaska"`. */ key: string; /** Its name in English. */ name: string; /** Its outline, one SVG path in map units. */ outline: string; /** The colour of whoever holds it. */ fill: string; /** How it is ringed, if it is. */ ring: TenkaRing; /** Where its counter stands, in map units. */ at: readonly [number, number]; /** The armies standing …

One territory as a drawing needs it.

type TenkaLocale

type TenkaLocale = "en" | "ja";

The languages the package speaks: English and Japanese.

function tenkaMapModel

tenkaMapModel(game: Pick<TenkaGame, "owners" | "armies" | "map">, marks?: TenkaMapMarks, colours?: readonly string[]): TenkaMapModel

EVERYTHING A DRAWING OF THE WORLD NEEDS, for one game: each territory's outline in its owner's colour, its counter's place and armies, and its ring. Shared by the plain-DOM table and the React map, so both draw the same world from the same few rules.

type TenkaMapModel

type TenkaMapModel = { width: number; height: number; lands: readonly TenkaLand[]; seaLines: readonly (readonly number[])[]; continentBorders: string; };

The whole world as a drawing needs it, in map units: tenkaMapModel makes one, tenkaMapSvg and TenkaMap draw it.

function tenkaMapSvg

tenkaMapSvg(model: TenkaMapModel, options?: { label?: string; view?: TenkaView; pixels?: number; }): SVGSVGElement

THE WORLD AS AN SVG ELEMENT, drawn from a model (tenkaMapModel): the sea, each territory in its owner's colour, the continents' borders and the sea links, the rings of a choice, and a counter of armies on each. Every territory's shape and counter carries data-territory with its number, so one listener on the element can tell which was pressed, and each shape data-owner and data-armies as well.

label is the drawing's accessible name; view the part of the map to show (continentView); pixels the drawing's width on the screen, so that counters and rings are one size whatever is shown.

type TenkaRing

type TenkaRing = "chosen" | "reach" | "target" | null;

How a territory is ringed: the one chosen, one it can reach, or the target.

function tenkaSay

tenkaSay(line: string, values?: Readonly<Record<string, string | number>>): string

A line from a table of strings with its braces filled in: tenkaSay(strings.moveIn, { n: 3 }) is "Move 3 in". A brace with no value is left as it is.

function tenkaShapesOf

tenkaShapesOf(map: TenkaMapKey | undefined): TenkaShapes

How a game's map is drawn: the world unless the game says otherwise.

function tenkaStrings

tenkaStrings(locale: TenkaLocale | string | undefined, own?: Partial<TenkaStrings>): TenkaStrings

The table of strings for a locale with a page's own words laid over it. An unknown locale is English.

type TenkaStrings

type TenkaStrings = { [Name in keyof typeof EN]: string };

Every word the package shows, by name: one table of these for each language.

type TenkaTableHandle

type TenkaTableHandle = { /** The game as it stands. */ game: () => TenkaGame; /** A new game at the same table, with any options changed. */ newGame: (options?: Pick<TenkaTableOptions, "players" | "computers" | "rounds" | "seed" | "map">) => void; /** Put a game on the table: one read back by `tenkaFromJSON` or `decodeTenka`. Seats keep who plays them when the number of players is the same; otherwise every seat but…

What mountTenka hands back: the game being played, and the ways to change it from outside.

type TenkaTableOptions

type TenkaTableOptions = { /** The names round the table, in seat order: two to six. */ players?: readonly string[]; /** Which seats the computer plays. By default every seat but the first. */ computers?: readonly boolean[]; /** Rounds before the count: 10, 20, or 60 for the whole world. */ rounds?: number; /** The seed every deal and die is drawn from; a new one each game when absent. */ seed?: number; /** The map:…

What mountTenka may be given. Everything is optional: with nothing, it is a game of three against two computer players, to the last player standing.

type TenkaView

type TenkaView = readonly [number, number, number, number];

A part of the map to show, in map units: left, top, width, height (an SVG viewBox).

function territoryNameIn

territoryNameIn(strings: TenkaStrings, key: string): string

A territory's name in a table of strings, by its key (TENKA_TERRITORIES[n].key, such as "alaska"); "" for a key that is no territory's.

@johnmorrisdotca/tenka/react

TenkaMap TenkaMapProps TenkaTable TenkaTableProps

function TenkaMap

TenkaMap({ game, marks, colours, onTerritory, label, ...svg }: TenkaMapProps): import("/home/runner/work/tenka/tenka/node_modules/.pnpm/@types+react@19.3.0/node_modules/@types/react/index").JSX.Element

THE WORLD, AS A REACT COMPONENT: every territory in its owner's colour, its counter of armies, the rings of a choice, and a press on a territory or its counter reported by number. It draws; the rules stay in playTenka, and what a press means in tapTerritory. Scales to its container's width; style .tk-land, .tk-sea and the rest as you like.

type TenkaMapProps

type TenkaMapProps = { /** The game to draw: only who holds each territory and with how many armies is read. */ game: Pick<TenkaGame, "owners" | "armies">; /** What to light up: from `marksFor(game, choice)`. */ marks?: TenkaMapMarks; /** A colour for each seat. */ colours?: readonly string[]; /** A territory pressed. Without it the map is only a picture. */ onTerritory?: (territory: number) => void; /** The map's a…

What TenkaMap takes: a game to draw, and any attribute of its <svg>.

function TenkaTable

TenkaTable({ players, computers, rounds, seed, colours, computerDelayMs, onChange, locale, strings, theme, record, ...element }: TenkaTableProps): import("/home/runner/work/tenka/tenka/node_modules/.pnpm/@types+react@19.3.0/node_modules/@types/react/index").JSX.Element

A whole table against the computer, as a React component: the plain-DOM table (mountTenka) mounted into this component's element once the browser has it. Options are read when it mounts; give it a new key to start over with different ones.

type TenkaTableProps

type TenkaTableProps = TenkaTableOptions & Omit<HTMLAttributes<HTMLDivElement>, keyof TenkaTableOptions>;

What TenkaTable takes: the options of mountTenka, and any attribute of its <div>.