aboutsummaryrefslogtreecommitdiffstats
path: root/scoretbl.c
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2003-03-25 05:28:10 +0000
committerH. Peter Anvin <hpa@zytor.com>2003-03-25 05:28:10 +0000
commit37ae1353fb4ef6013b688b28cd82f06182ad3063 (patch)
tree7d7c3029d3b5f14d4da2997718b523e47362aea9 /scoretbl.c
parent1128ad360b5cccb1b82de092505e5ca1c4dbed8d (diff)
downloadgrv-37ae1353fb4ef6013b688b28cd82f06182ad3063.tar.gz
grv-37ae1353fb4ef6013b688b28cd82f06182ad3063.tar.xz
grv-37ae1353fb4ef6013b688b28cd82f06182ad3063.zip
Add support for high score tables, including the ability to generate
a (hopefully) unique ID for each game that we can use to match identical records.
Diffstat (limited to 'scoretbl.c')
-rw-r--r--scoretbl.c214
1 files changed, 214 insertions, 0 deletions
diff --git a/scoretbl.c b/scoretbl.c
new file mode 100644
index 0000000..9684c3d
--- /dev/null
+++ b/scoretbl.c
@@ -0,0 +1,214 @@
+#include <inttypes.h>
+#include <limits.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "highscore.h"
+
+#define MAXLINE 4096 /* Max length of a text line */
+
+struct bests bests;
+
+/*
+ * Insert a total; return a pointer to the player name string
+ * if applicable. Return place [1,MAX_BEST] or 0 if no ranking
+ */
+int highscore_add_total(uint64_t gameid, int64_t score,
+ int endlvl, unsigned char **pptr)
+{
+ struct best_total *tp = bests.total;
+ int i;
+
+ for ( i = 0 ; i < MAX_BEST ; i++, tp++ ) {
+ if ( score > tp->score ) {
+ memmove(tp+1, tp, (MAX_BEST-1-i)*sizeof(*tp));
+ tp->gameid = gameid;
+ tp->score = score;
+ tp->endlvl = endlvl;
+ if ( pptr ) *pptr = tp->player;
+ return i+1;
+ } else if ( gameid == tp->gameid && score == tp->score ) {
+ return i+1;
+ }
+ }
+
+ if ( pptr ) *pptr = NULL;
+ return 0;
+}
+
+/*
+ * Insert a level best score, return rank (or 0 if N/A)
+ */
+int highscore_add_level_score(uint64_t gameid, int lvl, int64_t score)
+{
+ struct best_level_score *tp;
+ int i;
+
+ if ( lvl < 0 || lvl >= MAX_LEVEL )
+ return 0;
+
+ tp = bests.level[lvl].score;
+
+ for ( i = 0 ; i < MAX_PER_LEVEL ; i++, tp++ ) {
+ if ( score > tp->score ) {
+ memmove(tp+1, tp, (MAX_PER_LEVEL-1-i)*sizeof(*tp));
+ tp->gameid = gameid;
+ tp->score = score;
+ return i+1;
+ } else if ( gameid == tp->gameid && score == tp->score ) {
+ return i+1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Insert a level best time, return rank (or 0 if N/A)
+ */
+int highscore_add_level_time(uint64_t gameid, int lvl, int32_t time_ms)
+{
+ struct best_level_time *tp;
+ int i;
+
+ if ( lvl < 0 || lvl >= MAX_LEVEL )
+ return 0;
+
+ tp = bests.level[lvl].time_ms;
+
+ for ( i = 0 ; i < MAX_PER_LEVEL ; i++, tp++ ) {
+ if ( time_ms < tp->time_ms ) {
+ memmove(tp+1, tp, (MAX_PER_LEVEL-1-i)*sizeof(*tp));
+ tp->gameid = gameid;
+ tp->time_ms = time_ms;
+ return i+1;
+ } else if ( gameid == tp->gameid && time_ms == tp->time_ms ) {
+ return i+1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Initialize the highscore data structures to "nothing"
+ */
+void highscore_init(void)
+{
+ int i, j;
+
+ memset(&bests, 0, sizeof bests);
+ for ( i = 0 ; i < MAX_LEVEL ; i++ )
+ for ( j = 0 ; j < MAX_PER_LEVEL ; j++ )
+ bests.level[i].time_ms[j].time_ms = NO_TIME;
+}
+
+/*
+ * Unquote a string
+ */
+static unsigned char *unquote(const char *i)
+{
+ static unsigned char buf[MAXLINE], *o;
+ char sb[3];
+ sb[2] = 0;
+
+ o = buf;
+ while ( isxdigit(sb[0] = i[0]) &&
+ isxdigit(sb[1] = i[1]) ) {
+ *o++ = (unsigned char)strtoul(sb, NULL, 16);
+ i += 2;
+ }
+ *o++ = '\0';
+
+ return buf;
+}
+
+/*
+ * Quote a string
+ */
+static char *quote(const unsigned char *i)
+{
+ static char buf[MAXLINE], *o;
+ unsigned char c;
+
+ o = buf;
+ while ( (c = *i++) ) {
+ sprintf(o, "%02x", c);
+ o += 2;
+ }
+ *o++ = '0'; /* End with 00 so even a null string is ok */
+ *o++ = '0';
+ *o++ = '\0';
+
+ return buf;
+}
+
+
+/*
+ * Parse an incoming high-score table. It can come either from the network
+ * or from a file.
+ */
+void highscore_parse(FILE *f)
+{
+ char line[MAXLINE], pname[MAXLINE];
+ uint64_t gameid;
+ long long score;
+ long time_ms;
+ int lvl;
+ unsigned char *pptr;
+
+ while ( fgets(line, MAXLINE, f) ) {
+ if ( !strchr(line, '\n') )
+ return; /* Something bogus here... */
+
+ if ( sscanf(line, "TS %llx %lld %d %[0-9a-f]",
+ &gameid, &score, &lvl, pname) == 4 ) {
+ highscore_add_total(gameid, score, lvl, &pptr);
+ if ( pptr ) {
+ strncpy(pptr, unquote(pname), PLAYER_LEN+1);
+ pptr[PLAYER_LEN] = '\0';
+ }
+ } else if ( sscanf(line, "LS %llx %d %lld",
+ &gameid, &lvl, &score) == 3 ) {
+ highscore_add_level_score(gameid, lvl-1, score);
+ } else if ( sscanf(line, "LT %llx %d %ld",
+ &gameid, &lvl, &time_ms) == 3 ) {
+ highscore_add_level_time(gameid, lvl-1, time_ms);
+ }
+ }
+}
+
+/*
+ * Write out a high score file; return 0 on success, -1 on failure
+ */
+int highscore_write(FILE *f)
+{
+ int i, j;
+
+ fprintf(f, "VC 1.0\n"); /* Version C 1.0 */
+
+ for ( i = 0 ; i < MAX_BEST ; i++ ) {
+ if ( bests.total[i].score ) {
+ fprintf(f, "TS %llx %lld %d %s\n", bests.total[i].gameid,
+ bests.total[i].score, bests.total[i].endlvl,
+ quote(bests.total[i].player));
+ }
+ }
+
+ for ( i = 0 ; i < MAX_LEVEL ; i++ ) {
+ for ( j = 0 ; j < MAX_PER_LEVEL ; j++ ) {
+ if ( bests.level[i].score[j].score )
+ fprintf(f, "LS %llx %d %lld\n", bests.level[i].score[j].gameid,
+ i+1, bests.level[i].score[j].score);
+ }
+ for ( j = 0 ; j < MAX_PER_LEVEL ; j++ ) {
+ if ( bests.level[i].time_ms[j].time_ms != NO_TIME )
+ fprintf(f, "LT %llx %d %d\n", bests.level[i].time_ms[j].gameid,
+ i+1, bests.level[i].time_ms[j].time_ms);
+ }
+ }
+
+ return ferror(f);
+}