aboutsummaryrefslogtreecommitdiff

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

  • ClaudePlayer searches 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.
  • NeuralPlayer uses 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.