Project Info
Inspiration
We wanted to create a party game that feels physical and social without asking a group to buy controllers, install an app, create accounts, or configure complicated device pairing. In a shared room, the laptop becomes the arena, while the phones people already have become the controllers. The idea came from the energy of couch multiplayer and arcade game nights. The best moments are quick to understand, visible to everyone, and easy to laugh about, even for people who do not usually play games. Flick Arena brings that energy into the browser through a playful office battle where a paper ball, coffee mug, stapler, or desk bomb can become the winning shot. Our goal was to make the journey from opening the game to landing the first hit feel almost immediate: Create a room, scan the QR code, aim, flick, and play.
What it does
Flick Arena is a browser-based local multiplayer party game for two to four players. A host opens the game on a laptop or shared display and creates a temporary room. Players scan the room’s QR code with their phones, no account or app installation is required. Each phone becomes a controller: drag to aim, then physically flick the phone forward to throw. A large THROW button is always available as a fallback. Players complete a short interactive tutorial, receive a color and player number, and mark themselves ready. The host starts a 60-second Office Mayhem round. Players bounce office objects through a physics-driven arena, hit opponents, destroy props, and compete for the highest score. Results show the final rankings, match statistics, and round awards. Play Again starts another match without requiring everyone to reconnect. The host screen carries the action, drama, and shared game state, while each phone remains a simple, glanceable controller. Players can focus on the arena and each other instead of staring at separate screens. A read-only Spectator Display can also be opened on a television, projector, tablet, or second laptop. It renders a synchronized version of the match while the original host remains authoritative and available for room management. Flick Arena also avoids making enhanced browser capabilities mandatory. If motion access is denied, blocked, or unsupported, the player can still compete using the on-screen Throw button. Motion sensing, haptics, audio, fullscreen, and wake lock improve the experience but are not required for the core game to function.
How we built it
React, TanStack Start, and TanStack Router handle the landing page, host lobby, QR joining flow, phone controller, results, settings, spectator route, and user-facing error states. Phaser 4 renders the host-side game. Phaser Matter Physics handles thrown-object movement, rotation, bouncing, platforms, collisions, destructible props, and player hits at a fixed logical resolution. Cloudflare Workers serve the application and its HTTP and WebSocket routes from one deployment. Cloudflare Durable Objects coordinate temporary rooms, one host connection, up to four controller connections, player assignment, ready states, reconnection grace periods, spectator connections, and validated realtime message relay. Native WebSockets carry low-latency aim input, throw events, lobby state, connection updates, host feedback, and spectator synchronization. TypeScript and Zod keep the controller protocol, room messages, game bridge, spectator snapshots, and domain events typed and validated at trust boundaries. Procedural textures, local SVG-style assets, and generated WAV effects keep the core visual and audio assets local, original, and reproducible instead of depending on remote asset or font CDNs. Codex and GPT-5.6 were used throughout development to scaffold systems, implement gameplay features, migrate the prototype to Phaser, generate tests and sound effects, audit the product against its manifesto, debug browser-specific behavior, and iterate on the controller and visual experience. The architecture deliberately keeps responsibilities separated: React owns the product interface and browser networking. Phones own input capture, aim control, flick detection, and haptic feedback. Phaser owns gameplay simulation, rendering, audio, physics, effects, and match presentation. The host browser remains authoritative for collisions, scoring, timers, prop damage, and results. The Durable Object coordinates connections and relays validated messages; it does not run the game loop or physics. Spectator displays render synchronized snapshots and presentation events but cannot control or authoritatively change the match. This separation allowed us to redesign major interactions without replacing the multiplayer foundation.
Challenges we ran into
The hardest problem was making a phone motion controller feel understandable and consistent across devices. Our first implementation used phone orientation for aiming. In practice, players were unsure whether they should tilt, rotate, point, or swing their phones, and different devices and browsers reported sensor axes differently. We replaced that interaction with a clearer hybrid control scheme: players drag directly on the phone screen to aim and use a short forward phone flick only for throwing. The new approach preserves the physical identity of Flick Arena while making aiming precise and immediately understandable. The trajectory on the shared screen reacts directly to the phone’s aim surface, and the Throw button ensures that denied or unsupported motion access never prevents someone from playing. We also had to make a realtime browser game feel reliable in a room. Player identity must survive an accidental refresh, a temporary disconnect should not immediately surrender a player slot, and malformed or stale input must never reach the host simulation. Durable Object sessions, server-assigned identities, a short reconnection grace period, Zod schemas, input timestamps, role-specific permissions, and strict room boundaries handle those cases. The spectator display introduced a different synchronization challenge. The host remains the only authoritative simulation, while spectator screens receive periodic snapshots and discrete presentation events. Interpolation keeps projectile and character movement smooth without video streaming, duplicating the physics simulation, or allowing spectators to influence gameplay. Another challenge was balancing readable chaos with satisfying physics. Multiple bouncing projectiles, destructible office props, particles, character reactions, floating scores, audio, camera shake, and a 60-second timer all need to feel energetic without hiding the arena or making it difficult to identify players and scores. Finally, mobile-browser behavior varies widely. Motion permissions, vibration, audio autoplay, wake lock, fullscreen support, background-tab behavior, and sensor availability differ across iPhone Safari, Android Chrome, Brave, and individual devices. We treated these capabilities as progressive enhancements and built explicit fallbacks rather than assuming every browser behaves identically.
Accomplishments we're proud of
A complete room-to-round experience with no accounts, downloads, Bluetooth pairing, or dedicated gaming hardware. A polished two-to-four-player office arena running on one authoritative host display. A clear hybrid controller: drag to aim, flick to throw, and tap THROW if motion is unavailable. A short onboarding flow that teaches the controls through interaction instead of long instructions. Stable player colors and numbers across the controller, host arena, lobby, scoreboard, spectator display, and results screen. Matter-powered projectiles with different weight, speed, rotation, bounce, impact, and scoring characteristics. Destructible office props with damage reactions, particles, debris, score feedback, and object-specific sound effects. Immediate action feedback through trajectory previews, character anticipation, projectile trails, hit-stop, camera shake, floating scores, audio, particles, and haptics where supported. A read-only spectator display that can show the match on another laptop, television, tablet, or projector without transferring authority away from the host. Fast replay flow that resets the round without forcing players to scan the QR code or reconnect. Graceful degradation through touch and keyboard fallbacks, optional motion permission, reconnection recovery, wake-lock recovery, muted-by-default spectator audio, and clear browser error states. A typed and validated realtime protocol that keeps controller, host, and spectator permissions separate. A reproducible local asset pipeline for procedural textures and generated WAV sound effects. A public browser-first experience that works without a custom native application. We are also proud of the development process itself. Codex allowed us to move quickly across networking, Phaser gameplay, visual systems, sound generation, automated testing, and browser debugging, while we retained responsibility for the product decisions: simplifying the controls, limiting the first release to one game mode, prioritizing fallbacks, and focusing on replayability instead of adding unnecessary scope.
What we learned
The most important lesson was that “using a phone as a controller” is not the same as “putting game controls on a phone.” The phone interface should remain simple and glanceable. The shared screen should carry the game state, spectacle, competition, and emotional feedback. If players spend the entire match studying their phones, the social experience has failed. We also learned that physical controls must be designed around clear intent rather than raw sensor availability. A technically impressive motion system is not useful when players cannot understand what movement the game expects. Separating direct touch aiming from physical throwing made both actions easier to learn and more reliable. Browser capabilities are best treated as optional layers. Motion sensors, vibration, audio autoplay, wake lock, fullscreen, and stable network conditions vary by browser and device. A signature interaction can use those capabilities, but the game still needs to function when one of them is unavailable. On the engineering side, separating the React interface, typed game bridge, host-authoritative Phaser simulation, Durable Object relay, and read-only spectator renderer made iteration much safer. We could replace orientation aiming, redesign the controller, introduce spectators, and improve presentation without rewriting the room system or allowing multiple sources of gameplay truth. We also learned that game feel is a chain rather than a single effect. A throw only feels satisfying when the controller responds, the character anticipates, the projectile launches clearly, the sound matches its weight, the collision is readable, the target reacts, and the score confirms the result. Missing one part can make an otherwise correct mechanic feel weak. Finally, using Codex effectively required more than asking it to generate code. The best results came from giving it clear product constraints, breaking work into verifiable milestones, asking it to inspect rendered output, preserving known-good architecture, and combining its implementation speed with real human playtesting and product judgment.
What's next
The immediate next step is broader real-world playtesting with groups using the phones and browsers we intend to support, especially iPhone Safari and Android Chrome over HTTPS. We want to measure: How long it takes a new player to join and land their first throw Whether the controls are understood without verbal explanation How reliable physical flick detection feels across devices Whether players look primarily at the shared screen Whether scoring and projectile differences are obvious Whether the arena remains readable with four active players Whether players voluntarily choose Play Again Based on those sessions, we will tune flick sensitivity, projectile balance, aim ranges, round pacing, audio levels, visual hierarchy, destruction effects, and the final countdown. We also want to continue replacing procedural placeholder artwork with stronger authored visuals while preserving the cohesive office-arcade style and gameplay readability. After the first mode is consistently fun, reliable, and easy to understand, we would explore carefully selected additions that preserve its immediacy: More interactions inside the Office Mayhem arena Additional projectile and prop behaviors Stronger round-to-round variation Improved spectator presentation for events and larger displays More accessibility settings and control alternatives Additional local party arenas and modes Event and venue-focused presentation tools We would keep the focus on one excellent local multiplayer experience before considering persistent progression, public matchmaking, remote online play, or a broader game platform.
Flick Arena
Flick Arena is a local multiplayer office battle. One laptop renders the Phaser game; two to four phones join the room over WebSockets and become drag-and-flick controllers.
The host owns the match, physics, scoring, and results. The Durable Object only owns room membership, player slots, reconnection grace, and validated message relay.
[!IMPORTANT] Testing with real phones requires an HTTPS URL that the phones can reach. Deploying the app is the recommended path. The local
http://localhost:3000workflow below is intended for testing with additional desktop tabs; a phone cannot use the laptop'slocalhost, and physical flick sensors are not reliably available over plain HTTP. An HTTPS development tunnel is also supported if you do not want to deploy yet.
Local desktop test
pnpm install
pnpm dev
- Open
http://localhost:3000and click Create Game. - Open the controller URL in two to four additional desktop tabs.
- Complete the short controller tutorial: drag the aim marker, then tap THROW for a practice throw.
- Mark at least two controllers ready, then start the round on the host.
- Test dragging, the 3-2-1-FLICK countdown, the 60-second timer, the scoreboard, and Play Again.
Keyboard fallback:
- Player 1:
A/Daim,WorSpacethrow - Player 2:
Left/Rightaim,Arrow Upthrow - Player 3:
J/Laim,Ithrow - Player 4:
Numpad 4/Numpad 6aim,Numpad 8throw
Development builds also allow a one-player demo round.
Test with iPhone or Android controllers
Use the deployed HTTPS app for real-phone testing. Motion sensors require a secure context, and the URL must be reachable from both the laptop and each phone. Alternatively, expose the local app through an HTTPS development tunnel.
pnpm cf-typegen
pnpm deploy
- Open the deployed HTTPS URL on the laptop and create a game.
- Scan the host QR code with each phone’s camera.
- Drag the marker left and right to aim. Release without firing.
- Tap Enable Physical Flick if you want motion throws; permission is optional because THROW always works.
- Hold the phone securely and make a short forward motion, or tap THROW.
- Confirm the assigned number/color, then tap Mark Me Ready.
- Start the round on the host. Drag to aim and flick to throw.
The controller stores a room-scoped session ID in localStorage, so a refresh can reclaim the same player slot during the approximately 15-second reconnect grace period. A fifth simultaneous controller is rejected.
Commands
pnpm dev
pnpm build
pnpm typecheck
pnpm test
pnpm generate:sfx
pnpm cf-typegen
pnpm deploy
The host game uses Phaser 4.2.1 with Phaser Matter Physics. Phaser is imported and initialized only from the client-side effect in src/components/PhaserGame.tsx; it is not part of the SSR execution path.
AI-assisted development
Flick Arena was built through a human-directed, AI-assisted workflow using GPT-5.6 in two complementary environments.
GPT-5.6 Sol in ChatGPT was used as a collaborative thinking partner during the early and iterative design stages. It helped explore the initial concept, brainstorm game mechanics and player interactions, compare technical approaches, identify edge cases, and turn ideas into practical implementation plans. This included reasoning about the host/controller experience, multiplayer room behavior, mobile-device constraints, and the overall structure of the game.
GPT-5.6 in Codex was used for hands-on implementation inside the codebase. Working from those ideas and plans, Codex created and modified the application code, connected the game and controller flows, implemented supporting systems, investigated problems, and ran relevant development checks while refining the result.
The process was iterative rather than a single generated output: ideas developed in ChatGPT informed the implementation in Codex, and discoveries made during implementation fed back into further planning and refinement. The project remained human-guided throughout—its goals, creative direction, constraints, trade-offs, and final decisions were directed and reviewed by the developer.
Local art and audio
Arena sprites, props, projectiles, debris, and impact particles are generated locally at boot by src/game/art/createProceduralTextures.ts. The UI uses system fonts and CSS shapes, so the game does not depend on remote art or font CDNs.
The UI, countdown, throw, impact, and destruction cues in public/assets/audio are deterministic WAV files generated by scripts/generate-sfx.mjs. Regenerate the same set with pnpm generate:sfx, or choose another repeatable variation with pnpm generate:sfx -- --seed=1234. Keep the supplied public/soundtrack.mp3 separate: it is an external project asset, not generated by that script.
Host audio controls expose master, music, and effects levels plus mute. Settings persist in localStorage; playback unlocks after user interaction, pools overlapping effects, fades the soundtrack between phases, and pauses music when the page is hidden.
Device limitations and known rough edges
- iPhone Safari requires motion permission from a direct user gesture and normally requires HTTPS. Motion permission is only used for physical flick detection; it is not required to aim or use the Throw button.
- The controller requests Screen Wake Lock automatically over HTTPS so supported browsers do not dim or auto-lock during play. Battery saver, low power mode, older browsers, or a browser policy can reject or revoke it; the controller will show a warning and remain usable, but you must then disable auto-lock manually or keep the phone awake.
- Manually locking the phone or sending the browser to the background pauses reliable motion and WebSocket control. Unlock or return to the controller page to let it reacquire the wake lock; a native app would be required for guaranteed locked-screen control.
- Some iPhone browsers and embedded browsers do not expose
navigator.vibrate; haptic feedback is optional and fails silently. - Android motion event names and sensor values vary by browser and device. Chrome over HTTPS is the recommended Android test target; unsupported or denied sensors fall back to the Throw button.
- Sound and haptics are enhancement layers. If autoplay is blocked or audio is muted, visual effects and controls remain functional.
- Procedural art keeps the project self-contained. Its shapes and materials are intentionally arcade-styled rather than production illustration assets; character silhouette and destruction tuning remain the best candidates for a future authored-art pass.
No physical-device test is claimed by this README; verify iPhone and Android behavior on the actual devices you intend to support.
Analysis
View
Metric
No commits on this project resolved to a GitHub account.
Technology
- CSSIn code
- ReactIn code
- Tailwind CSSIn code
- TypeScriptIn code
4 of 4 appear in the indexed code.
AI coding agents
- CursorConfig
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
1.2 MB
Source files
82
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
LordRonz/flick
145 files · 10.9 MB · @ 39252e1
Structure
Interface
9 files · 6%Screens, components and styles rendered to the user.
API & routing
9 files · 6%Request entry points: routes, handlers and controllers.
Application logic
39 files · 27%Domain rules, services and shared utilities.
+3 more
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- TypeScript66%
- YAML20%
- Markdown8%
- CSS6%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
package.json
npm · 42- @t3-oss/env-core
- @tailwindcss/vite
- @tanstack/react-devtools
- @tanstack/react-form
- @tanstack/react-router
- @tanstack/react-router-devtools
- @tanstack/react-router-ssr-query
- @tanstack/react-start
- @tanstack/router-plugin
- @types/qrcode
- class-variance-authority
- clsx
- lucide-react
- nitro
- phaser
- qrcode
- radix-ui
- react
- +24 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.