~/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
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]