Add Checkers, Backgammon, Mastermind, and Pirates and Bulgars
The four games that were listed but not written. Nothing in the catalogue is marked "coming soon" any more. Checkers is English draughts on the thirty-two dark squares, numbered the way the game is written down. Captures are compulsory and a multiple jump is one move object, so a player cannot stop halfway through one — true by construction rather than by a rule that has to be enforced somewhere. The move generator matches the published perft counts at one to four ply (7, 49, 302, 1469), which fold in compulsory capture, crowning and the men's forward-only rule all at once. Backgammon's hard rule is not how a checker moves but that a player must use as much of the roll as they can, and that cannot be checked one move at a time: a move can be illegal precisely because playing it strands the other die. So the generator works backwards from whole turns — memoised depth-first over the remaining dice — and offers only the first steps of the longest plays. Recomputing it after each checker is what makes the rule true by construction. Every die comes from the table's seed and a count of rolls made, which is in the state rather than beside it so a game is reproducible from what crosses the wire. There is no doubling cube; the rules document says so plainly. Mastermind is played as a duel — both players are set a code and both are breaking one — because the pencil-and-paper game gives the codemaker nothing to do. It ends at the foot of a round, never mid-round, or going first would be worth half a guess. It is the one game here with anything to hide, so viewFor does real work: the host sends each device only its own code, and a device that was never given a code refuses to score a guess against it rather than quietly answering "nothing". Pirates and Bulgars is Sackson's theming of Asalto, and the rules document is explicit about which parts are attested and which had to be chosen — the book could not be obtained. Two hunters who take by jumping against twenty-four who cannot take at all, on a thirty-three point cross whose drawn lines come from the same adjacency table the moves come from, so the board and the rules cannot disagree. 130 unit tests across the four engines, all run locally as well as in CI: game/ has no Android in it, which is what makes that possible. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019Pr6B8xDDtJJcb6WJMw7Ud
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Backgammon
|
||||
|
||||
Twenty-four points, fifteen checkers a side, and two dice. Bring everything
|
||||
round to your home board and bear it off before your opponent does.
|
||||
|
||||
## The board
|
||||
|
||||
Points are numbered 1 to 24. **White runs down the numbers and bears off past
|
||||
1; Black runs up them and bears off past 24.** White's home board is points 1–6,
|
||||
Black's is 19–24.
|
||||
|
||||
A point belongs to whoever has two or more checkers on it, and is closed to the
|
||||
other player. A point with exactly one checker on it is a **blot**, and can be
|
||||
hit.
|
||||
|
||||
## The opening
|
||||
|
||||
Each player throws one die and the higher number starts, playing both dice as
|
||||
thrown. A tie is thrown again, so the game never opens on a double.
|
||||
|
||||
## A turn
|
||||
|
||||
Throw two dice and move one checker for each. **A double is four moves**, not
|
||||
two.
|
||||
|
||||
The rule that decides most disputes is this one: **you must use as much of the
|
||||
roll as you can.** If there is a way to play both dice, you must play both, even
|
||||
when playing one would suit you better. If only one can be played, it must be
|
||||
the higher one. The app enforces this by working out every way the whole roll
|
||||
could be played before offering you anything, so a move that would strand the
|
||||
other die is simply never on the board. That is why a legal-looking move
|
||||
sometimes cannot be tapped: playing it would waste half your roll.
|
||||
|
||||
## Hitting and the bar
|
||||
|
||||
Landing on a blot sends it to the bar. A player with a checker on the bar must
|
||||
bring it back on before moving anything else, entering in the opponent's home
|
||||
board: White enters on 24 minus the die, Black on the die itself. If every entry
|
||||
point is closed the turn is lost, and it can be lost several times over.
|
||||
|
||||
## Bearing off
|
||||
|
||||
Once all fifteen checkers are in your home board you may start bearing off. An
|
||||
exact die takes a checker off the point that needs it. A bigger die may take one
|
||||
off your highest occupied point, but only when nothing is standing further back —
|
||||
an overshooting die may never take a checker that a smaller die could have
|
||||
reached.
|
||||
|
||||
## Winning
|
||||
|
||||
Fifteen off wins. How much it is worth depends on how badly the loser lost:
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Single**, 1 point | the loser has borne at least one checker off |
|
||||
| **Gammon**, 2 points | the loser has borne nothing off |
|
||||
| **Backgammon**, 3 points | and still has a checker on the bar or in the winner's home |
|
||||
|
||||
Counting gammons is an option. It changes nothing about the moves and everything
|
||||
about whether a hopeless game is worth playing out.
|
||||
|
||||
## Not implemented
|
||||
|
||||
**There is no doubling cube.** A single game is played rather than a match to a
|
||||
number of points, and with nothing being kept between games there is nothing for
|
||||
a cube to double. It is the largest thing missing from this implementation and
|
||||
the one a serious player will notice first.
|
||||
|
||||
## The dice
|
||||
|
||||
Every die in the game comes from the table's own seed and a count of how many
|
||||
rolls have been made, so a game is reproducible from the state it started in and
|
||||
a host and a client can never disagree about what was thrown. The seed travels
|
||||
with the state, which is safe: backgammon has nothing hidden, and the host is
|
||||
authoritative about every roll in any case.
|
||||
|
||||
## What the computer does
|
||||
|
||||
Not a search in the sense the chess and checkers players are one — there is no
|
||||
tree to walk, because the opponent's reply depends on dice nobody has thrown
|
||||
yet. Instead it weighs **whole turns**: every distinct way the roll can be
|
||||
played, scored by the board it leaves behind.
|
||||
|
||||
Weighing turns rather than checkers is the point. A backgammon move is only good
|
||||
in company — splitting the back checkers is right or wrong depending on what the
|
||||
other die did — and a player choosing one checker at a time would never see it.
|
||||
|
||||
At the strongest level it also averages what the dice might do to it next: all
|
||||
twenty-one distinct rolls, weighted properly (6-5 comes up twice as often as
|
||||
5-5), taking the opponent's best answer to each.
|
||||
|
||||
The evaluation is built on the pip count, because backgammon is a race. Around
|
||||
that: a blot is only a liability if it can be hit, so exposure is weighted by
|
||||
how far away the nearest enemy checker is; a made point in your own home board
|
||||
is worth having because it is one more number your opponent cannot come in on;
|
||||
and a long run of consecutive points is what traps a checker behind it.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Checkers
|
||||
|
||||
English draughts, on the thirty-two dark squares of an eight-by-eight board.
|
||||
Twelve pieces a side, Black moves first.
|
||||
|
||||
## The board
|
||||
|
||||
Squares are numbered 1 to 32 the way the game has always been written down:
|
||||
square 1 at the top left, running along each row and down the board to 32. Black
|
||||
starts on 1–12, White on 21–32, and the two middle rows are empty. Only the dark
|
||||
squares are ever played on, so a move is two numbers — `11-15` for a quiet move,
|
||||
`11x18` for a jump.
|
||||
|
||||
## Moving
|
||||
|
||||
A man moves one square diagonally **forwards**. Black moves down the board,
|
||||
White up it.
|
||||
|
||||
A man that reaches the far row is **crowned** and becomes a king, which moves
|
||||
one square diagonally in either direction.
|
||||
|
||||
## Taking
|
||||
|
||||
You take by jumping an enemy piece and landing on the empty square directly
|
||||
beyond it. Three rules follow, and all three are enforced by the engine rather
|
||||
than left to the players:
|
||||
|
||||
- **A capture is compulsory.** If anything can be taken anywhere on the board,
|
||||
a quiet move is not a legal move at all. The app simply will not offer one.
|
||||
- **A multiple jump is one turn.** Where a jump can be followed by another with
|
||||
the same piece, it must be — and the whole chain is a single move. A shorter
|
||||
prefix of a longer capture is never offered, so there is no way to stop
|
||||
halfway.
|
||||
- **A man may not jump backwards.** Only kings take in both directions.
|
||||
|
||||
You do *not* have to take the longest capture available: any complete jump will
|
||||
do. That is the English rule; international draughts requires the maximum.
|
||||
|
||||
## Crowning mid-jump
|
||||
|
||||
A man that jumps into the far row is crowned and **the turn ends there**, even
|
||||
with another jump available. This is the English rule and it is on by default.
|
||||
Turn *Crowning ends the turn* off and the man carries on jumping as a man,
|
||||
crowned only if it finishes on the back row — which is the international rule.
|
||||
|
||||
## Winning
|
||||
|
||||
A player who has no pieces, or who has pieces but no legal move, has lost. The
|
||||
two are the same condition as far as the engine is concerned: no moves is a
|
||||
loss, whatever the reason.
|
||||
|
||||
## Draws
|
||||
|
||||
- **Threefold repetition** — the same position with the same player to move,
|
||||
three times over.
|
||||
- **Eighty plies without progress** — forty moves each with nothing taken and
|
||||
nothing crowned. Both are options.
|
||||
|
||||
## Flying kings
|
||||
|
||||
Off by default. Turned on, a king slides any distance along a diagonal and takes
|
||||
an enemy piece from a distance, landing on any empty square beyond it — the
|
||||
international king.
|
||||
|
||||
**One honest deviation:** a piece taken by a flying king is lifted immediately.
|
||||
International draughts leaves captured pieces standing until the turn ends, so
|
||||
they block the rest of the sequence, which occasionally forbids a chain this
|
||||
implementation allows. Fixing it properly means tracking captured-but-present
|
||||
pieces through the whole search, and it is written down here rather than
|
||||
pretended away.
|
||||
|
||||
## What the computer does
|
||||
|
||||
Negamax with alpha-beta pruning and iterative deepening, to seven ply at the
|
||||
strongest level — deeper than the chess player reaches, because compulsory
|
||||
capture keeps the branching factor low.
|
||||
|
||||
There is no quiescence search and it does not need one: the usual failure of a
|
||||
fixed-depth search is stopping in the middle of an exchange, and compulsory
|
||||
capture handles that by itself, since a position where a piece has just been
|
||||
taken usually has the recapture as its only legal move.
|
||||
|
||||
Its evaluation counts material first, with kings worth well over a man, and then
|
||||
two things beginners underrate: **men are worth more the closer they are to
|
||||
being crowned**, and **the back row is worth holding**, because an empty back
|
||||
row is a road to a crown for the other side. The two pull against each other,
|
||||
which is about right.
|
||||
|
||||
## The move generator is checked against published numbers
|
||||
|
||||
`CheckersRulesTest` counts the leaf nodes below the opening position — 7, 49,
|
||||
302 and 1469 at one to four ply — and those are the published figures for
|
||||
English draughts. A generator that matches them that far has very little room
|
||||
left to be wrong: the counts fold in compulsory capture, crowning and the
|
||||
men's forward-only rule all at once.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Mastermind
|
||||
|
||||
A hidden code of coloured pegs, and a fixed number of guesses to break it.
|
||||
|
||||
## Played as a duel
|
||||
|
||||
The pencil-and-paper game is one person setting a code and another breaking it,
|
||||
which makes a poor two-handed game: one player would sit and watch.
|
||||
|
||||
Here **both players are set a code and both are breaking one**, a guess each in
|
||||
turn. Whoever cracks theirs first wins.
|
||||
|
||||
That symmetry is why the game ends at the foot of a round rather than the moment
|
||||
somebody gets it. If the player who went first breaks the code on their sixth
|
||||
guess, the player who replied still gets a sixth guess of their own — and
|
||||
equalling it is a draw. Ending it any earlier would make going first worth half
|
||||
a guess.
|
||||
|
||||
## The answer
|
||||
|
||||
Each guess is answered with pegs:
|
||||
|
||||
- a **dark** peg for a colour in the right place;
|
||||
- a **light** peg for a colour that is in the code but somewhere else.
|
||||
|
||||
**A colour is never counted twice.** Three reds guessed against a code with one
|
||||
red is worth exactly one peg, however they are arranged. The pegs say how many,
|
||||
never which, and they are deliberately not lined up under the code they answer —
|
||||
a tidy row would invite reading a position into them that is not there.
|
||||
|
||||
## Options
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Colours** | 6 or 8 |
|
||||
| **Pegs** | 3, 4 or 5 |
|
||||
| **Guesses each** | 8, 10 or 12 |
|
||||
| **Repeated colours** | on by default; off makes every peg a different colour |
|
||||
| **Opponent** | Casual, Club or Strong |
|
||||
|
||||
The setup screen shows how many codes are possible, which is the honest measure
|
||||
of how hard a game will be: 1,296 for the standard four pegs and six colours,
|
||||
32,768 for five pegs and eight.
|
||||
|
||||
## Ending
|
||||
|
||||
- One player breaks it and the other does not — that player wins.
|
||||
- Both break it in the same round — a draw.
|
||||
- Neither breaks it before the guesses run out — a draw.
|
||||
|
||||
## Keeping the codes secret
|
||||
|
||||
This is the only game here with anything to hide, and it is the only one whose
|
||||
`viewFor` does real work. The host holds both codes and sends each device **only
|
||||
its own**: the code you are trying to break is never on your device in the first
|
||||
place, so there is nothing to find by digging through what arrived over the
|
||||
network.
|
||||
|
||||
A guess is scored by the host, which is the only machine that can score it. A
|
||||
device asked to score a guess against a code it was never given refuses rather
|
||||
than quietly answering "nothing" — tested, because that failure would look like
|
||||
a very hard code rather than like a bug.
|
||||
|
||||
Both codes are revealed when the game ends. There is nothing left to protect,
|
||||
and a code breaker wants to see what they were up against.
|
||||
|
||||
## What the computer does
|
||||
|
||||
There is no tree to search — a guess cannot be answered badly, and the opponent
|
||||
makes no reply — so the whole game is in choosing what to try next.
|
||||
|
||||
- **Casual** guesses anything it has not tried, ruled out or not.
|
||||
- **Club** never guesses a code the answers have already eliminated. That one
|
||||
rule is most of the strength, and it cracks a standard code in about five.
|
||||
- **Strong** is Knuth's idea one ply deep: among the codes still standing, it
|
||||
plays the one whose *worst* possible answer leaves the fewest candidates
|
||||
behind, so it cannot be handed a reply that tells it little. Both lists are
|
||||
sampled when they are large — the full calculation on a fresh game is over a
|
||||
million scorings for a choice a sample makes just as well.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Pirates and Bulgars
|
||||
|
||||
Two pirates hold a stronghold at the head of a cross-shaped board. Twenty-four
|
||||
Bulgars come at it from the other three arms. Only one side can take pieces, and
|
||||
it is not the big one.
|
||||
|
||||
## Note the caveats about the reconstruction
|
||||
|
||||
**"Dalmatian Pirates and the Volga Bulgars" is Sid Sackson's theming, in *The
|
||||
Book of Classic Board Games*, of the game usually called Asalto** — itself a
|
||||
nineteenth-century descendant of Fox and Geese. That much is attested.
|
||||
|
||||
The book could not be obtained, so **what is implemented here is Asalto**, and
|
||||
where Asalto's own sources disagree a choice had to be made. What follows says
|
||||
which is which, in the same spirit as the Ta Yü document: the game is playable
|
||||
and coherent, and it is not a transcription of a rulebook nobody here has read.
|
||||
|
||||
**Attested** — the shape of the board and its thirty-three points; the
|
||||
stronghold being one nine-point arm; two hunters against twenty-four; the
|
||||
hunters taking by jumping and the crowd being unable to take at all; the crowd
|
||||
winning by filling the stronghold or by penning the hunters in; the hunters
|
||||
winning by cutting the crowd below the number needed to fill it.
|
||||
|
||||
**Chosen here** —
|
||||
|
||||
- **Which side is which.** Sackson's theme names two peoples; nothing available
|
||||
said which of them is the pair and which the crowd. The pirates are the two,
|
||||
on the reasoning that a raiding party is small and a stronghold is what you
|
||||
raid.
|
||||
- **The diagonals.** Boards of this family are printed with diagonals in a
|
||||
pattern rather than everywhere. The pattern used is the usual one: a diagonal
|
||||
exists where the row and column of a point add to an even number, which gives
|
||||
the centre eight lines and its neighbours four.
|
||||
- **Compulsory capture instead of huffing.** Traditionally a hunter who fails to
|
||||
take when it could is *huffed* — removed by the other player. That is the same
|
||||
rule with an extra step and a way to forget to use it, so a pirate that can
|
||||
take simply must. It is an option, and turning it off restores the choice
|
||||
without restoring the punishment.
|
||||
- **Bulgars may not retreat.** Standard for the soldiers in Asalto, and stated
|
||||
here as an option because it is the single rule that most changes how the game
|
||||
feels.
|
||||
- **The draw rule.** No source gives one. Sixty plies with nothing taken and no
|
||||
ground gained is called a draw, because two sides that can each hold a
|
||||
position otherwise have no reason ever to stop.
|
||||
|
||||
## The board
|
||||
|
||||
```
|
||||
c7 d7 e7 ← the stronghold, nine points
|
||||
c6 d6 e6
|
||||
c5 d5 e5
|
||||
a4 b4 c4 d4 e4 f4 g4
|
||||
a3 b3 c3 d3 e3 f3 g3 (the middle band is seven wide)
|
||||
a2 b2 c2 d2 e2 f2 g2
|
||||
c1 d1 e1
|
||||
```
|
||||
|
||||
Thirty-three points: a seven-by-seven grid with the four corners cut away. The
|
||||
top arm — nine points — is the stronghold, which leaves exactly twenty-four
|
||||
points outside it. That is not a coincidence: the board and the pieces are cut
|
||||
to each other, and the game opens with every point occupied except the seven the
|
||||
pirates are not standing on.
|
||||
|
||||
Lines run orthogonally between all neighbouring points, and diagonally where the
|
||||
row and column add to an even number. **The lines the screen draws come from the
|
||||
same table the moves come from**, so what you can see and what you may play
|
||||
cannot disagree.
|
||||
|
||||
## The two sides
|
||||
|
||||
**The Bulgars** — twenty-four. Move one step along a line, towards the
|
||||
stronghold or across it, **never back down the board**. They cannot take
|
||||
anything at all.
|
||||
|
||||
**The pirates** — two. Move one step along a line in any direction, and take by
|
||||
jumping an adjacent Bulgar to the empty point directly beyond. A jump may be
|
||||
followed by another with the same pirate, and then it must be: a chain is one
|
||||
turn, and it cannot be abandoned halfway.
|
||||
|
||||
The Bulgars move first. The pirates are already where they want to be.
|
||||
|
||||
## Winning
|
||||
|
||||
**The Bulgars win** by occupying all nine points of the stronghold, or by
|
||||
leaving the pirates with no legal move.
|
||||
|
||||
**The pirates win** by cutting the Bulgars below nine, at which point there are
|
||||
not enough of them left to fill the stronghold however long the game runs.
|
||||
|
||||
## What the computer does
|
||||
|
||||
Negamax with alpha-beta pruning and iterative deepening, over the same rules
|
||||
engine the game runs on.
|
||||
|
||||
The interesting part of a hunt game is that the two sides want opposite shapes
|
||||
out of the same board, and one evaluation has to speak for both. The terms are
|
||||
written from the Bulgars' point of view and negated for the pirates, so the two
|
||||
cannot drift apart and start valuing the same position differently.
|
||||
|
||||
What it counts: how many Bulgars are left and how far up the board they have
|
||||
come; how much of the stronghold they hold; how much room the pirates have to
|
||||
work in; and — the term that does the most work — **how many Bulgars are
|
||||
standing where a pirate could jump them**. A Bulgar with an empty point behind
|
||||
it on a line is a Bulgar about to be taken, and not being jumpable is most of
|
||||
staying alive in this game.
|
||||
Reference in New Issue
Block a user