curltastic/README.md
Jason Dekarske 94c6949426 more changes
2026-07-11 10:13:48 -07:00

3.6 KiB
Raw Blame History

curltastic

A multiplayer 2D curling game for mobile browser. Any number of clients can join a room, pick Team 1 or Team 2 freely, and throw when it is that team's turn (solo pass-and-play or remote opponents). Default palette: Team 1 red, Team 2 yellow.

  • Backend — Rust, Axum, WebSocket, Rapier2D physics (server-authoritative, 120 Hz).
  • Frontend — TypeScript, Vite, Canvas2D, portrait-first touch UI.

Run locally

  1. Install Rust via rustup (if not already installed).
  2. Start the backend:
    cd backend
    cargo run --release
    # listens on 0.0.0.0:3000
    
  3. Start the frontend dev server:
    cd frontend
    npm install
    npm run dev
    # opens on 0.0.0.0:5173 by default
    
  4. Open one or more browser tabs to the same room URL, e.g.:
    http://localhost:5173/?room=DEMO1
    
    Use the team dropdown to switch between Team 1 and Team 2 anytime. Use Copy share link to invite another device.

Controls

  • On your team's turn, drag on solid ice to place the broom (aim is not limited to the house).
  • Otherwise, drag to pan (default framing shows house + sidelines; pan up toward the hog line).
  • Team dropdown: free switch mid-game (solo: throw for Team 1, switch, throw for Team 2).
  • Velocity slider (release speed, m/s), curl buttons, friction scalar 0.51.5 (local; multiplies ice µ(v) table).
  • Tap THROW. Anyone joined as the current turn team may throw.
  • Parallel multi-stone trajectories; rotation θ comes from physics.
  • Skeuomorphic 2×8 stones-left HUD; end-of-end modal with scoreboard (auto ~5s + manual dismiss).

Physics (high level)

  • Pure initial velocity (m/s), not discrete weight.
  • Ice friction µ(v) table (interpolated) × local scalar; linear and angular damping share the table.
  • Curl: initial |ω| = 5 rot / 14 s; lateral continuous model (clockwise → right).
  • Stonestone contacts are nearly elastic (STONE_RESTITUTION = 0.9) so takeouts launch both rocks along the impact line instead of plastic-sticking.
  • Back line and sidelines are not colliders — touch → out of play after sim.
  • Stone ids: { team, n } with n = 1…8 per team per end.
  • Foot-derived radii use FEET_TO_METERS = 0.3048.

Architecture / protocol

  • WebSocket JSON, snake_case tags.
  • game_state includes totals, hammer, turn_team, scoreboard (per end: hammer, team1 pts, team2 pts), stones_remaining, stones.
  • trajectories message: { stones: [{ stone_id, rotation, team, trajectory: [[x,y,theta], ...] }] } sampled at 40 Hz from 120 Hz sim (t = index / 40).
  • No room-full limit; no end_scored message (scoreboard replaces it).

E2E tests

With the backend running (cargo run --release on :3000):

cd e2e
npm install -g ws   # or npm install ws
node e2e_test.cjs
node e2e_score.cjs
node e2e_persistence.cjs
node e2e_multi_client.cjs
node e2e_end_score.cjs          # full end → scoreboard entry + next end
node collision_trajectory_qa.cjs
node load_test.cjs              # optional concurrent rooms

Unit tests: cd backend && cargo test, cd frontend && npm test.

Branches (this work)

  • jasonlooked — PR-A refactor: multi-client, multi-path animation, free broom, camera, FEET_TO_METERS.
  • pr2-features — PR-B features: physics rewrite, team1/2 wire, scoreboard, HUD, modal (branched from PR-A tip).

Limitations

  • No accounts, persistence, anti-cheat, replay log, or turn timer.
  • Sweeping not implemented; friction/curl not shared-room state.
  • Stones past back/sideline/hog rules are removed from play after simulation.