English | 简体中文
A minimalist daily puzzle app. Open it once a day and the app hands you exactly one randomly assigned puzzle — Sudoku, Binary (Takuzu/Binairo), Nonogram (Picross), or Slitherlink — then closes the loop with a sharp-tongued one-liner. New puzzle at local midnight.
No social feeds, no leaderboards, no fill-in hints — just today's one game.
iOS App Store (2.1.0): 傻了么 / Brainfool · released 2026-06-09. Android (Google Play) is not published yet.
For AI / contributors: production invariants and layer rules live in AGENTS.md; verification, code review, and manual QA checklists in docs/DEVELOPMENT.md and docs/TESTING.md; the GSD workflow in CLAUDE.md.
- Features
- Tech Stack
- Requirements
- Quick Start
- Scripts
- Project Structure
- Development Tools
- Testing
- Build & Release
- Roadmap
- Usage Examples
- License
- Documentation
| Capability | Description |
|---|---|
| Daily puzzle | Game type and board are derived from the local calendar day + a seed; reopening the same day is deterministic |
| Sudoku | Standard 9×9, conflict highlighting, complete / surrender |
| Binary | 8×8, four 0s and four 1s per row and column, no triples, no duplicate rows/columns |
| Nonogram | 8×8 Picross with row/column clues, validated on completion, pattern revealed on the result screen |
| Slitherlink | 7×7 single closed loop on edges, conflict highlighting, loop revealed on the result screen |
| Offline-first | Puzzles are generated and validated on-device; no network required |
| Persistence | Today's state is saved to AsyncStorage; resume after force-quit |
| Result screen | Randomized humorous copy + Reanimated entrance animations + "come back tomorrow" |
| Session timer | MM:SS elapsed time; foreground/background and system-clock correction (no backward jumps) |
| Rules sheet | A ? next to the game title opens the how-to-play modal |
| v1.1 Emoji recap | "Copy recap" on the result screen: emoji grid + time/streak written to the clipboard (expo-clipboard) |
| v1.1 Stats cards | Today's time, days completed this week, all-time longest streak (three compact cards) |
| v1.1 Rating prompt | Threshold-gated, delayed system store-review request after a win (expo-store-review; dismissable, non-blocking) |
| v1.1 Defensive flow | Daily selection solvability check + built-in fallback; snapshot repair; strips playState when completed contradicts an incomplete board |
| v1.2 System locale | Follows device zh / en (English brand Brainfool); bilingual privacy policy; no in-app language setting in release |
| v2.0 Slitherlink | 7×7 added to the daily rotation; tri-state edges, conflict highlighting, spoiler-free recap |
| v2.0 Streak Freeze | One shield granted weekly (max 2 stacked); a single missed day auto-consumes a shield on next open |
| v2.0 Missed-yesterday recall | When a day is missed without a shield, the game header shows a recall subline (mutually exclusive with the shield line) |
| v2.1 Weekday difficulty | Monday→Sunday ramp within the same game type (no UI difficulty label); APP_SALT / type selection unchanged |
| v2.1 Month calendar | "View this month" on the result screen → bottom sheet with four states (win / surrender / miss / shield) + streak & monthly summary |
| v2.1 Daily reminder | First-win soft ask + 20:00 game-screen banner + ReminderSheet; one local routine notification |
| v2.1 Month gallery | "Generate this month's gallery" → portrait PNG shared via the system sheet |
Out of scope: accounts/login, a personal stats dashboard (the summary is merged into the calendar), fill-in hints, social/leaderboards/friends (see the Roadmap).
- Expo SDK 54 + expo-router (file-based routing)
- React Native 0.81 · React 19 · TypeScript (strict)
- NativeWind v4 (Tailwind CSS)
- react-native-reanimated (result-screen animations)
- @react-native-async-storage/async-storage (local persistence)
- Puzzle logic: pure TypeScript (
lib/puzzles/) — generators, solvers, and validators
Targets iOS and Android (managed workflow; no committed ios/ / android/ native directories).
- Node.js 22 LTS (matches CI; repo
.nvmrcis22) - npm 11+ (matches CI;
package-lock.jsonis canonical — install withnpm ci) - iOS: Xcode + Simulator (macOS only), or Expo Go
- Android: Android Studio emulator, or a device with Expo Go
# Clone
git clone https://github.com/moyunzero/foolish-you.git
cd foolish-you
# Install dependencies
npm install
# Start the dev server
npm startIn the terminal, press i for the iOS Simulator, a for the Android emulator, or scan the QR code with Expo Go.
If you changed babel.config.js (e.g. the Reanimated plugin) or native dependencies, start with a cleared cache:
npx expo start -c| Command | Description |
|---|---|
npm start |
Start the Expo dev server |
npm run ios |
Run on the iOS Simulator/device (requires prebuild or a dev client) |
npm run android |
Run on Android |
npm run web |
Web preview (not the primary target) |
npm test |
Run Jest unit + RTL tests (puzzles, storage, context, screens) |
npm run test:migration |
Run only the storage-migration golden samples (matches CI) |
npm run typecheck |
TypeScript strict check (tsc --noEmit) |
npm run lint |
ESLint (expo lint) |
For the full tree and "where new code goes," see docs/DEVELOPMENT.md § Code layout. Architecture and data flow are in docs/ARCHITECTURE.md.
foolish-you/
├── app/ # expo-router screens (no puzzle algorithms)
├── components/
│ ├── grid/ # SudokuGrid, BinaryGrid, NonogramGrid, SudokuNumpad
│ ├── slitherlink/ # SlitherlinkBoard (tri-state edges)
│ ├── game/ # per-type Sections, Header/Footer, rules modal
│ ├── result/ # badges, stats cards, recap, Nonogram/Slitherlink reveal cards
│ ├── ui/ · legal/ · dev/
├── contexts/ # DailyGameContext, DevToolsUiContext
├── hooks/ # per-type board hooks, useGameBoardSession, useElapsedTimer
├── lib/
│ ├── date/ · daily/ · puzzles/ · storage/ · streak/ · completion/
│ ├── share/ · stats/ · rating/ · time/ · copy/ · i18n/ · dev/ · platform/
├── locales/ # zh / en
├── constants/ # config, design, dev, legal
└── __tests__/ # lib/ · contexts/ · hooks/ · components/ · screens/
- Launch →
DailyGameContextloads or creates today's record (dateKey+seed+gameType+ board). - Selection →
lib/puzzles/dailySelectorSafe.ts(solvability check + fallback when needed) stably randomizes across Sudoku / Binary / Nonogram / Slitherlink from the date seed and generates a solvable board. - Play →
app/game.tsxrenders the matching grid and footer bygameType; progress is debounced to local storage. - End → complete or surrender →
app/result.tsxshows copy and animations; a new game starts automatically oncedateKeychanges the next day.
In development (__DEV__), a dev panel on the home screen lets you regenerate today, force a game type, and preview locales via the settings placeholder. See constants/dev.ts.
// constants/dev.ts
export const DEV_FORCE_GAME_TYPE: GameType | null = 'sudoku'; // null = same random as production; 'binary' | 'nonogram' | 'slitherlink'Release builds never include this panel.
npm testCoverage: date utilities, RNG, daily and safe selection, Sudoku/Binary/Nonogram/Slitherlink generation and validation, storage migration/recovery, completion history, recap and stats, rating thresholds, streaks, i18n (incl. en-smoke), and context/screen RTL — 476 tests. Plus npm run test:migration golden samples. UI animations and on-device layout are covered by manual QA.
Use EAS Build or a local prebuild:
# Install and sign in first: npm install -g eas-cli && eas login
eas build --platform ios
eas build --platform android
# Or use the project scripts (preview profile)
npm run build:preview:ios
npm run build:preview:androidapp.json configures the app name 傻了么, bundle ID com.moyunzero.foolish-you, and a dark UI. The current iOS production build 2.1.0 is shipped via the EAS production profile to App Store Connect. Retention KPIs are read from App Store Connect Analytics — there is no third-party analytics SDK in the app.
Full internal detail in
.planning/ROADMAP.md.
- Offline-first:
dateKey + seeddetermines the board; never fetched remotely. - One game a day: never becomes "three puzzles a day" — the tension comes from uniqueness.
- Won't build: leaderboards, friends, IM, IAP, ads, hint buttons, shields stacking beyond 2.
- Sharp humor is the brand moat; every line of copy must clear that bar.
| Stage | D1 | D7 | D30 | Rating | Complete → Share |
|---|---|---|---|---|---|
| v1.0 (shipped baseline) | — | — | — | live | 0% (no entry) |
| v1.1 target (≈ 3 mo) | 32% | 12% | 5% | ≥ 4.4 | ≥ 3% |
Current v2.1 (2.1.0, iOS live) |
TBD (ASC) | TBD (ASC) | TBD (ASC) | ≥ 4.5 target | ≥ 5% target |
| Post-v2.0 (≈ 6 mo) | 35%+ | 15%+ | 7%+ | ≥ 4.5 | ≥ 5% |
| 12-month target | 38% | 18% | 9% | 4.6 | 7% |
The goal is to move "傻了么" from the puzzle-subcategory median into the Top 25% (industry 35/15/5 baseline).
| Version | Status | Scope | Key bet (data anchor) |
|---|---|---|---|
| v1.0 | Released | Daily Sudoku / Binary / Nonogram (no Slitherlink), local progress, streaks, result animations, timer, rules modal | — |
| v1.1 | Released (1.1.x) |
① Emoji recap copy (lib/share/ + expo-clipboard); ② rating prompt (win + completion-count thresholds, expo-store-review); ③ three stat cards (today's time / weekly completions / all-time longest streak, historicalMax); ④ defenses: selectDailyGameSafe, snapshot recoverSnapshot, timer correction, migration + recovery tests, dev recovery log |
Wordle's 90→300K DAU came from one-tap emoji sharing |
| v1.2 | Released (1.2.0) |
System locale zh/en (expo-localization); English brand Brainfool; locales/ + useI18n; bilingual privacy; DevTools settings placeholder (no storage writes, no release entry) |
Overseas readability + store compliance |
| v2.0 | Live (in 2.1.0) |
✅ Slitherlink 7×7; ✅ Streak Freeze; ✅ missed-yesterday recall | Duolingo: streak lifespan +48% |
| v2.1 | Live (2.1.0; App Store 2026-06-09) |
✅ Weekday difficulty; ✅ month calendar + summary; ✅ daily reminder (A+D); ✅ month gallery PNG | NYT Mini/Midi cadence · D1→D2 |
| v3.0 | Planned | iCloud / Google end-to-end sync or QR import/export; 30-day history archive | Avoid conflict with offline-first / no-social |
| v4.0 | Planned | Anonymous challenge codes; "Year in 傻了么" annual long-image | Conversation between friends, no friend list |
Full decision rationale, A/B designs, and trade-offs are in .planning/ROADMAP.md (gitignored; not published with the repo).
- Launch the app →
app/index.tsxroutes toapp/game.tsxbased on today's record. - The game header
GameScreenHeadershows the date, session time, type title, streak subline (streakLine), and an optional shield/recall subline (GameStreakSubline— shield and missed-yesterday are mutually exclusive). - Fill the board, then tap Complete in the footer (
GameScreenFooter) → on a valid solution you reach the result screen; surrender does not count toward the streak. - On the result screen you can tap Copy recap (requires a valid
playState; if the board was stripped during recovery, only copy and stats are shown). - A new puzzle loads automatically once the local
dateKeychanges the next day.
- Only wins count toward the streak; surrendering does not call
applyCheckIn(seecontexts/DailyGameContext.tsx). - Logic:
lib/streak/streakLogic.ts(+1 per consecutive day, reset on a gap); persistence:lib/storage/streakStorage.ts(key@foolish-you/streak-v1, schema v3, incl.historicalMax,freezeCount). - Streak Freeze:
lib/streak/freezeLogic.ts— one shield granted on the first open of each ISO week (max 2); if exactly one day was missed since the last check-in with no real completion, one shield is auto-consumed on hydrate. - Missed-yesterday recall:
lib/streak/missedYesterdayBanner.ts— when a day is missed without consuming a shield, the game header shows recall copy (mutually exclusive with the shield line). - Copy: header streak in
lib/copy/streak.ts; shield/recall inlib/copy/freeze.tsandlib/copy/missedYesterday.ts; result-card shield suffix inlocales/*/copy.ts.
The GitHub Actions workflow .github/workflows/ci.yml runs on push and PR to main / master:
npm run typecheck # tsc --noEmit
npm test # Jest: unit (*.test.ts) + rtl (*.test.tsx)
npm run test:migration # storage-migration golden samples
npm run lint # expo lint
npm run lockfile:verify-eas # npm 10 ci; recommended before an EAS buildOptional split:
npm run test:unit # pure logic: puzzles, storage, streaks
npm run test:rtl # context- and screen-level RTL testsNo open-source license has been specified yet. If you fork or redistribute, please confirm permission with the repository maintainer first.
Puzzle algorithms and product inspiration come from classic Sudoku and Takuzu/Binairo rules; delivered on the Expo and React Native ecosystem.
| Document | Description |
|---|---|
| AGENTS.md | AI/contributors: production invariants, layer rules, verification entry points |
| docs/ARCHITECTURE.md | Architecture, data flow, offline-first and persistence |
| docs/GETTING-STARTED.md | Install and first run |
| docs/DEVELOPMENT.md | Day-to-day development, CI checks, DevTools |
| docs/TESTING.md | Jest dual projects and the manual QA checklist |
| docs/CONFIGURATION.md | app.json, EAS, constants and storage keys |