43,23 → 43,23 |
* Major whacks since then. |
*/ |
#include <errno.h> |
#include <err.h> |
#include <fcntl.h> |
#include <pwd.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <time.h> |
#include <term.h> |
#include <unistd.h> |
#include <sys/param.h> |
#include <sys/stat.h> |
#include <sys/types.h> |
/* #include <err.h> */ |
/* #include <fcntl.h> */ |
/* #include <pwd.h> */ |
/* #include <stdio.h> */ |
/* #include <stdlib.h> */ |
/* #include <string.h> */ |
/* #include <time.h> */ |
/* #include <term.h> */ |
/* #include <unistd.h> */ |
/* #include <sys/param.h> */ |
/* #include <sys/stat.h> */ |
/* #include <sys/types.h> */ |
|
#include "pathnames.h" |
#include "screen.h" |
#include "tetris.h" |
#include "scores.h" |
#include "tetris.h" |
|
/* |
* Within this code, we can hang onto one extra "high score", leaving |
72,16 → 72,16 |
#define NUMSPOTS (MAXHISCORES + 1) |
#define NLEVELS (MAXLEVEL + 1) |
|
static time_t now; |
static int nscores; |
static int gotscores; |
static struct highscore scores[NUMSPOTS]; |
/* static time_t now; */ |
/* static int nscores; */ |
/* static int gotscores; */ |
/* static struct highscore scores[NUMSPOTS]; */ |
|
static int checkscores(struct highscore *, int); |
static int cmpscores(const void *, const void *); |
static void getscores(FILE **); |
static void printem(int, int, struct highscore *, int, const char *); |
static char *thisuser(void); |
/* static int checkscores(struct highscore *, int); */ |
/* static int cmpscores(const void *, const void *); */ |
/* static void getscores(FILE **); */ |
/* static void printem(int, int, struct highscore *, int, const char *); */ |
/* static char *thisuser(void); */ |
|
/* |
* Read the score file. Can be called from savescore (before showscores) |
92,148 → 92,150 |
* |
* Note, we assume closing the stdio file releases the lock. |
*/ |
static void |
getscores(FILE **fpp) |
{ |
int sd, mint, lck, mask, i; |
char *mstr, *human; |
FILE *sf; |
/* static void */ |
/* getscores(FILE **fpp) */ |
/* { */ |
/* int sd, mint, lck, mask, i; */ |
/* char *mstr, *human; */ |
/* FILE *sf; */ |
|
if (fpp != NULL) { |
mint = O_RDWR | O_CREAT; |
mstr = "r+"; |
human = "read/write"; |
lck = LOCK_EX; |
} else { |
mint = O_RDONLY; |
mstr = "r"; |
human = "reading"; |
lck = LOCK_SH; |
} |
setegid(egid); |
mask = umask(S_IWOTH); |
sd = open(_PATH_SCOREFILE, mint, 0666); |
(void)umask(mask); |
setegid(gid); |
if (sd < 0) { |
if (fpp == NULL) { |
nscores = 0; |
return; |
} |
err(1, "cannot open %s for %s", _PATH_SCOREFILE, human); |
} |
setegid(egid); |
if ((sf = fdopen(sd, mstr)) == NULL) |
err(1, "cannot fdopen %s for %s", _PATH_SCOREFILE, human); |
setegid(gid); |
/* if (fpp != NULL) { */ |
/* mint = O_RDWR | O_CREAT; */ |
/* mstr = "r+"; */ |
/* human = "read/write"; */ |
/* lck = LOCK_EX; */ |
/* } else { */ |
/* mint = O_RDONLY; */ |
/* mstr = "r"; */ |
/* human = "reading"; */ |
/* lck = LOCK_SH; */ |
/* } */ |
/* setegid(egid); */ |
/* mask = umask(S_IWOTH); */ |
/* sd = open(_PATH_SCOREFILE, mint, 0666); */ |
/* (void)umask(mask); */ |
/* setegid(gid); */ |
/* if (sd < 0) { */ |
/* if (fpp == NULL) { */ |
/* nscores = 0; */ |
/* return; */ |
/* } */ |
/* err(1, "cannot open %s for %s", _PATH_SCOREFILE, human); */ |
/* } */ |
/* setegid(egid); */ |
/* if ((sf = fdopen(sd, mstr)) == NULL) */ |
/* err(1, "cannot fdopen %s for %s", _PATH_SCOREFILE, human); */ |
/* setegid(gid); */ |
|
/* |
* Grab a lock. |
*/ |
if (flock(sd, lck)) |
warn("warning: score file %s cannot be locked", |
_PATH_SCOREFILE); |
/* /\* */ |
/* * Grab a lock. */ |
/* *\/ */ |
/* if (flock(sd, lck)) */ |
/* warn("warning: score file %s cannot be locked", */ |
/* _PATH_SCOREFILE); */ |
|
nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf); |
if (ferror(sf)) |
err(1, "error reading %s", _PATH_SCOREFILE); |
for (i = 0; i < nscores; i++) |
if (scores[i].hs_level < MINLEVEL || |
scores[i].hs_level > MAXLEVEL) |
errx(1, "scorefile %s corrupt", _PATH_SCOREFILE); |
/* nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf); */ |
/* if (ferror(sf)) */ |
/* err(1, "error reading %s", _PATH_SCOREFILE); */ |
/* for (i = 0; i < nscores; i++) */ |
/* if (scores[i].hs_level < MINLEVEL || */ |
/* scores[i].hs_level > MAXLEVEL) */ |
/* errx(1, "scorefile %s corrupt", _PATH_SCOREFILE); */ |
|
if (fpp) |
*fpp = sf; |
else |
(void)fclose(sf); |
} |
/* if (fpp) */ |
/* *fpp = sf; */ |
/* else */ |
/* (void)fclose(sf); */ |
/* } */ |
|
void |
savescore(int level) |
{ |
struct highscore *sp; |
int i; |
int change; |
FILE *sf; |
const char *me; |
return; |
} |
/* struct highscore *sp; */ |
/* int i; */ |
/* int change; */ |
/* FILE *sf; */ |
/* const char *me; */ |
|
getscores(&sf); |
gotscores = 1; |
(void)time(&now); |
/* getscores(&sf); */ |
/* gotscores = 1; */ |
/* (void)time(&now); */ |
|
/* |
* Allow at most one score per person per level -- see if we |
* can replace an existing score, or (easiest) do nothing. |
* Otherwise add new score at end (there is always room). |
*/ |
change = 0; |
me = thisuser(); |
for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { |
if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) |
continue; |
if (score > sp->hs_score) { |
(void)printf("%s bettered %s %d score of %d!\n", |
"\nYou", "your old level", level, |
sp->hs_score * sp->hs_level); |
sp->hs_score = score; /* new score */ |
sp->hs_time = now; /* and time */ |
change = 1; |
} else if (score == sp->hs_score) { |
(void)printf("%s tied %s %d high score.\n", |
"\nYou", "your old level", level); |
sp->hs_time = now; /* renew it */ |
change = 1; /* gotta rewrite, sigh */ |
} /* else new score < old score: do nothing */ |
break; |
} |
if (i >= nscores) { |
strlcpy(sp->hs_name, me, sizeof sp->hs_name); |
sp->hs_level = level; |
sp->hs_score = score; |
sp->hs_time = now; |
nscores++; |
change = 1; |
} |
/* /\* */ |
/* * Allow at most one score per person per level -- see if we */ |
/* * can replace an existing score, or (easiest) do nothing. */ |
/* * Otherwise add new score at end (there is always room). */ |
/* *\/ */ |
/* change = 0; */ |
/* me = thisuser(); */ |
/* for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { */ |
/* if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) */ |
/* continue; */ |
/* if (score > sp->hs_score) { */ |
/* (void)printf("%s bettered %s %d score of %d!\n", */ |
/* "\nYou", "your old level", level, */ |
/* sp->hs_score * sp->hs_level); */ |
/* sp->hs_score = score; /\* new score *\/ */ |
/* sp->hs_time = now; /\* and time *\/ */ |
/* change = 1; */ |
/* } else if (score == sp->hs_score) { */ |
/* (void)printf("%s tied %s %d high score.\n", */ |
/* "\nYou", "your old level", level); */ |
/* sp->hs_time = now; /\* renew it *\/ */ |
/* change = 1; /\* gotta rewrite, sigh *\/ */ |
/* } /\* else new score < old score: do nothing *\/ */ |
/* break; */ |
/* } */ |
/* if (i >= nscores) { */ |
/* strlcpy(sp->hs_name, me, sizeof sp->hs_name); */ |
/* sp->hs_level = level; */ |
/* sp->hs_score = score; */ |
/* sp->hs_time = now; */ |
/* nscores++; */ |
/* change = 1; */ |
/* } */ |
|
if (change) { |
/* |
* Sort & clean the scores, then rewrite. |
*/ |
nscores = checkscores(scores, nscores); |
rewind(sf); |
if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores || |
fflush(sf) == EOF) |
warnx("error writing %s: %s\n\t-- %s", |
_PATH_SCOREFILE, strerror(errno), |
"high scores may be damaged"); |
} |
(void)fclose(sf); /* releases lock */ |
} |
/* if (change) { */ |
/* /\* */ |
/* * Sort & clean the scores, then rewrite. */ |
/* *\/ */ |
/* nscores = checkscores(scores, nscores); */ |
/* rewind(sf); */ |
/* if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores || */ |
/* fflush(sf) == EOF) */ |
/* warnx("error writing %s: %s\n\t-- %s", */ |
/* _PATH_SCOREFILE, strerror(errno), */ |
/* "high scores may be damaged"); */ |
/* } */ |
/* (void)fclose(sf); /\* releases lock *\/ */ |
/* } */ |
|
/* |
* Get login name, or if that fails, get something suitable. |
* The result is always trimmed to fit in a score. |
*/ |
static char * |
thisuser(void) |
{ |
const char *p; |
struct passwd *pw; |
static char u[sizeof(scores[0].hs_name)]; |
/* static char * */ |
/* thisuser(void) */ |
/* { */ |
/* const char *p; */ |
/* struct passwd *pw; */ |
/* static char u[sizeof(scores[0].hs_name)]; */ |
|
if (u[0]) |
return (u); |
p = getlogin(); |
if (p == NULL || *p == '\0') { |
pw = getpwuid(getuid()); |
if (pw != NULL) |
p = pw->pw_name; |
else |
p = " ???"; |
} |
strlcpy(u, p, sizeof(u)); |
return (u); |
} |
/* if (u[0]) */ |
/* return (u); */ |
/* p = getlogin(); */ |
/* if (p == NULL || *p == '\0') { */ |
/* pw = getpwuid(getuid()); */ |
/* if (pw != NULL) */ |
/* p = pw->pw_name; */ |
/* else */ |
/* p = " ???"; */ |
/* } */ |
/* strlcpy(u, p, sizeof(u)); */ |
/* return (u); */ |
/* } */ |
|
/* |
* Score comparison function for qsort. |
241,25 → 243,25 |
* If two scores are equal, the person who had the score first is |
* listed first in the highscore file. |
*/ |
static int |
cmpscores(const void *x, const void *y) |
{ |
const struct highscore *a, *b; |
long l; |
/* static int */ |
/* cmpscores(const void *x, const void *y) */ |
/* { */ |
/* const struct highscore *a, *b; */ |
/* long l; */ |
|
a = x; |
b = y; |
l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; |
if (l < 0) |
return (-1); |
if (l > 0) |
return (1); |
if (a->hs_time < b->hs_time) |
return (-1); |
if (a->hs_time > b->hs_time) |
return (1); |
return (0); |
} |
/* a = x; */ |
/* b = y; */ |
/* l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; */ |
/* if (l < 0) */ |
/* return (-1); */ |
/* if (l > 0) */ |
/* return (1); */ |
/* if (a->hs_time < b->hs_time) */ |
/* return (-1); */ |
/* if (a->hs_time > b->hs_time) */ |
/* return (1); */ |
/* return (0); */ |
/* } */ |
|
/* |
* If we've added a score to the file, we need to check the file and ensure |
270,70 → 272,70 |
* removed, unless they are someone's personal best. |
* Caveat: the highest score on each level is always kept. |
*/ |
static int |
checkscores(struct highscore *hs, int num) |
{ |
struct highscore *sp; |
int i, j, k, numnames; |
int levelfound[NLEVELS]; |
struct peruser { |
char *name; |
int times; |
} count[NUMSPOTS]; |
struct peruser *pu; |
/* static int */ |
/* checkscores(struct highscore *hs, int num) */ |
/* { */ |
/* struct highscore *sp; */ |
/* int i, j, k, numnames; */ |
/* int levelfound[NLEVELS]; */ |
/* struct peruser { */ |
/* char *name; */ |
/* int times; */ |
/* } count[NUMSPOTS]; */ |
/* struct peruser *pu; */ |
|
/* |
* Sort so that highest totals come first. |
* |
* levelfound[i] becomes set when the first high score for that |
* level is encountered. By definition this is the highest score. |
*/ |
qsort((void *)hs, nscores, sizeof(*hs), cmpscores); |
for (i = MINLEVEL; i < NLEVELS; i++) |
levelfound[i] = 0; |
numnames = 0; |
for (i = 0, sp = hs; i < num;) { |
/* |
* This is O(n^2), but do you think we care? |
*/ |
for (j = 0, pu = count; j < numnames; j++, pu++) |
if (strcmp(sp->hs_name, pu->name) == 0) |
break; |
if (j == numnames) { |
/* |
* Add new user, set per-user count to 1. |
*/ |
pu->name = sp->hs_name; |
pu->times = 1; |
numnames++; |
} else { |
/* |
* Two ways to keep this score: |
* - Not too many (per user), still has acct, & |
* score not dated; or |
* - High score on this level. |
*/ |
if ((pu->times < MAXSCORES && |
getpwnam(sp->hs_name) != NULL && |
sp->hs_time + EXPIRATION >= now) || |
levelfound[sp->hs_level] == 0) |
pu->times++; |
else { |
/* |
* Delete this score, do not count it, |
* do not pass go, do not collect $200. |
*/ |
num--; |
for (k = i; k < num; k++) |
hs[k] = hs[k + 1]; |
continue; |
} |
} |
levelfound[sp->hs_level] = 1; |
i++, sp++; |
} |
return (num > MAXHISCORES ? MAXHISCORES : num); |
} |
/* /\* */ |
/* * Sort so that highest totals come first. */ |
/* * */ |
/* * levelfound[i] becomes set when the first high score for that */ |
/* * level is encountered. By definition this is the highest score. */ |
/* *\/ */ |
/* qsort((void *)hs, nscores, sizeof(*hs), cmpscores); */ |
/* for (i = MINLEVEL; i < NLEVELS; i++) */ |
/* levelfound[i] = 0; */ |
/* numnames = 0; */ |
/* for (i = 0, sp = hs; i < num;) { */ |
/* /\* */ |
/* * This is O(n^2), but do you think we care? */ |
/* *\/ */ |
/* for (j = 0, pu = count; j < numnames; j++, pu++) */ |
/* if (strcmp(sp->hs_name, pu->name) == 0) */ |
/* break; */ |
/* if (j == numnames) { */ |
/* /\* */ |
/* * Add new user, set per-user count to 1. */ |
/* *\/ */ |
/* pu->name = sp->hs_name; */ |
/* pu->times = 1; */ |
/* numnames++; */ |
/* } else { */ |
/* /\* */ |
/* * Two ways to keep this score: */ |
/* * - Not too many (per user), still has acct, & */ |
/* * score not dated; or */ |
/* * - High score on this level. */ |
/* *\/ */ |
/* if ((pu->times < MAXSCORES && */ |
/* getpwnam(sp->hs_name) != NULL && */ |
/* sp->hs_time + EXPIRATION >= now) || */ |
/* levelfound[sp->hs_level] == 0) */ |
/* pu->times++; */ |
/* else { */ |
/* /\* */ |
/* * Delete this score, do not count it, */ |
/* * do not pass go, do not collect $200. */ |
/* *\/ */ |
/* num--; */ |
/* for (k = i; k < num; k++) */ |
/* hs[k] = hs[k + 1]; */ |
/* continue; */ |
/* } */ |
/* } */ |
/* levelfound[sp->hs_level] = 1; */ |
/* i++, sp++; */ |
/* } */ |
/* return (num > MAXHISCORES ? MAXHISCORES : num); */ |
/* } */ |
|
/* |
* Show current scores. This must be called after savescore, if |
345,99 → 347,102 |
void |
showscores(int level) |
{ |
struct highscore *sp; |
int i, n, c; |
const char *me; |
int levelfound[NLEVELS]; |
return; |
} |
|
if (!gotscores) |
getscores((FILE **)NULL); |
(void)printf("\n\t\t Tetris High Scores\n"); |
/* struct highscore *sp; */ |
/* int i, n, c; */ |
/* const char *me; */ |
/* int levelfound[NLEVELS]; */ |
|
/* |
* If level == 0, the person has not played a game but just asked for |
* the high scores; we do not need to check for printing in highlight |
* mode. If SOstr is null, we can't do highlighting anyway. |
*/ |
me = level && SOstr ? thisuser() : NULL; |
/* if (!gotscores) */ |
/* getscores((FILE **)NULL); */ |
/* (void)printf("\n\t\t Tetris High Scores\n"); */ |
|
/* |
* Set times to 0 except for high score on each level. |
*/ |
for (i = MINLEVEL; i < NLEVELS; i++) |
levelfound[i] = 0; |
for (i = 0, sp = scores; i < nscores; i++, sp++) { |
if (levelfound[sp->hs_level]) |
sp->hs_time = 0; |
else { |
sp->hs_time = 1; |
levelfound[sp->hs_level] = 1; |
} |
} |
/* /\* */ |
/* * If level == 0, the person has not played a game but just asked for */ |
/* * the high scores; we do not need to check for printing in highlight */ |
/* * mode. If SOstr is null, we can't do highlighting anyway. */ |
/* *\/ */ |
/* me = level && SOstr ? thisuser() : NULL; */ |
|
/* |
* Page each screenful of scores. |
*/ |
for (i = 0, sp = scores; i < nscores; sp += n) { |
n = 20; |
if (i + n > nscores) |
n = nscores - i; |
printem(level, i + 1, sp, n, me); |
if ((i += n) < nscores) { |
(void)printf("\nHit RETURN to continue."); |
(void)fflush(stdout); |
while ((c = getchar()) != '\n') |
if (c == EOF) |
break; |
(void)printf("\n"); |
} |
} |
/* /\* */ |
/* * Set times to 0 except for high score on each level. */ |
/* *\/ */ |
/* for (i = MINLEVEL; i < NLEVELS; i++) */ |
/* levelfound[i] = 0; */ |
/* for (i = 0, sp = scores; i < nscores; i++, sp++) { */ |
/* if (levelfound[sp->hs_level]) */ |
/* sp->hs_time = 0; */ |
/* else { */ |
/* sp->hs_time = 1; */ |
/* levelfound[sp->hs_level] = 1; */ |
/* } */ |
/* } */ |
|
if (nscores == 0) |
printf("\t\t\t - none to date.\n"); |
} |
/* /\* */ |
/* * Page each screenful of scores. */ |
/* *\/ */ |
/* for (i = 0, sp = scores; i < nscores; sp += n) { */ |
/* n = 20; */ |
/* if (i + n > nscores) */ |
/* n = nscores - i; */ |
/* printem(level, i + 1, sp, n, me); */ |
/* if ((i += n) < nscores) { */ |
/* (void)printf("\nHit RETURN to continue."); */ |
/* (void)fflush(stdout); */ |
/* while ((c = getchar()) != '\n') */ |
/* if (c == EOF) */ |
/* break; */ |
/* (void)printf("\n"); */ |
/* } */ |
/* } */ |
|
static void |
printem(int level, int offset, struct highscore *hs, int n, const char *me) |
{ |
struct highscore *sp; |
int row, highlight, i; |
char buf[100]; |
#define TITLE "Rank Score Name (points/level)" |
#define TITL2 "==========================================================" |
/* if (nscores == 0) */ |
/* printf("\t\t\t - none to date.\n"); */ |
/* } */ |
|
printf("%s\n%s\n", TITLE, TITL2); |
/* static void */ |
/* printem(int level, int offset, struct highscore *hs, int n, const char *me) */ |
/* { */ |
/* struct highscore *sp; */ |
/* int row, highlight, i; */ |
/* char buf[100]; */ |
/* #define TITLE "Rank Score Name (points/level)" */ |
/* #define TITL2 "==========================================================" */ |
|
highlight = 0; |
/* printf("%s\n%s\n", TITLE, TITL2); */ |
|
for (row = 0; row < n; row++) { |
sp = &hs[row]; |
(void)snprintf(buf, sizeof(buf), |
"%3d%c %6d %-31s (%6d on %d)\n", |
row + offset, sp->hs_time ? '*' : ' ', |
sp->hs_score * sp->hs_level, |
sp->hs_name, sp->hs_score, sp->hs_level); |
/* Print leaders every three lines */ |
if ((row + 1) % 3 == 0) { |
for (i = 0; i < sizeof(buf); i++) |
if (buf[i] == ' ') |
buf[i] = '_'; |
} |
/* |
* Highlight if appropriate. This works because |
* we only get one score per level. |
*/ |
if (me != NULL && |
sp->hs_level == level && |
sp->hs_score == score && |
strcmp(sp->hs_name, me) == 0) { |
putpad(SOstr); |
highlight = 1; |
} |
(void)printf("%s", buf); |
if (highlight) { |
putpad(SEstr); |
highlight = 0; |
} |
} |
} |
/* highlight = 0; */ |
|
/* for (row = 0; row < n; row++) { */ |
/* sp = &hs[row]; */ |
/* (void)snprintf(buf, sizeof(buf), */ |
/* "%3d%c %6d %-31s (%6d on %d)\n", */ |
/* row + offset, sp->hs_time ? '*' : ' ', */ |
/* sp->hs_score * sp->hs_level, */ |
/* sp->hs_name, sp->hs_score, sp->hs_level); */ |
/* /\* Print leaders every three lines *\/ */ |
/* if ((row + 1) % 3 == 0) { */ |
/* for (i = 0; i < sizeof(buf); i++) */ |
/* if (buf[i] == ' ') */ |
/* buf[i] = '_'; */ |
/* } */ |
/* /\* */ |
/* * Highlight if appropriate. This works because */ |
/* * we only get one score per level. */ |
/* *\/ */ |
/* if (me != NULL && */ |
/* sp->hs_level == level && */ |
/* sp->hs_score == score && */ |
/* strcmp(sp->hs_name, me) == 0) { */ |
/* putpad(SOstr); */ |
/* highlight = 1; */ |
/* } */ |
/* (void)printf("%s", buf); */ |
/* if (highlight) { */ |
/* putpad(SEstr); */ |
/* highlight = 0; */ |
/* } */ |
/* } */ |
/* } */ |