aboutsummaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--Makefile39
-rw-r--r--grv.c4
-rw-r--r--grv.h4
-rw-r--r--grvscored.c78
-rwxr-xr-xhigh2dat.pl63
-rw-r--r--highscore.h50
-rw-r--r--keyboard.c6
-rw-r--r--play.c3
-rw-r--r--scoretbl.c214
9 files changed, 461 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3fd812f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+CC = gcc -W -Wall
+OPTFLAGS = -O -g
+INCLUDE = -I/usr/include/SDL
+CFLAGS = $(OPTFLAGS) $(INCLUDE)
+LDFLAGS =
+LIBS = -lSDL -lpthread
+
+ALL = grv grvscored
+
+OBJS = grv.o drawlevel.o play.o action.o bullets.o mystery.o utils.o \
+ keyboard.o graphics.o grvfont.o random.o
+
+SCORED = grvscored.o scoretbl.o
+
+.SUFFIXES: .c .o .i .s
+
+all: $(ALL)
+
+grv: $(OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
+
+grvscored: $(SCORED)
+ $(CC) $(LDFLAGS) -o $@ $(SCORED)
+
+.c.o:
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+.c.i:
+ $(CC) $(CFLAGS) -E -o $@ $<
+
+.c.s:
+ $(CC) $(CFLAGS) -S -o $@ $<
+
+grvfont.c: grvfont.psf psftoc.pl
+ $(PERL) psftoc.pl < $< > $@ || rm -f $@
+
+clean:
+ rm -f *.o *.i *.s $(ALL)
+
diff --git a/grv.c b/grv.c
index 4b3580d..9edc1f3 100644
--- a/grv.c
+++ b/grv.c
@@ -16,6 +16,10 @@ struct monster ghost[MAXGHOST];
void init_gameparams(void)
{
+ /* As good a start as any... */
+ gp.gameid = (uint64_t)genrand_int32() << 32;
+ gp.have_id = 0;
+
gp.Sc = 0;
gp.Life = 3;
gp.SBs = 0;
diff --git a/grv.h b/grv.h
index c5b2021..36ca09b 100644
--- a/grv.h
+++ b/grv.h
@@ -59,6 +59,10 @@ struct gameparams {
Status_Done, /* Player done with level */
Status_Quit, /* Level exit (treasure) */
} Status;
+
+ uint64_t gameid; /* (Hopefully) unique ID for high score list */
+ int have_id; /* Is ID frozen yet? */
+
int nwhite; /* Number of white cherries */
int nextwhite; /* Next white cherry (if any) */
struct xy whitecherrylist[190];
diff --git a/grvscored.c b/grvscored.c
new file mode 100644
index 0000000..0108603
--- /dev/null
+++ b/grvscored.c
@@ -0,0 +1,78 @@
+/*
+ * grvscored.c
+ *
+ * Simple highscore server -- run this from (x)inetd on ports
+ * 22392 with option -s and 22393 with option -r
+ */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/file.h>
+
+#include "highscore.h"
+
+int score_send(const char *file)
+{
+ FILE *f = fopen(file, "r");
+
+ if ( !f )
+ exit(1);
+
+ flock(fileno(f), LOCK_SH);
+ highscore_parse(f);
+ fclose(f);
+
+ return highscore_write(stdout) ? 1 : 0;
+}
+
+int score_recv(const char *file)
+{
+ FILE *f = fopen(file, "r+");
+ FILE *t;
+ char *tp;
+
+ if ( !f || !(tp = alloca(strlen(file)+5)) )
+ exit(1);
+
+ sprintf(tp, "%s.tmp", file);
+ if ( !(t = fopen(tp, "w")) )
+ exit(1);
+
+ flock(fileno(f), LOCK_EX);
+ highscore_parse(f);
+ rewind(f);
+ highscore_parse(stdin);
+ if ( highscore_write(t) ) {
+ unlink(tp);
+ fclose(f);
+ return 1;
+ } else {
+ rename(tp, file);
+ fclose(f);
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ if ( argc != 3 ) {
+ fprintf(stderr, "Usage: %s -s|r file\n", argv[0]);
+ exit(1);
+ }
+
+ highscore_init();
+
+ if ( argv[1][0] == '-' && argv[1][1] == 's' ) {
+ return score_send(argv[2]);
+ } else if ( argv[1][0] == '-' && argv[1][1] == 'r' ) {
+ return score_recv(argv[2]);
+ }
+
+ exit(1);
+}
diff --git a/high2dat.pl b/high2dat.pl
new file mode 100755
index 0000000..32b3357
--- /dev/null
+++ b/high2dat.pl
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+#
+# Convert the binary grv_high file to the text-based grvscore.dat
+#
+
+eval { use bytes; };
+
+$gameid = 0;
+
+print "VC 1.0\n";
+
+sub quote($) {
+ my($i) = @_;
+ my($o) = '';
+ my($x);
+
+ for ( $x = 0 ; $x < length($i) ; $x++ ) {
+ $o .= sprintf("%02x", ord(substr($i,$x,1)));
+ }
+ $o .= '00';
+ return $o;
+}
+
+for ( $i = 0 ; $i < 100 ; $i++ ) {
+ read(STDIN, $hd, 23);
+ ($score, $player, $level, $time) = unpack("dA9vf", $hd);
+ printf STDERR "<..> %14f %-9s %2d %f\n", $score, $player, $level, $time;
+ $level++;
+ $player = quote($player);
+ $score = int($score+0.5);
+ $time = int($time*1000+0.5);
+ $time = ($time > 1800000) ? 0x7fffffff : $time;
+ $gameid++;
+ print "TS $gameid $score $level $player\n";
+}
+for ( $l = 0 ; $l < 75 ; $l++ ) {
+ for ( $i = 0 ; $i < 3 ; $i++ ) {
+ read(STDIN, $hd, 23);
+ ($score, $player, $level, $time) = unpack("dA9vf", $hd);
+ printf STDERR "<%2d> %14f %-9s %2d %f\n", $l,$score, $player, $level, $time;
+ if ( $level == $l ) {
+ $level++;
+ $score = int($score+0.5);
+ $time = int($time*1000+0.5);
+ $time = ($time > 1800000) ? 0x7fffffff : $time;
+ $gameid++;
+ print "LT $gameid $level $time\n" if ( $time != 0x7fffffff );
+ }
+ }
+ for ( $i = 0 ; $i < 3 ; $i++ ) {
+ read(STDIN, $hd, 23);
+ ($score, $player, $level, $time) = unpack("dA9vf", $hd);
+ printf STDERR "<%2d> %14f %-9s %2d %f\n", $l, $score, $player, $level, $time;
+ if ( 1 || $level == $l ) {
+ $level++;
+ $score = int($score+0.5);
+ $time = int($time*1000+0.5);
+ $time = ($time > 1800000) ? 0x7fffffff : $time;
+ $gameid++;
+ print "LS $gameid $level $score\n" if ( $score );
+ }
+ }
+}
diff --git a/highscore.h b/highscore.h
new file mode 100644
index 0000000..bb531ee
--- /dev/null
+++ b/highscore.h
@@ -0,0 +1,50 @@
+/*
+ * highscore.h
+ */
+
+#ifndef HIGHSCORE_H
+#define HIGHSCORE_H 1
+
+#include <inttypes.h>
+
+#define PLAYER_LEN 11 /* Max length of player alias */
+#define MAX_BEST 100 /* Number of best_total to keep track of */
+#define MAX_LEVEL 75 /* Max levels to keep track of */
+#define MAX_PER_LEVEL 3 /* Max "bests" per level */
+
+struct best_total {
+ uint64_t gameid; /* GameID */
+ int64_t score; /* Final score */
+ int32_t endlvl; /* Ending level (-1) */
+ unsigned char player[PLAYER_LEN+1]; /* Player alias */
+};
+
+struct best_level {
+ struct best_level_score {
+ uint64_t gameid;
+ int64_t score;
+ } score[MAX_PER_LEVEL];
+ struct best_level_time {
+ uint64_t gameid;
+ int32_t time_ms;
+ } time_ms[MAX_PER_LEVEL];
+};
+
+struct bests {
+ struct best_total total[MAX_BEST];
+ struct best_level level[MAX_LEVEL];
+};
+
+extern struct bests bests;
+
+#define NO_TIME 0x7fffffff /* Max int32_t value */
+
+int highscore_add_total(uint64_t gameid, int64_t score, int endlvl, unsigned char **pptr);
+int highscore_add_level_score(uint64_t gameid, int lvl, int64_t score);
+int highscore_add_level_time(uint64_t gameid, int lvl, int32_t time_ms);
+void highscore_init(void);
+void highscore_parse(FILE *f);
+int highscore_write(FILE *f);
+
+
+#endif
diff --git a/keyboard.c b/keyboard.c
index 06d57b9..69f4732 100644
--- a/keyboard.c
+++ b/keyboard.c
@@ -26,6 +26,12 @@ static int queuedkeys = 0;
*/
void push_key(SDL_KeyboardEvent *ke)
{
+ /* Hack to add some nondeterminism into the gameid's */
+ if ( !gp.have_id ) {
+ gp.gameid = (gp.gameid << 5 | gp.gameid >> 59)
+ + ((uint64_t)SDL_GetTicks() << 32) + genrand_int32();
+ }
+
if ( queuedkeys >= KEYQUEUELEN )
return; /* Drop it */
diff --git a/play.c b/play.c
index f951749..62acbe2 100644
--- a/play.c
+++ b/play.c
@@ -587,6 +587,9 @@ static void end_level(int done)
int e;
int64_t sbon;
+ gp.have_id = 1; /* We now need the game_id */
+ fprintf(stderr, "GameID: %llx\n", gp.gameid);
+
color(0,gp.c2);
lprint(3, 6, "浜様様様様様様様様様様様様様融");
for ( e = 4 ; e <= 21 ; e++ ) {
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);
+}