From 013ac7742311556022304e8b30ca170d48b3a016 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Thu, 7 May 2015 16:32:32 -0400 Subject: initial commit --- tetris/scores.c | 960 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 960 insertions(+) create mode 100644 tetris/scores.c (limited to 'tetris/scores.c') diff --git a/tetris/scores.c b/tetris/scores.c new file mode 100644 index 0000000..eca0ce3 --- /dev/null +++ b/tetris/scores.c @@ -0,0 +1,960 @@ +/* $NetBSD: scores.c,v 1.22 2014/03/22 19:05:30 dholland Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek and Darren F. Provine. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)scores.c 8.1 (Berkeley) 5/31/93 + */ + +/* + * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu) + * modified 22 January 1992, to limit the number of entries any one + * person has. + * + * Major whacks since then. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include /* 20150211 bkw: for flock() */ +#include +#include +#include + +#include "pathnames.h" +#include "screen.h" +#include "scores.h" +#include "tetris.h" + +/* + * Allow updating the high scores unless we're built as part of /rescue. + */ +#ifndef RESCUEDIR +#define ALLOW_SCORE_UPDATES +#endif + +/* + * Within this code, we can hang onto one extra "high score", leaving + * room for our current score (whether or not it is high). + * + * We also sometimes keep tabs on the "highest" score on each level. + * As long as the scores are kept sorted, this is simply the first one at + * that level. + */ +#define NUMSPOTS (MAXHISCORES + 1) +#define NLEVELS (MAXLEVEL + 1) + +static time_t now; +static int nscores; +static int gotscores; +static struct highscore scores[NUMSPOTS]; + +static int checkscores(struct highscore *, int); +static int cmpscores(const void *, const void *); +static void getscores(int *); +static void printem(int, int, struct highscore *, int, const char *); +static char *thisuser(void); + +/* contents chosen to be a highly illegal username */ +static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://"; + +#define HSH_ENDIAN_NATIVE 0x12345678 +#define HSH_ENDIAN_OPP 0x78563412 + +/* current file format version */ +#define HSH_VERSION 1 + +/* codes for scorefile_probe return */ +#define SCOREFILE_ERROR (-1) +#define SCOREFILE_CURRENT 0 /* 40-byte */ +#define SCOREFILE_CURRENT_OPP 1 /* 40-byte, opposite-endian */ +#define SCOREFILE_599 2 /* 36-byte */ +#define SCOREFILE_599_OPP 3 /* 36-byte, opposite-endian */ +#define SCOREFILE_50 4 /* 32-byte */ +#define SCOREFILE_50_OPP 5 /* 32-byte, opposite-endian */ + +/* + * Check (or guess) what kind of score file contents we have. + */ +static int +scorefile_probe(int sd) +{ + struct stat st; + int t1, t2, t3, tx; + ssize_t result; + uint32_t numbers[3], offset56, offset60, offset64; + + if (fstat(sd, &st) < 0) { + warn("Score file %s: fstat", _PATH_SCOREFILE); + return -1; + } + + t1 = st.st_size % sizeof(struct highscore_ondisk) == 0; + t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0; + t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0; + tx = t1 + t2 + t3; + if (tx == 1) { + /* Size matches exact number of one kind of records */ + if (t1) { + return SCOREFILE_CURRENT; + } else if (t2) { + return SCOREFILE_599; + } else { + return SCOREFILE_50; + } + } else if (tx == 0) { + /* Size matches nothing, pick most likely as default */ + goto wildguess; + } + + /* + * File size is multiple of more than one structure size. + * (For example, 288 bytes could be 9*hso50 or 8*hso599.) + * Read the file and see if we can figure out what's going + * on. This is the layout of the first two records: + * + * offset hso / current hso_599 hso_50 + * (40-byte) (36-byte) (32-byte) + * + * 0 name #0 name #0 name #0 + * 4 : : : + * 8 : : : + * 12 : : : + * 16 : : : + * 20 score #0 score #0 score #0 + * 24 level #0 level #0 level #0 + * 28 (pad) time #0 time #0 + * 32 time #0 name #1 + * 36 name #1 : + * 40 name #1 : : + * 44 : : : + * 48 : : : + * 52 : : score #1 + * 56 : score #1 level #1 + * 60 score #1 level #1 time #1 + * 64 level #1 time #1 name #2 + * 68 (pad) : : + * 72 time #1 name #2 : + * 76 : : : + * 80 --- end --- + * + * There are a number of things we could check here, but the + * most effective test is based on the following restrictions: + * + * - The level must be between 1 and 9 (inclusive) + * - All times must be after 1985 and are before 2038, + * so the high word must be 0 and the low word may not be + * a small value. + * - Integer values of 0 or 1-9 cannot be the beginning of + * a login name string. + * - Values of 1-9 are probably not a score. + * + * So we read the three words at offsets 56, 60, and 64, and + * poke at the values to try to figure things... + */ + + if (lseek(sd, 56, SEEK_SET) < 0) { + warn("Score file %s: lseek", _PATH_SCOREFILE); + return -1; + } + result = read(sd, &numbers, sizeof(numbers)); + if (result < 0) { + warn("Score file %s: read", _PATH_SCOREFILE); + return -1; + } + if ((size_t)result != sizeof(numbers)) { + /* + * The smallest file whose size divides by more than + * one of the sizes is substantially larger than 64, + * so this should *never* happen. + */ + warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE); + return -1; + } + + offset56 = numbers[0]; + offset60 = numbers[1]; + offset64 = numbers[2]; + + if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) { + /* 40-byte structure */ + return SCOREFILE_CURRENT; + } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) { + /* 36-byte structure */ + return SCOREFILE_599; + } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) { + /* 32-byte structure */ + return SCOREFILE_50; + } + + /* None was a valid level; try opposite endian */ + offset64 = bswap32(offset64); + offset60 = bswap32(offset60); + offset56 = bswap32(offset56); + + if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) { + /* 40-byte structure */ + return SCOREFILE_CURRENT_OPP; + } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) { + /* 36-byte structure */ + return SCOREFILE_599_OPP; + } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) { + /* 32-byte structure */ + return SCOREFILE_50_OPP; + } + + /* That didn't work either, dunno what's going on */ + wildguess: + warnx("Score file %s is likely corrupt", _PATH_SCOREFILE); + if (sizeof(void *) == 8 && sizeof(time_t) == 8) { + return SCOREFILE_CURRENT; + } else if (sizeof(time_t) == 8) { + return SCOREFILE_599; + } else { + return SCOREFILE_50; + } +} + +/* + * Copy a string safely, making sure it's null-terminated. + */ +static void +readname(char *to, size_t maxto, const char *from, size_t maxfrom) +{ + size_t amt; + + amt = maxto < maxfrom ? maxto : maxfrom; + memcpy(to, from, amt); + to[maxto-1] = '\0'; +} + +/* + * Copy integers, byte-swapping if desired. + */ +static int32_t +read32(int32_t val, int doflip) +{ + if (doflip) { + val = bswap32(val); + } + return val; +} + +static int64_t +read64(int64_t val, int doflip) +{ + if (doflip) { + val = bswap64(val); + } + return val; +} + +/* + * Read up to MAXHISCORES scorefile_ondisk entries. + */ +static int +readscores(int sd, int doflip) +{ + struct highscore_ondisk buf[MAXHISCORES]; + ssize_t result; + int i; + + result = read(sd, buf, sizeof(buf)); + if (result < 0) { + warn("Score file %s: read", _PATH_SCOREFILE); + return -1; + } + nscores = result / sizeof(buf[0]); + + for (i=0; ihs_level != level || strcmp(sp->hs_name, me) != 0) + continue; + if (score > sp->hs_score) { + (void)printf("%s bettered %s %d score of %d!\n", + "\nYou", "your old level", level, + sp->hs_score * sp->hs_level); + sp->hs_score = score; /* new score */ + sp->hs_time = now; /* and time */ + change = 1; + } else if (score == sp->hs_score) { + (void)printf("%s tied %s %d high score.\n", + "\nYou", "your old level", level); + sp->hs_time = now; /* renew it */ + change = 1; /* gotta rewrite, sigh */ + } /* else new score < old score: do nothing */ + break; + } + if (i >= nscores) { + strcpy(sp->hs_name, me); + sp->hs_level = level; + sp->hs_score = score; + sp->hs_time = now; + nscores++; + change = 1; + } + + if (change) { + /* + * Sort & clean the scores, then rewrite. + */ + nscores = checkscores(scores, nscores); + putscores(sd); + } + closescores(sd); +} + +/* + * Get login name, or if that fails, get something suitable. + * The result is always trimmed to fit in a score. + */ +static char * +thisuser(void) +{ + const char *p; + struct passwd *pw; + size_t l; + static char u[sizeof(scores[0].hs_name)]; + + if (u[0]) + return (u); + p = getlogin(); + if (p == NULL || *p == '\0') { + pw = getpwuid(getuid()); + if (pw != NULL) + p = pw->pw_name; + else + p = " ???"; + } + l = strlen(p); + if (l >= sizeof(u)) + l = sizeof(u) - 1; + memcpy(u, p, l); + u[l] = '\0'; + return (u); +} + +/* + * Score comparison function for qsort. + * + * If two scores are equal, the person who had the score first is + * listed first in the highscore file. + */ +static int +cmpscores(const void *x, const void *y) +{ + const struct highscore *a, *b; + long l; + + a = x; + b = y; + l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; + if (l < 0) + return (-1); + if (l > 0) + return (1); + if (a->hs_time < b->hs_time) + return (-1); + if (a->hs_time > b->hs_time) + return (1); + return (0); +} + +/* + * If we've added a score to the file, we need to check the file and ensure + * that this player has only a few entries. The number of entries is + * controlled by MAXSCORES, and is to ensure that the highscore file is not + * monopolised by just a few people. People who no longer have accounts are + * only allowed the highest score. Scores older than EXPIRATION seconds are + * removed, unless they are someone's personal best. + * Caveat: the highest score on each level is always kept. + */ +static int +checkscores(struct highscore *hs, int num) +{ + struct highscore *sp; + int i, j, k, numnames; + int levelfound[NLEVELS]; + struct peruser { + char *name; + int times; + } count[NUMSPOTS]; + struct peruser *pu; + + /* + * Sort so that highest totals come first. + * + * levelfound[i] becomes set when the first high score for that + * level is encountered. By definition this is the highest score. + */ + qsort((void *)hs, nscores, sizeof(*hs), cmpscores); + for (i = MINLEVEL; i < NLEVELS; i++) + levelfound[i] = 0; + numnames = 0; + for (i = 0, sp = hs; i < num;) { + /* + * This is O(n^2), but do you think we care? + */ + for (j = 0, pu = count; j < numnames; j++, pu++) + if (strcmp(sp->hs_name, pu->name) == 0) + break; + if (j == numnames) { + /* + * Add new user, set per-user count to 1. + */ + pu->name = sp->hs_name; + pu->times = 1; + numnames++; + } else { + /* + * Two ways to keep this score: + * - Not too many (per user), still has acct, & + * score not dated; or + * - High score on this level. + */ + if ((pu->times < MAXSCORES && + getpwnam(sp->hs_name) != NULL && + sp->hs_time + EXPIRATION >= now) || + levelfound[sp->hs_level] == 0) + pu->times++; + else { + /* + * Delete this score, do not count it, + * do not pass go, do not collect $200. + */ + num--; + for (k = i; k < num; k++) + hs[k] = hs[k + 1]; + continue; + } + } + if (sp->hs_level < NLEVELS && sp->hs_level >= 0) + levelfound[sp->hs_level] = 1; + i++, sp++; + } + return (num > MAXHISCORES ? MAXHISCORES : num); +} + +/* + * Show current scores. This must be called after savescore, if + * savescore is called at all, for two reasons: + * - Showscores munches the time field. + * - Even if that were not the case, a new score must be recorded + * before it can be shown anyway. + */ +void +showscores(int level) +{ + struct highscore *sp; + int i, n, c; + const char *me; + int levelfound[NLEVELS]; + + if (!gotscores) + getscores(NULL); + (void)printf("\n\t\t\t Tetris High Scores\n"); + + /* + * If level == 0, the person has not played a game but just asked for + * the high scores; we do not need to check for printing in highlight + * mode. If SOstr is null, we can't do highlighting anyway. + */ + me = level && enter_standout_mode ? thisuser() : NULL; + + /* + * Set times to 0 except for high score on each level. + */ + for (i = MINLEVEL; i < NLEVELS; i++) + levelfound[i] = 0; + for (i = 0, sp = scores; i < nscores; i++, sp++) { + if (sp->hs_level < NLEVELS && sp->hs_level >= 0) { + if (levelfound[sp->hs_level]) + sp->hs_time = 0; + else { + sp->hs_time = 1; + levelfound[sp->hs_level] = 1; + } + } + } + + /* + * Page each screenful of scores. + */ + for (i = 0, sp = scores; i < nscores; sp += n) { + n = 40; + if (i + n > nscores) + n = nscores - i; + printem(level, i + 1, sp, n, me); + if ((i += n) < nscores) { + (void)printf("\nHit RETURN to continue."); + (void)fflush(stdout); + while ((c = getchar()) != '\n') + if (c == EOF) + break; + (void)printf("\n"); + } + } +} + +static void +printem(int level, int offset, struct highscore *hs, int n, const char *me) +{ + struct highscore *sp; + int nrows, row, col, item, i, highlight; + char buf[100]; +#define TITLE "Rank Score Name (points/level)" + + /* + * This makes a nice two-column sort with headers, but it's a bit + * convoluted... + */ + printf("%s %s\n", TITLE, n > 1 ? TITLE : ""); + + highlight = 0; + nrows = (n + 1) / 2; + + for (row = 0; row < nrows; row++) { + for (col = 0; col < 2; col++) { + item = col * nrows + row; + if (item >= n) { + /* + * Can only occur on trailing columns. + */ + (void)putchar('\n'); + continue; + } + sp = &hs[item]; + (void)snprintf(buf, sizeof(buf), + "%3d%c %6d %-11s (%6d on %d)", + item + offset, sp->hs_time ? '*' : ' ', + sp->hs_score * sp->hs_level, + sp->hs_name, sp->hs_score, sp->hs_level); + /* + * Highlight if appropriate. This works because + * we only get one score per level. + */ + if (me != NULL && + sp->hs_level == level && + sp->hs_score == score && + strcmp(sp->hs_name, me) == 0) { + putpad(enter_standout_mode); + highlight = 1; + } + (void)printf("%s", buf); + if (highlight) { + putpad(exit_standout_mode); + highlight = 0; + } + + /* fill in spaces so column 1 lines up */ + if (col == 0) + for (i = 40 - strlen(buf); --i >= 0;) + (void)putchar(' '); + else /* col == 1 */ + (void)putchar('\n'); + } + } +} -- cgit v1.2.3