# HanjaKanaMun - Project Plan ## Overview Odin + raylib multilingual memory card game for learning Japanese (Hiragana/Katakana), Korean (Hangul), and Thai scripts. Commands (see `justfile`): `just run`, `just check`, `just test`, `just package `, `just package-windows ` ## Current Status ### Completed - [x] Code split into modules under `src/` (themes, assets, audio, ui, card, decks, memory, profile, screens) - [x] `.odin` files tracked in git (`.gitignore` used to ignore `*.odin`) - [x] Fonts: M PLUS Rounded 1c (UI + kana), Jua (Hangul), Mali (Thai), all OFL, loaded via `LoadFontEx` with explicit codepoints - [x] Per-language themes (`themes.Theme`) with flag-based card backs: Japan (Hinomaru), Korea (Taegukgi), Thailand (Trairong) - [x] Card flip animation (art rendered to textures and squashed), lift, shadow, lighting; matched cards pulse; cards are dealt in at game start - [x] Sound effects synthesized in code (flip, match, mismatch, win, button click), no audio files needed - [x] Decks: Hiragana (46), Katakana (46), Hangul basic jamo (24), Thai consonants (42); symbol ↔ reading pairs - [x] Difficulty levels: Easy 3x4 (6 pairs), Normal 4x6 (12), Hard 4x8 (16) - [x] Keyboard play: arrows / WASD / hjkl move focus, Enter/Space flips; number keys pick menu entries; Esc goes back (quits from main menu) - [x] High scores: best moves, best time and games played per deck + difficulty, plus last difficulty, saved as JSON in the user data folder - [x] Screen fade-in transitions - [x] Win panel with best score / "New best!", Play Again (Enter) / Menu - [x] Tooling: justfile, ols/odinfmt config for neovim, tests, Gitea CI (build/test, Windows cross-build, tagged releases) ### Ideas / Next - [ ] Hangul syllable blocks deck (e.g. 가 ga, 나 na), Thai vowels (needs care: combining marks) - [ ] Hiragana ↔ katakana pairing mode, dakuten (が, ぱ…) deck - [ ] Settings: sound on/off, volume - [ ] Test the Windows build on a real machine - [ ] macOS release build (would need a macOS runner or osxcross) ## Known Issues / Gotchas 1. **Font glyphs must be listed**: raylib only bakes the codepoints passed to `LoadFontEx`. Any new non-ASCII text needs its characters added in `assets.codepoints_for` (or the `EXTRA_*` strings), otherwise it renders as `?`. The `fonts_cover_every_deck_character` test checks deck text. 2. **Old font failure root cause**: `DroidSansJapanese.ttf` was a GitHub HTML page saved from a blob URL, not a font. Always download fonts from raw URLs and check them with `file`. 3. **Thai shaping**: raylib does no text shaping, so Thai combining vowels/tone marks would be misplaced. Thai cards use standalone consonants; Thai UI text avoids marks (win message is English). 4. Tests share the memory game's global state, so they must run single-threaded (`just test` sets `ODIN_TEST_THREADS=1`). 5. The game changes directory to the executable's folder at startup so `assets/` is found from anywhere. 6. **Windows builds are cross-compiled**: Odin can't link Windows binaries on Linux, so `just windows` builds objects with `-build-mode:obj` and links with `lld-link` against an xwin SDK. The VC runtime is linked statically; only Windows system DLLs are needed. New Odin core usage can need new import libs (e.g. `ole32.lib` for `os.user_data_dir`); the CI `windows` job catches this. Not yet tested on a real Windows machine. 7. **Odin constant arrays can't be indexed at runtime**: lookup tables (`DECKS`, `LAYOUTS`, …) are `@(rodata)` globals instead of `::` constants. 8. **Profile memory**: `profile` always allocates from the heap allocator, because the test runner frees each test's allocator afterwards. ## File Structure ``` HanjaKanaMun/ ├── AGENTS.md # Project plan (this file) ├── README.md ├── justfile # build/run/check/test/package/windows recipes ├── ols.json # ols language server settings (neovim) ├── odinfmt.json # odinfmt formatter settings (4 spaces, LF) ├── .gitea/ │ ├── actions/setup/ # shared CI toolchain setup (apt, Odin, just) │ └── workflows/ │ ├── gitea-build.yaml # check + test + build, and Windows cross-build, on main/develop │ └── gitea-release.yaml # on tags v*: Linux + Windows packages, package registry upload, Gitea release ├── main.odin # Window/audio setup, screen state machine, transitions ├── src/ │ ├── themes/themes.odin # Theme struct, flag colors, Screen/Flag enums, layout constants │ ├── assets/assets.odin # Font styles and codepoint lists, font loading │ ├── audio/audio.odin # Synthesized sound effects, play() │ ├── ui/ui.odin # Text helpers, buttons (with hotkeys), hover cursor │ ├── card/card.odin # Card struct, flag card-back art, face textures, flip/deal animation │ ├── decks/decks.odin # Languages, decks and their entries (symbol, reading, hint) │ ├── memory/memory.odin # Memory game state, logic, keyboard focus, drawing │ ├── profile/profile.odin # Best scores and settings, JSON in the user data folder │ └── screens/screens.odin # Main menu and language menus ├── tests/ │ ├── memory_test.odin # Decks, font coverage, game rules, layout, keyboard focus, card animation │ └── profile_test.odin # Score records, JSON round trip, win recording └── assets/ # Fonts (+ OFL license files) ``` ## Notes - NO rm -rf or starting over - Make small, incremental changes - Test after each change (`just check`, `just test`, then `just run`) - Keep existing functionality working - Commit after each working step