Inspiration

As a mahjong player, I kept wishing for quick, bite-sized drills to sharpen tile efficiency and fu-scoring — skills that usually take years of table time to build. So I built Mahjong Tsujigiri! (麻雀 辻斬る!), a training app that turns those drills into 60-second rounds.

What it does

Mahjong Tsujigiri! offers three practice modes:

  • Full Flush — advanced discard puzzles using a single suit
  • What to Discard — build efficient hands under a 60-second clock
  • Fu Practice — read a completed hand and calculate its fu, with a full breakdown afterward

Every mode also has an Oni Challenge: a 5-second-per-question sprint where consecutive correct answers double your score, up to 8x. Players track history, climb samurai-inspired ranks, and compare scores on global and weekly leaderboards. The app works in both Japanese and English.

How I built it

  • Next.js 16 + TypeScript + React 19, compiled to a static export and wrapped with Capacitor 8 for iOS and Android
  • Supabase for the problem bank and leaderboard
  • RevenueCat + the Capacitor Purchases SDK power the freemium model: free players get 5 rounds a day (or a rewarded-ad refill), and Premium subscribers (monthly/annual) or a one-time Remove Ads purchase unlock more
  • AdMob for banner/interstitial ads, gated by Google's UMP consent flow for EEA users
  • The same Next.js codebase also ships as a web app and a LINE mini-app, so non-native players can practice too

Challenges I ran into

  • A Capacitor "thenable" trap: Capacitor plugins are Proxies that return a function for every property access, so they look like they carry a .then method. An async function that returned the Purchases plugin made JavaScript treat it as a Promise and await a native .then call that never resolves — the purchase screen hung forever. The fix was to reference the plugin synchronously instead of returning it from an async function.
  • iOS 27's UIScene enforcement: a build that had shipped fine for months started crashing on launch on iOS 27 devices, because the app had never adopted the UIScene lifecycle. Capacitor's own scene-delegate proxy doesn't implement UIWindowSceneDelegate, so I had to write a real SceneDelegate that builds the window itself and forwards Capacitor's own URL and continuity handling.
  • Static export routing on native: Capacitor's local web server maps any extension-less path to the root index.html, which breaks a statically exported, localized Next.js site (e.g. /en/). Fixed with a custom iOS router plus a reload-safe path rewriter that works on both platforms.

What's next

  • Monthly problem-set refreshes (the subscription's core promise)
  • A daily-challenge mode and a "missed problems" review mode
  • A wider Android rollout via Google Play

Built With

Share this project:

Updates

Submission history