Collaborative full stack project · Realtime game systems

DuckyArena

Realtime 3v3 Duckie battles where hidden lane strategy and technical answers decide a server-authoritative best-of-three match.

Six authenticated players join a private room, form BLUE and RED teams, select distinct Duckies, secretly claim three lanes and reveal three simultaneous 1v1 matchups. Correctness, response timing and Signature Abilities feed a deterministic combat engine; the first team to win two lanes takes the match.

  • React
  • Vite
  • Node.js
  • Express
  • Socket.IO
  • PostgreSQL
  • Docker
  • GitHub Actions
DuckyArena combat screen with two Duckies, HP bars, technical question, four answers, ability and lane score.
A realtime technical-question duel with server-resolved HP, Signature Ability state and the global three-lane score.

DuckyArena is a competitive realtime 3v3 web game built from a collaborative academic foundation and later taken through a structured professionalization pass.

The current slice connects a snapshot-driven React interface to Express, Socket.IO and PostgreSQL. It covers the complete six-player private-room flow from Duckie selection and hidden TOP, MID and BOTTOM deployment through combat, persisted results and profile statistics.

DuckyArena character selection showing four Duckies, roles, abilities and team rosters.
Four role-defined Duckies and their Signature Abilities presented within the live BLUE/RED team selection state.

The academic base contained useful full stack pieces, but they did not yet communicate one dependable game experience. The challenge was to connect authenticated identity, hidden pregame decisions, three concurrent combats and durable results without allowing clients to become gameplay authorities.

The professionalization pass established a server-authoritative match lifecycle, separated persistent REST concerns from live Socket.IO gameplay and projected only player-authorized information into each snapshot. A focused presentation pass then gave the flow a coherent farm-arena identity without changing game rules.

BLUE and RED each contain three authenticated players. Every team assigns exactly one player to TOP, MID and BOTTOM; rival assignments remain hidden until all lanes are locked and the server reveals the three independent duels. The first team to win two lanes wins the match, and an unresolved third lane becomes CANCELLED.

  1. Authenticate
  2. Create or join a private room
  3. Assemble six players
  4. Choose a Duckie
  5. Lock a lane in secret
  6. Reveal three matchups
  7. Resolve realtime combat
  8. Persist result and profile statistics
DuckyArena farm arena with three selectable lanes, BLUE tokens and hidden RED deployment.
BLUE deploys across Orchard, Farmyard and Pond while rival assignments remain hidden until the server reveal.
DuckyArena matchup reveal showing six players paired across Orchard, Farmyard and Pond lanes.
The authoritative reveal exposes all three BLUE vs RED lane matchups and emphasizes the current player's duel.

React renders catalog, room, match and profile projections and sends player intentions. Express handles durable identity, profile and catalog concerns; Socket.IO manages the authenticated live match lifecycle. The backend remains the authority for every gameplay outcome.

  1. React + ViteRenders per-player snapshots and submits player intentions.
  2. Express RESTHandles durable identity, profile and catalog concerns.
  3. Socket.IOCoordinates authenticated rooms, pregame and live combat.
  4. Domain modulesResolve deterministic combat and authoritative match state.
  5. PostgreSQLPersists identity, completed results and profile statistics.
  • Authenticated player identity is independent from socket IDs.
  • Per-player projections preserve rival information before reveal.
  • Three isolated lane combats resolve into one best-of-three match.
  • Active rooms, timers and combat live in one Node.js process.

Built on a collaborative academic foundation, I took the current implementation through a structured professionalization pass spanning:

Realtime backend and domain

Stabilized authenticated Socket.IO identity, private-room and pregame lifecycles, hidden-information projections, three-lane orchestration and deterministic server-authoritative combat.

Data and persistence

Integrated bcrypt/JWT identity with PostgreSQL and transactional, idempotent storage for one match and its six participants, plus authenticated aggregate profile statistics.

Frontend and gameplay integration

Connected the React flow from authentication and room entry through selection, reveal, combat, result and profile, using server snapshots as the source of truth.

Quality and reproducibility

Consolidated regression coverage, GitHub Actions, PostgreSQL initialization and migration checks, Dockerized services, health checks and a stack smoke test.

Visual and game feel

Defined the visual system and presentation layer for four Duckies, hidden lane strategy, matchup reveal, duel combat and match results while keeping gameplay and backend rules frozen.

Server authority

Decision: Clients submit intentions; the backend validates and resolves outcomes.

Trade-off: The backend owns more lifecycle complexity and must remain available during a match.

REST and Socket.IO split

Decision: REST serves durable concerns; Socket.IO serves the live match.

Trade-off: Authentication and errors must remain aligned across both boundaries.

Player identity beyond sockets

Decision: Sockets bind to authenticated profiles rather than becoming player identities.

Trade-off: Membership and reconnection require explicit identity reconciliation.

Per-player projections

Decision: Snapshots contain only information each player may know.

Trade-off: Projection code and secrecy regression tests add complexity.

Deterministic combat

Decision: Canonical inputs and explicit rules resolve answers, timing, abilities and sudden death.

Trade-off: Visual feedback must follow authoritative snapshots rather than predict effects.

Live memory, durable results

Decision: Active combat remains in one process; completed results persist in PostgreSQL.

Trade-off: A process restart loses active matches and the live state cannot scale horizontally.

The functional product initially communicated its quiz mechanics more strongly than its competitive-game structure. The presentation pass introduced four coherent Duckie identities, a fighting-game-inspired character select, an illustrated three-lane farm arena, authoritative matchup reveal, duel-focused combat HUD and a conclusive best-of-three result screen.

Assets describe the world. React describes current state. Backend decides truth. Visual feedback amplifies authoritative events but never changes damage, timing, cooldowns, secrecy or match resolution.

The finished slice is supported by automated backend, frontend, database and delivery checks plus one manually verified real six-player flow.

  • 64 backend tests covering authentication, pregame, secrecy, combat, abilities, three-lane resolution, reconnect, persistence and statistics.
  • Backend lint, frontend lint and production build checks.
  • GitHub Actions jobs for backend, frontend, PostgreSQL integration and Docker reproducibility.
  • Clean PostgreSQL initialization and explicit historical migration verification.
  • Docker Compose health checks and an automated stack smoke test.
  • Five captures from one authoritative match verified with six real authenticated players.
  • Browser E2E is not automated; the full visual flow was verified through targeted real-client and harness checks.

Outcomes

  • Connected the academic components into one coherent end-to-end product slice.
  • Established explicit authority and secrecy boundaries for realtime multiplayer interactions.
  • Made completed results durable and profile statistics queryable after Node.js restarts.
  • Turned local setup and critical validation into reproducible Docker and CI workflows.

Architectural limits

  • Matches require exactly six players in private rooms.
  • Active live state exists in one Node.js process.
  • A process restart loses active matches.
  • The realtime layer does not support horizontal scaling.
  • No public deployment is available.
DuckyArena victory result showing final BLUE and RED score, three lane outcomes and saved match status.
The match closes with a clear 2-of-3 result, per-lane outcomes, cancelled-lane semantics and persisted profile evidence.