catch { snail }

Odd Bot Out: a party game where GPT-3 hides among your relatives

December 29, 2021

Between the cheese course and the board games this Christmas, I made my family play against a language model, and I’d like to report the results as if they were science.

The game is embarrassingly simple. Everyone gets a question on their phone — “what’s the worst gift you ever received?”, “describe your morning in four words”, “invent a rule for this dinner”. Everyone types an answer. The server quietly adds one more answer, written by GPT-3. All answers appear shuffled on the TV, and everyone votes for which one is the bot. Bot survives → bot scores. Bot caught → humans score.

I called it Odd Bot Out. One evening of Flask, a phone-friendly page, a davinci completion call, and a shared screen. Now that the API waitlist is gone, anyone can run this at their own dinner table for pennies — a full evening of rounds cost me about forty cents.

Version 1: instant humiliation

My first prompt was the obvious one — “Answer the following question like a human player in a party game” — and the bot was caught eight rounds out of eight. Unanimously. My mother, who calls WhatsApp “the WhatsApp,” identified the machine in under three seconds.

Looking at the transcripts, the tells were brutal:

  • It wrote essays. Humans, typing on phones with wine in hand, answer “socks. again.” The model produced three elegant sentences with a topic and a conclusion.
  • Immaculate punctuation. Capital letters, commas, a closing period. Nobody at my table has used a capital letter on a phone since 2015.
  • It answered the question. Genuinely, helpfully, on-topic. Humans dodge, riff, or use the answer box to bully their brother.
  • It belonged nowhere. Every human answer leaked the room — the dog barking during dinner, dad’s parking incident, a running joke from 2009. The bot’s answers were universal, and universal turned out to be the strongest tell of all.

Version 2: engineering mediocrity

So the fix wasn’t making the model smarter. It was making it lazier. The winning prompt contains no instructions at all — just a fake transcript of the game itself: a few questions, each followed by short, lowercase, slightly disappointing “player answers” I wrote by hand. The whole bot is this:

import openai

FAKE_TRANSCRIPT = """Q: what's your secret talent?
A: parallel parking. thats it
Q: describe your job badly
A: i move numbers between boxes
Q: whats always in your fridge?
A: mustard and regret
Q: best thing about winter?
A: it ends
"""

def degrade(text):
    """Essays and punctuation are how it gets caught."""
    text = text.strip().lower()
    return text[:-1] if text.endswith(".") else text

def ask_bot(question):
    resp = openai.Completion.create(
        engine="davinci",
        prompt=FAKE_TRANSCRIPT + f"Q: {question}\nA:",
        max_tokens=16,          # physically cannot write an essay
        temperature=0.9,        # some chaos, it's a party
        stop=["\n", "Q:"],      # one answer, then shut up
    )
    return degrade(resp.choices[0].text)

Note what’s not here: no instructions, no “pretend to be human,” no persona. Plain davinci is a continuation engine, so the fake transcript does everything — it establishes the format, the register, and the level of effort, and the model simply continues the document it finds itself in. Every one of my hand-written fake answers is doing more work than a billion parameters.

I drew one ethical line for myself: degrade() deletes polish, but no injecting fake typos. If it survives, it should survive by register, not by costume.

Result across the rest of the holidays, 23 rounds with five to seven players: the bot survived 10 — and twice a human got the most bot-votes instead. My uncle was declared a machine for answering too cleverly. He has not recovered.

What the scoreboard actually measures

Here’s what the game taught me, and I think it generalizes beyond my living room.

We casually treat “passes for human” as a summit — as if text becomes human-like at the top of some quality scale. The party game says the opposite: the bot got caught whenever it was too good, and survived by being unremarkable. Fluency, structure, correctness — those read as machine. Laziness, fragments, mild rudeness — those read as person. In a room of casual humans, the Turing test is not a test of intelligence. It’s a test of calibrated mediocrity, and calibrating mediocrity is a real engineering problem: my prompt’s fake lazy answers were doing more work than the 175 billion parameters.

The tell the model never beat, though, was placelessness. I could match the register of a tired human, but not the address of one. Human answers had a stake in that specific room on that specific evening; the model’s best efforts were from nowhere, for anyone. The final round’s question was “invent a rule for this dinner,” and the winning human answer referenced the gravy incident from an hour earlier. No amount of temperature gets you the gravy incident. Context isn’t flavor — it’s identity, and the model’s only context was whatever I smuggled into the prompt.

Which suggests the uncomfortable corollary: give the machine the gravy incident — feed the room’s chatter into the prompt — and that last tell starts to dissolve. I tested a mild version, seeding tonight’s earlier answers into the context, and the bot immediately got better at blending. I stopped there, partly because it felt like enough, partly because it was genuinely a little eerie, and partly because it was time for dessert.

Recipe, if you want it

The whole server is small enough to read over coffee. One route collects answers, one builds the board by sneaking the bot in and shuffling, one counts votes:

import random
from flask import Flask, request, jsonify

app = Flask(__name__)
game = {"question": "", "answers": {}, "board": [], "votes": {}, "scores": {}}

@app.route("/ask", methods=["POST"])           # host sets the question
def ask():
    game.update(question=request.form["q"], answers={}, votes={})
    return "", 204

@app.route("/answer", methods=["POST"])        # players submit from phones
def answer():
    game["answers"][request.form["player"]] = request.form["text"]
    return "", 204

@app.route("/board")                           # the TV polls this
def board():
    if not game["board"]:
        entries = list(game["answers"].items())
        entries.append(("__bot__", ask_bot(game["question"])))
        random.shuffle(entries)
        game["board"] = entries
    return jsonify([text for _, text in game["board"]])

@app.route("/vote", methods=["POST"])          # index of the suspected bot
def vote():
    game["votes"][request.form["player"]] = int(request.form["idx"])
    return "", 204

@app.route("/reveal")
def reveal():
    bot_idx = next(i for i, (who, _) in enumerate(game["board"]) if who == "__bot__")
    for player, idx in game["votes"].items():
        game["scores"][player] = game["scores"].get(player, 0) + (idx == bot_idx)
    caught = list(game["votes"].values()).count(bot_idx) > len(game["votes"]) / 2
    game["board"] = []
    return jsonify(bot=bot_idx, caught=caught, scores=game["scores"])

A global dict as a database, no auth, race conditions if your family votes aggressively — it’s a living-room toy and proud of it. The front end is one HTML page polling /board; the “TV” is a laptop and an HDMI cable. Rotate who writes the question.

Two warnings from the field: cap the bot at sixteen tokens or it will monologue, and prepare for the real endgame — after round three, the humans start impersonating the bot on purpose, and the game quietly turns into everyone performing their idea of a machine performing a person. That meta-round is the best round. I have notes about what that means, but they can wait for another post.

Final score for the holidays: humans 13, machine 10, uncle still legally a robot.


My personal notes.
I write about code.

© 2026, Built with Gatsby and a tiny Snail