Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda2258fde | ||
|
|
ad7d19b85a |
No files matched your search
@@ -49,7 +49,7 @@ jobs:
|
|||||||
- name: Setup Toolchain
|
- name: Setup Toolchain
|
||||||
uses: ./.gitea/actions/setup
|
uses: ./.gitea/actions/setup
|
||||||
with:
|
with:
|
||||||
packages: lld zip
|
packages: lld
|
||||||
|
|
||||||
- name: Cache Windows SDK
|
- name: Cache Windows SDK
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Setup Toolchain
|
- name: Setup Toolchain
|
||||||
uses: ./.gitea/actions/setup
|
uses: ./.gitea/actions/setup
|
||||||
with:
|
with:
|
||||||
packages: lld zip
|
packages: lld
|
||||||
|
|
||||||
- name: Set Version
|
- name: Set Version
|
||||||
run: echo "GAME_VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
|
run: echo "GAME_VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
|
||||||
@@ -60,5 +60,4 @@ jobs:
|
|||||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
||||||
with:
|
with:
|
||||||
files: |-
|
files: |-
|
||||||
build/${{ env.PROJECT_NAME }}_*.tar.gz
|
build/${{ env.PROJECT_NAME }}_*
|
||||||
build/${{ env.PROJECT_NAME }}_*.zip
|
|
||||||
@@ -19,3 +19,4 @@ HanjaKanaMun
|
|||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
build/
|
build/
|
||||||
|
*.dSYM/
|
||||||
@@ -10,7 +10,7 @@ Commands (see `justfile`): `just run`, `just check`, `just test`, `just package
|
|||||||
### Completed
|
### Completed
|
||||||
- [x] Code split into modules under `src/` (themes, assets, audio, ui, card, decks, memory, profile, 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] Fonts: M PLUS Rounded 1c (UI + kana), Jua (Hangul), Mali (Thai), all OFL, loaded via `LoadFontEx` with explicit codepoints
|
- [x] Fonts: M PLUS Rounded 1c (UI + kana), Jua (Hangul), Mali (Thai), all OFL, embedded via `#load` and loaded with `LoadFontFromMemory` + explicit codepoints - release builds are a single self-contained executable
|
||||||
- [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 (art rendered to textures and squashed), lift, shadow, lighting; matched cards pulse; cards are dealt in at game start
|
- [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
|
||||||
@@ -30,15 +30,16 @@ Commands (see `justfile`): `just run`, `just check`, `just test`, `just package
|
|||||||
- [ ] macOS release build (would need a macOS runner or osxcross)
|
- [ ] macOS release build (would need a macOS runner or osxcross)
|
||||||
|
|
||||||
## 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.codepoints_for` (or the `EXTRA_*` strings), otherwise it renders as `?`. The `fonts_cover_every_deck_character` test checks deck text.
|
1. **Font glyphs must be listed**: raylib only bakes the codepoints passed to `LoadFontFromMemory`. 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 would be misplaced. Thai cards use standalone consonants; Thai UI text avoids marks (win message is English).
|
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. **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.
|
||||||
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.
|
6. **Odin constant arrays can't be indexed at runtime**: lookup tables (`DECKS`, `LAYOUTS`, …) are `@(rodata)` globals instead of `::` constants.
|
||||||
7. **Odin constant arrays can't be indexed at runtime**: lookup tables (`DECKS`, `LAYOUTS`, …) are `@(rodata)` globals instead of `::` constants.
|
7. **Profile memory**: `profile` always allocates from the heap allocator, because the test runner frees each test's allocator afterwards.
|
||||||
8. **Profile memory**: `profile` always allocates from the heap allocator, because the test runner frees each test's allocator afterwards.
|
8. **`odin test` segfaults if the stack rlimit is "unlimited"**: the CI runner's container sets `ulimit -s unlimited`, which makes `getrlimit(RLIMIT_STACK).rlim_cur` come back as `RLIM_INFINITY` (a huge unsigned value). Odin's `core:thread` passes that straight to `pthread_attr_setstacksize`, `pthread_create` then fails, and `thread.create()` returns `nil` - which `core:testing`'s `pool_init` dereferences unconditionally, segfaulting the whole test binary (exit 139) before it prints anything. `just test` works around it with `ulimit -s 8192` on the same line as `odin test` (`just` runs each recipe line in its own shell, so a separate line wouldn't stick). Upstream Odin bug (core:thread/core:testing don't handle a failed thread creation); not our code.
|
||||||
9. **`odin test` segfaults if the stack rlimit is "unlimited"**: the CI runner's container sets `ulimit -s unlimited`, which makes `getrlimit(RLIMIT_STACK).rlim_cur` come back as `RLIM_INFINITY` (a huge unsigned value). Odin's `core:thread` passes that straight to `pthread_attr_setstacksize`, `pthread_create` then fails, and `thread.create()` returns `nil` - which `core:testing`'s `pool_init` dereferences unconditionally, segfaulting the whole test binary (exit 139) before it prints anything. `just test` works around it with `ulimit -s 8192` before running `odin test`. Upstream Odin bug (core:thread/core:testing don't handle a failed thread creation); not our code.
|
9. **CI jobs share one self-hosted runner**: `build` and `windows` in `gitea-build.yaml` both clone `actions/checkout`/`actions/cache` into the same cache directory on the runner. Starting them at the same time can race on that clone and fail one job outright, so `windows` runs with `needs: build`.
|
||||||
|
10. **Release artifacts are bare executables**, not archives: fonts are embedded in the binary (see gotcha 1), so there's nothing else to ship alongside it.
|
||||||
|
|
||||||
## File Structure
|
## File Structure
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>English</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.apple.xcode.dsym.HanjaKanaMun</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>dSYM</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -28,9 +28,9 @@ Requires [Odin](https://odin-lang.org) (dev-2026-09) and [just](https://just.sys
|
|||||||
just run # build and play (debug)
|
just run # build and play (debug)
|
||||||
just check # type-check with -vet
|
just check # type-check with -vet
|
||||||
just test # run tests
|
just test # run tests
|
||||||
just package 1.0 # native release build -> build/HanjaKanaMun_1.0_<os>_<arch>.tar.gz
|
just package 1.0 # native release build -> build/HanjaKanaMun_1.0_<os>_<arch> (self-contained executable)
|
||||||
just windows-deps # Linux only: fetch Windows SDK/CRT (xwin) and raylib.lib
|
just windows-deps # Linux only: fetch Windows SDK/CRT (xwin) and raylib.lib
|
||||||
just package-windows 1.0 # cross-compiled Windows build -> build/HanjaKanaMun_1.0_windows_x86_64.zip
|
just package-windows 1.0 # cross-compiled Windows build -> build/HanjaKanaMun_1.0_windows_x86_64.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
The Windows build needs `lld-link`. On macOS, `brew install lld` and set `LLD_LINK`, and point `XWIN_SDK` and
|
The Windows build needs `lld-link`. On macOS, `brew install lld` and set `LLD_LINK`, and point `XWIN_SDK` and
|
||||||
|
|||||||
@@ -36,16 +36,15 @@ test:
|
|||||||
# `ulimit` line wouldn't carry over to the `odin test` line.)
|
# `ulimit` line wouldn't carry over to the `odin test` line.)
|
||||||
ulimit -s 8192 && odin test tests -vet -define:ODIN_TEST_THREADS=1 -out:{{build_dir}}/tests
|
ulimit -s 8192 && odin test tests -vet -define:ODIN_TEST_THREADS=1 -out:{{build_dir}}/tests
|
||||||
|
|
||||||
# Optimized native build into build/<os>/, with the assets it needs at runtime
|
# Optimized native build into build/<os>/<name> (self-contained: fonts are embedded)
|
||||||
release:
|
release:
|
||||||
mkdir -p {{build_dir}}/{{os()}}/{{name}}
|
mkdir -p {{build_dir}}/{{os()}}
|
||||||
odin build . -vet -o:speed -out:{{build_dir}}/{{os()}}/{{name}}/{{name}}
|
odin build . -vet -o:speed -out:{{build_dir}}/{{os()}}/{{name}}
|
||||||
cp -R assets {{build_dir}}/{{os()}}/{{name}}/
|
|
||||||
|
|
||||||
# Native release build packed as build/<name>_<version>_<os>_<arch>.tar.gz
|
# Native release build as build/<name>_<version>_<os>_<arch>
|
||||||
package version="dev": release
|
package version="dev": release
|
||||||
tar -czf {{build_dir}}/{{name}}_{{version}}_{{os()}}_{{arch()}}.tar.gz -C {{build_dir}}/{{os()}} {{name}}
|
cp {{build_dir}}/{{os()}}/{{name}} {{build_dir}}/{{name}}_{{version}}_{{os()}}_{{arch()}}
|
||||||
@echo "{{build_dir}}/{{name}}_{{version}}_{{os()}}_{{arch()}}.tar.gz"
|
@echo "{{build_dir}}/{{name}}_{{version}}_{{os()}}_{{arch()}}"
|
||||||
|
|
||||||
# Download the Windows SDK + CRT (via xwin, accepting Microsoft's license) and raylib.lib. Linux x86_64 only.
|
# Download the Windows SDK + CRT (via xwin, accepting Microsoft's license) and raylib.lib. Linux x86_64 only.
|
||||||
windows-deps:
|
windows-deps:
|
||||||
@@ -67,12 +66,12 @@ windows-deps:
|
|||||||
unzip -o -j {{build_dir}}/odin-windows.zip 'dist/vendor/raylib/windows/raylib.lib' -d "{{raylib_windows}}"
|
unzip -o -j {{build_dir}}/odin-windows.zip 'dist/vendor/raylib/windows/raylib.lib' -d "{{raylib_windows}}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Cross-compile a Windows x86_64 build into build/windows/ (run `just windows-deps` first)
|
# Cross-compile a self-contained Windows x86_64 build into build/windows/ (run `just windows-deps` first)
|
||||||
windows:
|
windows:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
obj_dir={{build_dir}}/obj-windows
|
obj_dir={{build_dir}}/obj-windows
|
||||||
out_dir={{build_dir}}/windows/{{name}}
|
out_dir={{build_dir}}/windows
|
||||||
mkdir -p "$obj_dir" "$out_dir"
|
mkdir -p "$obj_dir" "$out_dir"
|
||||||
rm -f "$obj_dir"/*.obj
|
rm -f "$obj_dir"/*.obj
|
||||||
odin build . -vet -o:speed -target:windows_amd64 -build-mode:obj -out:"$obj_dir/{{name}}.obj"
|
odin build . -vet -o:speed -target:windows_amd64 -build-mode:obj -out:"$obj_dir/{{name}}.obj"
|
||||||
@@ -88,12 +87,11 @@ windows:
|
|||||||
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 ole32.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
|
# Windows build as build/<name>_<version>_windows_x86_64.exe
|
||||||
package-windows version="dev": windows
|
package-windows version="dev": windows
|
||||||
cd {{build_dir}}/windows && zip -qr ../{{name}}_{{version}}_windows_x86_64.zip {{name}}
|
cp {{build_dir}}/windows/{{name}}.exe {{build_dir}}/{{name}}_{{version}}_windows_x86_64.exe
|
||||||
@echo "{{build_dir}}/{{name}}_{{version}}_windows_x86_64.zip"
|
@echo "{{build_dir}}/{{name}}_{{version}}_windows_x86_64.exe"
|
||||||
|
|
||||||
# Format all Odin files with odinfmt (settings in odinfmt.json)
|
# Format all Odin files with odinfmt (settings in odinfmt.json)
|
||||||
fmt:
|
fmt:
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ main :: proc() {
|
|||||||
raylib.InitWindow(themes.SCREEN_WIDTH, themes.SCREEN_HEIGHT, "HanjaKanaMun")
|
raylib.InitWindow(themes.SCREEN_WIDTH, themes.SCREEN_HEIGHT, "HanjaKanaMun")
|
||||||
defer raylib.CloseWindow()
|
defer raylib.CloseWindow()
|
||||||
raylib.SetExitKey(.KEY_NULL)
|
raylib.SetExitKey(.KEY_NULL)
|
||||||
// Assets are loaded relative to the executable, so it can be started from anywhere
|
|
||||||
raylib.ChangeDirectory(raylib.GetApplicationDirectory())
|
|
||||||
raylib.SetTargetFPS(60)
|
raylib.SetTargetFPS(60)
|
||||||
|
|
||||||
raylib.InitAudioDevice()
|
raylib.InitAudioDevice()
|
||||||
|
|||||||
+12
-8
@@ -12,12 +12,14 @@ Font_Style :: enum {
|
|||||||
THAI, // Thai script (Mali SemiBold)
|
THAI, // Thai script (Mali SemiBold)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fonts are baked into the executable so a release is a single self-contained
|
||||||
|
// binary: no assets/ folder has to ship (or be found) alongside it at runtime.
|
||||||
@(rodata)
|
@(rodata)
|
||||||
FONT_PATHS := [Font_Style]cstring{
|
FONT_DATA := [Font_Style][]u8{
|
||||||
.REGULAR = "assets/MPLUSRounded1c-Medium.ttf",
|
.REGULAR = #load("../../assets/MPLUSRounded1c-Medium.ttf"),
|
||||||
.BOLD = "assets/MPLUSRounded1c-ExtraBold.ttf",
|
.BOLD = #load("../../assets/MPLUSRounded1c-ExtraBold.ttf"),
|
||||||
.KOREAN = "assets/Jua-Regular.ttf",
|
.KOREAN = #load("../../assets/Jua-Regular.ttf"),
|
||||||
.THAI = "assets/Mali-SemiBold.ttf",
|
.THAI = #load("../../assets/Mali-SemiBold.ttf"),
|
||||||
}
|
}
|
||||||
|
|
||||||
fonts: [Font_Style]raylib.Font
|
fonts: [Font_Style]raylib.Font
|
||||||
@@ -56,10 +58,12 @@ codepoints_for :: proc(style: Font_Style, allocator := context.allocator) -> [dy
|
|||||||
load_font :: proc(style: Font_Style) -> raylib.Font {
|
load_font :: proc(style: Font_Style) -> raylib.Font {
|
||||||
codepoints := codepoints_for(style)
|
codepoints := codepoints_for(style)
|
||||||
defer delete(codepoints)
|
defer delete(codepoints)
|
||||||
path := FONT_PATHS[style]
|
data := FONT_DATA[style]
|
||||||
font := raylib.LoadFontEx(path, FONT_BASE_SIZE, raw_data(codepoints), i32(len(codepoints)))
|
font := raylib.LoadFontFromMemory(
|
||||||
|
".ttf", raw_data(data), i32(len(data)), FONT_BASE_SIZE, raw_data(codepoints), i32(len(codepoints)),
|
||||||
|
)
|
||||||
if !raylib.IsFontValid(font) || font.texture.id == 0 {
|
if !raylib.IsFontValid(font) || font.texture.id == 0 {
|
||||||
raylib.TraceLog(.WARNING, "ASSETS: Falling back to default font, could not load %s", path)
|
raylib.TraceLog(.WARNING, "ASSETS: Falling back to default font, could not load style %v", style)
|
||||||
return raylib.GetFontDefault()
|
return raylib.GetFontDefault()
|
||||||
}
|
}
|
||||||
// Mipmaps keep small UI text smooth when the large atlas is scaled down
|
// Mipmaps keep small UI text smooth when the large atlas is scaled down
|
||||||
|
|||||||
Reference in new issue
Block a user