diff --git a/README.md b/README.md index 5eac445..801ca29 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Hnefatafl The Viking board game, in the browser: the attackers must take the king, the -king must reach a corner. Copenhagen rules on the eleven-by-eleven board by -default; Tablut, the nine-by-nine game Linnaeus wrote down in 1732, as an -option, on a board that shows its age. +king must reach the edge. Tablut, the nine-by-nine game Linnaeus wrote down in +1732, by default, on a board that shows its age; Copenhagen rules on the +eleven-by-eleven board, where the king needs a corner, as an option. Built on the game kit: `src/lib/game/` is the game (rules as data in `rules.ts`, the board's mechanics in `board.ts`, the engine in `index.ts`, diff --git a/src/lib/game/index.test.ts b/src/lib/game/index.test.ts index b7aae62..460a866 100644 --- a/src/lib/game/index.test.ts +++ b/src/lib/game/index.test.ts @@ -21,7 +21,7 @@ const E9 = '.........'; describe('the opening', () => { it('lays Copenhagen out with 24 attackers, 12 defenders and the king on the throne', () => { - const s = createGame(names); + const s = createGame(names, 1, 1, { set: 'copenhagen', side: 'defenders' }); expect(s.size).toBe(11); expect(s.cells.filter((p) => p === 'a').length).toBe(24); expect(s.cells.filter((p) => p === 'd').length).toBe(12); @@ -29,8 +29,8 @@ describe('the opening', () => { expect(s.toMove).toBe('attackers'); }); - it('lays Tablut out on nine by nine with 16 attackers and 8 defenders', () => { - const s = createGame(names, 1, 1, { set: 'tablut', side: 'defenders' }); + it('lays Tablut out on nine by nine with 16 attackers and 8 defenders, and plays it when no rules are chosen', () => { + const s = createGame(names); expect(s.size).toBe(9); expect(s.cells.filter((p) => p === 'a').length).toBe(16); expect(s.cells.filter((p) => p === 'd').length).toBe(8); @@ -70,7 +70,7 @@ describe('moving', () => { }); it('refuses the wrong side, a stranger, and an impossible square, with reasons', () => { - const s = createGame(names); + const s = createGame(names, 1, 1, { set: 'copenhagen', side: 'defenders' }); expect(validateMove(s, 'A', { from: at(11, { x: 5, y: 3 }), to: at(11, { x: 5, y: 2 }) })).toMatch(/not your side/); expect(validateMove(s, 'B', { from: at(11, { x: 5, y: 3 }), to: at(11, { x: 5, y: 2 }) })).toMatch(/not one of your pieces/); expect(validateMove(s, 'B', { from: at(11, { x: 3, y: 0 }), to: at(11, { x: 3, y: 5 }) })).toMatch(/cannot move there/); @@ -139,7 +139,7 @@ describe('the king', () => { }); it('needs four attackers on the throne, and three beside it', () => { - const s = createGame(names); + const s = createGame(names, 1, 1, { set: 'copenhagen', side: 'defenders' }); const t = throneOf(11); // Clear the defenders and ring the throne with three attackers; the fourth arrives. const onThrone = s.cells.map((p) => (p === 'd' ? '.' : p)) as Piece[]; @@ -216,7 +216,7 @@ describe('the ends of the game', () => { }, 120_000); it('opens with the same move every time, so a ledger replays to the same choice', () => { - const s = createGame(names, 3); + const s = createGame(names, 3, 1, { set: 'copenhagen', side: 'defenders' }); expect(movesOf(rulesetOf(s), s.cells, 'attackers').length).toBe(116); expect(chooseMove(s, 'B')).toEqual({ from: 3, to: 2 }); }); diff --git a/src/lib/game/index.ts b/src/lib/game/index.ts index db2427d..cea99f5 100644 --- a/src/lib/game/index.ts +++ b/src/lib/game/index.ts @@ -13,6 +13,8 @@ import { chooseMove } from './bot'; export { at, beyond, cellOf, cornersOf, isEdge, pieceWord, sideOf, squareName, throneOf, type Input, type Piece, type Side } from './board'; const CURRENT_RULES = 1; +/** The rules a table plays when the host chooses none. */ +const DEFAULT_SET: Ruleset['id'] = 'tablut'; export interface Player { id: SeatId; @@ -64,7 +66,7 @@ export function rulesetOf(state: State): Ruleset { // --- Setting up --------------------------------------------------------------- export function createGame(names: Record, _seed = 0, rules = CURRENT_RULES, options?: TableOptions): State { - const set = options?.set === 'tablut' ? RULESETS.tablut : RULESETS.copenhagen; + const set = RULESETS[options?.set as Ruleset['id']] ?? RULESETS[DEFAULT_SET]; const size = set.size; const cells: Piece[] = Array(size * size).fill('.'); for (const c of set.attackers) cells[at(size, c)] = 'a'; @@ -205,10 +207,10 @@ export const game: GameSpec = { key: 'set', label: 'Rules', choices: [ - { value: 'copenhagen', label: 'Copenhagen, 11 by 11', note: 'The modern tournament game. The king escapes to a corner and falls only to four attackers, three beside the throne.' }, - { value: 'tablut', label: 'Tablut, 9 by 9, after Linnaeus', note: 'The older game. The king escapes to any edge square, and away from the throne two attackers take him like any piece.' } + { value: 'tablut', label: 'Tablut, 9 by 9, after Linnaeus', note: 'The older game. The king escapes to any edge square, and away from the throne two attackers take him like any piece.' }, + { value: 'copenhagen', label: 'Copenhagen, 11 by 11', note: 'The modern tournament game. The king escapes to a corner and falls only to four attackers, three beside the throne.' } ], - default: 'copenhagen' + default: DEFAULT_SET }, { key: 'side', label: 'You play', choices: SIDES, default: 'defenders' } ],