1 /* $OpenBSD: scores.c,v 1.25 2019/06/28 13:32:52 deraadt Exp $ */ 2 /* $NetBSD: scores.c,v 1.2 1995/04/22 07:42:38 cgd Exp $ */ 3 4 /*- 5 * Copyright (c) 1992, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Chris Torek and Darren F. Provine. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * @(#)scores.c 8.1 (Berkeley) 5/31/93 36 */ 37 38 /* 39 * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu) 40 * modified 22 January 1992, to limit the number of entries any one 41 * person has. 42 * 43 * Major whacks since then. 44 */ 45 #include <err.h> 46 #include <errno.h> 47 #include <fcntl.h> 48 #include <limits.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <term.h> 53 #include <time.h> 54 #include <unistd.h> 55 56 #include "scores.h" 57 #include "screen.h" 58 #include "tetris.h" 59 60 /* 61 * Within this code, we can hang onto one extra "high score", leaving 62 * room for our current score (whether or not it is high). 63 * 64 * We also sometimes keep tabs on the "highest" score on each level. 65 * As long as the scores are kept sorted, this is simply the first one at 66 * that level. 67 */ 68 #define NUMSPOTS (MAXHISCORES + 1) 69 #define NLEVELS (MAXLEVEL + 1) 70 71 static time_t now; 72 static int nscores; 73 static int gotscores; 74 static struct highscore scores[NUMSPOTS]; 75 76 static int checkscores(struct highscore *, int); 77 static int cmpscores(const void *, const void *); 78 static void getscores(FILE **); 79 static void printem(int, int, struct highscore *, int, const char *); 80 static char *thisuser(void); 81 82 /* 83 * Read the score file. Can be called from savescore (before showscores) 84 * or showscores (if savescore will not be called). If the given pointer 85 * is not NULL, sets *fpp to an open file pointer that corresponds to a 86 * read/write score file that is locked with LOCK_EX. Otherwise, the 87 * file is locked with LOCK_SH for the read and closed before return. 88 * 89 * Note, we assume closing the stdio file releases the lock. 90 */ 91 static void 92 getscores(FILE **fpp) 93 { 94 int sd, mint, i; 95 char *mstr, *human; 96 FILE *sf; 97 98 if (fpp != NULL) { 99 mint = O_RDWR | O_CREAT; 100 mstr = "r+"; 101 human = "read/write"; 102 *fpp = NULL; 103 } else { 104 mint = O_RDONLY; 105 mstr = "r"; 106 human = "reading"; 107 } 108 109 sd = open(scorepath, mint, 0666); 110 if (sd == -1) { 111 if (fpp == NULL) { 112 nscores = 0; 113 return; 114 } 115 err(1, "cannot open %s for %s", scorepath, human); 116 } 117 if ((sf = fdopen(sd, mstr)) == NULL) 118 err(1, "cannot fdopen %s for %s", scorepath, human); 119 120 nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf); 121 if (ferror(sf)) 122 err(1, "error reading %s", scorepath); 123 for (i = 0; i < nscores; i++) 124 if (scores[i].hs_level < MINLEVEL || 125 scores[i].hs_level > MAXLEVEL) 126 errx(1, "scorefile %s corrupt", scorepath); 127 128 if (fpp) 129 *fpp = sf; 130 else 131 (void)fclose(sf); 132 } 133 134 void 135 savescore(int level) 136 { 137 struct highscore *sp; 138 int i; 139 int change; 140 FILE *sf; 141 const char *me; 142 143 getscores(&sf); 144 gotscores = 1; 145 (void)time(&now); 146 147 /* 148 * Allow at most one score per person per level -- see if we 149 * can replace an existing score, or (easiest) do nothing. 150 * Otherwise add new score at end (there is always room). 151 */ 152 change = 0; 153 me = thisuser(); 154 for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { 155 if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) 156 continue; 157 if (score > sp->hs_score) { 158 (void)printf("%s bettered %s %d score of %d!\n", 159 "\nYou", "your old level", level, 160 sp->hs_score * sp->hs_level); 161 sp->hs_score = score; /* new score */ 162 sp->hs_time = now; /* and time */ 163 change = 1; 164 } else if (score == sp->hs_score) { 165 (void)printf("%s tied %s %d high score.\n", 166 "\nYou", "your old level", level); 167 sp->hs_time = now; /* renew it */ 168 change = 1; /* gotta rewrite, sigh */ 169 } /* else new score < old score: do nothing */ 170 break; 171 } 172 if (i >= nscores) { 173 strlcpy(sp->hs_name, me, sizeof sp->hs_name); 174 sp->hs_level = level; 175 sp->hs_score = score; 176 sp->hs_time = now; 177 nscores++; 178 change = 1; 179 } 180 181 if (change) { 182 /* 183 * Sort & clean the scores, then rewrite. 184 */ 185 nscores = checkscores(scores, nscores); 186 if (fseek(sf, 0L, SEEK_SET) == -1) 187 err(1, "fseek"); 188 if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores || 189 fflush(sf) == EOF) 190 warnx("error writing scorefile: %s\n\t-- %s", 191 strerror(errno), 192 "high scores may be damaged"); 193 } 194 (void)fclose(sf); /* releases lock */ 195 } 196 197 /* 198 * Get login name, or if that fails, get something suitable. 199 * The result is always trimmed to fit in a score. 200 */ 201 static char * 202 thisuser(void) 203 { 204 const char *p; 205 static char u[sizeof(scores[0].hs_name)]; 206 207 if (u[0]) 208 return (u); 209 p = getenv("LOGNAME"); 210 if (p == NULL || *p == '\0') 211 p = getenv("USER"); 212 if (p == NULL || *p == '\0') 213 p = getlogin(); 214 if (p == NULL || *p == '\0') 215 p = " ???"; 216 strlcpy(u, p, sizeof(u)); 217 return (u); 218 } 219 220 /* 221 * Score comparison function for qsort. 222 * 223 * If two scores are equal, the person who had the score first is 224 * listed first in the highscore file. 225 */ 226 static int 227 cmpscores(const void *x, const void *y) 228 { 229 const struct highscore *a, *b; 230 long l; 231 232 a = x; 233 b = y; 234 l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; 235 if (l < 0) 236 return (-1); 237 if (l > 0) 238 return (1); 239 if (a->hs_time < b->hs_time) 240 return (-1); 241 if (a->hs_time > b->hs_time) 242 return (1); 243 return (0); 244 } 245 246 /* 247 * If we've added a score to the file, we need to check the file and ensure 248 * that this player has only a few entries. The number of entries is 249 * controlled by MAXSCORES, and is to ensure that the highscore file is not 250 * monopolised by just a few people. People who no longer have accounts are 251 * only allowed the highest score. Scores older than EXPIRATION seconds are 252 * removed, unless they are someone's personal best. 253 * Caveat: the highest score on each level is always kept. 254 */ 255 static int 256 checkscores(struct highscore *hs, int num) 257 { 258 struct highscore *sp; 259 int i, j, k, nrnames; 260 int levelfound[NLEVELS]; 261 struct peruser { 262 char *name; 263 int times; 264 } count[NUMSPOTS]; 265 struct peruser *pu; 266 267 /* 268 * Sort so that highest totals come first. 269 * 270 * levelfound[i] becomes set when the first high score for that 271 * level is encountered. By definition this is the highest score. 272 */ 273 qsort((void *)hs, nscores, sizeof(*hs), cmpscores); 274 for (i = MINLEVEL; i < NLEVELS; i++) 275 levelfound[i] = 0; 276 nrnames = 0; 277 for (i = 0, sp = hs; i < num;) { 278 /* 279 * This is O(n^2), but do you think we care? 280 */ 281 for (j = 0, pu = count; j < nrnames; j++, pu++) 282 if (strcmp(sp->hs_name, pu->name) == 0) 283 break; 284 if (j == nrnames) { 285 /* 286 * Add new user, set per-user count to 1. 287 */ 288 pu->name = sp->hs_name; 289 pu->times = 1; 290 nrnames++; 291 } else { 292 /* 293 * Two ways to keep this score: 294 * - Not too many (per user), still has acct, & 295 * score not dated; or 296 * - High score on this level. 297 */ 298 if ((pu->times < MAXSCORES && 299 sp->hs_time + EXPIRATION >= now) || 300 levelfound[sp->hs_level] == 0) 301 pu->times++; 302 else { 303 /* 304 * Delete this score, do not count it, 305 * do not pass go, do not collect $200. 306 */ 307 num--; 308 for (k = i; k < num; k++) 309 hs[k] = hs[k + 1]; 310 continue; 311 } 312 } 313 levelfound[sp->hs_level] = 1; 314 i++, sp++; 315 } 316 return (num > MAXHISCORES ? MAXHISCORES : num); 317 } 318 319 /* 320 * Show current scores. This must be called after savescore, if 321 * savescore is called at all, for two reasons: 322 * - Showscores munches the time field. 323 * - Even if that were not the case, a new score must be recorded 324 * before it can be shown anyway. 325 */ 326 void 327 showscores(int level) 328 { 329 struct highscore *sp; 330 int i, n, c; 331 const char *me; 332 int levelfound[NLEVELS]; 333 334 if (!gotscores) 335 getscores((FILE **)NULL); 336 (void)printf("\n\t\t Tetris High Scores\n"); 337 338 /* 339 * If level == 0, the person has not played a game but just asked for 340 * the high scores; we do not need to check for printing in highlight 341 * mode. If SOstr is null, we can't do highlighting anyway. 342 */ 343 me = level && SOstr ? thisuser() : NULL; 344 345 /* 346 * Set times to 0 except for high score on each level. 347 */ 348 for (i = MINLEVEL; i < NLEVELS; i++) 349 levelfound[i] = 0; 350 for (i = 0, sp = scores; i < nscores; i++, sp++) { 351 if (levelfound[sp->hs_level]) 352 sp->hs_time = 0; 353 else { 354 sp->hs_time = 1; 355 levelfound[sp->hs_level] = 1; 356 } 357 } 358 359 /* 360 * Page each screenful of scores. 361 */ 362 for (i = 0, sp = scores; i < nscores; sp += n) { 363 n = 20; 364 if (i + n > nscores) 365 n = nscores - i; 366 printem(level, i + 1, sp, n, me); 367 if ((i += n) < nscores) { 368 (void)printf("\nHit RETURN to continue."); 369 (void)fflush(stdout); 370 while ((c = getchar()) != '\n') 371 if (c == EOF) 372 break; 373 (void)printf("\n"); 374 } 375 } 376 377 if (nscores == 0) 378 printf("\t\t\t - none to date.\n"); 379 } 380 381 static void 382 printem(int level, int offset, struct highscore *hs, int n, const char *me) 383 { 384 struct highscore *sp; 385 int row, highlight, i; 386 char buf[100]; 387 #define TITLE "Rank Score Name (points/level)" 388 #define TITL2 "==========================================================" 389 390 printf("%s\n%s\n", TITLE, TITL2); 391 392 highlight = 0; 393 394 for (row = 0; row < n; row++) { 395 sp = &hs[row]; 396 (void)snprintf(buf, sizeof(buf), 397 "%3d%c %6d %-31s (%6d on %d)\n", 398 row + offset, sp->hs_time ? '*' : ' ', 399 sp->hs_score * sp->hs_level, 400 sp->hs_name, sp->hs_score, sp->hs_level); 401 /* Print leaders every three lines */ 402 if ((row + 1) % 3 == 0) { 403 for (i = 0; i < sizeof(buf); i++) 404 if (buf[i] == ' ') 405 buf[i] = '_'; 406 } 407 /* 408 * Highlight if appropriate. This works because 409 * we only get one score per level. 410 */ 411 if (me != NULL && 412 sp->hs_level == level && 413 sp->hs_score == score && 414 strcmp(sp->hs_name, me) == 0) { 415 putpad(SOstr); 416 highlight = 1; 417 } 418 (void)printf("%s", buf); 419 if (highlight) { 420 putpad(SEstr); 421 highlight = 0; 422 } 423 } 424 } 425