# 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 ` ## Current Status ### Completed - [x] Code split into modules under `src/` (themes, assets, audio, ui, card, memory, screens) - [x] `.odin` files tracked in git (`.gitignore` used to ignore `*.odin`) - [x] Real font: M PLUS Rounded 1c (Medium + ExtraBold, OFL) loaded via `LoadFontEx` with kana codepoints - [x] Per-language themes (`themes.Theme`) with flag-based card backs: Japan (Hinomaru), Korea (Taegukgi), Thailand (Trairong) - [x] Card flip animation: card art is rendered to textures and squashed horizontally, with lift, shadow and lighting; matched cards pulse - [x] Sound effects synthesized in code (flip, match, mismatch, win, button click), no audio files needed - [x] Japanese memory game: kana ↔ romaji pairs, 12 pairs picked at random from the 46 basic hiragana - [x] ESC goes back one screen (quits from the main menu); no longer closes the app everywhere - [x] Memory layout fixed (header and Back button no longer overlap cards), real shuffle, moves counted per pair - [x] Win panel with Play Again / Menu ### Pending - [ ] Keyboard support for card selection (1-9, A-L, etc.) - [ ] Korean memory game (needs a Hangul font, e.g. Noto Sans KR; theme and card back already exist) - [ ] Thai memory game (needs e.g. Noto Sans Thai; theme and card back already exist) - [ ] Katakana mode - [ ] Screen transitions - [ ] Score tracking and high scores - [ ] Difficulty levels (number of pairs) ## 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.build_codepoints`, otherwise it renders as `?`. 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 may be misplaced. Prefer standalone consonants on Thai cards. 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. Not yet tested on a real Windows machine. ## File Structure ``` HanjaKanaMun/ ├── AGENTS.md # Project plan (this file) ├── README.md ├── justfile # build/run/check/test/package 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 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 ├── src/ │ ├── themes/themes.odin # Theme struct, flag colors, Screen/Flag enums, layout constants │ ├── assets/assets.odin # Font loading (codepoints), get_font │ ├── audio/audio.odin # Synthesized sound effects, play() │ ├── ui/ui.odin # Text helpers, buttons, hover cursor │ ├── card/card.odin # Card struct, flag card-back art, face textures, flip animation │ ├── memory/memory.odin # Memory game state, logic and drawing │ └── screens/screens.odin # Main menu and language menus ├── tests/memory_test.odin # Game logic and card animation tests └── assets/ ├── MPLUSRounded1c-Medium.ttf ├── MPLUSRounded1c-ExtraBold.ttf └── MPLUSRounded1c-OFL.txt ``` ## 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