Add Katakana, Korean and Thai games and difficulty levels
- decks package: Hiragana, Katakana, Hangul jamo and Thai consonants (Thai readings carry the letter's name word, since sounds repeat) - Per-script fonts: Jua for Hangul, Mali for Thai (both OFL) - Memory game works with any deck; Easy 3x4, Normal 4x6, Hard 4x8 - Language menus pick a deck and difficulty, with number/arrow key shortcuts - Tests cover every deck, difficulty, and font glyph coverage Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4cfe73c9eb
commit
8214fda8f6
15 files changed
+1920
-229
No files matched your search
+73
-50
@@ -3,15 +3,11 @@ package screens
|
||||
import "core:math"
|
||||
import raylib "vendor:raylib"
|
||||
import card "../card"
|
||||
import decks "../decks"
|
||||
import memory "../memory"
|
||||
import themes "../themes"
|
||||
import ui "../ui"
|
||||
|
||||
LANGUAGES :: [3]struct{ theme: themes.Theme, screen: themes.Screen }{
|
||||
{themes.JAPAN, .JAPANESE_MENU},
|
||||
{themes.KOREA, .KOREAN_MENU},
|
||||
{themes.THAI, .THAI_MENU},
|
||||
}
|
||||
|
||||
// Draws a card back like a playing card lying on the table
|
||||
@(private)
|
||||
draw_card_art :: proc(flag: themes.Flag, rect: raylib.Rectangle, rotation: f32 = 0) {
|
||||
@@ -26,13 +22,18 @@ draw_card_art :: proc(flag: themes.Flag, rect: raylib.Rectangle, rotation: f32 =
|
||||
raylib.DrawTexturePro(texture, source, dest, origin, rotation, themes.WHITE)
|
||||
}
|
||||
|
||||
main_menu :: proc() -> themes.Screen {
|
||||
next := themes.Screen.MAIN_MENU
|
||||
@(private)
|
||||
NUMBER_KEYS :: [?]raylib.KeyboardKey{.ONE, .TWO, .THREE, .FOUR, .FIVE}
|
||||
|
||||
// Returns the language picked this frame, if any
|
||||
main_menu :: proc() -> Maybe(decks.Language) {
|
||||
raylib.ClearBackground(themes.MENU_BACKGROUND)
|
||||
languages := decks.LANGUAGES
|
||||
keys := NUMBER_KEYS
|
||||
|
||||
// Thin stripes along the top in each language's primary color
|
||||
stripe_w: i32 = themes.SCREEN_WIDTH / 3
|
||||
for lang, i in LANGUAGES {
|
||||
stripe_w: i32 = themes.SCREEN_WIDTH / len(decks.Language)
|
||||
for lang, i in languages {
|
||||
raylib.DrawRectangle(i32(i) * stripe_w, 0, stripe_w, 8, lang.theme.primary)
|
||||
}
|
||||
|
||||
@@ -46,66 +47,88 @@ main_menu :: proc() -> themes.Screen {
|
||||
start_x := (themes.SCREEN_WIDTH - total) / 2
|
||||
// Gentle bobbing so the menu feels alive
|
||||
time := f32(raylib.GetTime())
|
||||
for lang, i in LANGUAGES {
|
||||
picked: Maybe(decks.Language)
|
||||
for lang, language in languages {
|
||||
i := int(language)
|
||||
x := start_x + f32(i) * (tile_w + gap)
|
||||
bob := math.sin(time * 1.6 + f32(i) * 1.1) * 4
|
||||
rotation := f32(i - 1) * 4
|
||||
draw_card_art(lang.theme.flag, {x, 250 + bob, tile_w, tile_h}, rotation)
|
||||
button_rect := raylib.Rectangle{x + tile_w / 2 - themes.BUTTON_WIDTH / 2 + 30, 500, themes.BUTTON_WIDTH - 60, themes.BUTTON_HEIGHT}
|
||||
if ui.button(button_rect, lang.theme.name, lang.theme.primary) {
|
||||
next = lang.screen
|
||||
button_rect := raylib.Rectangle{x + tile_w / 2 - (themes.BUTTON_WIDTH - 60) / 2, 500, themes.BUTTON_WIDTH - 60, themes.BUTTON_HEIGHT}
|
||||
if ui.button(button_rect, lang.theme.name, lang.theme.primary, hotkey = keys[i]) {
|
||||
picked = language
|
||||
}
|
||||
}
|
||||
|
||||
ui.draw_text_centered("Esc to go back · Esc on this screen quits", themes.SCREEN_WIDTH / 2, 660, 18, raylib.Fade(themes.DARK_GRAY, 0.7))
|
||||
return next
|
||||
ui.draw_text_centered("1-3 pick a language · Esc goes back, and quits from here", themes.SCREEN_WIDTH / 2, 660, 18, raylib.Fade(themes.DARK_GRAY, 0.8))
|
||||
return picked
|
||||
}
|
||||
|
||||
// Shared layout for a language's menu screen. Returns the clicked option index, -1 for back, or nil.
|
||||
@(private)
|
||||
language_menu :: proc(theme: themes.Theme, subtitle: cstring, options: []cstring) -> Maybe(int) {
|
||||
Language_Menu_Result :: union {
|
||||
decks.Deck_Id, // start a game with this deck
|
||||
themes.Screen, // go to another screen
|
||||
}
|
||||
|
||||
// A language's menu: pick a deck and a difficulty
|
||||
language_menu :: proc(language: decks.Language, difficulty: ^memory.Difficulty) -> Language_Menu_Result {
|
||||
info := decks.LANGUAGES[language]
|
||||
theme := info.theme
|
||||
keys := NUMBER_KEYS
|
||||
raylib.ClearBackground(theme.background)
|
||||
raylib.DrawRectangle(0, 0, themes.SCREEN_WIDTH, 8, theme.primary)
|
||||
|
||||
ui.draw_title(theme.name, 70, theme.primary)
|
||||
ui.draw_text_centered(subtitle, themes.SCREEN_WIDTH / 2, 170, themes.SUBTITLE_FONT_SIZE, theme.ink)
|
||||
ui.draw_title(theme.name, 50, theme.primary)
|
||||
ui.draw_text_centered(info.native, themes.SCREEN_WIDTH / 2, 140, 30, theme.ink, info.symbol_style)
|
||||
|
||||
// A small fan of card backs
|
||||
for i in 0 ..< 3 {
|
||||
offset := f32(i - 1)
|
||||
draw_card_art(theme.flag, {themes.SCREEN_WIDTH / 2 - 55 + offset * 70, 235 + abs(offset) * 10, 110, 146}, offset * 9)
|
||||
draw_card_art(theme.flag, {themes.SCREEN_WIDTH / 2 - 50 + offset * 64, 195 + abs(offset) * 10, 100, 133}, offset * 9)
|
||||
}
|
||||
|
||||
result: Maybe(int)
|
||||
y: f32 = 440
|
||||
for option, i in options {
|
||||
if ui.button({(themes.SCREEN_WIDTH - themes.BUTTON_WIDTH) / 2, y, themes.BUTTON_WIDTH, themes.BUTTON_HEIGHT}, option, theme.primary) {
|
||||
result = i
|
||||
result: Language_Menu_Result
|
||||
|
||||
// One button per deck
|
||||
deck_list := decks.decks_for(language)
|
||||
button_w: f32 = 220
|
||||
gap: f32 = 24
|
||||
total := f32(len(deck_list)) * button_w + f32(len(deck_list) - 1) * gap
|
||||
x := (themes.SCREEN_WIDTH - total) / 2
|
||||
for deck, i in deck_list {
|
||||
rect := raylib.Rectangle{x, 380, button_w, themes.BUTTON_HEIGHT}
|
||||
if ui.button(rect, decks.DECKS[deck].name, theme.primary, hotkey = keys[i]) {
|
||||
result = deck
|
||||
}
|
||||
y += themes.BUTTON_HEIGHT + 20
|
||||
ui.draw_text_centered(decks.DECKS[deck].native, x + button_w / 2, 450, 22, theme.ink, info.symbol_style)
|
||||
x += button_w + gap
|
||||
}
|
||||
if ui.button({(themes.SCREEN_WIDTH - themes.BUTTON_WIDTH) / 2, y, themes.BUTTON_WIDTH, themes.BUTTON_HEIGHT}, "Back", theme.ink) {
|
||||
result = -1
|
||||
|
||||
// Difficulty selector
|
||||
names := memory.DIFFICULTY_NAMES
|
||||
layouts := memory.LAYOUTS
|
||||
ui.draw_text_centered("Difficulty (Left / Right)", themes.SCREEN_WIDTH / 2, 500, 20, raylib.Fade(theme.ink, 0.7))
|
||||
seg_w: f32 = 150
|
||||
seg_x := (themes.SCREEN_WIDTH - 3 * seg_w - 2 * 12) / 2
|
||||
for level in memory.Difficulty {
|
||||
selected := difficulty^ == level
|
||||
color := selected ? theme.primary : raylib.ColorLerp(theme.primary, themes.WHITE, 0.55)
|
||||
label := raylib.TextFormat("%s %d", names[level], layouts[level].rows * layouts[level].cols / 2)
|
||||
if ui.button({seg_x, 530, seg_w, 48}, label, color, font_size = 22, selected = selected) {
|
||||
difficulty^ = level
|
||||
}
|
||||
seg_x += seg_w + 12
|
||||
}
|
||||
if raylib.IsKeyPressed(.LEFT) && difficulty^ > .EASY {
|
||||
difficulty^ = memory.Difficulty(int(difficulty^) - 1)
|
||||
}
|
||||
if raylib.IsKeyPressed(.RIGHT) && difficulty^ < .HARD {
|
||||
difficulty^ = memory.Difficulty(int(difficulty^) + 1)
|
||||
}
|
||||
|
||||
if ui.button({30, 28, 120, 48}, "Back", theme.ink, font_size = 22) {
|
||||
result = themes.Screen.MAIN_MENU
|
||||
}
|
||||
hint := len(deck_list) > 1 ? cstring("1-2 pick a deck · Left / Right difficulty · Esc back") : cstring("1 start · Left / Right difficulty · Esc back")
|
||||
ui.draw_text_centered(hint, themes.SCREEN_WIDTH / 2, 660, 18, raylib.Fade(theme.ink, 0.6))
|
||||
return result
|
||||
}
|
||||
|
||||
japanese_menu :: proc() -> themes.Screen {
|
||||
choice, clicked := language_menu(themes.JAPAN, "ひらがな · Hiragana", {"Memory Game"}).?
|
||||
if !clicked { return .JAPANESE_MENU }
|
||||
return choice == 0 ? .JAPANESE_MEMORY : .MAIN_MENU
|
||||
}
|
||||
|
||||
korean_menu :: proc() -> themes.Screen {
|
||||
if _, clicked := language_menu(themes.KOREA, "Hangul · coming soon", {}).?; clicked {
|
||||
return .MAIN_MENU
|
||||
}
|
||||
return .KOREAN_MENU
|
||||
}
|
||||
|
||||
thai_menu :: proc() -> themes.Screen {
|
||||
if _, clicked := language_menu(themes.THAI, "Thai script · coming soon", {}).?; clicked {
|
||||
return .MAIN_MENU
|
||||
}
|
||||
return .THAI_MENU
|
||||
}
|
||||
Reference in new issue
Block a user