←Projects

Spyfall

2025

A real-time multiplayer version of the social deduction game Spyfall that I built as a web app. Players join a room using a code, get assigned a role, and try to figure out who the spy is without giving away the location.

ReactTypeScriptFirebaseTailwind CSSViteGitHub

Spyfall is a social deduction game where everyone gets the same secret location, except for one player: the spy. The spy has to answer questions and blend in without knowing where everyone else is. I wanted to turn the board game into something people could play directly from their phones, without needing the physical cards.

One person creates a room and shares a six-character code with everyone else. Once everyone joins, the host starts a round and each player privately receives their role and, if they are not the spy, the location. Players then take turns asking questions until the timer runs out, after which everyone votes on who they think the spy is.

One round
Join
Deal
Ask
Vote
room
K7MQ2X
open
open
open
open

Maya opens K7MQ2X. The others join on their phones.

01/06
A six-character code puts everyone in the same room. The location is dealt once. Questions run until the timer ends, and the vote is stored on the room.

Each player's screen is slightly different. Most players see the location and their assigned role, while the spy only sees that they are the spy. This way, everyone can use their phone without accidentally revealing the location to the person trying to figure it out.

What each phone shows
Maya
BeachLifeguard
Sam
BeachSurfer
You
BeachTourist
Riley
Spyunknown

Four phones. Nothing has been dealt.

01/05
Three players see Beach and a role. The spy's phone only says they are the spy. If someone names the place out loud, the spy hears it.

Keeping phones in sync

One of the main challenges was making sure every phone stayed in sync throughout the game. I used Firebase Realtime Database to store the game state, with one shared object for each room code. Every player subscribes to that object and builds their screen from the latest state.

Roles are assigned once when a round starts, rather than being recalculated on each device. Each phone then uses the shared game state to decide what to display, keeping the location hidden from the spy.

Things like removing players, ending a round early, and counting votes also update that same object. Initially, I ran into cases where different phones would disagree about which phase the game was in. Having every action update the same shared state helped keep the game consistent.

One object per room
games / K7MQ2X
phaselobby
locationnone
spynone
turnnone
votesnone
Maya
waiting
joined
no role
You
waiting
joined
no role
Riley
waiting
joined
no role

One object, keyed by the room code. Every phone is subscribed.

01/04
Firebase stores the round at games/K7MQ2X. Every phone subscribes, so a start, a turn, or a vote is one write. Maya and you see Beach. Riley's phone hides it.

Implementation

The frontend is built with React and TypeScript, bundled with Vite and styled using Tailwind CSS. Firebase Realtime Database handles the room state and sends updates to connected players.

I have used Spyfall to play actual games with other people, which is where I found and fixed most of the synchronization issues.