1 /* $NetBSD: scores.c,v 1.26 2021/05/02 12:50:46 rillig Exp $ */
4 * Copyright (c) 1992, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
8 * Chris Torek and Darren F. Provine.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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.
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
34 * @(#)scores.c 8.1 (Berkeley) 5/31/93
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
42 * Major whacks since then.
56 #include "pathnames.h"
62 * Allow updating the high scores unless we're built as part of /rescue.
65 #define ALLOW_SCORE_UPDATES
69 * Within this code, we can hang onto one extra "high score", leaving
70 * room for our current score (whether or not it is high).
72 * We also sometimes keep tabs on the "highest" score on each level.
73 * As long as the scores are kept sorted, this is simply the first one at
76 #define NUMSPOTS (MAXHISCORES + 1)
77 #define NLEVELS (MAXLEVEL + 1)
82 static struct highscore scores[NUMSPOTS];
84 static int checkscores(struct highscore *, int);
85 static int cmpscores(const void *, const void *);
86 static void getscores(int *);
87 static void printem(int, int, struct highscore *, int, const char *);
88 static char *thisuser(void);
90 /* contents chosen to be a highly illegal username */
91 static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://";
93 #define HSH_ENDIAN_NATIVE 0x12345678
94 #define HSH_ENDIAN_OPP 0x78563412
96 /* current file format version */
99 /* codes for scorefile_probe return */
100 #define SCOREFILE_ERROR (-1)
101 #define SCOREFILE_CURRENT 0 /* 40-byte */
102 #define SCOREFILE_CURRENT_OPP 1 /* 40-byte, opposite-endian */
103 #define SCOREFILE_599 2 /* 36-byte */
104 #define SCOREFILE_599_OPP 3 /* 36-byte, opposite-endian */
105 #define SCOREFILE_50 4 /* 32-byte */
106 #define SCOREFILE_50_OPP 5 /* 32-byte, opposite-endian */
109 * Check (or guess) what kind of score file contents we have.
112 scorefile_probe(int sd)
117 uint32_t numbers[3], offset56, offset60, offset64;
119 if (fstat(sd, &st) < 0) {
120 warn("Score file %s: fstat", _PATH_SCOREFILE);
124 t1 = st.st_size % sizeof(struct highscore_ondisk) == 0;
125 t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0;
126 t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0;
129 /* Size matches exact number of one kind of records */
131 return SCOREFILE_CURRENT;
133 return SCOREFILE_599;
137 } else if (tx == 0) {
138 /* Size matches nothing, pick most likely as default */
143 * File size is multiple of more than one structure size.
144 * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
145 * Read the file and see if we can figure out what's going
146 * on. This is the layout of the first two records:
148 * offset hso / current hso_599 hso_50
149 * (40-byte) (36-byte) (32-byte)
151 * 0 name #0 name #0 name #0
156 * 20 score #0 score #0 score #0
157 * 24 level #0 level #0 level #0
158 * 28 (pad) time #0 time #0
165 * 56 : score #1 level #1
166 * 60 score #1 level #1 time #1
167 * 64 level #1 time #1 name #2
169 * 72 time #1 name #2 :
173 * There are a number of things we could check here, but the
174 * most effective test is based on the following restrictions:
176 * - The level must be between 1 and 9 (inclusive)
177 * - All times must be after 1985 and are before 2038,
178 * so the high word must be 0 and the low word may not be
180 * - Integer values of 0 or 1-9 cannot be the beginning of
181 * a login name string.
182 * - Values of 1-9 are probably not a score.
184 * So we read the three words at offsets 56, 60, and 64, and
185 * poke at the values to try to figure things...
188 if (lseek(sd, 56, SEEK_SET) < 0) {
189 warn("Score file %s: lseek", _PATH_SCOREFILE);
192 result = read(sd, &numbers, sizeof(numbers));
194 warn("Score file %s: read", _PATH_SCOREFILE);
197 if ((size_t)result != sizeof(numbers)) {
199 * The smallest file whose size divides by more than
200 * one of the sizes is substantially larger than 64,
201 * so this should *never* happen.
203 warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE);
207 offset56 = numbers[0];
208 offset60 = numbers[1];
209 offset64 = numbers[2];
211 if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
212 /* 40-byte structure */
213 return SCOREFILE_CURRENT;
214 } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
215 /* 36-byte structure */
216 return SCOREFILE_599;
217 } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
218 /* 32-byte structure */
222 /* None was a valid level; try opposite endian */
223 offset64 = swap32(offset64);
224 offset60 = swap32(offset60);
225 offset56 = swap32(offset56);
227 if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
228 /* 40-byte structure */
229 return SCOREFILE_CURRENT_OPP;
230 } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
231 /* 36-byte structure */
232 return SCOREFILE_599_OPP;
233 } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
234 /* 32-byte structure */
235 return SCOREFILE_50_OPP;
238 /* That didn't work either, dunno what's going on */
240 warnx("Score file %s is likely corrupt", _PATH_SCOREFILE);
241 if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
242 return SCOREFILE_CURRENT;
243 } else if (sizeof(time_t) == 8) {
244 return SCOREFILE_599;
251 * Copy a string safely, making sure it's null-terminated.
254 readname(char *to, size_t maxto, const char *from, size_t maxfrom)
258 amt = maxto < maxfrom ? maxto : maxfrom;
259 memcpy(to, from, amt);
264 * Copy integers, byte-swapping if desired.
267 read32(int32_t val, int doflip)
276 read64(int64_t val, int doflip)
285 * Read up to MAXHISCORES scorefile_ondisk entries.
288 readscores(int sd, int doflip)
290 struct highscore_ondisk buf[MAXHISCORES];
294 result = read(sd, buf, sizeof(buf));
296 warn("Score file %s: read", _PATH_SCOREFILE);
299 nscores = result / sizeof(buf[0]);
301 for (i=0; i<nscores; i++) {
302 readname(scores[i].hs_name, sizeof(scores[i].hs_name),
303 buf[i].hso_name, sizeof(buf[i].hso_name));
304 scores[i].hs_score = read32(buf[i].hso_score, doflip);
305 scores[i].hs_level = read32(buf[i].hso_level, doflip);
306 scores[i].hs_time = read64(buf[i].hso_time, doflip);
312 * Read up to MAXHISCORES scorefile_ondisk_599 entries.
315 readscores599(int sd, int doflip)
317 struct highscore_ondisk_599 buf[MAXHISCORES];
321 result = read(sd, buf, sizeof(buf));
323 warn("Score file %s: read", _PATH_SCOREFILE);
326 nscores = result / sizeof(buf[0]);
328 for (i=0; i<nscores; i++) {
329 readname(scores[i].hs_name, sizeof(scores[i].hs_name),
330 buf[i].hso599_name, sizeof(buf[i].hso599_name));
331 scores[i].hs_score = read32(buf[i].hso599_score, doflip);
332 scores[i].hs_level = read32(buf[i].hso599_level, doflip);
334 * Don't bother pasting the time together into a
335 * 64-bit value; just take whichever half is nonzero.
338 read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0],
345 * Read up to MAXHISCORES scorefile_ondisk_50 entries.
348 readscores50(int sd, int doflip)
350 struct highscore_ondisk_50 buf[MAXHISCORES];
354 result = read(sd, buf, sizeof(buf));
356 warn("Score file %s: read", _PATH_SCOREFILE);
359 nscores = result / sizeof(buf[0]);
361 for (i=0; i<nscores; i++) {
362 readname(scores[i].hs_name, sizeof(scores[i].hs_name),
363 buf[i].hso50_name, sizeof(buf[i].hso50_name));
364 scores[i].hs_score = read32(buf[i].hso50_score, doflip);
365 scores[i].hs_level = read32(buf[i].hso50_level, doflip);
366 scores[i].hs_time = read32(buf[i].hso50_time, doflip);
372 * Read the score file. Can be called from savescore (before showscores)
373 * or showscores (if savescore will not be called). If the given pointer
374 * is not NULL, sets *fdp to an open file handle that corresponds to a
375 * read/write score file that is locked with LOCK_EX. Otherwise, the
376 * file is locked with LOCK_SH for the read and closed before return.
381 struct highscore_header header;
389 #ifdef ALLOW_SCORE_UPDATES
391 mint = O_RDWR | O_CREAT;
392 human = "read/write";
402 mask = umask(S_IWOTH);
403 sd = open(_PATH_SCOREFILE, mint, 0666);
409 * If the file simply isn't there because nobody's
410 * played yet, and we aren't going to be trying to
411 * update it, don't warn. Even if we are going to be
412 * trying to write it, don't fail -- we can still show
413 * the player the score they got.
416 if (fdp != NULL || errno != ENOENT) {
417 warn("Cannot open %s for %s", _PATH_SCOREFILE, human);
424 * XXX: failure here should probably be more fatal than this.
427 warn("warning: score file %s cannot be locked",
431 * The current format (since -current of 20090525) is
433 * struct highscore_header
434 * up to MAXHIGHSCORES x struct highscore_ondisk
436 * Before this, there is no header, and the contents
437 * might be any of three formats:
439 * highscore_ondisk (64-bit machines with 64-bit time_t)
440 * highscore_ondisk_599 (32-bit machines with 64-bit time_t)
441 * highscore_ondisk_50 (32-bit machines with 32-bit time_t)
443 * The first two appear in 5.99 between the time_t change and
444 * 20090525, depending on whether the compiler inserts
445 * structure padding before an unaligned 64-bit time_t. The
446 * last appears in 5.0 and earlier.
448 * Any or all of these might also appear on other OSes where
449 * this code has been ported.
451 * Since the old file has no header, we will have to guess
452 * which of these formats it has.
456 * First, look for a header.
458 result = read(sd, &header, sizeof(header));
460 warn("Score file %s: read", _PATH_SCOREFILE);
463 if (result != 0 && (size_t)result != sizeof(header)) {
464 warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE);
466 * File is hopelessly corrupt, might as well truncate it
467 * and start over with empty scores.
469 if (lseek(sd, 0, SEEK_SET) < 0) {
471 warn("Score file %s: lseek", _PATH_SCOREFILE);
474 if (ftruncate(sd, 0) == 0) {
482 /* Empty file; that just means there are no scores. */
486 * Is what we read a header, or the first 16 bytes of
487 * a score entry? hsh_magic_val is chosen to be
488 * something that is extremely unlikely to appear in
491 if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) {
492 /* Yes, we have a header. */
494 if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) {
497 } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) {
500 warnx("Score file %s: Unknown endian tag %u",
501 _PATH_SCOREFILE, header.hsh_endiantag);
505 if (header.hsh_version != HSH_VERSION) {
506 warnx("Score file %s: Unknown version code %u",
507 _PATH_SCOREFILE, header.hsh_version);
511 if (readscores(sd, doflip) < 0) {
516 * Ok, it wasn't a header. Try to figure out what
517 * size records we have.
519 result = scorefile_probe(sd);
520 if (lseek(sd, 0, SEEK_SET) < 0) {
521 warn("Score file %s: lseek", _PATH_SCOREFILE);
525 case SCOREFILE_CURRENT:
526 result = readscores(sd, 0 /* don't flip */);
528 case SCOREFILE_CURRENT_OPP:
529 result = readscores(sd, 1 /* do flip */);
532 result = readscores599(sd, 0 /* don't flip */);
534 case SCOREFILE_599_OPP:
535 result = readscores599(sd, 1 /* do flip */);
538 result = readscores50(sd, 0 /* don't flip */);
540 case SCOREFILE_50_OPP:
541 result = readscores50(sd, 1 /* do flip */);
569 #ifdef ALLOW_SCORE_UPDATES
571 * Paranoid write wrapper; unlike fwrite() it preserves errno.
574 dowrite(int sd, const void *vbuf, size_t len)
576 const char *buf = vbuf;
581 result = write(sd, buf+done, len-done);
583 if (errno == EINTR) {
592 #endif /* ALLOW_SCORE_UPDATES */
595 * Write the score file out.
600 #ifdef ALLOW_SCORE_UPDATES
601 struct highscore_header header;
602 struct highscore_ondisk buf[MAXHISCORES] = {0};
609 memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE);
610 header.hsh_endiantag = HSH_ENDIAN_NATIVE;
611 header.hsh_version = HSH_VERSION;
613 for (i=0; i<nscores; i++) {
614 memcpy(buf[i].hso_name, scores[i].hs_name,
615 sizeof(buf[i].hso_name));
616 buf[i].hso_score = scores[i].hs_score;
617 buf[i].hso_level = scores[i].hs_level;
618 buf[i].hso_pad = 0xbaadf00d;
619 buf[i].hso_time = scores[i].hs_time;
622 if (lseek(sd, 0, SEEK_SET) < 0) {
623 warn("Score file %s: lseek", _PATH_SCOREFILE);
626 if (dowrite(sd, &header, sizeof(header)) < 0 ||
627 dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) {
628 warn("Score file %s: write", _PATH_SCOREFILE);
633 warnx("high scores may be damaged");
636 #endif /* ALLOW_SCORE_UPDATES */
640 * Close the score file.
650 * Read and update the scores file with the current reults.
655 struct highscore *sp;
666 * Allow at most one score per person per level -- see if we
667 * can replace an existing score, or (easiest) do nothing.
668 * Otherwise add new score at end (there is always room).
672 for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) {
673 if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0)
675 if (score > sp->hs_score) {
676 (void)printf("%s bettered %s %d score of %d!\n",
677 "\nYou", "your old level", level,
678 sp->hs_score * sp->hs_level);
679 sp->hs_score = score; /* new score */
680 sp->hs_time = now; /* and time */
682 } else if (score == sp->hs_score) {
683 (void)printf("%s tied %s %d high score.\n",
684 "\nYou", "your old level", level);
685 sp->hs_time = now; /* renew it */
686 change = 1; /* gotta rewrite, sigh */
687 } /* else new score < old score: do nothing */
691 strcpy(sp->hs_name, me);
692 sp->hs_level = level;
693 sp->hs_score = score;
701 * Sort & clean the scores, then rewrite.
703 nscores = checkscores(scores, nscores);
710 * Get login name, or if that fails, get something suitable.
711 * The result is always trimmed to fit in a score.
719 static char u[sizeof(scores[0].hs_name)];
724 if (p == NULL || *p == '\0') {
725 pw = getpwuid(getuid());
740 * Score comparison function for qsort.
742 * If two scores are equal, the person who had the score first is
743 * listed first in the highscore file.
746 cmpscores(const void *x, const void *y)
748 const struct highscore *a, *b;
753 l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score;
758 if (a->hs_time < b->hs_time)
760 if (a->hs_time > b->hs_time)
766 * If we've added a score to the file, we need to check the file and ensure
767 * that this player has only a few entries. The number of entries is
768 * controlled by MAXSCORES, and is to ensure that the highscore file is not
769 * monopolised by just a few people. People who no longer have accounts are
770 * only allowed the highest score. Scores older than EXPIRATION seconds are
771 * removed, unless they are someone's personal best.
772 * Caveat: the highest score on each level is always kept.
775 checkscores(struct highscore *hs, int num)
777 struct highscore *sp;
778 int i, j, k, numnames;
779 int levelfound[NLEVELS];
787 * Sort so that highest totals come first.
789 * levelfound[i] becomes set when the first high score for that
790 * level is encountered. By definition this is the highest score.
792 qsort((void *)hs, nscores, sizeof(*hs), cmpscores);
793 for (i = MINLEVEL; i < NLEVELS; i++)
796 for (i = 0, sp = hs; i < num;) {
798 * This is O(n^2), but do you think we care?
800 for (j = 0, pu = count; j < numnames; j++, pu++)
801 if (strcmp(sp->hs_name, pu->name) == 0)
805 * Add new user, set per-user count to 1.
807 pu->name = sp->hs_name;
812 * Two ways to keep this score:
813 * - Not too many (per user), still has acct, &
814 * score not dated; or
815 * - High score on this level.
817 if ((pu->times < MAXSCORES &&
818 getpwnam(sp->hs_name) != NULL &&
819 sp->hs_time + EXPIRATION >= now) ||
820 levelfound[sp->hs_level] == 0)
824 * Delete this score, do not count it,
825 * do not pass go, do not collect $200.
828 for (k = i; k < num; k++)
833 if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
834 levelfound[sp->hs_level] = 1;
837 return (num > MAXHISCORES ? MAXHISCORES : num);
841 * Show current scores. This must be called after savescore, if
842 * savescore is called at all, for two reasons:
843 * - Showscores munches the time field.
844 * - Even if that were not the case, a new score must be recorded
845 * before it can be shown anyway.
848 showscores(int level)
850 struct highscore *sp;
853 int levelfound[NLEVELS];
857 (void)printf("\n\t\t\t Tetris High Scores\n");
860 * If level == 0, the person has not played a game but just asked for
861 * the high scores; we do not need to check for printing in highlight
862 * mode. If SOstr is null, we can't do highlighting anyway.
864 me = level && enter_standout_mode ? thisuser() : NULL;
867 * Set times to 0 except for high score on each level.
869 for (i = MINLEVEL; i < NLEVELS; i++)
871 for (i = 0, sp = scores; i < nscores; i++, sp++) {
872 if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
873 if (levelfound[sp->hs_level])
877 levelfound[sp->hs_level] = 1;
883 * Page each screenful of scores.
885 for (i = 0, sp = scores; i < nscores; sp += n) {
889 printem(level, i + 1, sp, n, me);
890 if ((i += n) < nscores) {
891 (void)printf("\nHit RETURN to continue.");
892 (void)fflush(stdout);
893 while ((c = getchar()) != '\n')
902 printem(int level, int offset, struct highscore *hs, int n, const char *me)
904 struct highscore *sp;
905 int nrows, row, col, item, i, highlight;
907 #define TITLE "Rank Score Name (points/level)"
910 * This makes a nice two-column sort with headers, but it's a bit
913 printf("%s %s\n", TITLE, n > 1 ? TITLE : "");
918 for (row = 0; row < nrows; row++) {
919 for (col = 0; col < 2; col++) {
920 item = col * nrows + row;
923 * Can only occur on trailing columns.
929 (void)snprintf(buf, sizeof(buf),
930 "%3d%c %6d %-11s (%6d on %d)",
931 item + offset, sp->hs_time ? '*' : ' ',
932 sp->hs_score * sp->hs_level,
933 sp->hs_name, sp->hs_score, sp->hs_level);
935 * Highlight if appropriate. This works because
936 * we only get one score per level.
939 sp->hs_level == level &&
940 sp->hs_score == score &&
941 strcmp(sp->hs_name, me) == 0) {
942 putpad(enter_standout_mode);
945 (void)printf("%s", buf);
947 putpad(exit_standout_mode);
951 /* fill in spaces so column 1 lines up */
953 for (i = 40 - strlen(buf); --i >= 0;)