GuessIt
Family-Feud-style guessing game. Participants submit short free‑text answers; the system groups similar answers into categories (with optional LLM clustering), selects the most-mentioned category, then reveals a board of predefined answers with scores. The round repeats until all answers are revealed or the team hits the fail limit.
Example structure
{
"type": "GuessIt",
"data": {
"text": "Nenne einen Bereich, wo man KI einsetzen kann.",
"answers": [
{ "position": 1, "answer": "Bildbearbeitung", "score": 35, "synonyms": ["Foto", "Bilderkennung"] },
{ "position": 2, "answer": "Kundensupport", "score": 25, "synonyms": ["Support", "Chatbot"] },
{ "position": 3, "answer": "Medizin", "score": 20, "synonyms": ["Gesundheit", "Diagnose"] },
{ "position": 4, "answer": "Bildung", "score": 15, "synonyms": ["Lehre", "Lernen"] },
{ "position": 5, "answer": "Finanzen", "score": 5, "synonyms": ["Bank", "Trading"] }
],
"answerTime": 180,
"maxFails": 3
}
}
Properties
| Key | Type | Default | Description |
|---|---|---|---|
data.text | string | — | The question prompt displayed to players. |
data.answers | array | — | Predefined board answers. Each entry: { position: 1..N, answer: string, score: number, synonyms?: string[] }. Scores typically sum to 100 to symbolize 100 AI's. |
data.answerTime | number | 180 | Seconds for the guess phase; > 0 enables auto‑advance on timeout. |
data.maxFails | number | 3 | Number of failed reveals allowed before game over. |
Synonyms make matching more forgiving. The server uses normalization + fuzzy matching; short answers can match via token or similarity. Unknown answers can be clustered via LLM into a new or existing category.
Phases (server index)
- Guess (0): Players submit free text answer. Auto‑advance when the timer ends or all connected players have valid answers.
- Tally (1): Host reviews categories; if there’s a tie, host chooses one.
- Reveal Answer (2): Host sees the selected category that will be checked.
- Reveal GameBoard (3): If the selected category matches a board answer, reveal it and add its score; otherwise increment fails.
- Post‑Reveal (4): Either continue to next round (reset inputs/whiteboard) or transition to game‑over states.
- Game Over (5): If max fails reached, reveal remaining answers flagged as revealed-by-gameover.
- Scoreboard (6): Final results: total score (0–100) and number of correct reveals.
Client protocol
OpCodes
ADD (0): Submit an answerMOVE (3): Drag whiteboard notes (host; internal from Whiteboard)SELECT_CATEGORY (5): Host picks a category when there’s a tieREPORT_WRONG_CATEGORY (6): Player reports a wrong categorization
Client → Server payloads
Submit answer (participants):
{
"opCode": 0,
"data": { "text": "%3Canswer%3E" }
}
Select tied category (host):
{
"opCode": 5,
"data": { "category": "%3CCategory%3E" }
}
Report wrong categorization (participant):
{
"opCode": 6,
"data": { "text": "<answer>", "category": "<shown category>" }
}
Server → Client update
interface GuessItData {
text: string; // question
index: 0|1|2|3|4|5|6; // phase index
answerTimeLeft?: number|null; // only in index 0
clusteredAnswer?: string|null; // index 2 and later
gameBoard?: {
entries: Array<{ position: number; answer: string|null; score: number|null; isRevealed: boolean; revealedByGameOver?: boolean }>;
currentScore: number;
currentFails: number;
maxFails: number;
revealedCount: number;
};
whiteboard?: any[]; // category + answer notes (host-visible)
tiedCategories?: string[]; // index 1 when tie detected
waitingForCategorySelection?: boolean;
scoreboard?: { totalScore: number; correctAnswers: number } | null; // index 6
// Personalized to each participant during early phases:
needsSecondAnswer?: boolean; // if answer invalid or already revealed
rejectionReason?: 'already_revealed'|'invalid_answer'|null;
playerSubmission?: { answer: string; category: string|null; isValid: boolean };
}
Behavior highlights
- Inputs are encoded on send. The server decodes, normalizes (e.g., accents, case), and matches against
answersor theirsynonyms. - Unknown answers are sent to an LLM clustering endpoint (optional); while pending, the participant sees a waiting state.
- The whiteboard shows categories and grouped answers, with counts and colors per category.
- Auto‑advance logic considers only connected, non‑host participants with valid answers.
- Fail reasons also create whiteboard categories like “Fehlantwort” or “Bereits aufgedeckt”.
Use cases
- Warm‑up brainstorming, icebreakers, opinion polls.
- Debriefs that converge open inputs onto canonical targets.
- Classroom or workshop games mirroring Family Feud mechanics.
LLM Integration
- Cluster endpoint: the web app calls
/api/llm/guessit/cluster(Next.js API route) which in turn forwards the result to Nakama via RPCguessit_llm_cluster_result. - Secure with
NEXT_LLM_SHARED_SECRET(present in both apps) so only trusted calls are accepted.
Environment Variables
Next.js (apps/web/.env.local)
OPENAI_API_KEY=<your-openai-key>
NEXT_LLM_SHARED_SECRET=<shared-secret>
NAKAMA_LLM_RPC_URL=http://localhost:7350 (local) | http://nakama:7350 (docker)
NAKAMA_HTTP_KEY=<nakama-http-key>
OPENAI_API_URL=<custom-openai-endpoint>
OPENAI_CLUSTER_MODEL=<default-model-name-for-clustering>
OPENAI_GENERATE_MODEL=<default-model-name-for-generation>
Nakama (apps/nakama/.env)
NEXT_LLM_URL_CLUSTER=http://host.docker.internal:3000/api/llm/guessit/cluster (local) | http://next-js:3000/api/llm/guessit/cluster (docker)
NEXT_LLM_URL_GENERATE=http://host.docker.internal:3000/api/llm/guessit/generate (local) | http://next-js:3000/api/llm/guessit/generate (docker)
NEXT_LLM_SHARED_SECRET=<shared-secret>
The NEXT_LLM_SHARED_SECRET must match between Nakama and Next.js. NAKAMA_LLM_RPC_URL points to your Nakama server; the GuessIt web API forwards results to the RPC endpoints using NAKAMA_HTTP_KEY. The Nakama backend calls the web clustering/generating endpoint defined by NEXT_LLM_URL_CLUSTER/GENERATE.