]>
git.cameronkatri.com Git - bsdgames-darwin.git/blob - tetris/scores.c
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 * Within this code, we can hang onto one extra "high score", leaving
63 * room for our current score (whether or not it is high).
65 * We also sometimes keep tabs on the "highest" score on each level.
66 * As long as the scores are kept sorted, this is simply the first one at
69 #define NUMSPOTS (MAXHISCORES + 1)
70 #define NLEVELS (MAXLEVEL + 1)
75 static struct highscore scores
[NUMSPOTS
];
77 static int checkscores(struct highscore
*, int);
78 static int cmpscores(const void *, const void *);
79 static void getscores(int *);
80 static void printem(int, int, struct highscore
*, int, const char *);
81 static char *thisuser(void);
83 /* contents chosen to be a highly illegal username */
84 static const char hsh_magic_val
[HSH_MAGIC_SIZE
] = "//:\0\0://";
86 #define HSH_ENDIAN_NATIVE 0x12345678
87 #define HSH_ENDIAN_OPP 0x78563412
89 /* current file format version */
92 /* codes for scorefile_probe return */
93 #define SCOREFILE_ERROR (-1)
94 #define SCOREFILE_CURRENT 0 /* 40-byte */
95 #define SCOREFILE_CURRENT_OPP 1 /* 40-byte, opposite-endian */
96 #define SCOREFILE_599 2 /* 36-byte */
97 #define SCOREFILE_599_OPP 3 /* 36-byte, opposite-endian */
98 #define SCOREFILE_50 4 /* 32-byte */
99 #define SCOREFILE_50_OPP 5 /* 32-byte, opposite-endian */
102 * Check (or guess) what kind of score file contents we have.
105 scorefile_probe(int sd
)
110 uint32_t numbers
[3], offset56
, offset60
, offset64
;
112 if (fstat(sd
, &st
) < 0) {
113 warn("Score file %s: fstat", _PATH_SCOREFILE
);
117 t1
= st
.st_size
% sizeof(struct highscore_ondisk
) == 0;
118 t2
= st
.st_size
% sizeof(struct highscore_ondisk_599
) == 0;
119 t3
= st
.st_size
% sizeof(struct highscore_ondisk_50
) == 0;
122 /* Size matches exact number of one kind of records */
124 return SCOREFILE_CURRENT
;
126 return SCOREFILE_599
;
130 } else if (tx
== 0) {
131 /* Size matches nothing, pick most likely as default */
136 * File size is multiple of more than one structure size.
137 * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
138 * Read the file and see if we can figure out what's going
139 * on. This is the layout of the first two records:
141 * offset hso / current hso_599 hso_50
142 * (40-byte) (36-byte) (32-byte)
144 * 0 name #0 name #0 name #0
149 * 20 score #0 score #0 score #0
150 * 24 level #0 level #0 level #0
151 * 28 (pad) time #0 time #0
158 * 56 : score #1 level #1
159 * 60 score #1 level #1 time #1
160 * 64 level #1 time #1 name #2
162 * 72 time #1 name #2 :
166 * There are a number of things we could check here, but the
167 * most effective test is based on the following restrictions:
169 * - The level must be between 1 and 9 (inclusive)
170 * - All times must be after 1985 and are before 2038,
171 * so the high word must be 0 and the low word may not be
173 * - Integer values of 0 or 1-9 cannot be the beginning of
174 * a login name string.
175 * - Values of 1-9 are probably not a score.
177 * So we read the three words at offsets 56, 60, and 64, and
178 * poke at the values to try to figure things...
181 if (lseek(sd
, 56, SEEK_SET
) < 0) {
182 warn("Score file %s: lseek", _PATH_SCOREFILE
);
185 result
= read(sd
, &numbers
, sizeof(numbers
));
187 warn("Score file %s: read", _PATH_SCOREFILE
);
190 if ((size_t)result
!= sizeof(numbers
)) {
192 * The smallest file whose size divides by more than
193 * one of the sizes is substantially larger than 64,
194 * so this should *never* happen.
196 warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE
);
200 offset56
= numbers
[0];
201 offset60
= numbers
[1];
202 offset64
= numbers
[2];
204 if (offset64
>= MINLEVEL
&& offset64
<= MAXLEVEL
) {
205 /* 40-byte structure */
206 return SCOREFILE_CURRENT
;
207 } else if (offset60
>= MINLEVEL
&& offset60
<= MAXLEVEL
) {
208 /* 36-byte structure */
209 return SCOREFILE_599
;
210 } else if (offset56
>= MINLEVEL
&& offset56
<= MAXLEVEL
) {
211 /* 32-byte structure */
215 /* None was a valid level; try opposite endian */
216 offset64
= bswap32(offset64
);
217 offset60
= bswap32(offset60
);
218 offset56
= bswap32(offset56
);
220 if (offset64
>= MINLEVEL
&& offset64
<= MAXLEVEL
) {
221 /* 40-byte structure */
222 return SCOREFILE_CURRENT_OPP
;
223 } else if (offset60
>= MINLEVEL
&& offset60
<= MAXLEVEL
) {
224 /* 36-byte structure */
225 return SCOREFILE_599_OPP
;
226 } else if (offset56
>= MINLEVEL
&& offset56
<= MAXLEVEL
) {
227 /* 32-byte structure */
228 return SCOREFILE_50_OPP
;
231 /* That didn't work either, dunno what's going on */
233 warnx("Score file %s is likely corrupt", _PATH_SCOREFILE
);
234 if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
235 return SCOREFILE_CURRENT
;
236 } else if (sizeof(time_t) == 8) {
237 return SCOREFILE_599
;
244 * Copy a string safely, making sure it's null-terminated.
247 readname(char *to
, size_t maxto
, const char *from
, size_t maxfrom
)
251 amt
= maxto
< maxfrom
? maxto
: maxfrom
;
252 memcpy(to
, from
, amt
);
257 * Copy integers, byte-swapping if desired.
260 read32(int32_t val
, int doflip
)
269 read64(int64_t val
, int doflip
)
278 * Read up to MAXHISCORES scorefile_ondisk entries.
281 readscores(int sd
, int doflip
)
283 struct highscore_ondisk buf
[MAXHISCORES
];
287 result
= read(sd
, buf
, sizeof(buf
));
289 warn("Score file %s: read", _PATH_SCOREFILE
);
292 nscores
= result
/ sizeof(buf
[0]);
294 for (i
=0; i
<nscores
; i
++) {
295 readname(scores
[i
].hs_name
, sizeof(scores
[i
].hs_name
),
296 buf
[i
].hso_name
, sizeof(buf
[i
].hso_name
));
297 scores
[i
].hs_score
= read32(buf
[i
].hso_score
, doflip
);
298 scores
[i
].hs_level
= read32(buf
[i
].hso_level
, doflip
);
299 scores
[i
].hs_time
= read64(buf
[i
].hso_time
, doflip
);
305 * Read up to MAXHISCORES scorefile_ondisk_599 entries.
308 readscores599(int sd
, int doflip
)
310 struct highscore_ondisk_599 buf
[MAXHISCORES
];
314 result
= read(sd
, buf
, sizeof(buf
));
316 warn("Score file %s: read", _PATH_SCOREFILE
);
319 nscores
= result
/ sizeof(buf
[0]);
321 for (i
=0; i
<nscores
; i
++) {
322 readname(scores
[i
].hs_name
, sizeof(scores
[i
].hs_name
),
323 buf
[i
].hso599_name
, sizeof(buf
[i
].hso599_name
));
324 scores
[i
].hs_score
= read32(buf
[i
].hso599_score
, doflip
);
325 scores
[i
].hs_level
= read32(buf
[i
].hso599_level
, doflip
);
327 * Don't bother pasting the time together into a
328 * 64-bit value; just take whichever half is nonzero.
331 read32(buf
[i
].hso599_time
[buf
[i
].hso599_time
[0] == 0],
338 * Read up to MAXHISCORES scorefile_ondisk_50 entries.
341 readscores50(int sd
, int doflip
)
343 struct highscore_ondisk_50 buf
[MAXHISCORES
];
347 result
= read(sd
, buf
, sizeof(buf
));
349 warn("Score file %s: read", _PATH_SCOREFILE
);
352 nscores
= result
/ sizeof(buf
[0]);
354 for (i
=0; i
<nscores
; i
++) {
355 readname(scores
[i
].hs_name
, sizeof(scores
[i
].hs_name
),
356 buf
[i
].hso50_name
, sizeof(buf
[i
].hso50_name
));
357 scores
[i
].hs_score
= read32(buf
[i
].hso50_score
, doflip
);
358 scores
[i
].hs_level
= read32(buf
[i
].hso50_level
, doflip
);
359 scores
[i
].hs_time
= read32(buf
[i
].hso50_time
, doflip
);
365 * Read the score file. Can be called from savescore (before showscores)
366 * or showscores (if savescore will not be called). If the given pointer
367 * is not NULL, sets *fdp to an open file handle that corresponds to a
368 * read/write score file that is locked with LOCK_EX. Otherwise, the
369 * file is locked with LOCK_SH for the read and closed before return.
374 struct highscore_header header
;
377 const char *mstr
, *human
;
383 mint
= O_RDWR
| O_CREAT
;
384 human
= "read/write";
393 mask
= umask(S_IWOTH
);
394 sd
= open(_PATH_SCOREFILE
, mint
, 0666);
400 * If the file simply isn't there because nobody's
401 * played yet, and we aren't going to be trying to
402 * update it, don't warn. Even if we are going to be
403 * trying to write it, don't fail -- we can still show
404 * the player the score they got.
406 if (fdp
!= NULL
|| errno
!= ENOENT
) {
407 warn("Cannot open %s for %s", _PATH_SCOREFILE
, human
);
414 * XXX: failure here should probably be more fatal than this.
417 warn("warning: score file %s cannot be locked",
421 * The current format (since -current of 20090525) is
423 * struct highscore_header
424 * up to MAXHIGHSCORES x struct highscore_ondisk
426 * Before this, there is no header, and the contents
427 * might be any of three formats:
429 * highscore_ondisk (64-bit machines with 64-bit time_t)
430 * highscore_ondisk_599 (32-bit machines with 64-bit time_t)
431 * highscore_ondisk_50 (32-bit machines with 32-bit time_t)
433 * The first two appear in 5.99 between the time_t change and
434 * 20090525, depending on whether the compiler inserts
435 * structure padding before an unaligned 64-bit time_t. The
436 * last appears in 5.0 and earlier.
438 * Any or all of these might also appear on other OSes where
439 * this code has been ported.
441 * Since the old file has no header, we will have to guess
442 * which of these formats it has.
446 * First, look for a header.
448 result
= read(sd
, &header
, sizeof(header
));
450 warn("Score file %s: read", _PATH_SCOREFILE
);
454 if (result
!= 0 && (size_t)result
!= sizeof(header
)) {
455 warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE
);
457 * File is hopelessly corrupt, might as well truncate it
458 * and start over with empty scores.
460 if (lseek(sd
, 0, SEEK_SET
) < 0) {
462 warn("Score file %s: lseek", _PATH_SCOREFILE
);
465 if (ftruncate(sd
, 0) == 0) {
474 /* Empty file; that just means there are no scores. */
478 * Is what we read a header, or the first 16 bytes of
479 * a score entry? hsh_magic_val is chosen to be
480 * something that is extremely unlikely to appear in
483 if (!memcmp(header
.hsh_magic
, hsh_magic_val
, HSH_MAGIC_SIZE
)) {
484 /* Yes, we have a header. */
486 if (header
.hsh_endiantag
== HSH_ENDIAN_NATIVE
) {
489 } else if (header
.hsh_endiantag
== HSH_ENDIAN_OPP
) {
492 warnx("Score file %s: Unknown endian tag %u",
493 _PATH_SCOREFILE
, header
.hsh_endiantag
);
497 if (header
.hsh_version
!= HSH_VERSION
) {
498 warnx("Score file %s: Unknown version code %u",
499 _PATH_SCOREFILE
, header
.hsh_version
);
503 if (readscores(sd
, doflip
) < 0) {
508 * Ok, it wasn't a header. Try to figure out what
509 * size records we have.
511 result
= scorefile_probe(sd
);
512 if (lseek(sd
, 0, SEEK_SET
) < 0) {
513 warn("Score file %s: lseek", _PATH_SCOREFILE
);
517 case SCOREFILE_CURRENT
:
518 result
= readscores(sd
, 0 /* don't flip */);
520 case SCOREFILE_CURRENT_OPP
:
521 result
= readscores(sd
, 1 /* do flip */);
524 result
= readscores599(sd
, 0 /* don't flip */);
526 case SCOREFILE_599_OPP
:
527 result
= readscores599(sd
, 1 /* do flip */);
530 result
= readscores50(sd
, 0 /* don't flip */);
532 case SCOREFILE_50_OPP
:
533 result
= readscores50(sd
, 1 /* do flip */);
560 * Paranoid write wrapper; unlike fwrite() it preserves errno.
563 dowrite(int sd
, const void *vbuf
, size_t len
)
565 const char *buf
= vbuf
;
570 result
= write(sd
, buf
+done
, len
-done
);
572 if (errno
== EINTR
) {
583 * Write the score file out.
588 struct highscore_header header
;
589 struct highscore_ondisk buf
[MAXHISCORES
];
596 memcpy(header
.hsh_magic
, hsh_magic_val
, HSH_MAGIC_SIZE
);
597 header
.hsh_endiantag
= HSH_ENDIAN_NATIVE
;
598 header
.hsh_version
= HSH_VERSION
;
600 for (i
=0; i
<nscores
; i
++) {
601 strncpy(buf
[i
].hso_name
, scores
[i
].hs_name
,
602 sizeof(buf
[i
].hso_name
));
603 buf
[i
].hso_score
= scores
[i
].hs_score
;
604 buf
[i
].hso_level
= scores
[i
].hs_level
;
605 buf
[i
].hso_pad
= 0xbaadf00d;
606 buf
[i
].hso_time
= scores
[i
].hs_time
;
609 if (lseek(sd
, 0, SEEK_SET
) < 0) {
610 warn("Score file %s: lseek", _PATH_SCOREFILE
);
613 if (dowrite(sd
, &header
, sizeof(header
)) < 0 ||
614 dowrite(sd
, buf
, sizeof(buf
[0]) * nscores
) < 0) {
615 warn("Score file %s: write", _PATH_SCOREFILE
);
620 warnx("high scores may be damaged");
624 * Close the score file.
634 * Read and update the scores file with the current reults.
639 struct highscore
*sp
;
650 * Allow at most one score per person per level -- see if we
651 * can replace an existing score, or (easiest) do nothing.
652 * Otherwise add new score at end (there is always room).
656 for (i
= 0, sp
= &scores
[0]; i
< nscores
; i
++, sp
++) {
657 if (sp
->hs_level
!= level
|| strcmp(sp
->hs_name
, me
) != 0)
659 if (score
> sp
->hs_score
) {
660 (void)printf("%s bettered %s %d score of %d!\n",
661 "\nYou", "your old level", level
,
662 sp
->hs_score
* sp
->hs_level
);
663 sp
->hs_score
= score
; /* new score */
664 sp
->hs_time
= now
; /* and time */
666 } else if (score
== sp
->hs_score
) {
667 (void)printf("%s tied %s %d high score.\n",
668 "\nYou", "your old level", level
);
669 sp
->hs_time
= now
; /* renew it */
670 change
= 1; /* gotta rewrite, sigh */
671 } /* else new score < old score: do nothing */
675 strcpy(sp
->hs_name
, me
);
676 sp
->hs_level
= level
;
677 sp
->hs_score
= score
;
685 * Sort & clean the scores, then rewrite.
687 nscores
= checkscores(scores
, nscores
);
694 * Get login name, or if that fails, get something suitable.
695 * The result is always trimmed to fit in a score.
703 static char u
[sizeof(scores
[0].hs_name
)];
708 if (p
== NULL
|| *p
== '\0') {
709 pw
= getpwuid(getuid());
724 * Score comparison function for qsort.
726 * If two scores are equal, the person who had the score first is
727 * listed first in the highscore file.
730 cmpscores(const void *x
, const void *y
)
732 const struct highscore
*a
, *b
;
737 l
= (long)b
->hs_level
* b
->hs_score
- (long)a
->hs_level
* a
->hs_score
;
742 if (a
->hs_time
< b
->hs_time
)
744 if (a
->hs_time
> b
->hs_time
)
750 * If we've added a score to the file, we need to check the file and ensure
751 * that this player has only a few entries. The number of entries is
752 * controlled by MAXSCORES, and is to ensure that the highscore file is not
753 * monopolised by just a few people. People who no longer have accounts are
754 * only allowed the highest score. Scores older than EXPIRATION seconds are
755 * removed, unless they are someone's personal best.
756 * Caveat: the highest score on each level is always kept.
759 checkscores(struct highscore
*hs
, int num
)
761 struct highscore
*sp
;
762 int i
, j
, k
, numnames
;
763 int levelfound
[NLEVELS
];
771 * Sort so that highest totals come first.
773 * levelfound[i] becomes set when the first high score for that
774 * level is encountered. By definition this is the highest score.
776 qsort((void *)hs
, nscores
, sizeof(*hs
), cmpscores
);
777 for (i
= MINLEVEL
; i
< NLEVELS
; i
++)
780 for (i
= 0, sp
= hs
; i
< num
;) {
782 * This is O(n^2), but do you think we care?
784 for (j
= 0, pu
= count
; j
< numnames
; j
++, pu
++)
785 if (strcmp(sp
->hs_name
, pu
->name
) == 0)
789 * Add new user, set per-user count to 1.
791 pu
->name
= sp
->hs_name
;
796 * Two ways to keep this score:
797 * - Not too many (per user), still has acct, &
798 * score not dated; or
799 * - High score on this level.
801 if ((pu
->times
< MAXSCORES
&&
802 getpwnam(sp
->hs_name
) != NULL
&&
803 sp
->hs_time
+ EXPIRATION
>= now
) ||
804 levelfound
[sp
->hs_level
] == 0)
808 * Delete this score, do not count it,
809 * do not pass go, do not collect $200.
812 for (k
= i
; k
< num
; k
++)
817 if (sp
->hs_level
< NLEVELS
&& sp
->hs_level
>= 0)
818 levelfound
[sp
->hs_level
] = 1;
821 return (num
> MAXHISCORES
? MAXHISCORES
: num
);
825 * Show current scores. This must be called after savescore, if
826 * savescore is called at all, for two reasons:
827 * - Showscores munches the time field.
828 * - Even if that were not the case, a new score must be recorded
829 * before it can be shown anyway.
832 showscores(int level
)
834 struct highscore
*sp
;
837 int levelfound
[NLEVELS
];
841 (void)printf("\n\t\t\t Tetris High Scores\n");
844 * If level == 0, the person has not played a game but just asked for
845 * the high scores; we do not need to check for printing in highlight
846 * mode. If SOstr is null, we can't do highlighting anyway.
848 me
= level
&& SOstr
? thisuser() : NULL
;
851 * Set times to 0 except for high score on each level.
853 for (i
= MINLEVEL
; i
< NLEVELS
; i
++)
855 for (i
= 0, sp
= scores
; i
< nscores
; i
++, sp
++) {
856 if (sp
->hs_level
< NLEVELS
&& sp
->hs_level
>= 0) {
857 if (levelfound
[sp
->hs_level
])
861 levelfound
[sp
->hs_level
] = 1;
867 * Page each screenful of scores.
869 for (i
= 0, sp
= scores
; i
< nscores
; sp
+= n
) {
873 printem(level
, i
+ 1, sp
, n
, me
);
874 if ((i
+= n
) < nscores
) {
875 (void)printf("\nHit RETURN to continue.");
876 (void)fflush(stdout
);
877 while ((c
= getchar()) != '\n')
886 printem(int level
, int offset
, struct highscore
*hs
, int n
, const char *me
)
888 struct highscore
*sp
;
889 int nrows
, row
, col
, item
, i
, highlight
;
891 #define TITLE "Rank Score Name (points/level)"
894 * This makes a nice two-column sort with headers, but it's a bit
897 printf("%s %s\n", TITLE
, n
> 1 ? TITLE
: "");
902 for (row
= 0; row
< nrows
; row
++) {
903 for (col
= 0; col
< 2; col
++) {
904 item
= col
* nrows
+ row
;
907 * Can only occur on trailing columns.
913 (void)snprintf(buf
, sizeof(buf
),
914 "%3d%c %6d %-11s (%6d on %d)",
915 item
+ offset
, sp
->hs_time
? '*' : ' ',
916 sp
->hs_score
* sp
->hs_level
,
917 sp
->hs_name
, sp
->hs_score
, sp
->hs_level
);
919 * Highlight if appropriate. This works because
920 * we only get one score per level.
923 sp
->hs_level
== level
&&
924 sp
->hs_score
== score
&&
925 strcmp(sp
->hs_name
, me
) == 0) {
929 (void)printf("%s", buf
);
935 /* fill in spaces so column 1 lines up */
937 for (i
= 40 - strlen(buf
); --i
>= 0;)