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