Skip to content

ulrik-s/watten

Repository files navigation

Watten

A Watten card-game trainer (Tyrolean / 33-card variant) written in Rust with a WebAssembly frontend. The bots use a memoized legal-completion search to pick their cards and to estimate per-card win rates for the human player.

Crate features

  • 33-card Watten deck (four suits × Seven..Ace plus the Weli)
  • Card, Suit, Rank, GameResult, Player, GameState
  • card_strength enforcing Rechte > Striker rank > trump > lead > rest
  • Seeing-player must-follow-trump rule (GameState::allowed_indices)
  • GameState::play_round / play_round_logged (non-interactive)
  • start_round_interactive + human_play + advance_bots (interactive)
  • raise_round(team) / concede_round(team) for "raising" and "geh"
  • Two move evaluators selectable via set_evaluator:
    • Evaluator::Search (default): memoized enumeration of all legal trick completions from the current state. Sub-second per round.
    • Evaluator::Database: legacy 120⁴ brute-force, kept as a benchmark fallback. cargo full exercises it.

Running

Quick start (play locally)

You need:

  • Rust (stable) + the wasm32-unknown-unknown target (rustup target add wasm32-unknown-unknown)
  • wasm-pack (cargo install wasm-pack)
  • Node.js 20+ (CI builds on Node 20)

Then, from the repository root:

make dev

Open http://localhost:5173 in your browser. The Vite dev server hot-reloads both the React frontend and the Rust → WASM bundle.

Equivalent without make:

npm start

Tests

cargo test           # fast Rust unit + integration tests
cargo full           # also runs the heavy database population tests

Frontend unit / integration tests (Vitest + Testing Library, builds wasm first):

cd frontend
npm install
npm test             # Vitest
npm run test:watch   # Vitest in watch mode

End-to-end browser tests (Playwright on Chromium, Firefox and WebKit):

cd frontend
npx playwright install   # first time only
npm run test:e2e

Linting & formatting

The repo is linted and formatted by a layered toolchain, all gated in CI:

Area Tools
Rust rustfmt, clippy (with pedantic), cargo-deny (advisories/licenses/bans/sources), cargo-machete (unused deps)
Front ESLint (typescript-eslint type-checked, react, react-hooks, react-refresh, jsx-a11y), Prettier, tsc --noEmit
Repo .editorconfig, lefthook git hooks

Run everything the way CI does, from the repository root:

make lint            # all linters (Rust + JS), no changes made
make fmt             # auto-format Rust (cargo fmt) + JS (prettier)
make audit           # cargo deny check + cargo machete

The lint config lives in Cargo.toml ([lints]), deny.toml, frontend/eslint.config.js, and frontend/.prettierrc.json. The Rust supply-chain tools install with:

cargo install cargo-deny cargo-machete   # or download prebuilt binaries

Git hooks

lefthook runs the fast checks before each commit (rustfmt, clippy, ESLint, Prettier) and the test suite before each push. It installs automatically via the root npm install (prepare script); to wire it up manually:

make hooks           # npx lefthook install

Hook behaviour is defined in lefthook.yml.

Code coverage

From the repository root:

npm run coverage         # Rust (cargo-llvm-cov) + JS (Vitest v8)
npm run coverage:rust    # Rust only — HTML in coverage-rust/html
npm run coverage:js      # JS only   — HTML in frontend/coverage

cargo llvm-cov requires the llvm-tools-preview rustup component and the cargo-llvm-cov cargo subcommand:

rustup component add llvm-tools-preview
cargo install cargo-llvm-cov

Deployment

Pushes to main trigger .github/workflows/gh-pages.yml, which builds the Vite bundle with VITE_BASE set to the repo path and deploys it to GitHub Pages. Enable Pages in repository settings → PagesSource: GitHub Actions once, and subsequent pushes deploy automatically. The deployed URL is reported in each workflow run.

CLI

cargo run

Plays a game in the terminal. Set verbose = true on GameState to see narration (already enabled by the CLI binary).

WebAssembly frontend

You will need wasm-pack and Node.js. From the repository root:

npm start

This installs JS dependencies, runs wasm-pack build --target web, and starts Vite. Open http://localhost:5173. The UI shows your hand with a per-card win-rate estimate (wins / total over all legal completions); opponent hand sizes; the current trick; the running scores; and Raise (+1) and Concede round buttons.

To build a production bundle and preview it:

cd frontend
npm run build
npm run serve

To run the Playwright UI tests (Firefox only):

cd frontend
npm run test:ui

Rules summary

  • Trump (Schlag) is the suit of the top card of the dealer's pile (the first card dealt to the dealer).
  • Striker rank (Weisen) is the rank of the top card of the forehand's pile (the first card dealt to the player after the dealer).
  • Card strength: Rechte (the trump-suit + striker-rank card) > Weli > any Striker > any trump > lead suit > rest. First-played wins ties between equal-strength cards (card_strength in src/game.rs).
  • Seeing players (dealer + forehand) must play a trump or striker when trump is led if they have one (enforced by GameState::allowed_indices).

Status / known limitations

  • The Critical cards (Maxi/Belli/Spitz) of Bavarian Watten are not modelled — this is the Tyrolean/generic 33-card variant.
  • Winning score defaults to 15 (game::WINNING_POINTS). Not yet runtime- configurable from the UI.

About

Watten card game trainer

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages