Extract mutable state from game.ts into a dedicated GameModel class to clarify data flow and state transitions. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
curltastic
A 1v1 async multiplayer 2D curling game for mobile browser.
- Backend — Rust, Axum, WebSocket, Rapier2D physics (server-authoritative, 120 Hz).
- Frontend — TypeScript, Vite, Canvas2D, portrait-first touch UI.
Run locally
- Install Rust via rustup (if not already installed).
- Start the backend:
cd backend cargo run --release # listens on 0.0.0.0:3000 - Start the frontend dev server:
cd frontend npm install npm run dev # opens on 0.0.0.0:5173 by default - Open two browser tabs to the same room URL, e.g.:
The first tab waits; the second starts the match. Use the Copy share link button to invite an opponent.http://localhost:5173/?room=DEMO1
Mobile devices
The frontend binds to 0.0.0.0 via --host. Find your machine's LAN IP and open http://<ip>:5173/?room=CODE on the phone. Both devices must be on the same Wi-Fi and able to reach the backend on port 3000.
Controls
- Drag inside the house to place the broom (aim point).
- Tap a weight
1–10. - Tap THROW.
- The server runs the physics and streams the trajectory; the frontend interpolates the animation.
Architecture
- WebSocket JSON protocol with tagged messages.
- Server simulates each throw at 120 Hz and sends a subset of
(x, y, t)path points at 40 Hz. - All game state, scoring, end management, and hammer rules live on the server.
- Disconnects are tolerated: the room and turn remain in memory.
E2E tests
With the backend and frontend dev server running:
cd e2e
npm install -g ws # or npm install ws locally in the project
node e2e_test.cjs # room lifecycle, throw, trajectory, out-of-play removal
node e2e_score.cjs # stones remain in play and alternate turns
Limitations / known simplifications
- No accounts, persistence, anti-cheat, replay log, or turn timer.
- Curl is fixed as a function of release speed (more curl at lower speed); sweeping is not implemented.
- Stones that pass the back line or leave the sheet are removed from play.
Description
Languages
TypeScript
43.5%
Rust
31.7%
JavaScript
15.6%
CSS
7.9%
Dockerfile
1%
Other
0.3%