1*57271Sbostic /*- 2*57271Sbostic * Copyright (c) 1992 The Regents of the University of California. 3*57271Sbostic * All rights reserved. 4*57271Sbostic * 5*57271Sbostic * %sccs.include.redist.c% 6*57271Sbostic * 7*57271Sbostic * @(#)scores.c 5.1 (Berkeley) 12/22/92 8*57271Sbostic */ 9*57271Sbostic 10*57271Sbostic /* 11*57271Sbostic * score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu) 12*57271Sbostic * modified 22 January 1992, to limit the number of entries any one 13*57271Sbostic * person has. 14*57271Sbostic * 15*57271Sbostic * Major whacks since then. 16*57271Sbostic */ 17*57271Sbostic #include <errno.h> 18*57271Sbostic #include <fcntl.h> 19*57271Sbostic #include <pwd.h> 20*57271Sbostic #include <stdio.h> 21*57271Sbostic #include <stdlib.h> 22*57271Sbostic #include <string.h> 23*57271Sbostic #include <time.h> 24*57271Sbostic #include <unistd.h> 25*57271Sbostic 26*57271Sbostic /* 27*57271Sbostic * XXX - need a <termcap.h> 28*57271Sbostic */ 29*57271Sbostic int tputs __P((const char *, int, int (*)(int))); 30*57271Sbostic 31*57271Sbostic #include "pathnames.h" 32*57271Sbostic #include "screen.h" 33*57271Sbostic #include "scores.h" 34*57271Sbostic #include "tetris.h" 35*57271Sbostic 36*57271Sbostic /* 37*57271Sbostic * Within this code, we can hang onto one extra "high score", leaving 38*57271Sbostic * room for our current score (whether or not it is high). 39*57271Sbostic * 40*57271Sbostic * We also sometimes keep tabs on the "highest" score on each level. 41*57271Sbostic * As long as the scores are kept sorted, this is simply the first one at 42*57271Sbostic * that level. 43*57271Sbostic */ 44*57271Sbostic #define NUMSPOTS (MAXHISCORES + 1) 45*57271Sbostic #define NLEVELS (MAXLEVEL + 1) 46*57271Sbostic 47*57271Sbostic static time_t now; 48*57271Sbostic static int nscores; 49*57271Sbostic static int gotscores; 50*57271Sbostic static struct highscore scores[NUMSPOTS]; 51*57271Sbostic 52*57271Sbostic static int checkscores __P((struct highscore *, int)); 53*57271Sbostic static int cmpscores __P((const void *, const void *)); 54*57271Sbostic static void getscores __P((FILE **)); 55*57271Sbostic static void printem __P((int, int, struct highscore *, int, const char *)); 56*57271Sbostic static char *thisuser __P((void)); 57*57271Sbostic 58*57271Sbostic /* 59*57271Sbostic * Read the score file. Can be called from savescore (before showscores) 60*57271Sbostic * or showscores (if savescore will not be called). If the given pointer 61*57271Sbostic * is not NULL, sets *fpp to an open file pointer that corresponds to a 62*57271Sbostic * read/write score file that is locked with LOCK_EX. Otherwise, the 63*57271Sbostic * file is locked with LOCK_SH for the read and closed before return. 64*57271Sbostic * 65*57271Sbostic * Note, we assume closing the stdio file releases the lock. 66*57271Sbostic */ 67*57271Sbostic static void 68*57271Sbostic getscores(fpp) 69*57271Sbostic FILE **fpp; 70*57271Sbostic { 71*57271Sbostic int sd, mint, lck; 72*57271Sbostic char *mstr, *human; 73*57271Sbostic FILE *sf; 74*57271Sbostic 75*57271Sbostic if (fpp != NULL) { 76*57271Sbostic mint = O_RDWR | O_CREAT; 77*57271Sbostic mstr = "r+"; 78*57271Sbostic human = "read/write"; 79*57271Sbostic lck = LOCK_EX; 80*57271Sbostic } else { 81*57271Sbostic mint = O_RDONLY; 82*57271Sbostic mstr = "r"; 83*57271Sbostic human = "reading"; 84*57271Sbostic lck = LOCK_SH; 85*57271Sbostic } 86*57271Sbostic sd = open(_PATH_SCOREFILE, mint, 0666); 87*57271Sbostic if (sd < 0) { 88*57271Sbostic if (fpp == NULL) { 89*57271Sbostic nscores = 0; 90*57271Sbostic return; 91*57271Sbostic } 92*57271Sbostic (void)fprintf(stderr, "tetris: cannot open %s for %s: %s\n", 93*57271Sbostic _PATH_SCOREFILE, human, strerror(errno)); 94*57271Sbostic exit(1); 95*57271Sbostic } 96*57271Sbostic if ((sf = fdopen(sd, mstr)) == NULL) { 97*57271Sbostic (void)fprintf(stderr, "tetris: cannot fdopen %s for %s: %s\n", 98*57271Sbostic _PATH_SCOREFILE, human, strerror(errno)); 99*57271Sbostic exit(1); 100*57271Sbostic } 101*57271Sbostic 102*57271Sbostic /* 103*57271Sbostic * Grab a lock. 104*57271Sbostic */ 105*57271Sbostic if (flock(sd, lck)) 106*57271Sbostic (void)fprintf(stderr, 107*57271Sbostic "tetris: warning: score file %s cannot be locked: %s\n", 108*57271Sbostic _PATH_SCOREFILE, strerror(errno)); 109*57271Sbostic 110*57271Sbostic nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf); 111*57271Sbostic if (ferror(sf)) { 112*57271Sbostic (void)fprintf(stderr, "tetris: error reading %s: %s\n", 113*57271Sbostic _PATH_SCOREFILE, strerror(errno)); 114*57271Sbostic exit(1); 115*57271Sbostic } 116*57271Sbostic 117*57271Sbostic if (fpp) 118*57271Sbostic *fpp = sf; 119*57271Sbostic else 120*57271Sbostic (void)fclose(sf); 121*57271Sbostic } 122*57271Sbostic 123*57271Sbostic void 124*57271Sbostic savescore(level) 125*57271Sbostic int level; 126*57271Sbostic { 127*57271Sbostic register struct highscore *sp; 128*57271Sbostic register int i; 129*57271Sbostic int change; 130*57271Sbostic FILE *sf; 131*57271Sbostic const char *me; 132*57271Sbostic 133*57271Sbostic getscores(&sf); 134*57271Sbostic gotscores = 1; 135*57271Sbostic (void)time(&now); 136*57271Sbostic 137*57271Sbostic /* 138*57271Sbostic * Allow at most one score per person per level -- see if we 139*57271Sbostic * can replace an existing score, or (easiest) do nothing. 140*57271Sbostic * Otherwise add new score at end (there is always room). 141*57271Sbostic */ 142*57271Sbostic change = 0; 143*57271Sbostic me = thisuser(); 144*57271Sbostic for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { 145*57271Sbostic if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) 146*57271Sbostic continue; 147*57271Sbostic if (score > sp->hs_score) { 148*57271Sbostic (void)printf("%s bettered %s %d score of %d!\n", 149*57271Sbostic "\nYou", "your old level", level, 150*57271Sbostic sp->hs_score * sp->hs_level); 151*57271Sbostic sp->hs_score = score; /* new score */ 152*57271Sbostic sp->hs_time = now; /* and time */ 153*57271Sbostic change = 1; 154*57271Sbostic } else if (score == sp->hs_score) { 155*57271Sbostic (void)printf("%s tied %s %d high score.\n", 156*57271Sbostic "\nYou", "your old level", level); 157*57271Sbostic sp->hs_time = now; /* renew it */ 158*57271Sbostic change = 1; /* gotta rewrite, sigh */ 159*57271Sbostic } /* else new score < old score: do nothing */ 160*57271Sbostic break; 161*57271Sbostic } 162*57271Sbostic if (i >= nscores) { 163*57271Sbostic strcpy(sp->hs_name, me); 164*57271Sbostic sp->hs_level = level; 165*57271Sbostic sp->hs_score = score; 166*57271Sbostic sp->hs_time = now; 167*57271Sbostic nscores++; 168*57271Sbostic change = 1; 169*57271Sbostic } 170*57271Sbostic 171*57271Sbostic if (change) { 172*57271Sbostic /* 173*57271Sbostic * Sort & clean the scores, then rewrite. 174*57271Sbostic */ 175*57271Sbostic nscores = checkscores(scores, nscores); 176*57271Sbostic rewind(sf); 177*57271Sbostic if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores || 178*57271Sbostic fflush(sf) == EOF) 179*57271Sbostic (void)fprintf(stderr, 180*57271Sbostic "tetris: error writing %s: %s -- %s\n", 181*57271Sbostic _PATH_SCOREFILE, strerror(errno), 182*57271Sbostic "high scores may be damaged"); 183*57271Sbostic } 184*57271Sbostic (void)fclose(sf); /* releases lock */ 185*57271Sbostic } 186*57271Sbostic 187*57271Sbostic /* 188*57271Sbostic * Get login name, or if that fails, get something suitable. 189*57271Sbostic * The result is always trimmed to fit in a score. 190*57271Sbostic */ 191*57271Sbostic static char * 192*57271Sbostic thisuser() 193*57271Sbostic { 194*57271Sbostic register const char *p; 195*57271Sbostic register struct passwd *pw; 196*57271Sbostic register size_t l; 197*57271Sbostic static char u[sizeof(scores[0].hs_name)]; 198*57271Sbostic 199*57271Sbostic if (u[0]) 200*57271Sbostic return (u); 201*57271Sbostic p = getlogin(); 202*57271Sbostic if (p == NULL || *p == '\0') { 203*57271Sbostic pw = getpwuid(getuid()); 204*57271Sbostic if (pw != NULL) 205*57271Sbostic p = pw->pw_name; 206*57271Sbostic else 207*57271Sbostic p = " ???"; 208*57271Sbostic } 209*57271Sbostic l = strlen(p); 210*57271Sbostic if (l >= sizeof(u)) 211*57271Sbostic l = sizeof(u) - 1; 212*57271Sbostic bcopy(p, u, l); 213*57271Sbostic u[l] = '\0'; 214*57271Sbostic return (u); 215*57271Sbostic } 216*57271Sbostic 217*57271Sbostic /* 218*57271Sbostic * Score comparison function for qsort. 219*57271Sbostic * 220*57271Sbostic * If two scores are equal, the person who had the score first is 221*57271Sbostic * listed first in the highscore file. 222*57271Sbostic */ 223*57271Sbostic static int 224*57271Sbostic cmpscores(x, y) 225*57271Sbostic const void *x, *y; 226*57271Sbostic { 227*57271Sbostic register const struct highscore *a, *b; 228*57271Sbostic register long l; 229*57271Sbostic 230*57271Sbostic a = x; 231*57271Sbostic b = y; 232*57271Sbostic l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; 233*57271Sbostic if (l < 0) 234*57271Sbostic return (-1); 235*57271Sbostic if (l > 0) 236*57271Sbostic return (1); 237*57271Sbostic if (a->hs_time < b->hs_time) 238*57271Sbostic return (-1); 239*57271Sbostic if (a->hs_time > b->hs_time) 240*57271Sbostic return (1); 241*57271Sbostic return (0); 242*57271Sbostic } 243*57271Sbostic 244*57271Sbostic /* 245*57271Sbostic * If we've added a score to the file, we need to check the file and ensure 246*57271Sbostic * that this player has only a few entries. The number of entries is 247*57271Sbostic * controlled by MAXSCORES, and is to ensure that the highscore file is not 248*57271Sbostic * monopolised by just a few people. People who no longer have accounts are 249*57271Sbostic * only allowed the highest score. Scores older than EXPIRATION seconds are 250*57271Sbostic * removed, unless they are someone's personal best. 251*57271Sbostic * Caveat: the highest score on each level is always kept. 252*57271Sbostic */ 253*57271Sbostic static int 254*57271Sbostic checkscores(hs, num) 255*57271Sbostic register struct highscore *hs; 256*57271Sbostic int num; 257*57271Sbostic { 258*57271Sbostic register struct highscore *sp; 259*57271Sbostic register int i, j, k, numnames; 260*57271Sbostic int levelfound[NLEVELS]; 261*57271Sbostic struct peruser { 262*57271Sbostic char *name; 263*57271Sbostic int times; 264*57271Sbostic } count[NUMSPOTS]; 265*57271Sbostic register struct peruser *pu; 266*57271Sbostic 267*57271Sbostic /* 268*57271Sbostic * Sort so that highest totals come first. 269*57271Sbostic * 270*57271Sbostic * levelfound[i] becomes set when the first high score for that 271*57271Sbostic * level is encountered. By definition this is the highest score. 272*57271Sbostic */ 273*57271Sbostic qsort((void *)hs, nscores, sizeof(*hs), cmpscores); 274*57271Sbostic for (i = MINLEVEL; i < NLEVELS; i++) 275*57271Sbostic levelfound[i] = 0; 276*57271Sbostic numnames = 0; 277*57271Sbostic for (i = 0, sp = hs; i < num;) { 278*57271Sbostic /* 279*57271Sbostic * This is O(n^2), but do you think we care? 280*57271Sbostic */ 281*57271Sbostic for (j = 0, pu = count; j < numnames; j++, pu++) 282*57271Sbostic if (strcmp(sp->hs_name, pu->name) == 0) 283*57271Sbostic break; 284*57271Sbostic if (j == numnames) { 285*57271Sbostic /* 286*57271Sbostic * Add new user, set per-user count to 1. 287*57271Sbostic */ 288*57271Sbostic pu->name = sp->hs_name; 289*57271Sbostic pu->times = 1; 290*57271Sbostic numnames++; 291*57271Sbostic } else { 292*57271Sbostic /* 293*57271Sbostic * Two ways to keep this score: 294*57271Sbostic * - Not too many (per user), still has acct, & 295*57271Sbostic * score not dated; or 296*57271Sbostic * - High score on this level. 297*57271Sbostic */ 298*57271Sbostic if ((pu->times < MAXSCORES && 299*57271Sbostic getpwnam(sp->hs_name) != NULL && 300*57271Sbostic sp->hs_time + EXPIRATION >= now) || 301*57271Sbostic levelfound[sp->hs_level] == 0) 302*57271Sbostic pu->times++; 303*57271Sbostic else { 304*57271Sbostic /* 305*57271Sbostic * Delete this score, do not count it, 306*57271Sbostic * do not pass go, do not collect $200. 307*57271Sbostic */ 308*57271Sbostic num--; 309*57271Sbostic for (k = i; k < num; k++) 310*57271Sbostic hs[k] = hs[k + 1]; 311*57271Sbostic continue; 312*57271Sbostic } 313*57271Sbostic } 314*57271Sbostic levelfound[sp->hs_level] = 1; 315*57271Sbostic i++, sp++; 316*57271Sbostic } 317*57271Sbostic return (num > MAXHISCORES ? MAXHISCORES : num); 318*57271Sbostic } 319*57271Sbostic 320*57271Sbostic /* 321*57271Sbostic * Show current scores. This must be called after savescore, if 322*57271Sbostic * savescore is called at all, for two reasons: 323*57271Sbostic * - Showscores munches the time field. 324*57271Sbostic * - Even if that were not the case, a new score must be recorded 325*57271Sbostic * before it can be shown anyway. 326*57271Sbostic */ 327*57271Sbostic void 328*57271Sbostic showscores(level) 329*57271Sbostic int level; 330*57271Sbostic { 331*57271Sbostic register struct highscore *sp; 332*57271Sbostic register int i, n, c; 333*57271Sbostic const char *me; 334*57271Sbostic int levelfound[NLEVELS]; 335*57271Sbostic 336*57271Sbostic if (!gotscores) 337*57271Sbostic getscores((FILE **)NULL); 338*57271Sbostic (void)printf("\n\t\t\t Tetris High Scores\n"); 339*57271Sbostic 340*57271Sbostic /* 341*57271Sbostic * If level == 0, the person has not played a game but just asked for 342*57271Sbostic * the high scores; we do not need to check for printing in highlight 343*57271Sbostic * mode. If SOstr is null, we can't do highlighting anyway. 344*57271Sbostic */ 345*57271Sbostic me = level && SOstr ? thisuser() : NULL; 346*57271Sbostic 347*57271Sbostic /* 348*57271Sbostic * Set times to 0 except for high score on each level. 349*57271Sbostic */ 350*57271Sbostic for (i = MINLEVEL; i < NLEVELS; i++) 351*57271Sbostic levelfound[i] = 0; 352*57271Sbostic for (i = 0, sp = scores; i < nscores; i++, sp++) { 353*57271Sbostic if (levelfound[sp->hs_level]) 354*57271Sbostic sp->hs_time = 0; 355*57271Sbostic else { 356*57271Sbostic sp->hs_time = 1; 357*57271Sbostic levelfound[sp->hs_level] = 1; 358*57271Sbostic } 359*57271Sbostic } 360*57271Sbostic 361*57271Sbostic /* 362*57271Sbostic * Page each screenful of scores. 363*57271Sbostic */ 364*57271Sbostic for (i = 0, sp = scores; i < nscores; sp += n) { 365*57271Sbostic n = 40; 366*57271Sbostic if (i + n > nscores) 367*57271Sbostic n = nscores - i; 368*57271Sbostic printem(level, i + 1, sp, n, me); 369*57271Sbostic if ((i += n) < nscores) { 370*57271Sbostic (void)printf("\nHit RETURN to continue."); 371*57271Sbostic (void)fflush(stdout); 372*57271Sbostic while ((c = getchar()) != '\n') 373*57271Sbostic if (c == EOF) 374*57271Sbostic break; 375*57271Sbostic (void)printf("\n"); 376*57271Sbostic } 377*57271Sbostic } 378*57271Sbostic } 379*57271Sbostic 380*57271Sbostic static void 381*57271Sbostic printem(level, offset, hs, n, me) 382*57271Sbostic int level, offset; 383*57271Sbostic register struct highscore *hs; 384*57271Sbostic register int n; 385*57271Sbostic const char *me; 386*57271Sbostic { 387*57271Sbostic register struct highscore *sp; 388*57271Sbostic int nrows, row, col, item, i, highlight; 389*57271Sbostic char buf[100]; 390*57271Sbostic #define TITLE "Rank Score Name (points/level)" 391*57271Sbostic 392*57271Sbostic /* 393*57271Sbostic * This makes a nice two-column sort with headers, but it's a bit 394*57271Sbostic * convoluted... 395*57271Sbostic */ 396*57271Sbostic printf("%s %s\n", TITLE, n > 1 ? TITLE : ""); 397*57271Sbostic 398*57271Sbostic highlight = 0; 399*57271Sbostic nrows = (n + 1) / 2; 400*57271Sbostic 401*57271Sbostic for (row = 0; row < nrows; row++) { 402*57271Sbostic for (col = 0; col < 2; col++) { 403*57271Sbostic item = col * nrows + row; 404*57271Sbostic if (item >= n) { 405*57271Sbostic /* 406*57271Sbostic * Can only occur on trailing columns. 407*57271Sbostic */ 408*57271Sbostic (void)putchar('\n'); 409*57271Sbostic continue; 410*57271Sbostic } 411*57271Sbostic (void)printf(item + offset < 10 ? " " : " "); 412*57271Sbostic sp = &hs[item]; 413*57271Sbostic (void)sprintf(buf, 414*57271Sbostic "%d%c %6d %-11s (%d on %d)", 415*57271Sbostic item + offset, sp->hs_time ? '*' : ' ', 416*57271Sbostic sp->hs_score * sp->hs_level, 417*57271Sbostic sp->hs_name, sp->hs_score, sp->hs_level); 418*57271Sbostic /* 419*57271Sbostic * Highlight if appropriate. This works because 420*57271Sbostic * we only get one score per level. 421*57271Sbostic */ 422*57271Sbostic if (me != NULL && 423*57271Sbostic sp->hs_level == level && 424*57271Sbostic sp->hs_score == score && 425*57271Sbostic strcmp(sp->hs_name, me) == 0) { 426*57271Sbostic putpad(SOstr); 427*57271Sbostic highlight = 1; 428*57271Sbostic } 429*57271Sbostic (void)printf("%s", buf); 430*57271Sbostic if (highlight) { 431*57271Sbostic putpad(SEstr); 432*57271Sbostic highlight = 0; 433*57271Sbostic } 434*57271Sbostic 435*57271Sbostic /* fill in spaces so column 1 lines up */ 436*57271Sbostic if (col == 0) 437*57271Sbostic for (i = 40 - strlen(buf); --i >= 0;) 438*57271Sbostic (void)putchar(' '); 439*57271Sbostic else /* col == 1 */ 440*57271Sbostic (void)putchar('\n'); 441*57271Sbostic } 442*57271Sbostic } 443*57271Sbostic } 444