(linenum→info "unix/slp.c:2238")

bsd-games/2.17/tetris/scores.c

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