linije

Клон игре Color lines
git clone https://git.sr.ht/~strahinja/linije
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 443cce90c457fd326e3c1042616795c3ba42e030
родитељ 96d2bd44ee81077181261f4115dbe336511a7a87
Аутор: Страхиња Радић <sr@strahinja.org>
Датум:   Tue,  4 Aug 2026 16:54:55 +0000

Add hall of fame saving and loading

Diffstat:
MMakefile | 4++--
MTODO | 6+++---
Mhiscore.c | 195++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mhiscore.h | 10++++++++++
Mlib/replacepat | 4++--
Mlinije.c | 13+++++++++++++
Mlocal.h.in | 8+++++---
измењених датотека: 7, додавања: 228(+), брисања: 12(-)

diff --git a/Makefile b/Makefile @@ -6,7 +6,7 @@ include config.mk .SUFFIXES: .c .o .h.in .h .h.in.h: - sh -c 'DATADIR=$(DATADIR) lib/replacepat $< $@' + sh -c 'DATADIR=$(DATADIR) USERDIR=$(USERDIR) lib/replacepat $< $@' all: makedate makeversion $(PROG) if [ -f .rebuild ]; then rm .rebuild; make -e all; else true; fi @@ -25,7 +25,7 @@ makedate: makeversion: sh lib/makeversion -l -hiscore.o: hiscore.c hiscore.h +hiscore.o: hiscore.c hiscore.h local.h linije.o: linije.c hiscore.h local.h version.h diff --git a/TODO b/TODO @@ -1,9 +1,9 @@ TODO ==== -[~] High score? +[x] High score? [x] Updating high score - [ ] Saving high score to a file - [ ] Loading high score from a file + [x] Saving high score to a file + [x] Loading high score from a file < > Menu? diff --git a/hiscore.c b/hiscore.c @@ -3,11 +3,16 @@ * See the file LICENSE for exact copyright and license details. */ #include "hiscore.h" +#include "local.h" #include <SDL3/SDL.h> #include <assert.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> int hs_add(struct HiScoreEntry** table, const char* name, const int name_len, @@ -114,10 +119,11 @@ hs_init(struct HiScoreEntry** table) goto hs_init_malloc_error; for (p = *table; p < *table + HI_SCORE_MAX; p++) { - p->name = malloc(len); + /* Zero out for privacy and security */ + p->name = calloc(1, HI_SCORE_NAME_MAX); if (!p->name) goto hs_init_malloc_error; - memccpy(p->name, HI_SCORE_DEFAULT_NAME, 0, len); + memccpy(p->name, HI_SCORE_DEFAULT_NAME, 0, HI_SCORE_NAME_MAX); p->name_len = len - 1; p->score = 0; } @@ -128,3 +134,188 @@ hs_init_malloc_error: SDL_LogError(SDL_LOG_CATEGORY_ERROR, "malloc failed"); return 1; } + +/* USERDIR not existing is ok, create it in that case */ +char* +hs_get_hofdir(void) +{ + struct stat st; + static char* result = NULL; + char* home = NULL; + int result_size = PATH_MAX + 1; + + if (!result) + result = malloc(result_size); + else + goto hs_get_hofdir_skip_generating; + if (!result) + goto hs_get_hofdir_error; + + home = getenv("HOME"); + if (!home || !*home) + goto hs_get_hofdir_no_home; + + snprintf(result, result_size, "%s/%s", home, USERDIR); + + if (stat(result, &st) == 0) + goto hs_get_hofdir_skip_generating; + + if (errno == ENOENT || errno == ENOTDIR) + { + if (mkdir(result, + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) + < 0) + goto hs_get_hofdir_error; + } + else + goto hs_get_hofdir_error; + +hs_get_hofdir_skip_generating: + return result; + +hs_get_hofdir_no_home: + SDL_LogError(SDL_LOG_CATEGORY_ERROR, + "hs_get_hofdir: HOME environment variable is unset or empty"); + errno = ENOENT; + return NULL; + +hs_get_hofdir_error: + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_get_hofdir: %s", + strerror(errno)); + return NULL; +} + +/* + * File structure: + * HI_SCORE_MAX entries, each consisting of + * + * ,-----------------------------------------------------------. + * | name (HI_SCORE_NAME_MAX unsigned chars) | + * |-----------------------------------------------------------| + * | score 2-byte unsigned int BE | + * `-----------------------------------------------------------' + */ +int +hs_load(struct HiScoreEntry** table, const char* hofdir) +{ + FILE* infile = NULL; + char* filename = NULL; + char name[HI_SCORE_NAME_MAX + 1]; + int filename_size = PATH_MAX + 1; + int name_len, i; + unsigned int score; + uint8_t byte; + + if (!hofdir) + goto hs_load_error; + + filename = malloc(filename_size); + if (!filename) + goto hs_load_error; + + snprintf(filename, filename_size, "%s/%s", hofdir, HOFFILE); + + if (!(infile = fopen(filename, "rb"))) + goto hs_load_error; + + i = 0; + while (!feof(infile) && i < HI_SCORE_MAX) + { + /* Zero out for privacy and security */ + memset(name, 0, HI_SCORE_NAME_MAX); + if (fread(name, HI_SCORE_NAME_MAX, 1, infile) < 1) + { + if (feof(infile)) + continue; + else + { + fclose(infile); + goto hs_load_error; + } + } + name_len = strlen(name); + if (fread(&byte, 1, 1, infile) < 1) + { + fclose(infile); + goto hs_load_error; + } + score = byte << 8; + if (fread(&byte, 1, 1, infile) < 1) + { + fclose(infile); + goto hs_load_error; + } + score |= byte; + hs_add(table, name, name_len, score); + i++; + } + + if (fclose(infile) == EOF) + goto hs_load_error; + + free(filename); + return 0; + +hs_load_error: + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_load: %s", strerror(errno)); + return 1; +} + +int +hs_write(struct HiScoreEntry* table, const char* hofdir) +{ + FILE* outfile = NULL; + struct HiScoreEntry* p = NULL; + char* filename = NULL; + int filename_size = PATH_MAX + 1; + int i; + uint8_t byte; + + if (!hofdir) + goto hs_write_error; + + filename = malloc(filename_size); + if (!filename) + goto hs_write_error; + + snprintf(filename, filename_size, "%s/%s", hofdir, HOFFILE); + + if (!(outfile = fopen(filename, "wb"))) + goto hs_write_error; + + for (p = table; p < table + HI_SCORE_MAX; p++) + { + for (i = 0; i < HI_SCORE_NAME_MAX; i++) + { + errno = 0; + byte = p->name[i]; + if (fwrite(&byte, 1, 1, outfile) < 1) + { + fclose(outfile); + goto hs_write_error; + } + } + errno = 0; + byte = HIBYTE(p->score); + if (fwrite(&byte, 1, 1, outfile) < 1) + { + fclose(outfile); + goto hs_write_error; + } + byte = LOBYTE(p->score); + if (fwrite(&byte, 1, 1, outfile) < 1) + { + fclose(outfile); + goto hs_write_error; + } + } + if (fclose(outfile) == EOF) + goto hs_write_error; + + free(filename); + return 0; + +hs_write_error: + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_write: %s", strerror(errno)); + return 1; +} diff --git a/hiscore.h b/hiscore.h @@ -6,6 +6,13 @@ #define HI_SCORE_MAX 10 #define HI_SCORE_NAME_MAX 40 +#ifndef PATH_MAX +#define PATH_MAX _POSIX_PATH_MAX +#endif + +#define HIBYTE(i) ((uint8_t)(((i) & 0xFF00) >> 8)) +#define LOBYTE(i) ((uint8_t)(((i) & 0xFF))) + struct HiScoreEntry { char* name; int name_len; @@ -18,4 +25,7 @@ int hs_assign(const struct HiScoreEntry* source, struct HiScoreEntry* dest); int hs_assign_ss(const struct HiScoreEntry* source, struct HiScoreEntry* dest); int hs_assign_ps(const char* name, const int name_len, const unsigned int score, struct HiScoreEntry* dest); +char* hs_get_hofdir(void); int hs_init(struct HiScoreEntry** table); +int hs_load(struct HiScoreEntry** table, const char* hofdir); +int hs_write(struct HiScoreEntry* table, const char* hofdir); diff --git a/lib/replacepat b/lib/replacepat @@ -6,5 +6,5 @@ DATE=$(cat date 2>/dev/null) VERSION=$(cat version 2>/dev/null) sed -e 's|%VERSION%|'"$VERSION"'|g' -e 's|%DATE%|'"$DATE"'|g' \ - -e 's|%DATADIR%|'"$DATADIR"'|g' "$infile" \ - > "$outfile" + -e 's|%DATADIR%|'"$DATADIR"'|g' -e 's|%USERDIR%|'"$USERDIR"'|g' \ + "$infile" > "$outfile" diff --git a/linije.c b/linije.c @@ -1265,6 +1265,13 @@ input_name_callback(struct State* state) state->player_name[i]); hs_add(&state->hall_of_fame, state->player_name, state->player_name_length, state->score); + + if (hs_write(state->hall_of_fame, hs_get_hofdir())) + { + state->running = 1; + return; + } + prepare_dialog(state, GAME_OVER_DIALOG_WIDTH, GAME_OVER_DIALOG_HEIGHT, game_over_text, 0, 1); } @@ -2218,6 +2225,12 @@ main(int argc, char** argv) init_state(&state); hs_init(&hof); + if (hs_load(&hof, hs_get_hofdir())) + { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_load: %s", + strerror(errno)); + exit(1); + } state.hall_of_fame = hof; diff --git a/local.h.in b/local.h.in @@ -2,6 +2,8 @@ * any later version. Copyright (C) 2025-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ -#define DATADIR "%DATADIR%" -#define TER12_PNG "ter12.png" -#define SPRITES_PNG "sprites.png" +#define DATADIR "%DATADIR%" +#define USERDIR "%USERDIR%" +#define TER12_PNG "ter12.png" +#define SPRITES_PNG "sprites.png" +#define HOFFILE "linije.hof"