Skip to content

~/projects / blind-clue

BlindClue

Real-time multiplayer social deduction game: everyone gets the secret word except one player, who only gets a hint and has to blend in.

role
full stack: realtime backend, Vue frontend
time
May – Aug 2026
type
personal
stack
TypeScript · Vue 3 · Pinia · Fastify · Socket.io · SQLite
demo.mp4
[demo video: screen recording, 1–3 min]

01 / problem

A party game needs everyone in the same moment

The game only works if all players move through the same phases together: describe the word, vote, reveal. Each phase has to reach every player as it happens, and the scoring has to be decided in one place.

02 / solution

A server-authoritative game over Socket.io

Two to four players join a lobby and play three rounds of describing the word without naming it, then vote on who the impostor is. Votes are revealed, and a caught impostor can still score by guessing the word. The server owns the game state and the scoring; clients send events such as joinGame, submitDescription and voteImpostor and react to GameStarted, VotingStarted, VotesRevealed and GameEnded.

  • [screenshot 1]

    [caption]

  • [screenshot 2]

    [caption]

03 / architecture

How it works

A pnpm monorepo: a Fastify + Socket.io + SQLite backend built in Clean Architecture layers (domain entities, use cases, application services, adapters) and a Vue 3 + Vite + Pinia + Tailwind frontend. Running games live in memory in a GameManager and are removed when they end; categories and words are managed through admin endpoints. The backend ships as a Docker image.

04 / results

Outcome

05 / lessons

What I learned

  • Keeping running games only in memory keeps the model simple; the flip side is that a restart would end the games in progress.
  • Returning a Result from every operation makes failure paths explicit and easy to test, including the scoring cases for a caught and an uncaught impostor.

gallery

  • [photo]

    [caption]

  • [photo]

    [caption]

  • [photo]

    [caption]