Inspiration
This project was born from a desire to create an engaging, educational game that combines historical numeral systems with competitive gameplay. The Roman numeral conversion mechanic offers a unique cognitive challenge—players must quickly parse ancient notation and convert it to modern Arabic numbers, a skill that exercises both pattern recognition and mental math. The idea of hosting this on Reddit's Devvit platform presented an opportunity to reach a large community and create a leaderboard-driven gaming experience that encourages repeated play and friendly competition.
What We Learned
Building What's the Number? provided invaluable lessons across multiple domains:
Game Development & Phaser 3: We mastered Phaser's scene architecture, discovering that reusable scenes require careful cleanup (destroying objects on
create()to avoid texture reference stale pointers) and explicit state passing (avoidingundefinedinscene.start()) to prevent subtle bugs across scene transitions.Mobile-First UX Design: Creating a responsive layout taught us the importance of a
scaleFactorapproach: allows UI elements to scale uniformly across devices. Mobile keyboard support required an unconventional solution—an invisible HTML<input>overlay positioned over the Phaser canvas—because native soft keyboards don't integrate seamlessly with game engines.
How We Built It
The project follows a modular, scene-based architecture:
Client (Phaser 3): Seven distinct scenes handle different game states (Boot, Preloader, MainMenu, Play, Result, Rule, Score), each managing its own lifecycle and UI. Button interactions are abstracted into a
createMenuButton()helper that ensures consistent styling and prevents visual glitches from scale animation accumulation.Server (Express.js + Devvit): Two main endpoints (
/api/gamesfor questions and scoring,/api/scoresfor leaderboard) decouple game logic from the client. The server determines difficulty via Roman numeral character length, calculates scores using a formula that rewards speed ($\text{score} \propto \text{level} \times \text{restTime} + \text{cumulativeScore}$), and updates Redis only when a new personal best is achieved.Data Flow: Play scene → Submit triggers async POST to
/api/games→ server calculates score and next question → Result scene displays feedback with server-calculated score → Next button either caches the response for immediate play or fetches a new question.Build Pipeline: Vite handles bundling for both client and server with separate
tsconfig.jsonfiles per workspace. The production build creates adist/folder consumed by Devvit's upload.
Challenges Faced
Scene Reuse Pitfalls: Phaser scenes can be reused (e.g., Play scene used for every round), but reuse without cleanup causes texture reference errors (
drawImage(null)) and duplicate event handlers. Solution: destroy UI objects increate()and guard texture access withif (this.textures.exists()).Duplicate Keystroke Bug: Registering keyboard listeners without removing them on scene shutdown caused keystrokes to be processed multiple times. Solution: store the listener reference and explicitly remove it during
shutdown().Mobile Keyboard Integration: Phaser's text input objects don't trigger native soft keyboards reliably. Solution: create an invisible HTML
<input>overlay, position it over the game canvas, and sync its value with Phaser text via event listeners.Layout Responsive Scaling: A naive approach (setting positions directly on every resize event) caused layout thrashing. Solution: compute
scaleFactoronce, apply it consistently to all positions and scales, and delayrefreshLayout()by 16ms to allow the canvas to fully resize.Data Passing Across Scene Transitions: Passing
undefinedtoscene.start()caused the scene to reuse old data from the previous run. Solution: always pass an explicit object (even if empty,{}) to force fresh initialization.Score Calculation Authorization: Early prototypes calculated scores on the client, allowing cheating. Solution: moved all score logic server-side; the server owns the authoritative score and passes it back to the Result scene.
Log in or sign up for Devpost to join the conversation.