]> git.cameronkatri.com Git - bsdgames-darwin.git/blob - tetris/scores.c
Fix merge conflicts
[bsdgames-darwin.git] / tetris / scores.c
1 /* $NetBSD: scores.c,v 1.26 2021/05/02 12:50:46 rillig 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/stat.h>
52 #include <time.h>
53 #include <term.h>
54 #include <unistd.h>
55
56 #include "pathnames.h"
57 #include "screen.h"
58 #include "scores.h"
59 #include "tetris.h"
60
61 #include <libkern/OSByteOrder.h>
62
63 /*
64 * Allow updating the high scores unless we're built as part of /rescue.
65 */
66 #ifndef RESCUEDIR
67 #define ALLOW_SCORE_UPDATES
68 #endif
69
70 /*
71 * Within this code, we can hang onto one extra "high score", leaving
72 * room for our current score (whether or not it is high).
73 *
74 * We also sometimes keep tabs on the "highest" score on each level.
75 * As long as the scores are kept sorted, this is simply the first one at
76 * that level.
77 */
78 #define NUMSPOTS (MAXHISCORES + 1)
79 #define NLEVELS (MAXLEVEL + 1)
80
81 static time_t now;
82 static int nscores;
83 static int gotscores;
84 static struct highscore scores[NUMSPOTS];
85
86 static int checkscores(struct highscore *, int);
87 static int cmpscores(const void *, const void *);
88 static void getscores(int *);
89 static void printem(int, int, struct highscore *, int, const char *);
90 static char *thisuser(void);
91
92 /* contents chosen to be a highly illegal username */
93 static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://";
94
95 #define HSH_ENDIAN_NATIVE 0x12345678
96 #define HSH_ENDIAN_OPP 0x78563412
97
98 /* current file format version */
99 #define HSH_VERSION 1
100
101 /* codes for scorefile_probe return */
102 #define SCOREFILE_ERROR (-1)
103 #define SCOREFILE_CURRENT 0 /* 40-byte */
104 #define SCOREFILE_CURRENT_OPP 1 /* 40-byte, opposite-endian */
105 #define SCOREFILE_599 2 /* 36-byte */
106 #define SCOREFILE_599_OPP 3 /* 36-byte, opposite-endian */
107 #define SCOREFILE_50 4 /* 32-byte */
108 #define SCOREFILE_50_OPP 5 /* 32-byte, opposite-endian */
109
110 /*
111 * Check (or guess) what kind of score file contents we have.
112 */
113 static int
114 scorefile_probe(int sd)
115 {
116 struct stat st;
117 int t1, t2, t3, tx;
118 ssize_t result;
119 uint32_t numbers[3], offset56, offset60, offset64;
120
121 if (fstat(sd, &st) < 0) {
122 warn("Score file %s: fstat", _PATH_SCOREFILE);
123 return -1;
124 }
125
126 t1 = st.st_size % sizeof(struct highscore_ondisk) == 0;
127 t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0;
128 t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0;
129 tx = t1 + t2 + t3;
130 if (tx == 1) {
131 /* Size matches exact number of one kind of records */
132 if (t1) {
133 return SCOREFILE_CURRENT;
134 } else if (t2) {
135 return SCOREFILE_599;
136 } else {
137 return SCOREFILE_50;
138 }
139 } else if (tx == 0) {
140 /* Size matches nothing, pick most likely as default */
141 goto wildguess;
142 }
143
144 /*
145 * File size is multiple of more than one structure size.
146 * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
147 * Read the file and see if we can figure out what's going
148 * on. This is the layout of the first two records:
149 *
150 * offset hso / current hso_599 hso_50
151 * (40-byte) (36-byte) (32-byte)
152 *
153 * 0 name #0 name #0 name #0
154 * 4 : : :
155 * 8 : : :
156 * 12 : : :
157 * 16 : : :
158 * 20 score #0 score #0 score #0
159 * 24 level #0 level #0 level #0
160 * 28 (pad) time #0 time #0
161 * 32 time #0 name #1
162 * 36 name #1 :
163 * 40 name #1 : :
164 * 44 : : :
165 * 48 : : :
166 * 52 : : score #1
167 * 56 : score #1 level #1
168 * 60 score #1 level #1 time #1
169 * 64 level #1 time #1 name #2
170 * 68 (pad) : :
171 * 72 time #1 name #2 :
172 * 76 : : :
173 * 80 --- end ---
174 *
175 * There are a number of things we could check here, but the
176 * most effective test is based on the following restrictions:
177 *
178 * - The level must be between 1 and 9 (inclusive)
179 * - All times must be after 1985 and are before 2038,
180 * so the high word must be 0 and the low word may not be
181 * a small value.
182 * - Integer values of 0 or 1-9 cannot be the beginning of
183 * a login name string.
184 * - Values of 1-9 are probably not a score.
185 *
186 * So we read the three words at offsets 56, 60, and 64, and
187 * poke at the values to try to figure things...
188 */
189
190 if (lseek(sd, 56, SEEK_SET) < 0) {
191 warn("Score file %s: lseek", _PATH_SCOREFILE);
192 return -1;
193 }
194 result = read(sd, &numbers, sizeof(numbers));
195 if (result < 0) {
196 warn("Score file %s: read", _PATH_SCOREFILE);
197 return -1;
198 }
199 if ((size_t)result != sizeof(numbers)) {
200 /*
201 * The smallest file whose size divides by more than
202 * one of the sizes is substantially larger than 64,
203 * so this should *never* happen.
204 */
205 warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE);
206 return -1;
207 }
208
209 offset56 = numbers[0];
210 offset60 = numbers[1];
211 offset64 = numbers[2];
212
213 if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
214 /* 40-byte structure */
215 return SCOREFILE_CURRENT;
216 } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
217 /* 36-byte structure */
218 return SCOREFILE_599;
219 } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
220 /* 32-byte structure */
221 return SCOREFILE_50;
222 }
223
224 /* None was a valid level; try opposite endian */
225 offset64 = OSSwapInt32(offset64);
226 offset60 = OSSwapInt32(offset60);
227 offset56 = OSSwapInt32(offset56);
228
229 if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
230 /* 40-byte structure */
231 return SCOREFILE_CURRENT_OPP;
232 } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
233 /* 36-byte structure */
234 return SCOREFILE_599_OPP;
235 } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
236 /* 32-byte structure */
237 return SCOREFILE_50_OPP;
238 }
239
240 /* That didn't work either, dunno what's going on */
241 wildguess:
242 warnx("Score file %s is likely corrupt", _PATH_SCOREFILE);
243 if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
244 return SCOREFILE_CURRENT;
245 } else if (sizeof(time_t) == 8) {
246 return SCOREFILE_599;
247 } else {
248 return SCOREFILE_50;
249 }
250 }
251
252 /*
253 * Copy a string safely, making sure it's null-terminated.
254 */
255 static void
256 readname(char *to, size_t maxto, const char *from, size_t maxfrom)
257 {
258 size_t amt;
259
260 amt = maxto < maxfrom ? maxto : maxfrom;
261 memcpy(to, from, amt);
262 to[maxto-1] = '\0';
263 }
264
265 /*
266 * Copy integers, byte-swapping if desired.
267 */
268 static int32_t
269 read32(int32_t val, int doflip)
270 {
271 if (doflip) {
272 val = OSSwapInt32(val);
273 }
274 return val;
275 }
276
277 static int64_t
278 read64(int64_t val, int doflip)
279 {
280 if (doflip) {
281 val = OSSwapInt64(val);
282 }
283 return val;
284 }
285
286 /*
287 * Read up to MAXHISCORES scorefile_ondisk entries.
288 */
289 static int
290 readscores(int sd, int doflip)
291 {
292 struct highscore_ondisk buf[MAXHISCORES];
293 ssize_t result;
294 int i;
295
296 result = read(sd, buf, sizeof(buf));
297 if (result < 0) {
298 warn("Score file %s: read", _PATH_SCOREFILE);
299 return -1;
300 }
301 nscores = result / sizeof(buf[0]);
302
303 for (i=0; i<nscores; i++) {
304 readname(scores[i].hs_name, sizeof(scores[i].hs_name),
305 buf[i].hso_name, sizeof(buf[i].hso_name));
306 scores[i].hs_score = read32(buf[i].hso_score, doflip);
307 scores[i].hs_level = read32(buf[i].hso_level, doflip);
308 scores[i].hs_time = read64(buf[i].hso_time, doflip);
309 }
310 return 0;
311 }
312
313 /*
314 * Read up to MAXHISCORES scorefile_ondisk_599 entries.
315 */
316 static int
317 readscores599(int sd, int doflip)
318 {
319 struct highscore_ondisk_599 buf[MAXHISCORES];
320 ssize_t result;
321 int i;
322
323 result = read(sd, buf, sizeof(buf));
324 if (result < 0) {
325 warn("Score file %s: read", _PATH_SCOREFILE);
326 return -1;
327 }
328 nscores = result / sizeof(buf[0]);
329
330 for (i=0; i<nscores; i++) {
331 readname(scores[i].hs_name, sizeof(scores[i].hs_name),
332 buf[i].hso599_name, sizeof(buf[i].hso599_name));
333 scores[i].hs_score = read32(buf[i].hso599_score, doflip);
334 scores[i].hs_level = read32(buf[i].hso599_level, doflip);
335 /*
336 * Don't bother pasting the time together into a
337 * 64-bit value; just take whichever half is nonzero.
338 */
339 scores[i].hs_time =
340 read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0],
341 doflip);
342 }
343 return 0;
344 }
345
346 /*
347 * Read up to MAXHISCORES scorefile_ondisk_50 entries.
348 */
349 static int
350 readscores50(int sd, int doflip)
351 {
352 struct highscore_ondisk_50 buf[MAXHISCORES];
353 ssize_t result;
354 int i;
355
356 result = read(sd, buf, sizeof(buf));
357 if (result < 0) {
358 warn("Score file %s: read", _PATH_SCOREFILE);
359 return -1;
360 }
361 nscores = result / sizeof(buf[0]);
362
363 for (i=0; i<nscores; i++) {
364 readname(scores[i].hs_name, sizeof(scores[i].hs_name),
365 buf[i].hso50_name, sizeof(buf[i].hso50_name));
366 scores[i].hs_score = read32(buf[i].hso50_score, doflip);
367 scores[i].hs_level = read32(buf[i].hso50_level, doflip);
368 scores[i].hs_time = read32(buf[i].hso50_time, doflip);
369 }
370 return 0;
371 }
372
373 /*
374 * Read the score file. Can be called from savescore (before showscores)
375 * or showscores (if savescore will not be called). If the given pointer
376 * is not NULL, sets *fdp to an open file handle that corresponds to a
377 * read/write score file that is locked with LOCK_EX. Otherwise, the
378 * file is locked with LOCK_SH for the read and closed before return.
379 */
380 static void
381 getscores(int *fdp)
382 {
383 struct highscore_header header;
384 int sd, mint, lck;
385 mode_t mask;
386 const char *human;
387 int doflip;
388 int serrno;
389 ssize_t result;
390
391 #ifdef ALLOW_SCORE_UPDATES
392 if (fdp != NULL) {
393 mint = O_RDWR | O_CREAT;
394 human = "read/write";
395 lck = LOCK_EX;
396 } else
397 #endif
398 {
399 mint = O_RDONLY;
400 human = "reading";
401 lck = LOCK_SH;
402 }
403 setegid(egid);
404 mask = umask(S_IWOTH);
405 sd = open(_PATH_SCOREFILE, mint, 0666);
406 serrno = errno;
407 (void)umask(mask);
408 setegid(gid);
409 if (sd < 0) {
410 /*
411 * If the file simply isn't there because nobody's
412 * played yet, and we aren't going to be trying to
413 * update it, don't warn. Even if we are going to be
414 * trying to write it, don't fail -- we can still show
415 * the player the score they got.
416 */
417 errno = serrno;
418 if (fdp != NULL || errno != ENOENT) {
419 warn("Cannot open %s for %s", _PATH_SCOREFILE, human);
420 }
421 goto fail;
422 }
423
424 /*
425 * Grab a lock.
426 * XXX: failure here should probably be more fatal than this.
427 */
428 if (flock(sd, lck))
429 warn("warning: score file %s cannot be locked",
430 _PATH_SCOREFILE);
431
432 /*
433 * The current format (since -current of 20090525) is
434 *
435 * struct highscore_header
436 * up to MAXHIGHSCORES x struct highscore_ondisk
437 *
438 * Before this, there is no header, and the contents
439 * might be any of three formats:
440 *
441 * highscore_ondisk (64-bit machines with 64-bit time_t)
442 * highscore_ondisk_599 (32-bit machines with 64-bit time_t)
443 * highscore_ondisk_50 (32-bit machines with 32-bit time_t)
444 *
445 * The first two appear in 5.99 between the time_t change and
446 * 20090525, depending on whether the compiler inserts
447 * structure padding before an unaligned 64-bit time_t. The
448 * last appears in 5.0 and earlier.
449 *
450 * Any or all of these might also appear on other OSes where
451 * this code has been ported.
452 *
453 * Since the old file has no header, we will have to guess
454 * which of these formats it has.
455 */
456
457 /*
458 * First, look for a header.
459 */
460 result = read(sd, &header, sizeof(header));
461 if (result < 0) {
462 warn("Score file %s: read", _PATH_SCOREFILE);
463 goto sdfail;
464 }
465 if (result != 0 && (size_t)result != sizeof(header)) {
466 warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE);
467 /*
468 * File is hopelessly corrupt, might as well truncate it
469 * and start over with empty scores.
470 */
471 if (lseek(sd, 0, SEEK_SET) < 0) {
472 /* ? */
473 warn("Score file %s: lseek", _PATH_SCOREFILE);
474 goto sdfail;
475 }
476 if (ftruncate(sd, 0) == 0) {
477 result = 0;
478 } else {
479 goto sdfail;
480 }
481 }
482
483 if (result == 0) {
484 /* Empty file; that just means there are no scores. */
485 nscores = 0;
486 } else {
487 /*
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
491 * hs_name[].
492 */
493 if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) {
494 /* Yes, we have a header. */
495
496 if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) {
497 /* native endian */
498 doflip = 0;
499 } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) {
500 doflip = 1;
501 } else {
502 warnx("Score file %s: Unknown endian tag %u",
503 _PATH_SCOREFILE, header.hsh_endiantag);
504 goto sdfail;
505 }
506
507 if (header.hsh_version != HSH_VERSION) {
508 warnx("Score file %s: Unknown version code %u",
509 _PATH_SCOREFILE, header.hsh_version);
510 goto sdfail;
511 }
512
513 if (readscores(sd, doflip) < 0) {
514 goto sdfail;
515 }
516 } else {
517 /*
518 * Ok, it wasn't a header. Try to figure out what
519 * size records we have.
520 */
521 result = scorefile_probe(sd);
522 if (lseek(sd, 0, SEEK_SET) < 0) {
523 warn("Score file %s: lseek", _PATH_SCOREFILE);
524 goto sdfail;
525 }
526 switch (result) {
527 case SCOREFILE_CURRENT:
528 result = readscores(sd, 0 /* don't flip */);
529 break;
530 case SCOREFILE_CURRENT_OPP:
531 result = readscores(sd, 1 /* do flip */);
532 break;
533 case SCOREFILE_599:
534 result = readscores599(sd, 0 /* don't flip */);
535 break;
536 case SCOREFILE_599_OPP:
537 result = readscores599(sd, 1 /* do flip */);
538 break;
539 case SCOREFILE_50:
540 result = readscores50(sd, 0 /* don't flip */);
541 break;
542 case SCOREFILE_50_OPP:
543 result = readscores50(sd, 1 /* do flip */);
544 break;
545 default:
546 goto sdfail;
547 }
548 if (result < 0) {
549 goto sdfail;
550 }
551 }
552 }
553
554
555 if (fdp)
556 *fdp = sd;
557 else
558 close(sd);
559
560 return;
561
562 sdfail:
563 close(sd);
564 fail:
565 if (fdp != NULL) {
566 *fdp = -1;
567 }
568 nscores = 0;
569 }
570
571 #ifdef ALLOW_SCORE_UPDATES
572 /*
573 * Paranoid write wrapper; unlike fwrite() it preserves errno.
574 */
575 static int
576 dowrite(int sd, const void *vbuf, size_t len)
577 {
578 const char *buf = vbuf;
579 ssize_t result;
580 size_t done = 0;
581
582 while (done < len) {
583 result = write(sd, buf+done, len-done);
584 if (result < 0) {
585 if (errno == EINTR) {
586 continue;
587 }
588 return -1;
589 }
590 done += result;
591 }
592 return 0;
593 }
594 #endif /* ALLOW_SCORE_UPDATES */
595
596 /*
597 * Write the score file out.
598 */
599 static void
600 putscores(int sd)
601 {
602 #ifdef ALLOW_SCORE_UPDATES
603 struct highscore_header header;
604 struct highscore_ondisk buf[MAXHISCORES] = {0};
605 int i;
606
607 if (sd == -1) {
608 return;
609 }
610
611 memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE);
612 header.hsh_endiantag = HSH_ENDIAN_NATIVE;
613 header.hsh_version = HSH_VERSION;
614
615 for (i=0; i<nscores; i++) {
616 memcpy(buf[i].hso_name, scores[i].hs_name,
617 sizeof(buf[i].hso_name));
618 buf[i].hso_score = scores[i].hs_score;
619 buf[i].hso_level = scores[i].hs_level;
620 buf[i].hso_pad = 0xbaadf00d;
621 buf[i].hso_time = scores[i].hs_time;
622 }
623
624 if (lseek(sd, 0, SEEK_SET) < 0) {
625 warn("Score file %s: lseek", _PATH_SCOREFILE);
626 goto fail;
627 }
628 if (dowrite(sd, &header, sizeof(header)) < 0 ||
629 dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) {
630 warn("Score file %s: write", _PATH_SCOREFILE);
631 goto fail;
632 }
633 return;
634 fail:
635 warnx("high scores may be damaged");
636 #else
637 (void)sd;
638 #endif /* ALLOW_SCORE_UPDATES */
639 }
640
641 /*
642 * Close the score file.
643 */
644 static void
645 closescores(int sd)
646 {
647 flock(sd, LOCK_UN);
648 close(sd);
649 }
650
651 /*
652 * Read and update the scores file with the current reults.
653 */
654 void
655 savescore(int level)
656 {
657 struct highscore *sp;
658 int i;
659 int change;
660 int sd;
661 const char *me;
662
663 getscores(&sd);
664 gotscores = 1;
665 (void)time(&now);
666
667 /*
668 * Allow at most one score per person per level -- see if we
669 * can replace an existing score, or (easiest) do nothing.
670 * Otherwise add new score at end (there is always room).
671 */
672 change = 0;
673 me = thisuser();
674 for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) {
675 if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0)
676 continue;
677 if (score > sp->hs_score) {
678 (void)printf("%s bettered %s %d score of %d!\n",
679 "\nYou", "your old level", level,
680 sp->hs_score * sp->hs_level);
681 sp->hs_score = score; /* new score */
682 sp->hs_time = now; /* and time */
683 change = 1;
684 } else if (score == sp->hs_score) {
685 (void)printf("%s tied %s %d high score.\n",
686 "\nYou", "your old level", level);
687 sp->hs_time = now; /* renew it */
688 change = 1; /* gotta rewrite, sigh */
689 } /* else new score < old score: do nothing */
690 break;
691 }
692 if (i >= nscores) {
693 strcpy(sp->hs_name, me);
694 sp->hs_level = level;
695 sp->hs_score = score;
696 sp->hs_time = now;
697 nscores++;
698 change = 1;
699 }
700
701 if (change) {
702 /*
703 * Sort & clean the scores, then rewrite.
704 */
705 nscores = checkscores(scores, nscores);
706 putscores(sd);
707 }
708 closescores(sd);
709 }
710
711 /*
712 * Get login name, or if that fails, get something suitable.
713 * The result is always trimmed to fit in a score.
714 */
715 static char *
716 thisuser(void)
717 {
718 const char *p;
719 struct passwd *pw;
720 size_t l;
721 static char u[sizeof(scores[0].hs_name)];
722
723 if (u[0])
724 return (u);
725 p = getlogin();
726 if (p == NULL || *p == '\0') {
727 pw = getpwuid(getuid());
728 if (pw != NULL)
729 p = pw->pw_name;
730 else
731 p = " ???";
732 }
733 l = strlen(p);
734 if (l >= sizeof(u))
735 l = sizeof(u) - 1;
736 memcpy(u, p, l);
737 u[l] = '\0';
738 return (u);
739 }
740
741 /*
742 * Score comparison function for qsort.
743 *
744 * If two scores are equal, the person who had the score first is
745 * listed first in the highscore file.
746 */
747 static int
748 cmpscores(const void *x, const void *y)
749 {
750 const struct highscore *a, *b;
751 long l;
752
753 a = x;
754 b = y;
755 l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score;
756 if (l < 0)
757 return (-1);
758 if (l > 0)
759 return (1);
760 if (a->hs_time < b->hs_time)
761 return (-1);
762 if (a->hs_time > b->hs_time)
763 return (1);
764 return (0);
765 }
766
767 /*
768 * If we've added a score to the file, we need to check the file and ensure
769 * that this player has only a few entries. The number of entries is
770 * controlled by MAXSCORES, and is to ensure that the highscore file is not
771 * monopolised by just a few people. People who no longer have accounts are
772 * only allowed the highest score. Scores older than EXPIRATION seconds are
773 * removed, unless they are someone's personal best.
774 * Caveat: the highest score on each level is always kept.
775 */
776 static int
777 checkscores(struct highscore *hs, int num)
778 {
779 struct highscore *sp;
780 int i, j, k, numnames;
781 int levelfound[NLEVELS];
782 struct peruser {
783 char *name;
784 int times;
785 } count[NUMSPOTS];
786 struct peruser *pu;
787
788 /*
789 * Sort so that highest totals come first.
790 *
791 * levelfound[i] becomes set when the first high score for that
792 * level is encountered. By definition this is the highest score.
793 */
794 qsort((void *)hs, nscores, sizeof(*hs), cmpscores);
795 for (i = MINLEVEL; i < NLEVELS; i++)
796 levelfound[i] = 0;
797 numnames = 0;
798 for (i = 0, sp = hs; i < num;) {
799 /*
800 * This is O(n^2), but do you think we care?
801 */
802 for (j = 0, pu = count; j < numnames; j++, pu++)
803 if (strcmp(sp->hs_name, pu->name) == 0)
804 break;
805 if (j == numnames) {
806 /*
807 * Add new user, set per-user count to 1.
808 */
809 pu->name = sp->hs_name;
810 pu->times = 1;
811 numnames++;
812 } else {
813 /*
814 * Two ways to keep this score:
815 * - Not too many (per user), still has acct, &
816 * score not dated; or
817 * - High score on this level.
818 */
819 if ((pu->times < MAXSCORES &&
820 getpwnam(sp->hs_name) != NULL &&
821 sp->hs_time + EXPIRATION >= now) ||
822 levelfound[sp->hs_level] == 0)
823 pu->times++;
824 else {
825 /*
826 * Delete this score, do not count it,
827 * do not pass go, do not collect $200.
828 */
829 num--;
830 for (k = i; k < num; k++)
831 hs[k] = hs[k + 1];
832 continue;
833 }
834 }
835 if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
836 levelfound[sp->hs_level] = 1;
837 i++, sp++;
838 }
839 return (num > MAXHISCORES ? MAXHISCORES : num);
840 }
841
842 /*
843 * Show current scores. This must be called after savescore, if
844 * savescore is called at all, for two reasons:
845 * - Showscores munches the time field.
846 * - Even if that were not the case, a new score must be recorded
847 * before it can be shown anyway.
848 */
849 void
850 showscores(int level)
851 {
852 struct highscore *sp;
853 int i, n, c;
854 const char *me;
855 int levelfound[NLEVELS];
856
857 if (!gotscores)
858 getscores(NULL);
859 (void)printf("\n\t\t\t Tetris High Scores\n");
860
861 /*
862 * If level == 0, the person has not played a game but just asked for
863 * the high scores; we do not need to check for printing in highlight
864 * mode. If SOstr is null, we can't do highlighting anyway.
865 */
866 me = level && enter_standout_mode ? thisuser() : NULL;
867
868 /*
869 * Set times to 0 except for high score on each level.
870 */
871 for (i = MINLEVEL; i < NLEVELS; i++)
872 levelfound[i] = 0;
873 for (i = 0, sp = scores; i < nscores; i++, sp++) {
874 if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
875 if (levelfound[sp->hs_level])
876 sp->hs_time = 0;
877 else {
878 sp->hs_time = 1;
879 levelfound[sp->hs_level] = 1;
880 }
881 }
882 }
883
884 /*
885 * Page each screenful of scores.
886 */
887 for (i = 0, sp = scores; i < nscores; sp += n) {
888 n = 40;
889 if (i + n > nscores)
890 n = nscores - i;
891 printem(level, i + 1, sp, n, me);
892 if ((i += n) < nscores) {
893 (void)printf("\nHit RETURN to continue.");
894 (void)fflush(stdout);
895 while ((c = getchar()) != '\n')
896 if (c == EOF)
897 break;
898 (void)printf("\n");
899 }
900 }
901 }
902
903 static void
904 printem(int level, int offset, struct highscore *hs, int n, const char *me)
905 {
906 struct highscore *sp;
907 int nrows, row, col, item, i, highlight;
908 char buf[100];
909 #define TITLE "Rank Score Name (points/level)"
910
911 /*
912 * This makes a nice two-column sort with headers, but it's a bit
913 * convoluted...
914 */
915 printf("%s %s\n", TITLE, n > 1 ? TITLE : "");
916
917 highlight = 0;
918 nrows = (n + 1) / 2;
919
920 for (row = 0; row < nrows; row++) {
921 for (col = 0; col < 2; col++) {
922 item = col * nrows + row;
923 if (item >= n) {
924 /*
925 * Can only occur on trailing columns.
926 */
927 (void)putchar('\n');
928 continue;
929 }
930 sp = &hs[item];
931 (void)snprintf(buf, sizeof(buf),
932 "%3d%c %6d %-11s (%6d on %d)",
933 item + offset, sp->hs_time ? '*' : ' ',
934 sp->hs_score * sp->hs_level,
935 sp->hs_name, sp->hs_score, sp->hs_level);
936 /*
937 * Highlight if appropriate. This works because
938 * we only get one score per level.
939 */
940 if (me != NULL &&
941 sp->hs_level == level &&
942 sp->hs_score == score &&
943 strcmp(sp->hs_name, me) == 0) {
944 putpad(enter_standout_mode);
945 highlight = 1;
946 }
947 (void)printf("%s", buf);
948 if (highlight) {
949 putpad(exit_standout_mode);
950 highlight = 0;
951 }
952
953 /* fill in spaces so column 1 lines up */
954 if (col == 0)
955 for (i = 40 - strlen(buf); --i >= 0;)
956 (void)putchar(' ');
957 else /* col == 1 */
958 (void)putchar('\n');
959 }
960 }
961 }