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
.thenmethod. Anasync functionthat returned the Purchases plugin made JavaScript treat it as a Promise and await a native.thencall 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 realSceneDelegatethat 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
- admob
- android
- capacitor
- cloudflare
- ios
- line
- next.js
- react
- revenuecat
- supabase
- typescript
- workers
Log in or sign up for Devpost to join the conversation.