1 /* $NetBSD: scores.c,v 1.17 2009/06/01 04:03:26 dholland 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
= bswap32(offset64
);
224 offset60
= bswap32(offset60
);
225 offset56
= bswap32(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
;
384 const char *mstr
, *human
;
389 #ifdef ALLOW_SCORE_UPDATES
391 mint
= O_RDWR
| O_CREAT
;
392 human
= "read/write";
403 mask
= umask(S_IWOTH
);
404 sd
= open(_PATH_SCOREFILE
, mint
, 0666);
410 * If the file simply isn't there because nobody's
411 * played yet, and we aren't going to be trying to
412 * update it, don't warn. Even if we are going to be
413 * trying to write it, don't fail -- we can still show
414 * 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
);
464 if (result
!= 0 && (size_t)result
!= sizeof(header
)) {
465 warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE
);
467 * File is hopelessly corrupt, might as well truncate it
468 * and start over with empty scores.
470 if (lseek(sd
, 0, SEEK_SET
) < 0) {
472 warn("Score file %s: lseek", _PATH_SCOREFILE
);
475 if (ftruncate(sd
, 0) == 0) {
484 /* Empty file; that just means there are no scores. */
488 * Is what we read a header, or the first 16 bytes of
489 * a score entry? hsh_magic_val is chosen to be
490 * something that is extremely unlikely to appear in
493 if (!memcmp(header
.hsh_magic
, hsh_magic_val
, HSH_MAGIC_SIZE
)) {
494 /* Yes, we have a header. */
496 if (header
.hsh_endiantag
== HSH_ENDIAN_NATIVE
) {
499 } else if (header
.hsh_endiantag
== HSH_ENDIAN_OPP
) {
502 warnx("Score file %s: Unknown endian tag %u",
503 _PATH_SCOREFILE
, header
.hsh_endiantag
);
507 if (header
.hsh_version
!= HSH_VERSION
) {
508 warnx("Score file %s: Unknown version code %u",
509 _PATH_SCOREFILE
, header
.hsh_version
);
513 if (readscores(sd
, doflip
) < 0) {
518 * Ok, it wasn't a header. Try to figure out what
519 * size records we have.
521 result
= scorefile_probe(sd
);
522 if (lseek(sd
, 0, SEEK_SET
) < 0) {
523 warn("Score file %s: lseek", _PATH_SCOREFILE
);
527 case SCOREFILE_CURRENT
:
528 result
= readscores(sd
, 0 /* don't flip */);
530 case SCOREFILE_CURRENT_OPP
:
531 result
= readscores(sd
, 1 /* do flip */);
534 result
= readscores599(sd
, 0 /* don't flip */);
536 case SCOREFILE_599_OPP
:
537 result
= readscores599(sd
, 1 /* do flip */);
540 result
= readscores50(sd
, 0 /* don't flip */);
542 case SCOREFILE_50_OPP
:
543 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
];
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 strncpy(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
&& SOstr
? 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) {
945 (void)printf("%s", buf
);
951 /* fill in spaces so column 1 lines up */
953 for (i
= 40 - strlen(buf
); --i
>= 0;)