← Back to games

📖 Sensor Games Docs

Everything a new user (or an LLM helping you) needs to know.

🌀 What is this?

Sensor Games is a small Node/Express site for playing real-time multiplayer games that use a phone's motion sensors (accelerometer, gyroscope/orientation). One person hosts a room on a big screen or their own phone; everyone else joins the room from their own phone browser. Gameplay data flows two ways:

🎮 Available games

đŸŽ¯ Gyroscope Test

A target roll angle appears. Tilt your phone to match it, then tap to lock in. Closest angle wins; speed breaks ties.

đŸŽŧ Instrument Symphony

Each player is assigned a random instrument. Their live accelerometer motion becomes a voice in the host's audio mix.

📊 Sensor Skeleton

A minimal starter/reference game. The play screen plots the player's own accelerometer and gyroscope values live (before anything is sent), while continuously streaming the same data to the server. The host screen polls the server and renders a live scrolling chart per player. Use this as a template for building new sensor-driven games.

🐾 Trail Blazer

Everyone starts at the host's origin on a shared canvas. Each player's phone streams raw accelerometer data; the host integrates it into a 2D walking velocity/position per player (with damping so it settles instead of drifting) and draws a colored trail behind each dot. The camera smoothly pans and zooms out to keep the host origin and every walker in view.

đŸ•šī¸ How to host & join a room

  1. Open the site's homepage (/) and pick a game, or go straight to /rooms?game=<game-id>.
  2. The host taps Host, names the room, and a short room code + PeerJS host id are registered via POST /api/rooms.
  3. Players open /rooms?game=<game-id>, see the room in the live list (polled every 5s from GET /api/rooms), and tap it to join.
  4. Joining opens a PeerJS connection directly to the host and navigates to that game's play.html?peer=<hostPeerId>&room=<roomId>.
  5. The host's room is removed (DELETE /api/rooms/:roomId) when the host closes the tab or ends the game.
âš ī¸ HTTPS required for sensors on iOS/modern browsers. DeviceMotionEvent / DeviceOrientationEvent (and their permission prompts) only work in a secure context — https:// or http://localhost. Testing over a plain LAN IP (e.g. http://192.168.1.20:3000) will silently skip the permission prompt and no sensor data will flow. Use a tunnel such as ngrok (ngrok http 3000) to get an HTTPS URL for phone testing, or deploy the app (e.g. to Render) which serves everything over HTTPS already. All API and PeerJS calls use relative paths / location.hostname, so no code changes are needed — the same site works unmodified at any host, including https://fidget-camp-workshop.onrender.com.

📡 Sensor streaming API

Independent of any particular game's PeerJS messages, every play screen continuously streams raw sensor readings to the server once a room is joined and sensor permission is granted. This lets a host — or any other tool — read everyone's live sensor data over plain HTTP, without needing a PeerJS connection.

MethodPathDescription
POST /api/devices/:room Upsert one device's latest sensor reading for a room.
GET /api/devices/:room Array of every device's latest reading in that room.
GET /api/devices/all Object of { roomId: [devices...] } for every room.

POST body shape:

{
  "deviceId": "peer-id-or-any-stable-id",
  "name": "Player name",
  "orientation": { "alpha": 0, "beta": 0, "gamma": 0 },
  "motion":      { "x": 0, "y": 0, "z": 0 },
  "rotationRate": { "alpha": 0, "beta": 0, "gamma": 0 }
}

Devices that stop posting for 15 seconds are automatically dropped (player left / closed tab). The shared helper public/js/device-stream.js wraps this:

// On a play.html page, after sensor permission is granted:
const handle = startDeviceStreaming({
  roomId, deviceId: peer.id, name: playerName,
  onSample: (latest) => { /* plot latest.motion / latest.orientation locally */ }
});
handle.stop(); // when leaving

// On a host.html page:
const poll = pollRoomDevices(roomId, (deviceList) => { /* render everyone's data */ });
poll.stop();

// Reusable scrolling canvas chart used by Sensor Skeleton:
const plot = createSensorPlot(canvasEl, [
  { key: 'x', color: '#f87171' }, { key: 'y', color: '#4ade80' }, { key: 'z', color: '#60a5fa' }
]);
plot.push({ x, y, z });

🏠 Room lobby API

MethodPathDescription
GET /api/rooms?game=<id> List open rooms, optionally filtered by game.
POST /api/rooms Create a room: { name, game, hostPeerId } → { roomId }.
PATCH /api/rooms/:roomId Update playerCount and refresh the room's heartbeat.
DELETE /api/rooms/:roomId Close a room (called on host disconnect / end game).

Rooms are held in memory and expire automatically if not heartbeated (PATCH) within 60 seconds.

đŸ—‚ī¸ Project structure

server.js                  Express app, room API, device sensor API, PeerJS server (/peerjs)
public/
  index.html                Game picker / homepage
  rooms.html                Room lobby (host/join) for a given ?game=
  docs.html                 This page
  js/device-stream.js       Shared sensor streaming + polling + canvas plotting helpers
  css/style.css             Shared dark theme styles
  games/<game-id>/
    host.html               Host screen: creates room, runs game logic, shows players
    play.html               Player screen: joins room, reads sensors, plays the game

➕ Adding a new game

  1. Create public/games/<game-id>/host.html and play.html (copy Sensor Skeleton as a starting point).
  2. Register the game in rooms.html's GAME_META map (name, hostPath, playPath).
  3. Add a card for it on index.html.
  4. Host creates the room via POST /api/rooms with a unique game id; players discover it via GET /api/rooms?game=....
  5. Use PeerJS for real-time game logic between players and host, and/or the /api/devices endpoints + device-stream.js helpers for raw sensor streaming/plotting.
✅ Copied instructions to clipboard!