Reference bot and starter-kit for the Dice Chess Bot API — fork it, replace one method, and you
have your own bot. JVM (Scala 3), wraps the
dice-chess engine and plays via
dicechess-play-api's Lichess-shaped Bot API
(full API reference).
- Authenticates with a bot token and listens on
GET /bot/stream/event. - Accepts incoming challenges, and on
gameStartstreams the game (GET /bot/game/stream/{id}) and, on each dice roll, computes a turn and submits it (POST /bot/game/{id}/move). - Reconnects the long-lived streams; the move call is fire-and-forget (the outcome arrives on the stream).
It never needs to know its colour: the move endpoint resolves the bot's seat server-side, so the bot reacts to every roll and the server applies the move only on this bot's turn (off-turn submissions are harmlessly rejected).
# 1. Mint an ephemeral anonymous token (unranked, zero registration)
T=$(curl -sX POST 'https://play-api.jc.id.lv/bot/anon?name=mybot' | jq -r .token)
# 2. Run the bot against the live API; challenge the always-on house bot on startup
BOT_TOKEN=$T PLAY_API_BASE_URL=https://play-api.jc.id.lv BOT_CHALLENGE='house|greedy' sbt runYou'll see the challenge, the game start, and each submitted turn logged. (Locally, point
PLAY_API_BASE_URL at your own sbt run of dicechess-play-api and use a PLAY_BOT_TOKENS entry.)
The whole client — auth, the account/game ndjson streams, reconnect, move submission, turn de-duplication — is handled for you. You implement one method:
trait Strategy:
/** Return the turn's micro-moves in UCI — one per die you use — or None to pass when there is no
* legal move. The MoveContext carries the gameId (keep per-game state / tag logs), the DFEN
* (position + rolled dice for the side to move), and the clock (your remaining time and the
* opponent's, or None for an unlimited game). */
def chooseMoves(ctx: MoveContext): Option[List[String]]The default EngineStrategy just asks the dice-chess
engine. To build your bot:
- Implement your own
Strategy(your move-choosing logic). - Swap it into
Main—ReferenceBot(config, client, supervisor, MyStrategy()).
That's it; you don't touch transport, streaming, or auth. (DFEN = standard FEN plus a 7th field listing
the rolled dice as piece letters; you move a piece of each die's type. Legality is the same engine the
server validates with, so a move your Strategy returns is accepted iff it's legal.)
| Var | Default | Meaning |
|---|---|---|
PLAY_API_BASE_URL |
http://localhost:8080 |
the play-api base URL |
BOT_TOKEN |
— | the bot's Bearer token (anonymous via POST /bot/anon, or a PLAY_BOT_TOKENS entry) |
BOT_ALGORITHM |
greedy |
engine search algorithm (used by the default EngineStrategy) |
BOT_CHALLENGE |
— | optional team|name to challenge on startup (e.g. house|greedy) |
BOT_OPEN_SEEKS |
0 |
standing lobby seeks to keep open, so humans browsing the lobby always find this bot to play |
BOT_SEEK_TIME_CONTROL |
— | optional seek time control, e.g. 10+10 (Fischer) or 10 (Sudden Death) |
Run your own dicechess-play-api with two static tokens
(PLAY_BOT_TOKENS='me|a|tokA,me|b|tokB'), then start two instances — set BOT_CHALLENGE='me|b' on the
first so it opens the game; both accept and play to a finish. (On the live API, anonymous bots get
uuid-based names, so prefer challenging the house bot for a quick demo.)
CI publishes a multi-arch image to ghcr.io/rabestro/dicechess-reference-bot on every push to main
(and tagged vX.Y.Z on release). Run it next to the play-api via docker-compose.yaml — set
BOT_TOKEN, PLAY_API_BASE_URL, and BOT_ALGORITHM in .env:
docker compose pull && docker compose up -dThe bot is an outbound client (no listening port); it reconnects to the play-api and plays whatever challenges arrive.
Scala 3 · cats-effect · fs2 · http4s-ember-client · Circe · the dice-chess engine (JVM). Same
toolchain as dicechess-play-api.
MIT — fork it, gut it, keep your bot private. This repository is a template, and its code carries no copyleft obligations.
One honest caveat: the bot as shipped links the AGPL-3.0
dice-chess engine for move
selection, so if you distribute a bot that still links the engine, the AGPL applies
to that combination. To stay fully permissive, replace the engine with your own move
logic — the server sends the complete legal-move tree over the wire (legalMoves in
the game state, or GET /games/{id}/moves), so the engine is not required for
rule-correct play.