157271Sbostic /*- 257271Sbostic * Copyright (c) 1992 The Regents of the University of California. 357271Sbostic * All rights reserved. 457271Sbostic * 5*57288Sbostic * This code is derived from software contributed to Berkeley by 6*57288Sbostic * Chris Torek and Darren F. Provine. 7*57288Sbostic * 857271Sbostic * %sccs.include.redist.c% 957271Sbostic * 10*57288Sbostic * @(#)scores.c 5.2 (Berkeley) 12/23/92 1157271Sbostic */ 1257271Sbostic 1357271Sbostic /* 14*57288Sbostic * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu) 1557271Sbostic * modified 22 January 1992, to limit the number of entries any one 1657271Sbostic * person has. 1757271Sbostic * 1857271Sbostic * Major whacks since then. 1957271Sbostic */ 2057271Sbostic #include <errno.h> 2157271Sbostic #include <fcntl.h> 2257271Sbostic #include <pwd.h> 2357271Sbostic #include <stdio.h> 2457271Sbostic #include <stdlib.h> 2557271Sbostic #include <string.h> 2657271Sbostic #include <time.h> 2757271Sbostic #include <unistd.h> 2857271Sbostic 2957271Sbostic /* 3057271Sbostic * XXX - need a <termcap.h> 3157271Sbostic */ 3257271Sbostic int tputs __P((const char *, int, int (*)(int))); 3357271Sbostic 3457271Sbostic #include "pathnames.h" 3557271Sbostic #include "screen.h" 3657271Sbostic #include "scores.h" 3757271Sbostic #include "tetris.h" 3857271Sbostic 3957271Sbostic /* 4057271Sbostic * Within this code, we can hang onto one extra "high score", leaving 4157271Sbostic * room for our current score (whether or not it is high). 4257271Sbostic * 4357271Sbostic * We also sometimes keep tabs on the "highest" score on each level. 4457271Sbostic * As long as the scores are kept sorted, this is simply the first one at 4557271Sbostic * that level. 4657271Sbostic */ 4757271Sbostic #define NUMSPOTS (MAXHISCORES + 1) 4857271Sbostic #define NLEVELS (MAXLEVEL + 1) 4957271Sbostic 5057271Sbostic static time_t now; 5157271Sbostic static int nscores; 5257271Sbostic static int gotscores; 5357271Sbostic static struct highscore scores[NUMSPOTS]; 5457271Sbostic 5557271Sbostic static int checkscores __P((struct highscore *, int)); 5657271Sbostic static int cmpscores __P((const void *, const void *)); 5757271Sbostic static void getscores __P((FILE **)); 5857271Sbostic static void printem __P((int, int, struct highscore *, int, const char *)); 5957271Sbostic static char *thisuser __P((void)); 6057271Sbostic 6157271Sbostic /* 6257271Sbostic * Read the score file. Can be called from savescore (before showscores) 6357271Sbostic * or showscores (if savescore will not be called). If the given pointer 6457271Sbostic * is not NULL, sets *fpp to an open file pointer that corresponds to a 6557271Sbostic * read/write score file that is locked with LOCK_EX. Otherwise, the 6657271Sbostic * file is locked with LOCK_SH for the read and closed before return. 6757271Sbostic * 6857271Sbostic * Note, we assume closing the stdio file releases the lock. 6957271Sbostic */ 7057271Sbostic static void 7157271Sbostic getscores(fpp) 7257271Sbostic FILE **fpp; 7357271Sbostic { 7457271Sbostic int sd, mint, lck; 7557271Sbostic char *mstr, *human; 7657271Sbostic FILE *sf; 7757271Sbostic 7857271Sbostic if (fpp != NULL) { 7957271Sbostic mint = O_RDWR | O_CREAT; 8057271Sbostic mstr = "r+"; 8157271Sbostic human = "read/write"; 8257271Sbostic lck = LOCK_EX; 8357271Sbostic } else { 8457271Sbostic mint = O_RDONLY; 8557271Sbostic mstr = "r"; 8657271Sbostic human = "reading"; 8757271Sbostic lck = LOCK_SH; 8857271Sbostic } 8957271Sbostic sd = open(_PATH_SCOREFILE, mint, 0666); 9057271Sbostic if (sd < 0) { 9157271Sbostic if (fpp == NULL) { 9257271Sbostic nscores = 0; 9357271Sbostic return; 9457271Sbostic } 9557271Sbostic (void)fprintf(stderr, "tetris: cannot open %s for %s: %s\n", 9657271Sbostic _PATH_SCOREFILE, human, strerror(errno)); 9757271Sbostic exit(1); 9857271Sbostic } 9957271Sbostic if ((sf = fdopen(sd, mstr)) == NULL) { 10057271Sbostic (void)fprintf(stderr, "tetris: cannot fdopen %s for %s: %s\n", 10157271Sbostic _PATH_SCOREFILE, human, strerror(errno)); 10257271Sbostic exit(1); 10357271Sbostic } 10457271Sbostic 10557271Sbostic /* 10657271Sbostic * Grab a lock. 10757271Sbostic */ 10857271Sbostic if (flock(sd, lck)) 10957271Sbostic (void)fprintf(stderr, 11057271Sbostic "tetris: warning: score file %s cannot be locked: %s\n", 11157271Sbostic _PATH_SCOREFILE, strerror(errno)); 11257271Sbostic 11357271Sbostic nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf); 11457271Sbostic if (ferror(sf)) { 11557271Sbostic (void)fprintf(stderr, "tetris: error reading %s: %s\n", 11657271Sbostic _PATH_SCOREFILE, strerror(errno)); 11757271Sbostic exit(1); 11857271Sbostic } 11957271Sbostic 12057271Sbostic if (fpp) 12157271Sbostic *fpp = sf; 12257271Sbostic else 12357271Sbostic (void)fclose(sf); 12457271Sbostic } 12557271Sbostic 12657271Sbostic void 12757271Sbostic savescore(level) 12857271Sbostic int level; 12957271Sbostic { 13057271Sbostic register struct highscore *sp; 13157271Sbostic register int i; 13257271Sbostic int change; 13357271Sbostic FILE *sf; 13457271Sbostic const char *me; 13557271Sbostic 13657271Sbostic getscores(&sf); 13757271Sbostic gotscores = 1; 13857271Sbostic (void)time(&now); 13957271Sbostic 14057271Sbostic /* 14157271Sbostic * Allow at most one score per person per level -- see if we 14257271Sbostic * can replace an existing score, or (easiest) do nothing. 14357271Sbostic * Otherwise add new score at end (there is always room). 14457271Sbostic */ 14557271Sbostic change = 0; 14657271Sbostic me = thisuser(); 14757271Sbostic for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { 14857271Sbostic if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) 14957271Sbostic continue; 15057271Sbostic if (score > sp->hs_score) { 15157271Sbostic (void)printf("%s bettered %s %d score of %d!\n", 15257271Sbostic "\nYou", "your old level", level, 15357271Sbostic sp->hs_score * sp->hs_level); 15457271Sbostic sp->hs_score = score; /* new score */ 15557271Sbostic sp->hs_time = now; /* and time */ 15657271Sbostic change = 1; 15757271Sbostic } else if (score == sp->hs_score) { 15857271Sbostic (void)printf("%s tied %s %d high score.\n", 15957271Sbostic "\nYou", "your old level", level); 16057271Sbostic sp->hs_time = now; /* renew it */ 16157271Sbostic change = 1; /* gotta rewrite, sigh */ 16257271Sbostic } /* else new score < old score: do nothing */ 16357271Sbostic break; 16457271Sbostic } 16557271Sbostic if (i >= nscores) { 16657271Sbostic strcpy(sp->hs_name, me); 16757271Sbostic sp->hs_level = level; 16857271Sbostic sp->hs_score = score; 16957271Sbostic sp->hs_time = now; 17057271Sbostic nscores++; 17157271Sbostic change = 1; 17257271Sbostic } 17357271Sbostic 17457271Sbostic if (change) { 17557271Sbostic /* 17657271Sbostic * Sort & clean the scores, then rewrite. 17757271Sbostic */ 17857271Sbostic nscores = checkscores(scores, nscores); 17957271Sbostic rewind(sf); 18057271Sbostic if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores || 18157271Sbostic fflush(sf) == EOF) 18257271Sbostic (void)fprintf(stderr, 18357271Sbostic "tetris: error writing %s: %s -- %s\n", 18457271Sbostic _PATH_SCOREFILE, strerror(errno), 18557271Sbostic "high scores may be damaged"); 18657271Sbostic } 18757271Sbostic (void)fclose(sf); /* releases lock */ 18857271Sbostic } 18957271Sbostic 19057271Sbostic /* 19157271Sbostic * Get login name, or if that fails, get something suitable. 19257271Sbostic * The result is always trimmed to fit in a score. 19357271Sbostic */ 19457271Sbostic static char * 19557271Sbostic thisuser() 19657271Sbostic { 19757271Sbostic register const char *p; 19857271Sbostic register struct passwd *pw; 19957271Sbostic register size_t l; 20057271Sbostic static char u[sizeof(scores[0].hs_name)]; 20157271Sbostic 20257271Sbostic if (u[0]) 20357271Sbostic return (u); 20457271Sbostic p = getlogin(); 20557271Sbostic if (p == NULL || *p == '\0') { 20657271Sbostic pw = getpwuid(getuid()); 20757271Sbostic if (pw != NULL) 20857271Sbostic p = pw->pw_name; 20957271Sbostic else 21057271Sbostic p = " ???"; 21157271Sbostic } 21257271Sbostic l = strlen(p); 21357271Sbostic if (l >= sizeof(u)) 21457271Sbostic l = sizeof(u) - 1; 21557271Sbostic bcopy(p, u, l); 21657271Sbostic u[l] = '\0'; 21757271Sbostic return (u); 21857271Sbostic } 21957271Sbostic 22057271Sbostic /* 22157271Sbostic * Score comparison function for qsort. 22257271Sbostic * 22357271Sbostic * If two scores are equal, the person who had the score first is 22457271Sbostic * listed first in the highscore file. 22557271Sbostic */ 22657271Sbostic static int 22757271Sbostic cmpscores(x, y) 22857271Sbostic const void *x, *y; 22957271Sbostic { 23057271Sbostic register const struct highscore *a, *b; 23157271Sbostic register long l; 23257271Sbostic 23357271Sbostic a = x; 23457271Sbostic b = y; 23557271Sbostic l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; 23657271Sbostic if (l < 0) 23757271Sbostic return (-1); 23857271Sbostic if (l > 0) 23957271Sbostic return (1); 24057271Sbostic if (a->hs_time < b->hs_time) 24157271Sbostic return (-1); 24257271Sbostic if (a->hs_time > b->hs_time) 24357271Sbostic return (1); 24457271Sbostic return (0); 24557271Sbostic } 24657271Sbostic 24757271Sbostic /* 24857271Sbostic * If we've added a score to the file, we need to check the file and ensure 24957271Sbostic * that this player has only a few entries. The number of entries is 25057271Sbostic * controlled by MAXSCORES, and is to ensure that the highscore file is not 25157271Sbostic * monopolised by just a few people. People who no longer have accounts are 25257271Sbostic * only allowed the highest score. Scores older than EXPIRATION seconds are 25357271Sbostic * removed, unless they are someone's personal best. 25457271Sbostic * Caveat: the highest score on each level is always kept. 25557271Sbostic */ 25657271Sbostic static int 25757271Sbostic checkscores(hs, num) 25857271Sbostic register struct highscore *hs; 25957271Sbostic int num; 26057271Sbostic { 26157271Sbostic register struct highscore *sp; 26257271Sbostic register int i, j, k, numnames; 26357271Sbostic int levelfound[NLEVELS]; 26457271Sbostic struct peruser { 26557271Sbostic char *name; 26657271Sbostic int times; 26757271Sbostic } count[NUMSPOTS]; 26857271Sbostic register struct peruser *pu; 26957271Sbostic 27057271Sbostic /* 27157271Sbostic * Sort so that highest totals come first. 27257271Sbostic * 27357271Sbostic * levelfound[i] becomes set when the first high score for that 27457271Sbostic * level is encountered. By definition this is the highest score. 27557271Sbostic */ 27657271Sbostic qsort((void *)hs, nscores, sizeof(*hs), cmpscores); 27757271Sbostic for (i = MINLEVEL; i < NLEVELS; i++) 27857271Sbostic levelfound[i] = 0; 27957271Sbostic numnames = 0; 28057271Sbostic for (i = 0, sp = hs; i < num;) { 28157271Sbostic /* 28257271Sbostic * This is O(n^2), but do you think we care? 28357271Sbostic */ 28457271Sbostic for (j = 0, pu = count; j < numnames; j++, pu++) 28557271Sbostic if (strcmp(sp->hs_name, pu->name) == 0) 28657271Sbostic break; 28757271Sbostic if (j == numnames) { 28857271Sbostic /* 28957271Sbostic * Add new user, set per-user count to 1. 29057271Sbostic */ 29157271Sbostic pu->name = sp->hs_name; 29257271Sbostic pu->times = 1; 29357271Sbostic numnames++; 29457271Sbostic } else { 29557271Sbostic /* 29657271Sbostic * Two ways to keep this score: 29757271Sbostic * - Not too many (per user), still has acct, & 29857271Sbostic * score not dated; or 29957271Sbostic * - High score on this level. 30057271Sbostic */ 30157271Sbostic if ((pu->times < MAXSCORES && 30257271Sbostic getpwnam(sp->hs_name) != NULL && 30357271Sbostic sp->hs_time + EXPIRATION >= now) || 30457271Sbostic levelfound[sp->hs_level] == 0) 30557271Sbostic pu->times++; 30657271Sbostic else { 30757271Sbostic /* 30857271Sbostic * Delete this score, do not count it, 30957271Sbostic * do not pass go, do not collect $200. 31057271Sbostic */ 31157271Sbostic num--; 31257271Sbostic for (k = i; k < num; k++) 31357271Sbostic hs[k] = hs[k + 1]; 31457271Sbostic continue; 31557271Sbostic } 31657271Sbostic } 31757271Sbostic levelfound[sp->hs_level] = 1; 31857271Sbostic i++, sp++; 31957271Sbostic } 32057271Sbostic return (num > MAXHISCORES ? MAXHISCORES : num); 32157271Sbostic } 32257271Sbostic 32357271Sbostic /* 32457271Sbostic * Show current scores. This must be called after savescore, if 32557271Sbostic * savescore is called at all, for two reasons: 32657271Sbostic * - Showscores munches the time field. 32757271Sbostic * - Even if that were not the case, a new score must be recorded 32857271Sbostic * before it can be shown anyway. 32957271Sbostic */ 33057271Sbostic void 33157271Sbostic showscores(level) 33257271Sbostic int level; 33357271Sbostic { 33457271Sbostic register struct highscore *sp; 33557271Sbostic register int i, n, c; 33657271Sbostic const char *me; 33757271Sbostic int levelfound[NLEVELS]; 33857271Sbostic 33957271Sbostic if (!gotscores) 34057271Sbostic getscores((FILE **)NULL); 34157271Sbostic (void)printf("\n\t\t\t Tetris High Scores\n"); 34257271Sbostic 34357271Sbostic /* 34457271Sbostic * If level == 0, the person has not played a game but just asked for 34557271Sbostic * the high scores; we do not need to check for printing in highlight 34657271Sbostic * mode. If SOstr is null, we can't do highlighting anyway. 34757271Sbostic */ 34857271Sbostic me = level && SOstr ? thisuser() : NULL; 34957271Sbostic 35057271Sbostic /* 35157271Sbostic * Set times to 0 except for high score on each level. 35257271Sbostic */ 35357271Sbostic for (i = MINLEVEL; i < NLEVELS; i++) 35457271Sbostic levelfound[i] = 0; 35557271Sbostic for (i = 0, sp = scores; i < nscores; i++, sp++) { 35657271Sbostic if (levelfound[sp->hs_level]) 35757271Sbostic sp->hs_time = 0; 35857271Sbostic else { 35957271Sbostic sp->hs_time = 1; 36057271Sbostic levelfound[sp->hs_level] = 1; 36157271Sbostic } 36257271Sbostic } 36357271Sbostic 36457271Sbostic /* 36557271Sbostic * Page each screenful of scores. 36657271Sbostic */ 36757271Sbostic for (i = 0, sp = scores; i < nscores; sp += n) { 36857271Sbostic n = 40; 36957271Sbostic if (i + n > nscores) 37057271Sbostic n = nscores - i; 37157271Sbostic printem(level, i + 1, sp, n, me); 37257271Sbostic if ((i += n) < nscores) { 37357271Sbostic (void)printf("\nHit RETURN to continue."); 37457271Sbostic (void)fflush(stdout); 37557271Sbostic while ((c = getchar()) != '\n') 37657271Sbostic if (c == EOF) 37757271Sbostic break; 37857271Sbostic (void)printf("\n"); 37957271Sbostic } 38057271Sbostic } 38157271Sbostic } 38257271Sbostic 38357271Sbostic static void 38457271Sbostic printem(level, offset, hs, n, me) 38557271Sbostic int level, offset; 38657271Sbostic register struct highscore *hs; 38757271Sbostic register int n; 38857271Sbostic const char *me; 38957271Sbostic { 39057271Sbostic register struct highscore *sp; 39157271Sbostic int nrows, row, col, item, i, highlight; 39257271Sbostic char buf[100]; 39357271Sbostic #define TITLE "Rank Score Name (points/level)" 39457271Sbostic 39557271Sbostic /* 39657271Sbostic * This makes a nice two-column sort with headers, but it's a bit 39757271Sbostic * convoluted... 39857271Sbostic */ 39957271Sbostic printf("%s %s\n", TITLE, n > 1 ? TITLE : ""); 40057271Sbostic 40157271Sbostic highlight = 0; 40257271Sbostic nrows = (n + 1) / 2; 40357271Sbostic 40457271Sbostic for (row = 0; row < nrows; row++) { 40557271Sbostic for (col = 0; col < 2; col++) { 40657271Sbostic item = col * nrows + row; 40757271Sbostic if (item >= n) { 40857271Sbostic /* 40957271Sbostic * Can only occur on trailing columns. 41057271Sbostic */ 41157271Sbostic (void)putchar('\n'); 41257271Sbostic continue; 41357271Sbostic } 41457271Sbostic (void)printf(item + offset < 10 ? " " : " "); 41557271Sbostic sp = &hs[item]; 41657271Sbostic (void)sprintf(buf, 41757271Sbostic "%d%c %6d %-11s (%d on %d)", 41857271Sbostic item + offset, sp->hs_time ? '*' : ' ', 41957271Sbostic sp->hs_score * sp->hs_level, 42057271Sbostic sp->hs_name, sp->hs_score, sp->hs_level); 42157271Sbostic /* 42257271Sbostic * Highlight if appropriate. This works because 42357271Sbostic * we only get one score per level. 42457271Sbostic */ 42557271Sbostic if (me != NULL && 42657271Sbostic sp->hs_level == level && 42757271Sbostic sp->hs_score == score && 42857271Sbostic strcmp(sp->hs_name, me) == 0) { 42957271Sbostic putpad(SOstr); 43057271Sbostic highlight = 1; 43157271Sbostic } 43257271Sbostic (void)printf("%s", buf); 43357271Sbostic if (highlight) { 43457271Sbostic putpad(SEstr); 43557271Sbostic highlight = 0; 43657271Sbostic } 43757271Sbostic 43857271Sbostic /* fill in spaces so column 1 lines up */ 43957271Sbostic if (col == 0) 44057271Sbostic for (i = 40 - strlen(buf); --i >= 0;) 44157271Sbostic (void)putchar(' '); 44257271Sbostic else /* col == 1 */ 44357271Sbostic (void)putchar('\n'); 44457271Sbostic } 44557271Sbostic } 44657271Sbostic } 447