A real-time multiplayer Tambola (Indian bingo/housie) game server. One GenServer process per active game, capable of hosting 100k+ concurrent games on a single machine.
Backend: Elixir/Phoenix, Phoenix Channels, Phoenix Presence Frontend: Vue 3, TypeScript, Pinia, Vite, Tailwind CSS
- Erlang/OTP 27+
- Elixir 1.18+
- Node.js 20+ and npm
- PostgreSQL 15+
# Clone and setup
git clone <repo-url> && cd mocha
mix setup # deps.get + ecto.setup + assets.setup
# Start development server
mix phx.server # Phoenix on :4000, Vite on :5173Visit localhost:4000 in your browser.
Pure Elixir — no web dependency. One GenServer per active game holds all in-memory state (board, tickets, struck numbers, prizes, bogeys). State changes are broadcast via Phoenix.PubSub as {event_atom, payload} tuples.
Phoenix Channels provide the real-time layer:
- UserSocket authenticates via bearer token
- GameChannel translates PubSub events to JSON pushes, handles player actions (strike, claim, chat, reaction)
- Presence tracks online players per game
Vue 3 SPA with hash-based routing:
- Pinia stores manage auth, theme, game state, chat, and presence
useChannelcomposable wraps the Phoenix socket/channel lifecycle- Pages: Auth, Home, Profile, NewGame, GamePlay, HostDashboard
- Google OAuth (redirects with bearer token)
- Magic link email (passwordless login)
- Bearer tokens for API + WebSocket auth
# Development
mix phx.server # start everything
iex -S mix phx.server # with interactive shell
# Backend tests
mix test # 79 tests + property tests
mix test test/mocha/game/server_test.exs # single file
# Frontend tests
cd assets && npx vitest run # unit tests
cd assets && npx tsc --noEmit # type check
# Production build
cd assets && npm run build # outputs to priv/static/Environment variables for OAuth:
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secretDatabase config is in config/dev.exs (defaults to postgres:postgres@localhost/mocha_dev).