A Discord word‑similarity chain game powered by an LLM “judge”.
Players start from a seed word and try to reach a goal word using one-word steps.
Every submission is judged for semantic relatedness (plus a little spelling/shape resemblance), and the bot reacts with a 0️⃣–🔟 similarity meter. That number becomes your rating for the play.
- Message must be one word matching:
^[A-Za-z][A-Za-z'\-]*$
(letters, apostrophe, hyphen allowed; no spaces, no emojis)
- No back-to-back plays from the same user. If you try, your message is deleted.
- If you post the same word as the last judged submission, your message is deleted.
- Per-user cooldown is enforced (default 2s). On cooldown, the bot reacts with 🕒.
- Bot calls OpenAI to judge similarity between the previous accepted word and your candidate.
- A play is accepted if:
- the model returns
decision: "pass", or - the numeric similarity score is ≥
SIMILARITY_THRESHOLD.
- the model returns
- Every judged message receives a rating from 0–10 (based on similarity meter emoji).
- Global leaderboard (
scores) uses sum of ratings across all time (with one exception):- Submitting the seed as the first word does not add points.
- End-of-round summary shows average rating per user for that round:
avg = sum(rating) / count(plays)(includes fails)
- A round starts with
last_word = seed. - When someone submits the goal (case-insensitive), the bot posts:
- the full accepted chain for the round
- a scoreboard (avg rating)
- pings participants (up to 20)
- Then it resets state and auto-restarts in 10 seconds.
All commands are under the parent command (default: !zuzu).
Prefix and parent command are configurable via environment variables.
!zuzu start
Starts a new round and pins the game to the current channel (unless already set)!zuzu stop
Stops the game in the channel!zuzu reset
Resets the round progress clock (new start will post a new seed→goal)!zuzu set-threshold <0-1>
SetsSIMILARITY_THRESHOLD(in-memory/env; restart to persist if desired)!zuzu set-cooldown <seconds>
SetsCOOLDOWN_SECONDS(0–3600)!zuzu set-model <model-name>
Sets the OpenAI model name used by the judge!zuzu set-channel [#channel]
Restricts bot/game to one channel per guild
!zuzu rules
Shows how to play and current threshold/cooldown!zuzu score
Shows your all-time rating total!zuzu leaderboard [N]
Shows top N players by all-time rating total!zuzu status
Shows current config + seed/goal + round progress + current chain!zuzu last
Shows last accepted word and who played it (note: see “Known issues” below)
- Python 3.10+ recommended
- Discord.py 2.4+
- OpenAI Python SDK 1.40+
- a Discord bot token + OpenAI API key
Dependencies are in requirements.txt:
discord.py>=2.4
aiosqlite>=0.20
python-dotenv>=1.0
openai>=1.40- Go to the Discord Developer Portal
- Create an app, add a bot, copy the bot token
- Enable Privileged Gateway Intents → Message Content (required for reading normal chat messages)
Copy .env.example to .env and fill in values:
cp .env.example .envKey settings:
| Variable | Default | What it does |
|---|---|---|
DISCORD_TOKEN |
(none) | Discord bot token |
OPENAI_API_KEY |
(none) | OpenAI API key for judging |
BOT_PREFIX |
! |
Command prefix |
PARENT_COMMAND |
zuzu |
Parent command group name |
DB_PATH |
./data/bot.sqlite |
SQLite database path |
MESSAGE_CONTENT_INTENT |
1 |
Must be 1 to read messages |
SIMILARITY_THRESHOLD |
0.38 |
Accept threshold (0–1) |
NEAR_MISS_BAND |
0.08 |
Display-only band for status/rules |
COOLDOWN_SECONDS |
2 |
Per-user cooldown |
MODEL_NAME |
gpt-4o-mini |
OpenAI model used by judge |
OPENAI_TIMEOUT_SECONDS |
15 |
Request timeout |
OPENAI_RETRIES |
2 |
Retries on transient errors |
SEED_GOALS_JSON |
data/seed_goal.json |
Seed→goal list source |
DEBUG_ZUZU |
1 |
Print debug logs when non-zero |
CRAFTS_PER_ROUND |
100 |
Target count shown in status |
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
pip install -r requirements.txt
python bot.pyZuzu supports a configurable pool of seed/goal pairs.
SEED_GOALS_JSON can be either:
- JSON text (directly in the env var), or
- a path to a JSON file (relative or absolute)
Accepted formats:
[
["water", "ocean"],
["cat", "tiger"]
]{
"pairs": [
["book", "library"],
["snow", "winter"]
]
}Rules:
- Pairs must be 2-element arrays
- Each item must be a single word (no spaces)
The bot tracks which pairs have been used per guild and avoids repeats until the list is exhausted.
Usage is stored in:
- DB table:
used_seed_goals - JSON mirror (default):
./data/used_seed_goals.json(configurable viaUSED_PAIRS_JSON)
Zuzu uses SQLite for persistence.
Primary tables:
game_state
Per guild+channel: active flag, last word, last user, round start time, seed, goalscores
Per guild+user: all-time points (sum of ratings)submissions
Every judged message: word, ok/fail, rating, timestampguild_settings
Allowed channel per guildused_seed_goals
Which seed→goal pairs have been used per guild
DB initializes and performs lightweight migrations automatically on startup (utils/db.py).
MIT. See LICENSE.