Windows build links ole32; CI cross-builds Windows on main/develop; docs
Build and Test / build (push) Failing after 23s
Build and Test / windows (push) Successful in 11m6s

- os.user_data_dir needs ole32.lib on Windows
- Build workflow gets a Windows cross-build job with a cached SDK
- Drop the backtick from Jua's codepoints (the font lacks it)
- Update AGENTS.md and README for decks, controls, scores and layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SansanandClaude Opus 5.5 committed 2026-10-01 21:38:07 +02:00
1 parent 3794a9e2e9
commit 26904b6852
5 files changed
+94 -36

No files matched your search

+31 -1
View File
@@ -1,7 +1,8 @@
#
# .gitea/workflows/gitea-build.yaml
#
# Checks, tests and builds on every push and pull request to main and develop.
# Checks, tests and builds (Linux, plus a Windows cross-build) on every push and
# pull request to main and develop.
#
name: "Build and Test"
@@ -31,3 +32,32 @@ jobs:
- name: Build
run: just release
# Cross-compiles for Windows so link breakages show up before a release
windows:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Toolchain
uses: ./.gitea/actions/setup
with:
packages: lld zip
- name: Cache Windows SDK
uses: actions/cache@v4
with:
path: |
build/xwin
build/xwin-sdk
build/raylib-windows
key: windows-sdk-${{ hashFiles('justfile') }}
- name: Windows Build
run: |
# Debian/Ubuntu may only ship a versioned lld-link
LLD_LINK="$(command -v lld-link || ls /usr/lib/llvm-*/bin/lld-link | sort -V | tail -1)"
export LLD_LINK
just windows-deps
just windows
+35 -31
View File
@@ -3,66 +3,70 @@
## 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 <version>`
Commands (see `justfile`): `just run`, `just check`, `just test`, `just package <version>`, `just package-windows <version>`
## Current Status
### Completed
- [x] Code split into modules under `src/` (themes, assets, audio, ui, card, memory, screens)
- [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] Real font: M PLUS Rounded 1c (Medium + ExtraBold, OFL) loaded via `LoadFontEx` with kana codepoints
- [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: card art is rendered to textures and squashed horizontally, with lift, shadow and lighting; matched cards pulse
- [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] 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
- [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)
### 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)
### 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.build_codepoints`, otherwise it renders as `?`.
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 may be misplaced. Prefer standalone consonants on Thai cards.
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. Not yet tested on a real Windows machine.
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 recipes
├── 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 on main/develop
│ ├── 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
├── 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 loading (codepoints), get_font
│ ├── assets/assets.odin # Font styles and codepoint lists, font loading
│ ├── 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
│ ├── 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 # Game logic and card animation tests
└── assets/
├── MPLUSRounded1c-Medium.ttf
├── MPLUSRounded1c-ExtraBold.ttf
└── MPLUSRounded1c-OFL.txt
├── 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
+22 -2
View File
@@ -2,6 +2,24 @@
Odin + raylib memory card game for learning Japanese, Korean and Thai scripts.
Match each symbol with its reading: hiragana and katakana with romaji, Hangul letters with their
Revised Romanization, and Thai consonants with their sound and name word. Three difficulties
(6, 12 or 16 pairs), best scores per deck, and a card deck themed on each country's flag.
## Controls
| | |
|---|---|
| Mouse | Click cards and buttons |
| Arrows / WASD / hjkl | Move between cards |
| Enter / Space | Flip the selected card (Enter also plays again) |
| 1-3 | Pick a language or deck in menus |
| Left / Right | Change difficulty |
| Esc | Back (quits from the main menu) |
Scores are saved to `profile.json` in the user data folder (`~/Library/Application Support/HanjaKanaMun`
on macOS, `~/.local/share/HanjaKanaMun` on Linux, `%APPDATA%\HanjaKanaMun` on Windows).
## Development
Requires [Odin](https://odin-lang.org) (dev-2026-09) and [just](https://just.systems).
@@ -27,7 +45,8 @@ Formatting goes through ols (`vim.lsp.buf.format()`), or `just fmt` if `odinfmt`
## CI
- `.gitea/workflows/gitea-build.yaml`: check, test and build on pushes and PRs to `main` and `develop`.
- `.gitea/workflows/gitea-build.yaml`: check, test and build on pushes and PRs to `main` and `develop`, plus a
Windows cross-build so link problems show up before a release.
- `.gitea/workflows/gitea-release.yaml`: on a version tag like `v1.0`, builds Linux and Windows packages, uploads them
to the Gitea generic package registry (`HanjaKanaMun/1.0`) and creates a Gitea release with both attached.
Needs a `TOKEN` secret with package write access.
@@ -37,4 +56,5 @@ Release: `git tag v1.0 && git push origin v1.0`
## Credits
Font: M PLUS Rounded 1c, SIL Open Font License (`assets/MPLUSRounded1c-OFL.txt`).
Fonts, all under the SIL Open Font License (license files in `assets/`):
M PLUS Rounded 1c (UI and kana), Jua (Hangul), Mali (Thai).
+1 -1
View File
@@ -81,7 +81,7 @@ windows:
/nodefaultlib:msvcrt /nodefaultlib:vcruntime /nodefaultlib:libucrt \
libcmt.lib libvcruntime.lib ucrt.lib \
kernel32.lib user32.lib gdi32.lib winmm.lib shell32.lib opengl32.lib \
advapi32.lib bcrypt.lib ntdll.lib synchronization.lib
advapi32.lib bcrypt.lib ntdll.lib synchronization.lib ole32.lib
cp -R assets "$out_dir/"
# Windows build packed as build/<name>_<version>_windows_x86_64.zip
+5 -1
View File
@@ -31,7 +31,11 @@ EXTRA_THAI :: "ไทย"
// Tests check that every deck's characters are covered.
codepoints_for :: proc(style: Font_Style, allocator := context.allocator) -> [dynamic]rune {
codepoints := make([dynamic]rune, allocator)
for r in rune(0x20) ..= 0x7E { append(&codepoints, r) } // ASCII
for r in rune(0x20) ..= 0x7E {
// Jua has no backtick; Korean text never uses it
if style == .KOREAN && r == '`' { continue }
append(&codepoints, r) // ASCII
}
switch style {
case .REGULAR, .BOLD:
for r in rune(0x3041) ..= 0x3096 { append(&codepoints, r) } // Hiragana