Building Mathastic Twice: One Math Puzzle Game, Two Native Architectures Tapadyuti Chatterjee, a software engineer, has released Mathastic, a native iOS and Android math puzzle game built with SwiftUI and Jetpack Compose. The game features multiple modes, difficulty levels, and a progression system, with a local-first architecture that avoids server dependencies. Chatterjee structured the two codebases with shared domain concepts but platform-specific implementations, emphasizing the importance of a stable product vocabulary. I recently released Mathastic , a fast-paced math puzzle game for iOS and Android. I’m Tapadyuti Chatterjee, a software engineer interested in distributed systems, mobile development, and practical applications of AI. You can learn more about my work on my personal website https://tapadyuti.com/ or connect with me on LinkedIn https://www.linkedin.com/in/tapadyutichatterjee/ . This is also my first post on DEV, so I wanted to go beyond a simple launch announcement. Instead, I want to share how the app works, how I structured the two native codebases, and what I learned while translating the same product into SwiftUI and Jetpack Compose. Mathastic is a native iOS and Android game that turns arithmetic practice into short, replayable runs. Players choose a game mode, difficulty, and operation mix, then build streaks, complete missions, earn XP, unlock themes, and track their performance over time. The iOS version uses SwiftUI, observable state, and UserDefaults . The Android version uses Jetpack Compose, a ViewModel with StateFlow , Hilt for dependency injection, and SharedPreferences with Gson. Both apps share the same product rules and domain concepts, but each follows the conventions of its platform instead of forcing an identical implementation. Transparency note: I used AI to help format and polish the wording of this article. The app, architecture, implementation decisions, and experiences described here are my own. The original idea was simple: arithmetic practice should feel less like a worksheet and more like a game you want to replay. A basic math quiz can ask a question, accept an answer, and display a score. That works, but it does not create much momentum. For Mathastic, I wanted each session to have a small emotional arc: That led to several game modes: Players can focus on addition, subtraction, multiplication, division, or a mixed set. Difficulty changes more than operand size: it also affects the timer, scoring, penalties, streak bonuses, and XP. The surrounding progression system—missions, achievements, levels, unlockable themes, score history, and operation-level accuracy—exists to give players a reason to return without getting in the way of the central activity. Although the iOS and Android projects are separate native applications, I kept their domain language deliberately similar. Both versions have equivalents of: GameConfiguration MathQuestion Score GameResult GameMission MissionProgress PlayerProfile OperationStat DailyChallenge Enumerations represent the major rule choices: difficulty, operation, game mode, theme, and mission type. This was one of the most useful architectural decisions in the project. When the product has a stable vocabulary, platform-specific code becomes easier to reason about. “Timed Sprint” should mean the same thing whether its state is stored in a Swift property wrapper or a Kotlin StateFlow . The UI implementations can differ. The game rules should not. Conceptually, Mathastic is divided into four layers: UI and navigation ↓ Game session state ↓ Question, mission, and challenge generation ↓ Local scores, progress, and preferences The UI renders the current state and sends player actions such as answering, requesting a hint, skipping a question, or ending a run. The game-state layer applies the rules: scoring, streaks, timing, progression, and mission updates. Small factory components generate questions, daily challenges, and missions. Finally, a local score store persists completed runs and derives higher-level information such as XP, levels, achievements, best scores, and operation accuracy. I chose a local-first design. Mathastic does not require an account or server round trip to begin a game. For this kind of app, immediate startup and offline play are more valuable than introducing a backend before it is necessary. The question factory is one of the most important pieces of the app. It selects an operation, chooses operands based on difficulty, calculates the correct result, and creates three plausible wrong answers. Mixed mode resolves to a specific operation for every question. A few details improve the experience: This logic is isolated from the visual layer. A screen should not need to know how to construct a valid division problem or produce convincing distractors. It only needs a MathQuestion containing a prompt, a correct answer, and a set of options. The Daily Challenge created an interesting requirement: randomness needed to be predictable. A normal run can generate a fresh sequence. A daily challenge should be tied to the day so that different sessions receive the same underlying challenge configuration. Both apps derive a numeric seed from the current date. That seed determines the day’s difficulty, operation mix, theme, and question sequence. This approach has several advantages: It was a good reminder that “random” and “uncontrolled” are not the same thing. Seeded randomness preserves variety while still giving the system repeatable behavior. The iOS app is written with SwiftUI . A NavigationStack begins at the welcome screen and moves into the active game configuration. Shared progress is held by a ScoreStore created as a StateObject at the app level and passed through the SwiftUI environment. The game screen uses SwiftUI state for the active session: Persistent player settings use @AppStorage , while completed scores are encoded and stored through UserDefaults . The score store publishes a derived snapshot containing the player profile, achievements, and operation insights. This creates a straightforward flow: changing game state causes SwiftUI to redraw the relevant parts of the interface, while saving a completed run rebuilds the player’s longer-term progress. For reminders, iOS uses UNUserNotificationCenter with a repeating calendar trigger. The app asks for notification permission only when the player chooses to enable the reminder, which was important to me. A reminder should be an opt-in convenience, not an automatic interruption. The Android app uses Kotlin , Jetpack Compose , Material 3 , and Navigation Compose . The main architectural difference is that the active game logic lives in a GameViewModel . The view model exposes an immutable StateFlow