leptons1618@anishgiri.dev:~/case-studies/quark less writeup.md
Quark — A Discord Bot That Guesses What You Are Thinking Of
Five Python files, one library doing the real work, and all the difficulty in the Discord-shaped part.
- published
- September 9, 2026
- read
- 3 min
- stack
- 5 tools
- outcomes
- 6
- media
- —
01The Problem
Akinator's elimination algorithm is a solved problem with a published Python package, so the interesting question was never the guessing. It was how to drive a stateful question-and-answer loop inside a Discord conversation, keep twenty questions legible in a chat feed, and ship a long-lived bot process without a web server anywhere in it.
02The Solution
Wrap the `akinator.py` package in a `discord.py` cog that owns only the interaction. The library decides when it is confident enough to guess; the bot keeps asking until it does, reading four possible answers — yes, no, probably, and back — from a list membership test. Two cogs load into one client, the game and a much smaller hosted-chatbot command, and the whole thing deploys as a Heroku `worker` because the only way in is the Discord gateway.
This is a small project, and that is the honest framing
Five Python files, a Procfile, and a runtime.txt pinned to Python 3.8. It was last touched in February 2022 and it does one silly thing well: you think of a character, and a Discord bot guesses it.
It is worth writing down because of how little is in it.
The whole game is a library call
Akinator’s algorithm is not reimplemented here. The akinator.py package on PyPI owns the elimination logic; Akinator/akinator.py owns the Discord-shaped part, and that is where the actual work is:
aki = ak.Akinator()
q = aki.start_game()
while aki.progression <= 80:
# ask q, take y / n / p / b, feed it back
The loop bound is progression, not a question count — Akinator decides when it is confident enough to guess, and the bot’s job is to keep asking until it says so. Answers are four keys: y, n, p for probably, and b to go back, which is why the input check is a list membership test rather than a parse.
Each question is posted as an embed with a zero-padded counter, so a twenty-question game does not render as Question - 1 through Question - 9 and then Question - 10 three lines lower.
Two cogs, one bot
quark.py is the client: a discord.py Bot with a ! prefix (mentioning it works too), help removed in favour of the embed shown on mention, and discord_components mounted for the button UI.
It loads two extensions. Akinator is the game above. ChatBot is a separate, much smaller thing — a plain requests call to a hosted chatbot API, guarded so it only answers in a guild and not in a DM. Two features, two cogs, and the interesting one does not stop the other from loading.
Deployed as a Heroku worker
worker: python quark.py
A long-lived process rather than a web dyno, because there is no HTTP surface — the only way in is the Discord gateway. runtime.txt pins python-3.8.12.
What I would fix first
The event handlers wrap their bodies in bare except: and except Exception: pass, which will swallow a genuine failure in a command and leave the user staring at silence. A discord.py command that raises already logs and reports itself; the broad catch was belt-and-braces that cost more in debuggability than it bought in uptime.
The second thing is that there is no test and no .env.example, and both would have cost about twenty minutes.
03Achievements
- Drives the `akinator.py` elimination library from inside a Discord cog, so the bot's own logic is just the conversation.
- Loops on the library's `progression` rather than a fixed question count, so the game ends when Akinator is confident rather than on a schedule.
- Four-way input handling — yes, no, probably and back — which is what makes `b` (go back a question) possible at all.
- Each question is posted as an embed with a zero-padded counter, so a long game stays scannable in a chat feed.
- Two independent cogs on one client, so a failure in the chatbot command cannot take the guessing game down with it.
- Deployed as a Heroku `worker` on Python 3.8.12, because there is no HTTP surface to route to.