Save best scores and last difficulty to a JSON profile

- profile package: best moves, best time and games played per
  deck/difficulty, stored in the user data folder
- Win panel shows the best (or New best!), language menu shows bests
- Profile memory always comes from the heap allocator (it outlives callers)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SansanandClaude Opus 5.5 committed 2026-10-01 21:35:43 +02:00
1 parent 685182b25d
commit 83ff9511ec
5 files changed
+225 -6

No files matched your search

+17 -3
View File
@@ -1,10 +1,13 @@
package memory
import "core:fmt"
import "core:math/rand"
import "core:strings"
import raylib "vendor:raylib"
import audio "../audio"
import card "../card"
import decks "../decks"
import profile "../profile"
import themes "../themes"
import ui "../ui"
@@ -56,9 +59,15 @@ elapsed: f32
resolve_timer: f32
win_t: f32 // fades in the win panel
restart_requested: bool
last_result: profile.Result // filled in when a game is won
focus: int // card selected with the keyboard
keyboard_active: bool // show the focus ring (hidden again when the mouse moves)
// Key for this deck and difficulty in the saved profile
score_key :: proc(deck_id: decks.Deck_Id, game_difficulty: Difficulty) -> string {
return fmt.tprintf("%v/%v", deck_id, game_difficulty)
}
// Deals a new game and renders the card faces. Needs the window to exist.
start :: proc(deck_id: decks.Deck_Id, game_difficulty: Difficulty) {
deal(deck_id, game_difficulty)
@@ -196,6 +205,7 @@ resolve :: proc() {
matches += 1
if matches == pair_count {
state = .OVER
last_result = profile.record_game(score_key(deck, difficulty), moves, elapsed)
audio.play(.WIN)
} else {
audio.play(.MATCH)
@@ -288,10 +298,14 @@ draw_win_panel :: proc() -> Maybe(themes.Screen) {
cx := panel.x + panel_w / 2
ui.draw_text_centered(language.congrats, cx, panel.y + 34, 56, raylib.Fade(theme.primary, win_t), language.congrats_style)
ui.draw_text_centered("All pairs found!", cx, panel.y + 112, 26, raylib.Fade(theme.ink, win_t))
minutes := int(elapsed) / 60
seconds := int(elapsed) % 60
stats := raylib.TextFormat("%d moves · %d:%02d", moves, minutes, seconds)
stats := strings.clone_to_cstring(fmt.tprintf("%d moves · %s", moves, profile.format_time(elapsed)), context.temp_allocator)
ui.draw_text_centered(stats, cx, panel.y + 156, 26, raylib.Fade(theme.ink, win_t), .BOLD)
best := last_result.record
best_text := strings.clone_to_cstring(fmt.tprintf("Best %d moves · %s", best.best_moves, profile.format_time(best.best_seconds)), context.temp_allocator)
if last_result.new_best_moves || last_result.new_best_time {
best_text = strings.clone_to_cstring(fmt.tprintf("New best! · %d games played", best.games), context.temp_allocator)
}
ui.draw_text_centered(best_text, cx, panel.y + 198, 22, raylib.Fade(theme.primary, win_t), .BOLD)
if win_t < 1 { return nil }
button_w: f32 = 200