On Wed, Nov 25, 2015 at 03:22:27PM -0500, Ted Unangst wrote: > Theo Buehler wrote: > > > > If you're not going to maintain a list of high scores of the user, you > > could still simplify snscore() further: The score file will just > > contain the user's score as a short, so you could get the user name > > using getlogin(2) instead of doing getuid() getpwuid(), etc. > > Keeping the struct player also seems unnecessary. > > I think this is the way to go, throughout games. The only things we need to > know are username (getlogin() or even $USER) and home dir ($HOME). Digging the > home dir out of passwd seems unnecessary (or even wrong, if i've set HOME to > be something else). > > For that matter, I'm not sure to prefer USER or getlogin. I'm leaning towards > USER precisely *because* it allows overriding. If I want to keep a set of > scores for different users by setting USER that seems reasonable to me.
I like this. I do prefer probing the environment and not touch the password file at all. So here's an implementation of a highscore file with ten top scores and entry customizable via $USER. The logic is simplistic: you only get an entry in the hall of fame if you strictly beat a previous score. If $USER is unset, default to "???". This is loosely based on Ricardo's patch and what was done in tetris. Index: Makefile =================================================================== RCS file: /cvs/src/games/snake/Makefile,v retrieving revision 1.11 diff -u -p -r1.11 Makefile --- Makefile 25 Nov 2015 23:18:11 -0000 1.11 +++ Makefile 26 Nov 2015 08:15:52 -0000 @@ -2,7 +2,7 @@ # @(#)Makefile 8.1 (Berkeley) 5/31/93 PROG= snake -SRCS= snake.c snscore.c +SRCS= snake.c MAN= snake.6 DPADD= ${LIBM} ${LIBCURSES} LDADD= -lm -lcurses Index: pathnames.h =================================================================== RCS file: pathnames.h diff -N pathnames.h --- pathnames.h 3 Jun 2003 03:01:41 -0000 1.2 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,38 +0,0 @@ -/* $OpenBSD: pathnames.h,v 1.2 2003/06/03 03:01:41 millert Exp $ */ -/* $NetBSD: pathnames.h,v 1.3 1995/04/22 08:34:33 cgd Exp $ */ - -/* - * Copyright (c) 1989, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)pathnames.h 8.1 (Berkeley) 5/31/93 - */ - -#define _PATH_RAWSCORES "/var/games/snakerawscores" -#ifdef LOGGING -#define _PATH_LOGFILE "/var/games/snake.log" -#endif Index: snake.6 =================================================================== RCS file: /cvs/src/games/snake/snake.6,v retrieving revision 1.11 diff -u -p -r1.11 snake.6 --- snake.6 13 Nov 2009 21:50:12 -0000 1.11 +++ snake.6 26 Nov 2015 08:15:52 -0000 @@ -114,13 +114,18 @@ which appears after the game is worth a To see who wastes time playing snake, run .Nm snake .Fl s . +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It Ev USER +Name displayed in high score file. +.El .Sh FILES -.Bl -tag -width /var/games/snakerawscores -compact -.It Pa /var/games/snakerawscores +.Bl -tag -width $HOME/.snake.scores -compact +.It Pa $HOME/.snake.scores database of personal bests -.\".It Pa /var/games/snake.log -.\"log of games played .El +.\".It Pa $HOME/.snake.log +.\"log of games played .Sh BUGS When playing on a small screen, it's hard to tell when you hit the edge of the screen. Index: snake.c =================================================================== RCS file: /cvs/src/games/snake/snake.c,v retrieving revision 1.16 diff -u -p -r1.16 snake.c --- snake.c 16 Nov 2014 04:49:49 -0000 1.16 +++ snake.c 26 Nov 2015 08:15:52 -0000 @@ -46,9 +46,10 @@ #include <curses.h> #include <err.h> +#include <errno.h> #include <fcntl.h> +#include <limits.h> #include <math.h> -#include <pwd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -57,8 +58,6 @@ #include <time.h> #include <unistd.h> -#include "pathnames.h" - #ifdef DEBUG #define cashvalue (loot-penalty)/25 #else @@ -79,7 +78,7 @@ struct point { #define TREASURE '$' #define GOAL '#' -#define TOPN 3 /* top scores to print if you lose */ +#define TOPN 10 /* top scores to print if you lose */ #define pchar(point, c) mvaddch((point)->line + 1, (point)->col + 1, (c)) /* Can't use terminal timing to do delay, in light of X */ @@ -96,14 +95,25 @@ int moves; int fast = 1; int rawscores; +FILE *sf; +char scorepath[PATH_MAX]; + +#define SCORES_ENTRIES (TOPN + 1) + +struct highscore { + char name[LOGIN_NAME_MAX]; + short score; +} scores[SCORES_ENTRIES]; +int nscores; + #ifdef LOGGING FILE *logfile; +char logpath[PATH_MAX]; #endif int lcnt, ccnt; /* user's idea of screen size */ int chunk; /* amount of money given at a time */ -void snscore(int, int); void chase(struct point *, struct point *); int chk(struct point *); @@ -112,9 +122,11 @@ void length(int); void mainloop(void); int post(int, int); int pushsnake(void); +int readscores(int); void setup(void); void snrand(struct point *); void snap(void); +void snscore(int, int); void spacewarp(int); void stop(int); int stretch(struct point *); @@ -123,6 +135,7 @@ void suspend(void); void win(struct point *); void winnings(int); + #ifdef LOGGING void logit(char *); #endif @@ -132,20 +145,18 @@ int wantstop; int main(int argc, char *argv[]) { - int ch, i; - struct sigaction sa; - gid_t gid; + struct sigaction sa; + int ch, i; + + if (pledge("stdio rpath wpath cpath tty", NULL) == -1) + err(1, "pledge"); - /* don't create the score file if it doesn't exist. */ - rawscores = open(_PATH_RAWSCORES, O_RDWR, 0664); #ifdef LOGGING - logfile = fopen(_PATH_LOGFILE, "a"); + snprintf(logpath, sizeof(logpath), "%s/%s", getenv("HOME"), + ".snake.log"); + logfile = fopen(logpath, "a"); #endif - /* revoke privs */ - gid = getgid(); - setresgid(gid, gid, gid); - while ((ch = getopt(argc, argv, "hl:stw:")) != -1) switch ((char)ch) { case 'w': /* width */ @@ -155,7 +166,10 @@ main(int argc, char *argv[]) lcnt = atoi(optarg); break; case 's': /* score */ - snscore(rawscores, 0); + if (readscores(0)) + snscore(rawscores, 0); + else + printf("no scores so far\n"); exit(0); break; case 't': /* slow terminal */ @@ -169,6 +183,7 @@ main(int argc, char *argv[]) exit(1); } + readscores(1); penalty = loot = 0; initscr(); #ifdef KEY_LEFT @@ -493,35 +508,49 @@ snrand(struct point *sp) int post(int iscore, int flag) { - short score = iscore; + struct highscore tmp; + int rank = nscores; short oldbest = 0; - uid_t uid = getuid(); /* I want to printf() the scores for terms that clear on endwin(), * but this routine also gets called with flag == 0 to see if * the snake should wink. If (flag) then we're at game end and * can printf. */ - if (rawscores == -1) { - if (flag) - warnx("Can't open score file %s", _PATH_RAWSCORES); - return(1); + if (flag == 0) + return (iscore > scores[0].score); + + rewind(sf); + + if (nscores == 0) { + nscores = 1; + scores[0].score = iscore; + oldbest = 0; + } else { + oldbest = scores[0].score; + scores[nscores].score = iscore; + if (nscores < TOPN) + nscores++; + } + + /* Insert this joker's current score */ + while (rank-- > 0 && iscore > scores[rank].score) { + memcpy(&tmp, &scores[rank], sizeof(struct highscore)); + memcpy(&scores[rank], &scores[rank+1], sizeof(struct highscore)); + memcpy(&scores[rank+1], &tmp, sizeof(struct highscore)); } - /* Figure out what happened in the past */ - lseek(rawscores, uid * sizeof(short), SEEK_SET); - read(rawscores, &oldbest, sizeof(short)); - if (!flag) - return (score > oldbest ? 1 : 0); - - /* Update this jokers best */ - if (score > oldbest) { - lseek(rawscores, uid * sizeof(short), SEEK_SET); - write(rawscores, &score, sizeof(short)); + + if (rank++ < 0) printf("\nYou bettered your previous best of $%d\n", oldbest); - } else - printf("\nYour best to date is $%d\n", oldbest); + else if (rank < nscores) + printf("\nYour score of $%d is ranked %d of all times!\n", + iscore, rank + 1); + + if (fwrite(scores, sizeof(scores[0]), nscores, sf) < nscores) + err(1, "fwrite"); + if (fclose(sf)) + err(1, "fclose"); - fsync(rawscores); /* See if we have a new champ */ snscore(rawscores, TOPN); return(1); @@ -924,6 +953,19 @@ length(int num) printf("You made %d moves.\n", num); } +void +snscore(int fd, int topn) +{ + int i; + + if (nscores == 0) + return; + + printf("%sSnake scores to date:\n", topn > 0 ? "Top " : ""); + for (i = 0; i < nscores; i++) + printf("%2d.\t$%d\t%s\n", i+1, scores[i].score, scores[i].name); +} + #ifdef LOGGING void logit(char *msg) @@ -938,3 +980,53 @@ logit(char *msg) } } #endif + +int +readscores(int create) +{ + const char *home; + const char *user; + const char *modstr; + int modint; + int ret; + + if (create == 0) { + modint = O_RDONLY; + modstr = "r"; + } else { + modint = O_RDWR | O_CREAT; + modstr = "r+"; + } + + home = getenv("HOME"); + if (home == NULL || *home == '\0') + err(1, "getenv"); + + ret = snprintf(scorepath, sizeof(scorepath), "%s/%s", home, + ".snake.scores"); + if (ret < 0 || ret >= PATH_MAX) + errc(1, ENAMETOOLONG, "%s/%s", home, ".snake.scores"); + + rawscores = open(scorepath, modint, 0666); + if (rawscores < 0) { + if (create == 0) + return 0; + err(1, "cannot open %s", scorepath); + } + if ((sf = fdopen(rawscores, modstr)) == NULL) + err(1, "cannot fdopen %s", scorepath); + nscores = fread(scores, sizeof(scores[0]), TOPN, sf); + if (ferror(sf)) + err(1, "error reading %s", scorepath); + + user = getenv("USER"); + if (user == NULL || *user == '\0') + user = "???"; + + if (nscores > TOPN) + nscores = TOPN; + strlcpy(scores[nscores].name, user, sizeof(scores[nscores].name)); + scores[nscores].score = 0; + + return 1; +} Index: snscore.c =================================================================== RCS file: snscore.c diff -N snscore.c --- snscore.c 18 Nov 2014 20:51:00 -0000 1.11 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,115 +0,0 @@ -/* $OpenBSD: snscore.c,v 1.11 2014/11/18 20:51:00 krw Exp $ */ -/* $NetBSD: snscore.c,v 1.5 1995/04/24 12:25:43 cgd Exp $ */ - -/* - * Copyright (c) 1980, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include <sys/types.h> -#include <err.h> -#include <fcntl.h> -#include <pwd.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include "pathnames.h" - -#define MAXPLAYERS 256 - -struct player { - uid_t uids; - short scores; - char *name; -} players[MAXPLAYERS], temp; - -void -snscore(int fd, int topn) -{ - uid_t uid; - short score; - int noplayers; - int i, j, notsorted; - char *q; - struct passwd *p; - - if (fd < 0) { - fd = open(_PATH_RAWSCORES, O_RDONLY, 0); - if (fd < 0) - errx(1, "Couldn't open raw scorefile"); - } - - lseek(fd, 0, SEEK_SET); - printf("%sSnake scores to date:\n", topn > 0 ? "Top " : ""); - /* read(fd, &whoallbest, sizeof(uid_t)); - * read(fd, &allbest, sizeof(short)); SCOREFILE FORMAT CHANGE - */ - noplayers = 0; - for (uid = 0; ; uid++) { - if (read(fd, &score, sizeof(short)) == 0) - break; - if (score > 0) { - if (noplayers >= MAXPLAYERS) - errx(2, "Too many entries in scorefile!"); - players[noplayers].uids = uid; - players[noplayers].scores = score; - p = getpwuid(uid); - if (p == NULL) - continue; - q = p -> pw_name; - if ((players[noplayers].name = strdup(q)) == NULL) - err(1, "strdup"); - - noplayers++; - } - } - - /* bubble sort scores */ - for (notsorted = 1; notsorted; ) { - notsorted = 0; - for (i = 0; i < noplayers - 1; i++) - if (players[i].scores < players[i + 1].scores) { - temp = players[i]; - players[i] = players[i + 1]; - players[i + 1] = temp; - notsorted++; - } - } - - if ((topn > 0) && (topn < noplayers)) - noplayers = topn; - j = 1; - for (i = 0; i < noplayers; i++) { - printf("%d:\t$%d\t%s\n", j, players[i].scores, players[i].name); - if (i < noplayers - 1 && - players[i].scores > players[i + 1].scores) - j = i + 2; - } - if (noplayers == 0) - printf("None.\n"); -}