whackrow
v0.1.7
Published
JS dev kit for game+controls over WebSocket with Vite dev server.
Maintainers
Readme
whackrow
Vanilla JS dev kit for building Whackrow-compatible games locally with a WebSocket loop for controls.
Quick commands (no setup in your app)
npx whack— scaffoldsdemo/index.html,demo/game.html,demo/controls.html, and root-levelmain.js+ControlsClient.jsnpx whack-controls— starts the local WebSocket relay at ws://localhost:3030 (also prints ws://LAN_IP:3030)npx whack-dev— starts the dev server and serves demo at local and LAN URLs
Local development (optional scripts)
- Start the WS server:
npx whack-controls - Start the dev server:
npx whack-dev - Open
http://localhost:5173/- Game shows a QR code pointing to Controls on your LAN origin (so phones can connect)
- Controls page connects to the WS and exposes a v‑stick (WASD/Arrows also work)
Important: use LAN URL (not localhost) on phone
- When testing across devices, open the Game and Controls pages using the LAN URL printed by the dev server (e.g.
http://192.168.x.x:5173). - The phone cannot reach
localhost, so the QR and the WebSocket must both target your LAN IP (e.g.ws://192.168.x.x:3030).
Game module API (demo/main.js)
Your game module should export startGame with the following runtime contract (the scaffolder generates demo/main.js based on this):
export async function startGame({ canvas, onTimerUpdate, onPerformanceUpdate, onWinner, onPlayerEliminated } = {}) {
// create render loop, keep internal state
return {
ensurePlayer(id) {},
setJoystickFor(id, payload) {},
reset() {},
pause() {},
resume() {},
};
}Controls client usage
<script type="module">
import { ControlsClient } from './ControlsClient.js'
const client = new ControlsClient({ url: 'ws://YOUR_HOST:3030', playerId: 'PHONE_123' })
client.connect()
client.attachVirtualJoystick(document.getElementById('joystick'))
// also supports keyboard (WASD/Arrows)
</script>Payload format (sent from controller)
{
"type": "move",
"vector": { "x": 0.96, "y": 0.28 },
"distance": 1,
"angle": { "radian": 0.28, "degree": 16.24 },
"direction": "right",
"playerId": "optional-id"
}The demo game uses vector to move a Whackrow logo on a fullscreen canvas. Add ?player=PHONE_XXXX to Controls URL to include a playerId.
Publishing on Whackrow
- Locally, you use the provided WebSocket relay and
ControlsClientto simulate inputs. - When your game is published on the Whackrow platform, Whackrow’s engine manages controllers and calls your exported functions from
main.jsdirectly (e.g.,ensurePlayer,setJoystickFor,reset,pause,resume). You won’t need the local WS relay in production.
Important compatibility notice
- Whackrow does not guarantee behavior if you change the function signatures or internal logic of the scaffolded
main.jsAPI or theControlsClientjoystick wiring. These functions are invoked by the Whackrow engine in production. - Recommendation: Treat the exported API as stable; build your game inside those hooks rather than modifying the API itself.
Notes
- The QR in game.html uses your LAN origin from the dev server. If no LAN IP is detected, it falls back to
location.origin. - You can override the WS URL via
?ws=ws://<host>:3030on both pages.
API Reference
Game module (main.js)
Export a single asynchronous function startGame that initializes your game and returns a control API.
Signature
export async function startGame(options?: {
canvas?: HTMLCanvasElement,
onWinner?: (p: { winnerId: string }) => void,
onPlayerEliminated?: (playerId: string) => void,
playerColorResolver?: (id: string, ctx: { usedColors: Set<string>, availableColors: string[] }) => string | void,
onTimerUpdate?: (p: { remainingMs: number }) => void,
onPerformanceUpdate?: (p: { fps: number, targetFps: number }) => void,
onBump?: (p: { attacker: string | null, victim: string | null, strength: number, victimOpposite: boolean }) => void
}): Promise<{
ensurePlayer: (id: string) => void,
setJoystickFor: (id: string, payload: JoystickPayload) => void,
reset: () => void,
pause: () => void,
resume: () => void
}>Parameters
canvas(optional): The target canvas to render into. If omitted, your module may query#gameCanvas.onWinner(optional): Notify platform when a winner is decided.onPlayerEliminated(optional): Notify when a player is eliminated.playerColorResolver(optional): Deterministically choose a player color.onTimerUpdate(optional): Report a visible game timer (ms remaining).onPerformanceUpdate(optional): Report currentfpsand target (30 or 60).onBump(optional): Report physics “bump” events with metadata.
Returned control API
ensurePlayer(id): Create/spawn the player with identifieridif missing.setJoystickFor(id, payload): Apply latest joystick input forid.reset(): Reset state for a new round (clear players, timers, etc.).pause(): Pause the game loop.resume(): Resume the game loop.
JoystickPayload
type JoystickPayload = {
type: 'move',
vector: { x: number, y: number }, // normalized -1..1
distance: number, // magnitude 0..1
angle: { radian: number, degree: number },
direction: 'up' | 'down' | 'left' | 'right',
playerId?: string
}Notes
vectorrepresents the instantaneous input direction and magnitude (normalized to unit circle). You typically multiply by a speed anddtto move entities.distanceis the magnitude [0..1]. If you usevectordirectly,distanceis typically redundant; it is provided for convenience and UI effects.directionis a coarse 4-way direction computed fromangleand can be useful for animations.
ControlsClient (ControlsClient.js)
Constructor
new ControlsClient(options?: { url?: string, throttleMs?: number, playerId?: string })Options
url(defaultws://localhost:3030): WebSocket endpoint to connect to.throttleMs(default16): Minimum ms between outgoing joystick messages (client-side rate limit).playerId(optional): If set, the client includes this id in all messages.
Methods
connect()- Opens the WebSocket. Sends
{ type: 'player_connected', role: 'controls', playerId? }on open.
- Opens the WebSocket. Sends
disconnect()- Closes the WebSocket and removes keyboard/joystick handlers.
attachVirtualJoystick(container: HTMLElement)- Renders a touch joystick “knob” inside
container. Pointer/touch input is clamped to a 70px radius and published continuously via RAF.
- Renders a touch joystick “knob” inside
detachVirtualJoystick()- Removes joystick handlers and UI.
Keyboard support
- WASD and Arrow keys are supported automatically after
connect(). Keyboard input generates the same joystick payloads as the virtual joystick.
Outgoing messages (from ControlsClient)
- On connect:
{ type: 'player_connected', role: 'controls', playerId? } - Joystick:
JoystickPayload(see schema above). Messages are throttled bythrottleMsand also emitted continuously while dragging.
Local WebSocket relay
- Start with
npm run controls. The server prints bothws://localhost:3030andws://<LAN_IP>:3030if available. - Behavior: it relays any text message to all other connected clients. This approximates multi-controller dev locally.
- Scope: this WebSocket relay and the
ControlsClientare for local development only. Do not ship or rely on the WS relay in production. On the Whackrow platform, controllers are managed by the platform and your game receives inputs via direct calls to yourmain.jsAPI (e.g.,ensurePlayer,setJoystickFor).
