Back to work

Mobile learning game · Paid contract

Kana Fighter

A game-based mobile application that helps first-time Japanese learners practice Hiragana and Katakana through guided lessons, drills, pronunciation audio, battles, and sequential progression.

Role
Contract React Native Developer & Technical Lead
Team
1 developer · 1 owner and content creator
Period
Status
TestFlight beta · Preparing for App Store release

Measured outcomes

10
TestFlight testers

Testing included Japanese learners, developers, and people connected to the owner.

iOS + Android
Validated build targets

The Android build was validated locally but was not distributed through Google Play testing.

Offline
Local-first progress

Learning state and progression are stored on the device.

Kana Fighter Quest battle with player and enemy health bars, animated characters, a Kana prompt, and a Romaji answer field.
Typed-answer QuestA learning prompt becomes a complete battle interaction connecting typed recall, input locking, animation, audio feedback, health changes, and progression.

Context

Memorizing Hiragana and Katakana can feel repetitive and disconnected from meaningful progress for first-time Japanese learners.

Dylan Wood defined the learning concept, pedagogy, Figma design, character artwork, and original visual and audio assets.

I was responsible for translating that creative direction into a complete React Native application and maintainable technical architecture.

The application needed to connect lessons, pronunciation, drills, typed-answer battles, stories, animation, audio, and sequential progression without depending on a backend service.

Development included pauses while new visual assets were produced, making a sustainable asset-production boundary especially important.

Users

  • People beginning Japanese study with no prior Kana knowledge.
  • Learners using an English-language interface to study Hiragana and Katakana.
  • TestFlight participants evaluating learning flow, readability, progression, and device behavior.

My responsibilities

  1. Technical planning and application architecture
  2. React Native, Expo, and TypeScript development
  3. Screen implementation from the owner’s Figma designs
  4. Navigation and learning-flow orchestration
  5. Local persistence architecture
  6. Drill and Quest game logic
  7. Progression and unlock state
  8. Animation, gesture, and audio integration
  9. Python-based asset processing and optimization
  10. Responsive interface implementation
  11. EAS and native build configuration
  12. iOS and Android build validation
  13. Manual testing and TestFlight issue resolution

Constraints

  • The product concept, pedagogy, learning content, Figma design, and original visual and audio assets were owned by Dylan Wood.
  • Progress needed to persist locally without authentication, a backend, or a network connection.
  • Learning, story, battle, and unlock states had to remain consistent across navigation and application restarts.
  • Frame-based character animation needed to remain responsive on the iPhone SE and older iPhone models.
  • Animation, audio, health changes, answer input, and result navigation needed to occur in a controlled sequence.
  • Raw player and opponent frames required consistent composition, dimensions, layering, naming, and optimization before integration.
  • The interface needed to remain readable across different screen sizes, safe areas, and keyboard states.
  • iOS deployment was performed through the owner’s account.
  • Android builds were validated but were not distributed through Google Play testing.

Implemented features

  • Guided intro story and contextual tutorial
  • Hiragana and Katakana learning maps
  • Fifteen progression rows for each Kana mode
  • Basic Kana, Dakuten, and Handakuten content
  • Kana detail screens with Romaji and pronunciation audio
  • Swipe navigation between Kana details
  • Kana-to-Romaji multiple-choice drills
  • Randomized Drill questions and answer order
  • Correct and incorrect Drill feedback
  • Row-level high-score persistence
  • Typed-answer Quest battles
  • Accepted Romaji answer normalization
  • Player and enemy health systems
  • Character attack and damage animation
  • Background music and effect audio
  • Input locking during battle feedback
  • Intro, pre-battle, and post-battle stories
  • Sequential row and mode unlocks
  • Victory, defeat, and retry flows
  • Hiragana completion and Katakana unlock
  • Shareable completion certificate
  • Background and effect-sound settings
  • Local progress reset
  • Google Form feedback and email bug reporting
  • Offline learning after bundled assets are available
Kana Fighter Hiragana progression map with completed, active, and locked Kana rows and Fight and Drill actions.
Progression mapLearners move through Kana rows sequentially. Completed, active, and locked stages make the next learning objective visible without introducing a separate account or backend.
Gameplay sequenceThe gameplay demonstration shows how answer input, battle feedback, character animation, health changes, and progression operate as one controlled sequence.

A learning loop built around visible progress

Each activity prepares the learner for the next step instead of presenting disconnected exercises.

  1. Learn

    Explore a Kana row with its characters, Romaji readings, and structured lesson content.

  2. Listen

    Use pronunciation audio to connect each visual character with its sound.

  3. Drill

    Practice recognition through randomized multiple-choice questions and immediate feedback.

  4. Battle

    Recall the answer by typing Romaji during a character-based Quest encounter.

  5. Receive feedback

    See the answer, animation, audio response, score, and health change as one controlled sequence.

  6. Unlock

    Complete the row, preserve progress locally, and open the next stage of the learning path.

From original artwork to application-ready animation

A Python and Pillow pipeline removed repetitive composition work while keeping creative ownership with the owner.

Choosing a smaller UI foundation

A broad component library added styling layers that were not helping this highly customized game interface.

Initial approach

  1. Gluestack components supplied a broad generated UI layer.
  2. NativeWind utilities did not always override Gluestack styles as expected.
  3. Visual issues required tracing styles across multiple systems.
  4. The dependency and generated-component footprint exceeded the needs of the application.

Revised approach

  1. React Native primitives provide the base behavior.
  2. NativeWind handles the primary styling layer.
  3. Focused shared components cover genuinely repeated interactions.
  4. The approved Figma design remains unchanged while design-related debugging becomes more direct.
After I explained the trade-offs, the owner agreed to the change. Development speed remained stable, while styling problems became faster to identify and resolve.

Technical decisions

Use a local-first progression architecture

Problem
The initial product needed persistent learning progress but did not require accounts, social features, cross-device synchronization, or server-managed content.
Options considered
  • Build a backend and user account system
  • Use an embedded relational database
  • Persist the focused learning state with AsyncStorage
Decision
Store intro state, Kana progression, unlock state, sound options, and Drill high scores locally with AsyncStorage.
Why
This reduced onboarding friction, supported offline use, and avoided adding account and infrastructure complexity that the product did not yet need.
Trade-offs
  • Progress does not synchronize between devices.
  • Deleting application data removes local progress.
  • Stored data changes require explicit initialization, normalization, and migration behavior.
  • Future account support would require a deliberate migration path.

Separate curriculum data from screen logic

Problem
Kana characters, pronunciation, Drill choices, Quest vocabulary, accepted Romaji answers, stories, audio, and animation mappings formed a connected content system that would be difficult to maintain inside screen components.
Options considered
  • Embed learning content directly in each screen
  • Introduce a remote content-management backend
  • Keep the curriculum in typed static data and generated JSON
Decision
Store curriculum and asset relationships outside screen logic and generate the Quest answer map from source vocabulary with a TypeScript script.
Why
The approach kept the product data-driven without introducing a backend or requiring dynamic asset imports that would conflict with the React Native bundle.
Trade-offs
  • Changing a Kana row key can affect several related maps.
  • Content relationships require cross-file consistency checks.
  • The owner cannot edit released learning content through an external CMS.
  • Generated JSON must remain synchronized with its source script.

Preload row-specific animation assets

Problem
Loading frame-based character assets only when an animation began could cause visible interruptions, particularly on smaller and older devices.
Options considered
  • Load frames only when each animation starts
  • Replace the supplied artwork with a different animation format
  • Preload the frame assets required for the current Kana row
Decision
Collect and preload the sprite sources required for the active row before enabling the Quest.
Why
This preserved the owner’s supplied artwork while making battles and feedback more dependable on devices including the iPhone SE and older iPhone models.
Trade-offs
  • Frame sequences increase the packaged asset size.
  • Preloading introduces a deliberate transition before interaction.
  • Memory use must be considered when preparing each row’s asset set.
  • An individual asset failure still requires a graceful fallback.

Sequence battle feedback before advancing state

Problem
Answer submission affected game state, audio, animation, health, input, and navigation. Repeated input or premature navigation could cause those systems to become visually inconsistent.
Options considered
  • Apply every state change immediately after submission
  • Use fixed delays for all state transitions
  • Lock input and advance through animation-completion callbacks
Decision
Treat each answer as a controlled sequence: lock input, play sound and animation, apply damage, wait for the animation cycle, and then advance or navigate.
Why
The visible battle feedback remains synchronized with the underlying game state, and repeated taps cannot create conflicting submissions.
Trade-offs
  • Animation components and game state require an explicit completion contract.
  • Interrupted animations need safe fallback behavior.
  • Timing and audio lifecycle behavior require device testing.

Automate animation asset preparation

Problem
The owner initially planned to deliver fully composited player and opponent frames, but manually combining every character and action would have created a significant repetitive workload.
Options considered
  • Have the owner manually produce every combined frame
  • Compose and adjust each frame manually during application development
  • Separate original artwork from an automated processing pipeline
Decision
Keep original artwork production with the owner and automate frame validation, composition, alignment, layering, cropping, resizing, and previews with Python and Pillow.
Why
The division preserved the owner’s creative control while removing mechanical asset work and producing a predictable structure for application integration.
Trade-offs
  • The processing scripts and their naming conventions became part of the production workflow.
  • Input sequences must follow consistent frame names and folder structures.
  • Pipeline changes need to remain synchronized with the application’s static asset maps.

Replace Gluestack with focused native components

Problem
Gluestack component styles sometimes prevented NativeWind classes from producing the expected result, making visual problems difficult to trace across multiple styling layers.
Options considered
  • Continue overriding Gluestack components
  • Add another wrapper layer around Gluestack
  • Replace the used components with React Native primitives and focused custom components
Decision
After discussing the trade-offs with the owner, preserve the approved visual design while replacing Gluestack with React Native primitives, NativeWind, and purpose-built shared components.
Why
The application used a relatively small set of highly customized game interactions and did not benefit enough from a broad generated UI system to justify its styling and maintenance cost.
Trade-offs
  • The project became responsible for maintaining its own shared component behavior.
  • Accessibility and interaction states must be handled deliberately.
  • Future general-purpose UI requirements may justify adopting selected external primitives.

Initialize persisted progression before dependent UI

Problem
Early TestFlight builds could read progression state before the initial AsyncStorage records were ready, producing incorrect locked or unlocked UI during startup.
Options considered
  • Render temporary default progression immediately
  • Block the entire application behind one global hydration screen
  • Centralize initialization and delay only progression-dependent UI until its stored state is ready
Decision
Centralize game-data initialization, normalize stored Kana status against the current schema, and delay progression-dependent Home controls until the relevant state has loaded.
Why
This prevents incorrect unlock states and provides recovery behavior for missing or malformed Kana progress without adding a global state-management dependency.
Trade-offs
  • Some screens require an explicit loading or hidden state.
  • Intro, options, and Drill data do not yet have the same normalization depth as Kana progression.
  • Local initialization behavior still requires testing with both new and existing application data.

Outcome

  1. The planned contract scope has been implemented.
  2. The application completed approximately one month of TestFlight testing with around ten participants.
  3. Both iOS and Android builds were validated.
  4. A first-launch issue involving initial progress creation and AsyncStorage loading was identified and addressed.
  5. TestFlight feedback about margins, text size, and readability led to responsive typography and spacing improvements.
  6. The interface was reviewed on the iPhone SE and older iPhone models.
  7. Replacing Gluestack did not reduce development speed and shortened design-related debugging.
  8. The Gluestack cleanup removed approximately thirty unused UI dependencies and thousands of lines of generated component code.
  9. The Python/Pillow pipeline reduced repetitive animation-composition work while preserving the owner’s original artwork.
  10. The owner is preparing the App Store deployment.
  11. Android distribution testing through Google Play remains pending.

Reflection

Kana Fighter taught me that technical leadership in a small client project is often about defining the right production boundary. The owner had a clear creative direction, while I was responsible for explaining which implementation choices would make that direction sustainable.

I initially assumed that using a comprehensive UI library would accelerate development. In practice, Gluestack’s styles sometimes conflicted with NativeWind and made design issues harder to trace. After discussing the trade-offs with the owner, I replaced it with React Native primitives and a small set of custom components. Development speed remained stable, while design-related debugging became faster.

The animation workflow led to a similar lesson. Asking the owner to manually produce every combined frame would have created an asset bottleneck. By separating original artwork from automated composition, I preserved the visual direction while reducing repetitive work through a Python and Pillow pipeline.

I am most satisfied with how a typed answer becomes a complete battle interaction. Input locking, audio, character animation, damage, progression, and navigation work as one sequence, helping the product feel like a game rather than a quiz with decorative graphics.

TestFlight reinforced that a design is not complete when it matches one screen. Feedback about margins, typography, and readability required testing and adjusting the application across smaller and older devices.

If I revisited the application, I would add versioned migration and validation for every persisted data group, introduce automated tests around the complete story, battle, and unlock flow, and add reduced-motion support.

I would retain the local-first architecture and smaller custom component system for the current product scope, while defining a clear migration boundary if cross-device progress became a real requirement.

Technology

  • React Native
  • Expo
  • Expo Router
  • TypeScript
  • NativeWind
  • AsyncStorage
  • React Native Reanimated
  • React Native Gesture Handler
  • Expo Audio