Skip to content

add online multiplayer via Cloudflare Workers and Durable Objects - #27

Open
kgregory wants to merge 8 commits into
mainfrom
feat/online-multiplayer
Open

add online multiplayer via Cloudflare Workers and Durable Objects#27
kgregory wants to merge 8 commits into
mainfrom
feat/online-multiplayer

Conversation

@kgregory

Copy link
Copy Markdown
Owner

Implements real-time two-player online mode using WebSockets. Players share a room code to connect; the server (Cloudflare Durable Object) is authoritative for all game state. Migrated from Partykit SDK to native Cloudflare Workers + WebSocket Hibernation API for deployment reliability.

  • New Cloudflare Worker in party/index.ts with GameRoom Durable Object
  • LobbyScreen for creating/joining rooms via room code or shareable link
  • useOnlineGame hook managing WebSocket lifecycle and server state sync
  • LocalPlayScreen extracted from index.tsx to isolate local game flow
  • PlayScreen for mode selection (local vs online)
  • EndScreen updated with online-specific messaging (quit/opponent left)
  • GameState context guards dimension resets in online mode (server is authoritative)
  • Shared partyTypes.ts for client/server message contracts
  • wrangler.toml replaces partykit.json; partykit devDependency removed

Implements real-time two-player online mode using WebSockets. Players
share a room code to connect; the server (Cloudflare Durable Object)
is authoritative for all game state. Migrated from Partykit SDK to
native Cloudflare Workers + WebSocket Hibernation API for deployment
reliability.

- New Cloudflare Worker in party/index.ts with GameRoom Durable Object
- LobbyScreen for creating/joining rooms via room code or shareable link
- useOnlineGame hook managing WebSocket lifecycle and server state sync
- LocalPlayScreen extracted from index.tsx to isolate local game flow
- PlayScreen for mode selection (local vs online)
- EndScreen updated with online-specific messaging (quit/opponent left)
- GameState context guards dimension resets in online mode (server is authoritative)
- Shared partyTypes.ts for client/server message contracts
- wrangler.toml replaces partykit.json; partykit devDependency removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scorefour Ready Ready Preview Jul 30, 2026 1:13am

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
partysocket v1.x changed the default URL from /party/{room} to
/parties/{name}/{room} (name defaults to "main"). Update the Worker
regex to match the new format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a player's socket drops and partysocket reconnects before the old
socket is cleaned up, the server rejects the new connection as full.
Previously this called socket.close(), permanently stopping reconnection.

Now we only stop reconnecting if we never received a welcome (genuine
rejection). If we already connected once, let partysocket retry — the
old socket will be cleaned up and the reconnect will succeed. Also
clear errorMessage on welcome so stale errors disappear after recovery.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
localPlayer was a live prop, not frozen in the EndSnapshot. When
navigating back to menu, useOnlineGame cleanup called setLocalPlayer(null)
before animating was set to true, causing one frame where winner was
set but localPlayer was null — hitting the "${winner} wins!" branch
instead of "You win!"/"You lose."

Fix: include localPlayer in the EndSnapshot so it stays stable through
the fade-out transition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two issues: (1) text-transform: capitalize applied to the whole string
capitalizes the letter after the apostrophe in some browsers, giving
"Red'S" instead of "Red's" — fixed by applying capitalize only to the
player name span. (2) gameMode flips to "local" in the same render
batch as setScreen("start"), so the local turn text flashed for one
frame before animating became true — fixed by guarding on
screen === "playing" so nothing renders during the transition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…line

- Lobby phase replaces the auto-start 2-player flow: players join and wait
  until the host clicks Start Game (enabled at ≥2 players, max 4)
- Player list shows all connected players with their chip, color name, host
  badge, and "(you)" marker
- Server assigns colors in join order (red→yellow→purple→green); turn
  rotation uses gamePlayers recorded at game-start time
- partyTypes: add lobbyPlayers/gamePlayers to RoomState, add start_game
  client message, replace "waiting" phase with "lobby"
- Worker deployed to Cloudflare

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
kgregory added a commit that referenced this pull request Jul 30, 2026
- Consolidate BoardDisplay + BoardShell into a single Board component
- Extract PlayingScreen to encapsulate the playing view alongside StartScreen/EndScreen
- Move DecorativeBoard into StartScreen as a private implementation detail
- Add Heading component shared across all three screens
- Add PlayerChips component with active-player emphasis and opacity for inactive chips
- Make QuitButton dumb (onQuit prop) and add expand/collapse confirmation pattern
- Add reset-to-defaults to GameSettings settings expansion
- Fix DecorativeBoard vertical centering (spanStart formula)
- Add DEFAULT_PLAYERS and DEFAULT_DIFFICULTY constants
- Add lint:fix, format, and format:fix npm scripts; include format in CI

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant