diff --git a/src/lib/game/index.test.ts b/src/lib/game/index.test.ts index 8f70114..dc24ba1 100644 --- a/src/lib/game/index.test.ts +++ b/src/lib/game/index.test.ts @@ -178,22 +178,21 @@ describe('the ends of the game', () => { expect(done.over?.reason).toMatch(/edge fort/); }); - it('forfeits the side that repeats a position a third time', () => { + it('lays a third repetition on the defenders, whoever brought it about', () => { let s = board([E11, '.a.........', E11, '..d........', ...Array(7).fill(E11)]); const a1 = at(11, { x: 1, y: 1 }); const a2 = at(11, { x: 2, y: 1 }); const d1 = at(11, { x: 2, y: 3 }); const d2 = at(11, { x: 3, y: 3 }); - // The position after the first move stands again at ply 5 and a third time at ply 9, - // when the attackers (Bo) are the side that brought it back. + // The position after the first move stands again at ply 5 and a third time at ply 9. const plies = [[a1, a2], [d1, d2], [a2, a1], [d2, d1]] as const; for (let ply = 0; ply < 8; ply++) { s = applyMove(s, plies[ply % 4][0], plies[ply % 4][1]); expect(s.over).toBeNull(); } s = applyMove(s, a1, a2); - expect(s.over?.reason).toBe('Bo repeats the position a third time and forfeits.'); - expect(s.over?.winner).toBe('A'); + expect(s.over?.reason).toMatch(/perpetual repetition/); + expect(s.over?.winner).toBe('B'); }); it('runs a whole game between two bots to a verdict on both boards', () => { diff --git a/src/lib/game/index.ts b/src/lib/game/index.ts index 9876112..6ff4eef 100644 --- a/src/lib/game/index.ts +++ b/src/lib/game/index.ts @@ -264,9 +264,9 @@ export function applyMove(previous: State, from: number, to: number): State { if (!state.over) { const key = positionKey(state); state.seen[key] = (state.seen[key] ?? 0) + 1; - const mover = state.players[seatOfSide(state, side)!]?.name ?? side; const next = state.players[seatOfSide(state, state.toMove)!]?.name ?? state.toMove; - if (state.seen[key] >= 3) win(state.toMove, `${mover} repeats the position a third time and forfeits.`); + // Perpetual repetition is forbidden, and the Copenhagen rule lays the loss on the defenders. + if (state.seen[key] >= 3) win('attackers', 'The position stands a third time: perpetual repetition loses for the defenders.'); else if (legalMoves(state, state.toMove).length === 0) win(side, `${next} has no move.`); } return state; diff --git a/src/routes/rules/+page.svelte b/src/routes/rules/+page.svelte index e9e36fd..e9ae281 100644 --- a/src/routes/rules/+page.svelte +++ b/src/routes/rules/+page.svelte @@ -67,7 +67,7 @@ attackers win.
- Linnaeus described the board, the pieces and the moves in a few Latin sentences with a diagram, in the journal of his journey through Lapland. His notes name the two sides, - say that every piece moves in straight lines like the rook, that a piece is taken between two enemies, that the king is taken by four (or three and the throne) and wins by - reaching the edge, and that the king alone may stand on the central square. The passage is short enough that every modern Tablut is a reading of it rather than a copy. -
-
- The text is in the public domain. A transcription of the Latin and the standard English translation belongs in docs/ beside this page, so that a rules question
- can be settled by the words the engine follows.
-
- Copenhagen is a modern ruleset, written and revised by its club, and this page follows it in the club's own terms: corner escape, an armed king captured by four, the - shieldwall, the edge fort, and encirclement. Where this page and the club's text differ, the club's text is right and this page has a bug. -
-+ The Copenhagen rules were settled by the Copenhagen Hnefatafl Club in 2012 and are kept, with their revisions, on + the club's page, which is also where the world's online tournaments are played. + In the club's terms: the attackers move first; every piece moves like a rook; a piece is captured between two enemies, or an enemy and a restricted square, and only + when the aggressor's move closes the trap; a row along the edge falls as a shieldwall when bracketed at both ends, a corner counting as a bracket; the king is captured + by four attackers, by three beside the throne, and never on the board edge; the king escapes to a corner; the defenders win an edge fort the attackers cannot break, if + the king can move; the attackers win by surrounding the king and every remaining defender with an unbroken ring; and perpetual repetition is forbidden, losing the game + for the defenders. The engine here plays those rules and nothing else. +
++ Carl Linnaeus watched Tablut played on embroidered reindeer hide during his journey through Lapland and set down the board and the rules in his journal, + Iter Lapponicum, in a mixture of Swedish and Latin: the royal fort at the centre, Arx regia, Konokis Lappon., cui nullus succedere potest, which no other + piece may enter; pieces that move per lineam rectam, non vero transversam, in straight lines and never diagonally; and the capture, si quis hostem inter duos + sibi hostes collocare possit, est occisus, a piece placed between two of its enemies is slain. He named the sides the Swedes and the Muscovites. James Edward Smith's + 1811 English translation, Lachesis Lapponica, spread the game but dropped two of Linnaeus's fourteen rules and mistranslated others, which is where the edge-escape + and corner-escape families of modern hnefatafl part ways. +
++ The Tablut on this table follows the reading of the manuscript made by Olli Salmi in 2013 to 2015, the reconstruction most modern Tablut players use: edge escape, no + piece but the king on or across the throne, the empty throne hostile to both sides, an armed king who is captured by two attackers away from the throne, by four on it, + and by three beside it. The Latin manuscript is in the public domain and is transcribed with a translation in + the tafl games article and its sources, and discussed rule by rule in + John Ashton's paper in The Heroic Age. +
If this page reads a rule differently from the text it follows, that is a bug: send word.
@@ -138,6 +145,12 @@ margin-bottom: 0.4rem; } + h3 { + font-size: 1.05rem; + font-style: italic; + margin: 1rem 0 0.3rem; + } + p + p { margin-top: 0.6rem; } @@ -150,20 +163,8 @@ margin-top: 0.4rem; } - details { - margin-top: 0.6rem; - padding: 0.5rem 0.8rem; - border: 1px solid var(--rule); - border-radius: 6px; - } - summary { - cursor: pointer; - } - details p { - margin-top: 0.6rem; - } .word { margin-top: 2rem;