Tigers and Sheep
A terminal version of Tigers and Sheep, a hunt game in the style of Bagh-Chal, written in Rust with ratatui. Play against a friend, a minimax computer player, or a neural network that learnt the game by playing itself.
Rules
The board is a 5×5 grid of points joined by lines. Every point has horizontal and vertical lines, and diagonals run through every other point (those where x + y is even).
- Tigers start in the four corners. A tiger steps one point along a line, or jumps two points along a line over a sheep, which kills that sheep.
- Sheep start off the board, 20 of them. Each sheep turn places one sheep on an empty point, until all 20 are placed. After that, a sheep turn moves one sheep a step along a line.
- Sheep move first, then the sides take turns.
- Tigers win by killing 5 sheep. Either side wins if the other has no legal move on its turn, so sheep win by trapping every tiger.
- Draw: the game is drawn when the same position, with the same side to move, comes up for the third time, as in chess.
Running
The toolchain comes from a Nix flake. With direnv it loads by itself; otherwise:
nix develop
Then start a game:
cargo run --release
With plain Rust (no Nix), cargo from a recent stable toolchain works too.
Controls
- Click an empty point to place a sheep, or click a piece and then one of its highlighted destinations.
- Click a move in the history to go back to just before it.
- q, Esc or Ctrl+C quits.
By default the computer plays itself. Pick the players with --sheep and --tiger: human
(you, with the mouse), claude (the minimax computer player), or a weights file for a neural
player. --time sets how many seconds a computer player may think per move (default 2).
cargo run --release -- --sheep human --tiger claude --time 5
--help lists every command and option.
Computer players
ClaudePlayersearches with alpha-beta minimax and iterative deepening. It uses a transposition table shared between symmetric boards, and killer moves. It scores positions with a hand-written evaluation: dead sheep, sheep left open to a jump, and trapped tigers.NeuralPlayeruses the same search, but scores positions with a small neural network. The network only sees the raw position (where the pieces are, sheep left to place, and whose move it is) and learns everything else by self-play.
Training the neural player
cargo run --release -- train [generations] [games per generation] [out dir] [--from <weights>]
The defaults are 50 generations of 500 games, saved to weights/. Each generation, the network
plays itself and learns from how those games went. Every 10 generations (--match-every) it also
plays matches against the generation it last tested (10 generations back) and against ClaudePlayer. The score against ClaudePlayer is the best guide to progress.
ClaudePlayer is only a yardstick, never something the network learns from.
Each generation is saved as gen-NNN.bin, and the newest as latest.bin. Running train again
carries on from the newest generation in the folder. --from starts from any weights file instead.
The other training settings (search depth, ε, λ, epochs and so on) are options too; see
train --help.
Then play against it by giving its weights file as one of the players:
cargo run --release -- --sheep human --tiger weights/latest.bin
Development
cargo test
cargo clippy
cargo run --profile profiling -- bench [depth]
bench searches six fixed positions to a fixed depth and prints each move, score and time. Add
--features search-stats to also count nodes, cache hits and cutoffs. plans.org keeps a history
of the optimisations and training experiments, with their results. docs/
explains how the search and training work, how the code is structured, how to build it, and how to
add a new player.
