Subversion Repositories HelenOS-historic

Rev

Rev 1466 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

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