Forkmate is in alpha: playable, but rough and still changing.
Contributions are welcome!
An online chess game where every game is an append-only event stream, and any game can be forked: rewind to an earlier position, play a different move, and see every branch of the game in one overview.
- Two players play a full, rules-correct game: all legal moves, castling, en passant and promotion.
- Play against the computer (Stockfish): pick the difficulty (Easy, Medium, Hard, Max) and your colour on the home page.
- A game ends by checkmate, stalemate, resignation, agreed draw, threefold repetition, the fifty-move rule or timeout.
- Rewind to any earlier position and play a different move to start a branch. Every branch is drawn in a git-style graph.
A game is a tree of positions, not a line. Rewinding is client-side and writes nothing; playing a move from an earlier position appends an event, and if that position already has a continuation it becomes a new branch. Only the player whose colour is to move at a position may branch from it.
Later: matchmaking and lobbies, Elo ratings, tournaments, engine analysis, variants such as Chess960. See docs/design-brainstorm.md for the roadmap.
CQRS / event sourcing on Phoenix: LiveView sends commands to a Game aggregate (one per game), events go to an EventStore, and projections build Ecto read models and broadcast updates over PubSub.
flowchart TB
ui["LiveView UI"] -->|commands| agg["Game aggregate"]
agg -->|events| es[("EventStore (Postgres)")]
es --> proj["Projections"]
proj --> rm[("Read models (Postgres)")]
proj -->|PubSub| ui
rm --> ui
- Rules: the aggregate calls the
Forkmate.Chess.Enginebehaviour, implemented by a Rust NIF overshakmaty(native/forkmate_chess). - Game tree:
MakeMovecarriesfrom_node_id; a node that already has a child creates a branch.MoveMadecarries SAN, the resulting FEN and clock timestamps, so read models and replay never re-run the rules. - Computer opponent: a bot is just a player id (
bot:stockfish:<level>). TheForkmate.Bots.Playerevent handler plays for bot seats with ordinaryMakeMovecommands, using a small pool of Stockfish processes over UCI. - Two databases:
Forkmate.Repofor read models,Forkmate.EventStorefor the event log. - UI: chess components live in
ForkmateWeb.GameComponentsand are developed in Phoenix Storybook at/storybook.
Requires Elixir, Rust 1.97+ and Docker (for Postgres). Versions are pinned in mise.toml, so mise install sets up the toolchain, including Stockfish.
bin/dev # starts Postgres, runs mix setup, then iex -S mix phx.serverOr, with your own Postgres: mix setup, then mix phx.server. Visit localhost:4000 to start a game, or localhost:4000/storybook for the component library.
The computer opponent needs Stockfish on PATH (or STOCKFISH_PATH); without it, "Play vs computer" reports the opponent as unavailable and everything else still works. In production, the event store reads EVENTSTORE_DATABASE_URL (falling back to DATABASE_URL) and EVENTSTORE_POOL_SIZE.
mix test # needs Postgres up; Stockfish tests are skipped without it
mix test path/to/file_test.exs # a single file
mix precommit # compile, format, credo --strict, cargo clippy/test, mix testRun mix precommit before committing.
- Stockfish (GPL-3.0) plays the computer opponent.
- shakmaty (GPL-3.0-or-later) is the chess rules library, bridged to Elixir with Rustler.
- Phoenix and LiveView, Commanded with EventStore, and Ecto make up the application stack.
- Phoenix Storybook hosts the component library.
- mise pins the toolchain and installs Stockfish.
Forkmate is licensed under the GNU GPL v3 (see LICENSE) because it builds on two GPL projects, shakmaty and Stockfish.
