A Go server that exposes chess as a REST API, with Stockfish behind it for move validation and computer play. The server owns the rules; clients own nothing but the display, which is why three very different ones talk to the same endpoints.
Games are anonymous by default. An account is optional: it records the game against you, keeps it past the 24 hours an anonymous game is kept, and stops anyone else from moving, taking back moves, or resigning for your side.
- Modes
- Human vs computer on the board; human vs human and computer vs computer through the terminal client or the API.
- Endings
- Checkmate, stalemate, resignation, draw by agreement, and automatic draws: dead position, threefold repetition, fifty-move rule.
- Engine
- Stockfish, skill level 0–20, per-move search budget 100–10000 ms.
- Positions
- Any legal FEN — puzzles, endgames, mid-game starts.
- Export
- Move list and current FEN to the clipboard from the board; full PGN with SAN, headers and how the game ended from the terminal client or the API.
- Clients
- Web board, terminal CLI, and the same CLI compiled to WebAssembly.
You are on the onion mirror, which serves static pages only. This needs the live API, so it only works at https://lixen.com.
Click a piece, then its square. A piece picker handles promotion; undo, draw offers and resignation sit beside the board, with live server and storage indicators and a move list that copies to the clipboard. Against the computer you can undo past a resignation and play on; the resignation stays on record.
You are on the onion mirror, which serves static pages only. This needs the live API, so it only works at https://lixen.com.
The terminal client is the Go CLI from the repository, compiled to WebAssembly and drawn by xterm.js. It opens in its own tab rather than in a frame here: the binary is around 10 MB, and there is no reason to spend that on a visitor who only came for the board.
- Start
helpfor the command list, thennewand answer the prompts: player type for each side, engine level and search time for a computer side, an optional starting FEN.- Two players
- Sign in, share the game ID,
joinit from a second client, andpollfor the opponent's moves, draw offers and resignation. - Shared state
- Same API as the board — a game started in one shows up in the other.
- Native build
- The same client runs as a normal terminal program against any instance; the WebAssembly build only swaps the I/O layer.
- Requires
- A modern browser with JavaScript and WebAssembly. WebGL2 draws the terminal when available; otherwise xterm.js falls back to its slower DOM renderer.
Base URL on this site: https://lixen.com/chess/api. JSON in and out, with
Content-Type: application/json on every POST and PUT. Where auth is optional,
send Authorization: Bearer <token> to act as your account.
| Endpoint | Method | Auth | Notes |
|---|---|---|---|
/auth/register | POST | — | Username, password, optional email; returns a token. 5/min per IP. |
/auth/login | POST | — | Username or email, and password. 10/min per IP. |
/auth/me | GET | required | The signed-in user. |
/auth/logout | POST | required | Revokes the session on the server. |
/auth/me | DELETE | required | Deletes your account; send the password again. |
/games | POST | optional | Player type, engine level and search time per colour; optional starting FEN. |
/games/{id} | GET | — | Game state. ?wait=true&moveCount=N long-polls up to 30 s. |
/games/{id}/moves | POST | optional | A UCI move (e2e4, e7e8q), or cccc for the engine’s move. |
/games/{id}/undo | POST | optional | Takes back count plies, default 1. |
/games/{id}/resign | POST | optional | Resigns; the side is inferred when only one is yours. |
/games/{id}/draw | POST | optional | offer, accept or decline; the computer answers from its evaluation. |
/games/{id}/players | PUT | optional | Reconfigures the players mid-game. |
/games/{id}/board | GET | — | The position as an ASCII board. |
/games/{id}/history | GET | — | Durable replay: every ply with UCI, SAN and the resulting FEN. |
/games/{id}/pgn | GET | — | PGN of the game, or up to ?ply=N. |
/games/{id} | DELETE | optional | Unloads the live game; its stored history remains. |
/users/me/games | GET | required | Your stored games, newest first; cursor-paged, filter by status and colour. |
Health is outside the API prefix: GET https://lixen.com/chess/health reports
server and storage status and the running build.
General rate limit is 10 requests a second per IP. A side claimed by an account acts only for that account, so two players cannot take back each other’s moves, and a resignation or agreed draw between them is final. The full reference is doc/api.md.
Transport
Processing
Execute(Command) entry point holds the game logic, so the transport is
swappable. Engine moves go to an async queue and the request returns at once.Long-polling
Engine pool
Persistence
Accounts
Runtime
- Server: Go, Fiber, PostgreSQL 18 (pgx; SQLite until v0.12), Stockfish over UCI
- Rules core: dependency-free Go move generator for SAN, PGN, draw rules and replay verification
- Web client: vanilla JavaScript and CSS, no framework, no build step
- Terminal client: Go → WebAssembly, xterm.js with the WebGL renderer
- Platforms: FreeBSD and Linux; the clients in any modern browser
- Toolchain: Go 1.27, static CGO-free binaries; one instance enforced by a PID lock file
- License: BSD-3-Clause
Build
- Clone
git clone https://github.com/lixenwraith/chess --depth 1- Build
cd chess && make server- Run
bin/chess-server -dsn 'dbname=chess'(Stockfish onPATH; without-dsngames live in memory only)- Deploy
- Scripts for a FreeBSD jail (how it runs here) and for Linux with systemd
are in
deploy/.