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