Windows build links ole32; CI cross-builds Windows on main/develop; docs
- 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:
1 parent
3794a9e2e9
commit
26904b6852
5 files changed
+94
-36
No files matched your search
@@ -1,7 +1,8 @@
|
|||||||
#
|
#
|
||||||
# .gitea/workflows/gitea-build.yaml
|
# .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"
|
name: "Build and Test"
|
||||||
@@ -31,3 +32,32 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: just release
|
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
|
||||||
@@ -3,66 +3,70 @@
|
|||||||
## Overview
|
## Overview
|
||||||
Odin + raylib multilingual memory card game for learning Japanese (Hiragana/Katakana), Korean (Hangul), and Thai scripts.
|
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
|
## Current Status
|
||||||
|
|
||||||
### Completed
|
### 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] `.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] 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] 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] Decks: Hiragana (46), Katakana (46), Hangul basic jamo (24), Thai consonants (42); symbol ↔ reading pairs
|
||||||
- [x] ESC goes back one screen (quits from the main menu); no longer closes the app everywhere
|
- [x] Difficulty levels: Easy 3x4 (6 pairs), Normal 4x6 (12), Hard 4x8 (16)
|
||||||
- [x] Memory layout fixed (header and Back button no longer overlap cards), real shuffle, moves counted per pair
|
- [x] Keyboard play: arrows / WASD / hjkl move focus, Enter/Space flips; number keys pick menu entries; Esc goes back (quits from main menu)
|
||||||
- [x] Win panel with Play Again / 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
|
### Ideas / Next
|
||||||
- [ ] Keyboard support for card selection (1-9, A-L, etc.)
|
- [ ] Hangul syllable blocks deck (e.g. 가 ga, 나 na), Thai vowels (needs care: combining marks)
|
||||||
- [ ] Korean memory game (needs a Hangul font, e.g. Noto Sans KR; theme and card back already exist)
|
- [ ] Hiragana ↔ katakana pairing mode, dakuten (が, ぱ…) deck
|
||||||
- [ ] Thai memory game (needs e.g. Noto Sans Thai; theme and card back already exist)
|
- [ ] Settings: sound on/off, volume
|
||||||
- [ ] Katakana mode
|
- [ ] Test the Windows build on a real machine
|
||||||
- [ ] Screen transitions
|
- [ ] macOS release build (would need a macOS runner or osxcross)
|
||||||
- [ ] Score tracking and high scores
|
|
||||||
- [ ] Difficulty levels (number of pairs)
|
|
||||||
|
|
||||||
## Known Issues / Gotchas
|
## 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`.
|
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`).
|
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.
|
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
|
## File Structure
|
||||||
```
|
```
|
||||||
HanjaKanaMun/
|
HanjaKanaMun/
|
||||||
├── AGENTS.md # Project plan (this file)
|
├── AGENTS.md # Project plan (this file)
|
||||||
├── README.md
|
├── README.md
|
||||||
├── justfile # build/run/check/test/package recipes
|
├── justfile # build/run/check/test/package/windows recipes
|
||||||
├── ols.json # ols language server settings (neovim)
|
├── ols.json # ols language server settings (neovim)
|
||||||
├── odinfmt.json # odinfmt formatter settings (4 spaces, LF)
|
├── odinfmt.json # odinfmt formatter settings (4 spaces, LF)
|
||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ ├── actions/setup/ # shared CI toolchain setup (apt, Odin, just)
|
│ ├── actions/setup/ # shared CI toolchain setup (apt, Odin, just)
|
||||||
│ └── workflows/
|
│ └── 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
|
│ └── 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/
|
├── src/
|
||||||
│ ├── themes/themes.odin # Theme struct, flag colors, Screen/Flag enums, layout constants
|
│ ├── 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()
|
│ ├── audio/audio.odin # Synthesized sound effects, play()
|
||||||
│ ├── ui/ui.odin # Text helpers, buttons, hover cursor
|
│ ├── ui/ui.odin # Text helpers, buttons (with hotkeys), hover cursor
|
||||||
│ ├── card/card.odin # Card struct, flag card-back art, face textures, flip animation
|
│ ├── card/card.odin # Card struct, flag card-back art, face textures, flip/deal animation
|
||||||
│ ├── memory/memory.odin # Memory game state, logic and drawing
|
│ ├── 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
|
│ └── screens/screens.odin # Main menu and language menus
|
||||||
├── tests/memory_test.odin # Game logic and card animation tests
|
├── tests/
|
||||||
└── assets/
|
│ ├── memory_test.odin # Decks, font coverage, game rules, layout, keyboard focus, card animation
|
||||||
├── MPLUSRounded1c-Medium.ttf
|
│ └── profile_test.odin # Score records, JSON round trip, win recording
|
||||||
├── MPLUSRounded1c-ExtraBold.ttf
|
└── assets/ # Fonts (+ OFL license files)
|
||||||
└── MPLUSRounded1c-OFL.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|||||||
@@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
Odin + raylib memory card game for learning Japanese, Korean and Thai scripts.
|
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
|
## Development
|
||||||
|
|
||||||
Requires [Odin](https://odin-lang.org) (dev-2026-09) and [just](https://just.systems).
|
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
|
## 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
|
- `.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.
|
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.
|
Needs a `TOKEN` secret with package write access.
|
||||||
@@ -37,4 +56,5 @@ Release: `git tag v1.0 && git push origin v1.0`
|
|||||||
|
|
||||||
## Credits
|
## 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).
|
||||||
@@ -81,7 +81,7 @@ windows:
|
|||||||
/nodefaultlib:msvcrt /nodefaultlib:vcruntime /nodefaultlib:libucrt \
|
/nodefaultlib:msvcrt /nodefaultlib:vcruntime /nodefaultlib:libucrt \
|
||||||
libcmt.lib libvcruntime.lib ucrt.lib \
|
libcmt.lib libvcruntime.lib ucrt.lib \
|
||||||
kernel32.lib user32.lib gdi32.lib winmm.lib shell32.lib opengl32.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/"
|
cp -R assets "$out_dir/"
|
||||||
|
|
||||||
# Windows build packed as build/<name>_<version>_windows_x86_64.zip
|
# Windows build packed as build/<name>_<version>_windows_x86_64.zip
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ EXTRA_THAI :: "ไทย"
|
|||||||
// Tests check that every deck's characters are covered.
|
// Tests check that every deck's characters are covered.
|
||||||
codepoints_for :: proc(style: Font_Style, allocator := context.allocator) -> [dynamic]rune {
|
codepoints_for :: proc(style: Font_Style, allocator := context.allocator) -> [dynamic]rune {
|
||||||
codepoints := make([dynamic]rune, allocator)
|
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 {
|
switch style {
|
||||||
case .REGULAR, .BOLD:
|
case .REGULAR, .BOLD:
|
||||||
for r in rune(0x3041) ..= 0x3096 { append(&codepoints, r) } // Hiragana
|
for r in rune(0x3041) ..= 0x3096 { append(&codepoints, r) } // Hiragana
|
||||||
|
|||||||
Reference in new issue
Block a user