1 11a5e2cf 2024-01-16 benni /* $NetBSD: scores.c,v 1.26 2021/05/02 12:50:46 rillig Exp $ */
4 11a5e2cf 2024-01-16 benni * Copyright (c) 1992, 1993
5 11a5e2cf 2024-01-16 benni * The Regents of the University of California. All rights reserved.
7 11a5e2cf 2024-01-16 benni * This code is derived from software contributed to Berkeley by
8 11a5e2cf 2024-01-16 benni * Chris Torek and Darren F. Provine.
10 11a5e2cf 2024-01-16 benni * Redistribution and use in source and binary forms, with or without
11 11a5e2cf 2024-01-16 benni * modification, are permitted provided that the following conditions
13 11a5e2cf 2024-01-16 benni * 1. Redistributions of source code must retain the above copyright
14 11a5e2cf 2024-01-16 benni * notice, this list of conditions and the following disclaimer.
15 11a5e2cf 2024-01-16 benni * 2. Redistributions in binary form must reproduce the above copyright
16 11a5e2cf 2024-01-16 benni * notice, this list of conditions and the following disclaimer in the
17 11a5e2cf 2024-01-16 benni * documentation and/or other materials provided with the distribution.
18 11a5e2cf 2024-01-16 benni * 3. Neither the name of the University nor the names of its contributors
19 11a5e2cf 2024-01-16 benni * may be used to endorse or promote products derived from this software
20 11a5e2cf 2024-01-16 benni * without specific prior written permission.
22 11a5e2cf 2024-01-16 benni * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 11a5e2cf 2024-01-16 benni * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 11a5e2cf 2024-01-16 benni * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 11a5e2cf 2024-01-16 benni * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 11a5e2cf 2024-01-16 benni * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 11a5e2cf 2024-01-16 benni * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 11a5e2cf 2024-01-16 benni * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 11a5e2cf 2024-01-16 benni * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 11a5e2cf 2024-01-16 benni * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 11a5e2cf 2024-01-16 benni * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 11a5e2cf 2024-01-16 benni * SUCH DAMAGE.
34 11a5e2cf 2024-01-16 benni * @(#)scores.c 8.1 (Berkeley) 5/31/93
38 11a5e2cf 2024-01-16 benni * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu)
39 11a5e2cf 2024-01-16 benni * modified 22 January 1992, to limit the number of entries any one
40 11a5e2cf 2024-01-16 benni * person has.
42 11a5e2cf 2024-01-16 benni * Major whacks since then.
44 11a5e2cf 2024-01-16 benni #include <err.h>
45 11a5e2cf 2024-01-16 benni #include <errno.h>
46 11a5e2cf 2024-01-16 benni #include <fcntl.h>
47 11a5e2cf 2024-01-16 benni #include <pwd.h>
48 11a5e2cf 2024-01-16 benni #include <stdio.h>
49 11a5e2cf 2024-01-16 benni #include <stdlib.h>
50 11a5e2cf 2024-01-16 benni #include <string.h>
51 11a5e2cf 2024-01-16 benni #include <sys/stat.h>
52 11a5e2cf 2024-01-16 benni #include <time.h>
53 11a5e2cf 2024-01-16 benni #include <term.h>
54 11a5e2cf 2024-01-16 benni #include <unistd.h>
56 84c906a7 2024-06-03 benni #ifdef __FreeBSD__
57 84c906a7 2024-06-03 benni # include <sys/endian.h>
58 84c906a7 2024-06-03 benni # define swap32 bswap32
59 84c906a7 2024-06-03 benni # define swap64 bswap64
60 7b083439 2024-06-06 benni #elif defined(__linux__)
61 7b083439 2024-06-06 benni # include <sys/file.h>
62 7b083439 2024-06-06 benni # define swap32 __builtin_bswap32
63 7b083439 2024-06-06 benni # define swap64 __builtin_bswap64
66 11a5e2cf 2024-01-16 benni #include "pathnames.h"
67 11a5e2cf 2024-01-16 benni #include "screen.h"
68 11a5e2cf 2024-01-16 benni #include "scores.h"
69 11a5e2cf 2024-01-16 benni #include "tetris.h"
72 11a5e2cf 2024-01-16 benni * Allow updating the high scores unless we're built as part of /rescue.
74 11a5e2cf 2024-01-16 benni #ifndef RESCUEDIR
75 11a5e2cf 2024-01-16 benni #define ALLOW_SCORE_UPDATES
79 11a5e2cf 2024-01-16 benni * Within this code, we can hang onto one extra "high score", leaving
80 11a5e2cf 2024-01-16 benni * room for our current score (whether or not it is high).
82 11a5e2cf 2024-01-16 benni * We also sometimes keep tabs on the "highest" score on each level.
83 11a5e2cf 2024-01-16 benni * As long as the scores are kept sorted, this is simply the first one at
84 11a5e2cf 2024-01-16 benni * that level.
86 11a5e2cf 2024-01-16 benni #define NUMSPOTS (MAXHISCORES + 1)
87 11a5e2cf 2024-01-16 benni #define NLEVELS (MAXLEVEL + 1)
89 11a5e2cf 2024-01-16 benni static time_t now;
90 11a5e2cf 2024-01-16 benni static int nscores;
91 11a5e2cf 2024-01-16 benni static int gotscores;
92 11a5e2cf 2024-01-16 benni static struct highscore scores[NUMSPOTS];
94 11a5e2cf 2024-01-16 benni static int checkscores(struct highscore *, int);
95 11a5e2cf 2024-01-16 benni static int cmpscores(const void *, const void *);
96 11a5e2cf 2024-01-16 benni static void getscores(int *);
97 11a5e2cf 2024-01-16 benni static void printem(int, int, struct highscore *, int, const char *);
98 11a5e2cf 2024-01-16 benni static char *thisuser(void);
100 11a5e2cf 2024-01-16 benni /* contents chosen to be a highly illegal username */
101 11a5e2cf 2024-01-16 benni static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://";
103 11a5e2cf 2024-01-16 benni #define HSH_ENDIAN_NATIVE 0x12345678
104 11a5e2cf 2024-01-16 benni #define HSH_ENDIAN_OPP 0x78563412
106 11a5e2cf 2024-01-16 benni /* current file format version */
107 11a5e2cf 2024-01-16 benni #define HSH_VERSION 1
109 11a5e2cf 2024-01-16 benni /* codes for scorefile_probe return */
110 11a5e2cf 2024-01-16 benni #define SCOREFILE_ERROR (-1)
111 11a5e2cf 2024-01-16 benni #define SCOREFILE_CURRENT 0 /* 40-byte */
112 11a5e2cf 2024-01-16 benni #define SCOREFILE_CURRENT_OPP 1 /* 40-byte, opposite-endian */
113 11a5e2cf 2024-01-16 benni #define SCOREFILE_599 2 /* 36-byte */
114 11a5e2cf 2024-01-16 benni #define SCOREFILE_599_OPP 3 /* 36-byte, opposite-endian */
115 11a5e2cf 2024-01-16 benni #define SCOREFILE_50 4 /* 32-byte */
116 11a5e2cf 2024-01-16 benni #define SCOREFILE_50_OPP 5 /* 32-byte, opposite-endian */
119 11a5e2cf 2024-01-16 benni * Check (or guess) what kind of score file contents we have.
121 11a5e2cf 2024-01-16 benni static int
122 11a5e2cf 2024-01-16 benni scorefile_probe(int sd)
124 11a5e2cf 2024-01-16 benni struct stat st;
125 11a5e2cf 2024-01-16 benni int t1, t2, t3, tx;
126 11a5e2cf 2024-01-16 benni ssize_t result;
127 11a5e2cf 2024-01-16 benni uint32_t numbers[3], offset56, offset60, offset64;
129 11a5e2cf 2024-01-16 benni if (fstat(sd, &st) < 0) {
130 11a5e2cf 2024-01-16 benni warn("Score file %s: fstat", _PATH_SCOREFILE);
131 11a5e2cf 2024-01-16 benni return -1;
134 11a5e2cf 2024-01-16 benni t1 = st.st_size % sizeof(struct highscore_ondisk) == 0;
135 11a5e2cf 2024-01-16 benni t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0;
136 11a5e2cf 2024-01-16 benni t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0;
137 11a5e2cf 2024-01-16 benni tx = t1 + t2 + t3;
138 11a5e2cf 2024-01-16 benni if (tx == 1) {
139 11a5e2cf 2024-01-16 benni /* Size matches exact number of one kind of records */
141 11a5e2cf 2024-01-16 benni return SCOREFILE_CURRENT;
142 11a5e2cf 2024-01-16 benni } else if (t2) {
143 11a5e2cf 2024-01-16 benni return SCOREFILE_599;
145 11a5e2cf 2024-01-16 benni return SCOREFILE_50;
147 11a5e2cf 2024-01-16 benni } else if (tx == 0) {
148 11a5e2cf 2024-01-16 benni /* Size matches nothing, pick most likely as default */
149 11a5e2cf 2024-01-16 benni goto wildguess;
153 11a5e2cf 2024-01-16 benni * File size is multiple of more than one structure size.
154 11a5e2cf 2024-01-16 benni * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
155 11a5e2cf 2024-01-16 benni * Read the file and see if we can figure out what's going
156 11a5e2cf 2024-01-16 benni * on. This is the layout of the first two records:
158 11a5e2cf 2024-01-16 benni * offset hso / current hso_599 hso_50
159 11a5e2cf 2024-01-16 benni * (40-byte) (36-byte) (32-byte)
161 11a5e2cf 2024-01-16 benni * 0 name #0 name #0 name #0
164 11a5e2cf 2024-01-16 benni * 12 : : :
165 11a5e2cf 2024-01-16 benni * 16 : : :
166 11a5e2cf 2024-01-16 benni * 20 score #0 score #0 score #0
167 11a5e2cf 2024-01-16 benni * 24 level #0 level #0 level #0
168 11a5e2cf 2024-01-16 benni * 28 (pad) time #0 time #0
169 11a5e2cf 2024-01-16 benni * 32 time #0 name #1
170 11a5e2cf 2024-01-16 benni * 36 name #1 :
171 11a5e2cf 2024-01-16 benni * 40 name #1 : :
172 11a5e2cf 2024-01-16 benni * 44 : : :
173 11a5e2cf 2024-01-16 benni * 48 : : :
174 11a5e2cf 2024-01-16 benni * 52 : : score #1
175 11a5e2cf 2024-01-16 benni * 56 : score #1 level #1
176 11a5e2cf 2024-01-16 benni * 60 score #1 level #1 time #1
177 11a5e2cf 2024-01-16 benni * 64 level #1 time #1 name #2
178 11a5e2cf 2024-01-16 benni * 68 (pad) : :
179 11a5e2cf 2024-01-16 benni * 72 time #1 name #2 :
180 11a5e2cf 2024-01-16 benni * 76 : : :
181 11a5e2cf 2024-01-16 benni * 80 --- end ---
183 11a5e2cf 2024-01-16 benni * There are a number of things we could check here, but the
184 11a5e2cf 2024-01-16 benni * most effective test is based on the following restrictions:
186 11a5e2cf 2024-01-16 benni * - The level must be between 1 and 9 (inclusive)
187 11a5e2cf 2024-01-16 benni * - All times must be after 1985 and are before 2038,
188 11a5e2cf 2024-01-16 benni * so the high word must be 0 and the low word may not be
189 11a5e2cf 2024-01-16 benni * a small value.
190 11a5e2cf 2024-01-16 benni * - Integer values of 0 or 1-9 cannot be the beginning of
191 11a5e2cf 2024-01-16 benni * a login name string.
192 11a5e2cf 2024-01-16 benni * - Values of 1-9 are probably not a score.
194 11a5e2cf 2024-01-16 benni * So we read the three words at offsets 56, 60, and 64, and
195 11a5e2cf 2024-01-16 benni * poke at the values to try to figure things...
198 11a5e2cf 2024-01-16 benni if (lseek(sd, 56, SEEK_SET) < 0) {
199 11a5e2cf 2024-01-16 benni warn("Score file %s: lseek", _PATH_SCOREFILE);
200 11a5e2cf 2024-01-16 benni return -1;
202 11a5e2cf 2024-01-16 benni result = read(sd, &numbers, sizeof(numbers));
203 11a5e2cf 2024-01-16 benni if (result < 0) {
204 11a5e2cf 2024-01-16 benni warn("Score file %s: read", _PATH_SCOREFILE);
205 11a5e2cf 2024-01-16 benni return -1;
207 11a5e2cf 2024-01-16 benni if ((size_t)result != sizeof(numbers)) {
209 11a5e2cf 2024-01-16 benni * The smallest file whose size divides by more than
210 11a5e2cf 2024-01-16 benni * one of the sizes is substantially larger than 64,
211 11a5e2cf 2024-01-16 benni * so this should *never* happen.
213 11a5e2cf 2024-01-16 benni warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE);
214 11a5e2cf 2024-01-16 benni return -1;
217 11a5e2cf 2024-01-16 benni offset56 = numbers[0];
218 11a5e2cf 2024-01-16 benni offset60 = numbers[1];
219 11a5e2cf 2024-01-16 benni offset64 = numbers[2];
221 11a5e2cf 2024-01-16 benni if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
222 11a5e2cf 2024-01-16 benni /* 40-byte structure */
223 11a5e2cf 2024-01-16 benni return SCOREFILE_CURRENT;
224 11a5e2cf 2024-01-16 benni } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
225 11a5e2cf 2024-01-16 benni /* 36-byte structure */
226 11a5e2cf 2024-01-16 benni return SCOREFILE_599;
227 11a5e2cf 2024-01-16 benni } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
228 11a5e2cf 2024-01-16 benni /* 32-byte structure */
229 11a5e2cf 2024-01-16 benni return SCOREFILE_50;
232 11a5e2cf 2024-01-16 benni /* None was a valid level; try opposite endian */
233 60af9557 2024-01-16 benni offset64 = swap32(offset64);
234 60af9557 2024-01-16 benni offset60 = swap32(offset60);
235 60af9557 2024-01-16 benni offset56 = swap32(offset56);
237 11a5e2cf 2024-01-16 benni if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
238 11a5e2cf 2024-01-16 benni /* 40-byte structure */
239 11a5e2cf 2024-01-16 benni return SCOREFILE_CURRENT_OPP;
240 11a5e2cf 2024-01-16 benni } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
241 11a5e2cf 2024-01-16 benni /* 36-byte structure */
242 11a5e2cf 2024-01-16 benni return SCOREFILE_599_OPP;
243 11a5e2cf 2024-01-16 benni } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
244 11a5e2cf 2024-01-16 benni /* 32-byte structure */
245 11a5e2cf 2024-01-16 benni return SCOREFILE_50_OPP;
248 11a5e2cf 2024-01-16 benni /* That didn't work either, dunno what's going on */
249 11a5e2cf 2024-01-16 benni wildguess:
250 11a5e2cf 2024-01-16 benni warnx("Score file %s is likely corrupt", _PATH_SCOREFILE);
251 11a5e2cf 2024-01-16 benni if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
252 11a5e2cf 2024-01-16 benni return SCOREFILE_CURRENT;
253 11a5e2cf 2024-01-16 benni } else if (sizeof(time_t) == 8) {
254 11a5e2cf 2024-01-16 benni return SCOREFILE_599;
256 11a5e2cf 2024-01-16 benni return SCOREFILE_50;
261 11a5e2cf 2024-01-16 benni * Copy a string safely, making sure it's null-terminated.
263 11a5e2cf 2024-01-16 benni static void
264 11a5e2cf 2024-01-16 benni readname(char *to, size_t maxto, const char *from, size_t maxfrom)
266 11a5e2cf 2024-01-16 benni size_t amt;
268 11a5e2cf 2024-01-16 benni amt = maxto < maxfrom ? maxto : maxfrom;
269 11a5e2cf 2024-01-16 benni memcpy(to, from, amt);
270 11a5e2cf 2024-01-16 benni to[maxto-1] = '\0';
274 11a5e2cf 2024-01-16 benni * Copy integers, byte-swapping if desired.
276 11a5e2cf 2024-01-16 benni static int32_t
277 11a5e2cf 2024-01-16 benni read32(int32_t val, int doflip)
279 11a5e2cf 2024-01-16 benni if (doflip) {
280 60af9557 2024-01-16 benni val = swap32(val);
282 11a5e2cf 2024-01-16 benni return val;
285 11a5e2cf 2024-01-16 benni static int64_t
286 11a5e2cf 2024-01-16 benni read64(int64_t val, int doflip)
288 11a5e2cf 2024-01-16 benni if (doflip) {
289 60af9557 2024-01-16 benni val = swap64(val);
291 11a5e2cf 2024-01-16 benni return val;
295 11a5e2cf 2024-01-16 benni * Read up to MAXHISCORES scorefile_ondisk entries.
297 11a5e2cf 2024-01-16 benni static int
298 11a5e2cf 2024-01-16 benni readscores(int sd, int doflip)
300 11a5e2cf 2024-01-16 benni struct highscore_ondisk buf[MAXHISCORES];
301 11a5e2cf 2024-01-16 benni ssize_t result;
304 11a5e2cf 2024-01-16 benni result = read(sd, buf, sizeof(buf));
305 11a5e2cf 2024-01-16 benni if (result < 0) {
306 11a5e2cf 2024-01-16 benni warn("Score file %s: read", _PATH_SCOREFILE);
307 11a5e2cf 2024-01-16 benni return -1;
309 11a5e2cf 2024-01-16 benni nscores = result / sizeof(buf[0]);
311 11a5e2cf 2024-01-16 benni for (i=0; i<nscores; i++) {
312 11a5e2cf 2024-01-16 benni readname(scores[i].hs_name, sizeof(scores[i].hs_name),
313 11a5e2cf 2024-01-16 benni buf[i].hso_name, sizeof(buf[i].hso_name));
314 11a5e2cf 2024-01-16 benni scores[i].hs_score = read32(buf[i].hso_score, doflip);
315 11a5e2cf 2024-01-16 benni scores[i].hs_level = read32(buf[i].hso_level, doflip);
316 11a5e2cf 2024-01-16 benni scores[i].hs_time = read64(buf[i].hso_time, doflip);
322 11a5e2cf 2024-01-16 benni * Read up to MAXHISCORES scorefile_ondisk_599 entries.
324 11a5e2cf 2024-01-16 benni static int
325 11a5e2cf 2024-01-16 benni readscores599(int sd, int doflip)
327 11a5e2cf 2024-01-16 benni struct highscore_ondisk_599 buf[MAXHISCORES];
328 11a5e2cf 2024-01-16 benni ssize_t result;
331 11a5e2cf 2024-01-16 benni result = read(sd, buf, sizeof(buf));
332 11a5e2cf 2024-01-16 benni if (result < 0) {
333 11a5e2cf 2024-01-16 benni warn("Score file %s: read", _PATH_SCOREFILE);
334 11a5e2cf 2024-01-16 benni return -1;
336 11a5e2cf 2024-01-16 benni nscores = result / sizeof(buf[0]);
338 11a5e2cf 2024-01-16 benni for (i=0; i<nscores; i++) {
339 11a5e2cf 2024-01-16 benni readname(scores[i].hs_name, sizeof(scores[i].hs_name),
340 11a5e2cf 2024-01-16 benni buf[i].hso599_name, sizeof(buf[i].hso599_name));
341 11a5e2cf 2024-01-16 benni scores[i].hs_score = read32(buf[i].hso599_score, doflip);
342 11a5e2cf 2024-01-16 benni scores[i].hs_level = read32(buf[i].hso599_level, doflip);
344 11a5e2cf 2024-01-16 benni * Don't bother pasting the time together into a
345 11a5e2cf 2024-01-16 benni * 64-bit value; just take whichever half is nonzero.
347 11a5e2cf 2024-01-16 benni scores[i].hs_time =
348 11a5e2cf 2024-01-16 benni read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0],
355 11a5e2cf 2024-01-16 benni * Read up to MAXHISCORES scorefile_ondisk_50 entries.
357 11a5e2cf 2024-01-16 benni static int
358 11a5e2cf 2024-01-16 benni readscores50(int sd, int doflip)
360 11a5e2cf 2024-01-16 benni struct highscore_ondisk_50 buf[MAXHISCORES];
361 11a5e2cf 2024-01-16 benni ssize_t result;
364 11a5e2cf 2024-01-16 benni result = read(sd, buf, sizeof(buf));
365 11a5e2cf 2024-01-16 benni if (result < 0) {
366 11a5e2cf 2024-01-16 benni warn("Score file %s: read", _PATH_SCOREFILE);
367 11a5e2cf 2024-01-16 benni return -1;
369 11a5e2cf 2024-01-16 benni nscores = result / sizeof(buf[0]);
371 11a5e2cf 2024-01-16 benni for (i=0; i<nscores; i++) {
372 11a5e2cf 2024-01-16 benni readname(scores[i].hs_name, sizeof(scores[i].hs_name),
373 11a5e2cf 2024-01-16 benni buf[i].hso50_name, sizeof(buf[i].hso50_name));
374 11a5e2cf 2024-01-16 benni scores[i].hs_score = read32(buf[i].hso50_score, doflip);
375 11a5e2cf 2024-01-16 benni scores[i].hs_level = read32(buf[i].hso50_level, doflip);
376 11a5e2cf 2024-01-16 benni scores[i].hs_time = read32(buf[i].hso50_time, doflip);
382 11a5e2cf 2024-01-16 benni * Read the score file. Can be called from savescore (before showscores)
383 11a5e2cf 2024-01-16 benni * or showscores (if savescore will not be called). If the given pointer
384 11a5e2cf 2024-01-16 benni * is not NULL, sets *fdp to an open file handle that corresponds to a
385 11a5e2cf 2024-01-16 benni * read/write score file that is locked with LOCK_EX. Otherwise, the
386 11a5e2cf 2024-01-16 benni * file is locked with LOCK_SH for the read and closed before return.
388 11a5e2cf 2024-01-16 benni static void
389 11a5e2cf 2024-01-16 benni getscores(int *fdp)
391 11a5e2cf 2024-01-16 benni struct highscore_header header;
392 11a5e2cf 2024-01-16 benni int sd, mint, lck;
393 11a5e2cf 2024-01-16 benni mode_t mask;
394 11a5e2cf 2024-01-16 benni const char *human;
395 11a5e2cf 2024-01-16 benni int doflip;
396 11a5e2cf 2024-01-16 benni int serrno;
397 11a5e2cf 2024-01-16 benni ssize_t result;
399 11a5e2cf 2024-01-16 benni #ifdef ALLOW_SCORE_UPDATES
400 11a5e2cf 2024-01-16 benni if (fdp != NULL) {
401 11a5e2cf 2024-01-16 benni mint = O_RDWR | O_CREAT;
402 11a5e2cf 2024-01-16 benni human = "read/write";
403 11a5e2cf 2024-01-16 benni lck = LOCK_EX;
407 11a5e2cf 2024-01-16 benni mint = O_RDONLY;
408 11a5e2cf 2024-01-16 benni human = "reading";
409 11a5e2cf 2024-01-16 benni lck = LOCK_SH;
411 11a5e2cf 2024-01-16 benni setegid(egid);
412 11a5e2cf 2024-01-16 benni mask = umask(S_IWOTH);
413 11a5e2cf 2024-01-16 benni sd = open(_PATH_SCOREFILE, mint, 0666);
414 11a5e2cf 2024-01-16 benni serrno = errno;
415 11a5e2cf 2024-01-16 benni (void)umask(mask);
416 11a5e2cf 2024-01-16 benni setegid(gid);
417 11a5e2cf 2024-01-16 benni if (sd < 0) {
419 11a5e2cf 2024-01-16 benni * If the file simply isn't there because nobody's
420 11a5e2cf 2024-01-16 benni * played yet, and we aren't going to be trying to
421 11a5e2cf 2024-01-16 benni * update it, don't warn. Even if we are going to be
422 11a5e2cf 2024-01-16 benni * trying to write it, don't fail -- we can still show
423 11a5e2cf 2024-01-16 benni * the player the score they got.
425 11a5e2cf 2024-01-16 benni errno = serrno;
426 11a5e2cf 2024-01-16 benni if (fdp != NULL || errno != ENOENT) {
427 11a5e2cf 2024-01-16 benni warn("Cannot open %s for %s", _PATH_SCOREFILE, human);
429 11a5e2cf 2024-01-16 benni goto fail;
433 11a5e2cf 2024-01-16 benni * Grab a lock.
434 11a5e2cf 2024-01-16 benni * XXX: failure here should probably be more fatal than this.
436 11a5e2cf 2024-01-16 benni if (flock(sd, lck))
437 11a5e2cf 2024-01-16 benni warn("warning: score file %s cannot be locked",
438 11a5e2cf 2024-01-16 benni _PATH_SCOREFILE);
441 11a5e2cf 2024-01-16 benni * The current format (since -current of 20090525) is
443 11a5e2cf 2024-01-16 benni * struct highscore_header
444 11a5e2cf 2024-01-16 benni * up to MAXHIGHSCORES x struct highscore_ondisk
446 11a5e2cf 2024-01-16 benni * Before this, there is no header, and the contents
447 11a5e2cf 2024-01-16 benni * might be any of three formats:
449 11a5e2cf 2024-01-16 benni * highscore_ondisk (64-bit machines with 64-bit time_t)
450 11a5e2cf 2024-01-16 benni * highscore_ondisk_599 (32-bit machines with 64-bit time_t)
451 11a5e2cf 2024-01-16 benni * highscore_ondisk_50 (32-bit machines with 32-bit time_t)
453 11a5e2cf 2024-01-16 benni * The first two appear in 5.99 between the time_t change and
454 11a5e2cf 2024-01-16 benni * 20090525, depending on whether the compiler inserts
455 11a5e2cf 2024-01-16 benni * structure padding before an unaligned 64-bit time_t. The
456 11a5e2cf 2024-01-16 benni * last appears in 5.0 and earlier.
458 11a5e2cf 2024-01-16 benni * Any or all of these might also appear on other OSes where
459 11a5e2cf 2024-01-16 benni * this code has been ported.
461 11a5e2cf 2024-01-16 benni * Since the old file has no header, we will have to guess
462 11a5e2cf 2024-01-16 benni * which of these formats it has.
466 11a5e2cf 2024-01-16 benni * First, look for a header.
468 11a5e2cf 2024-01-16 benni result = read(sd, &header, sizeof(header));
469 11a5e2cf 2024-01-16 benni if (result < 0) {
470 11a5e2cf 2024-01-16 benni warn("Score file %s: read", _PATH_SCOREFILE);
471 11a5e2cf 2024-01-16 benni goto sdfail;
473 11a5e2cf 2024-01-16 benni if (result != 0 && (size_t)result != sizeof(header)) {
474 11a5e2cf 2024-01-16 benni warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE);
476 11a5e2cf 2024-01-16 benni * File is hopelessly corrupt, might as well truncate it
477 11a5e2cf 2024-01-16 benni * and start over with empty scores.
479 11a5e2cf 2024-01-16 benni if (lseek(sd, 0, SEEK_SET) < 0) {
481 11a5e2cf 2024-01-16 benni warn("Score file %s: lseek", _PATH_SCOREFILE);
482 11a5e2cf 2024-01-16 benni goto sdfail;
484 11a5e2cf 2024-01-16 benni if (ftruncate(sd, 0) == 0) {
485 11a5e2cf 2024-01-16 benni result = 0;
487 11a5e2cf 2024-01-16 benni goto sdfail;
491 11a5e2cf 2024-01-16 benni if (result == 0) {
492 11a5e2cf 2024-01-16 benni /* Empty file; that just means there are no scores. */
493 11a5e2cf 2024-01-16 benni nscores = 0;
496 11a5e2cf 2024-01-16 benni * Is what we read a header, or the first 16 bytes of
497 11a5e2cf 2024-01-16 benni * a score entry? hsh_magic_val is chosen to be
498 11a5e2cf 2024-01-16 benni * something that is extremely unlikely to appear in
499 11a5e2cf 2024-01-16 benni * hs_name[].
501 11a5e2cf 2024-01-16 benni if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) {
502 11a5e2cf 2024-01-16 benni /* Yes, we have a header. */
504 11a5e2cf 2024-01-16 benni if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) {
505 11a5e2cf 2024-01-16 benni /* native endian */
506 11a5e2cf 2024-01-16 benni doflip = 0;
507 11a5e2cf 2024-01-16 benni } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) {
508 11a5e2cf 2024-01-16 benni doflip = 1;
510 11a5e2cf 2024-01-16 benni warnx("Score file %s: Unknown endian tag %u",
511 11a5e2cf 2024-01-16 benni _PATH_SCOREFILE, header.hsh_endiantag);
512 11a5e2cf 2024-01-16 benni goto sdfail;
515 11a5e2cf 2024-01-16 benni if (header.hsh_version != HSH_VERSION) {
516 11a5e2cf 2024-01-16 benni warnx("Score file %s: Unknown version code %u",
517 11a5e2cf 2024-01-16 benni _PATH_SCOREFILE, header.hsh_version);
518 11a5e2cf 2024-01-16 benni goto sdfail;
521 11a5e2cf 2024-01-16 benni if (readscores(sd, doflip) < 0) {
522 11a5e2cf 2024-01-16 benni goto sdfail;
526 11a5e2cf 2024-01-16 benni * Ok, it wasn't a header. Try to figure out what
527 11a5e2cf 2024-01-16 benni * size records we have.
529 11a5e2cf 2024-01-16 benni result = scorefile_probe(sd);
530 11a5e2cf 2024-01-16 benni if (lseek(sd, 0, SEEK_SET) < 0) {
531 11a5e2cf 2024-01-16 benni warn("Score file %s: lseek", _PATH_SCOREFILE);
532 11a5e2cf 2024-01-16 benni goto sdfail;
534 11a5e2cf 2024-01-16 benni switch (result) {
535 11a5e2cf 2024-01-16 benni case SCOREFILE_CURRENT:
536 11a5e2cf 2024-01-16 benni result = readscores(sd, 0 /* don't flip */);
538 11a5e2cf 2024-01-16 benni case SCOREFILE_CURRENT_OPP:
539 11a5e2cf 2024-01-16 benni result = readscores(sd, 1 /* do flip */);
541 11a5e2cf 2024-01-16 benni case SCOREFILE_599:
542 11a5e2cf 2024-01-16 benni result = readscores599(sd, 0 /* don't flip */);
544 11a5e2cf 2024-01-16 benni case SCOREFILE_599_OPP:
545 11a5e2cf 2024-01-16 benni result = readscores599(sd, 1 /* do flip */);
547 11a5e2cf 2024-01-16 benni case SCOREFILE_50:
548 11a5e2cf 2024-01-16 benni result = readscores50(sd, 0 /* don't flip */);
550 11a5e2cf 2024-01-16 benni case SCOREFILE_50_OPP:
551 11a5e2cf 2024-01-16 benni result = readscores50(sd, 1 /* do flip */);
554 11a5e2cf 2024-01-16 benni goto sdfail;
556 11a5e2cf 2024-01-16 benni if (result < 0) {
557 11a5e2cf 2024-01-16 benni goto sdfail;
564 11a5e2cf 2024-01-16 benni *fdp = sd;
566 11a5e2cf 2024-01-16 benni close(sd);
571 11a5e2cf 2024-01-16 benni close(sd);
573 11a5e2cf 2024-01-16 benni if (fdp != NULL) {
574 11a5e2cf 2024-01-16 benni *fdp = -1;
576 11a5e2cf 2024-01-16 benni nscores = 0;
579 11a5e2cf 2024-01-16 benni #ifdef ALLOW_SCORE_UPDATES
581 11a5e2cf 2024-01-16 benni * Paranoid write wrapper; unlike fwrite() it preserves errno.
583 11a5e2cf 2024-01-16 benni static int
584 11a5e2cf 2024-01-16 benni dowrite(int sd, const void *vbuf, size_t len)
586 11a5e2cf 2024-01-16 benni const char *buf = vbuf;
587 11a5e2cf 2024-01-16 benni ssize_t result;
588 11a5e2cf 2024-01-16 benni size_t done = 0;
590 11a5e2cf 2024-01-16 benni while (done < len) {
591 11a5e2cf 2024-01-16 benni result = write(sd, buf+done, len-done);
592 11a5e2cf 2024-01-16 benni if (result < 0) {
593 11a5e2cf 2024-01-16 benni if (errno == EINTR) {
596 11a5e2cf 2024-01-16 benni return -1;
598 11a5e2cf 2024-01-16 benni done += result;
602 11a5e2cf 2024-01-16 benni #endif /* ALLOW_SCORE_UPDATES */
605 11a5e2cf 2024-01-16 benni * Write the score file out.
607 11a5e2cf 2024-01-16 benni static void
608 11a5e2cf 2024-01-16 benni putscores(int sd)
610 11a5e2cf 2024-01-16 benni #ifdef ALLOW_SCORE_UPDATES
611 11a5e2cf 2024-01-16 benni struct highscore_header header;
612 11a5e2cf 2024-01-16 benni struct highscore_ondisk buf[MAXHISCORES] = {0};
615 11a5e2cf 2024-01-16 benni if (sd == -1) {
619 11a5e2cf 2024-01-16 benni memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE);
620 11a5e2cf 2024-01-16 benni header.hsh_endiantag = HSH_ENDIAN_NATIVE;
621 11a5e2cf 2024-01-16 benni header.hsh_version = HSH_VERSION;
623 11a5e2cf 2024-01-16 benni for (i=0; i<nscores; i++) {
624 11a5e2cf 2024-01-16 benni memcpy(buf[i].hso_name, scores[i].hs_name,
625 11a5e2cf 2024-01-16 benni sizeof(buf[i].hso_name));
626 11a5e2cf 2024-01-16 benni buf[i].hso_score = scores[i].hs_score;
627 11a5e2cf 2024-01-16 benni buf[i].hso_level = scores[i].hs_level;
628 11a5e2cf 2024-01-16 benni buf[i].hso_pad = 0xbaadf00d;
629 11a5e2cf 2024-01-16 benni buf[i].hso_time = scores[i].hs_time;
632 11a5e2cf 2024-01-16 benni if (lseek(sd, 0, SEEK_SET) < 0) {
633 11a5e2cf 2024-01-16 benni warn("Score file %s: lseek", _PATH_SCOREFILE);
634 11a5e2cf 2024-01-16 benni goto fail;
636 11a5e2cf 2024-01-16 benni if (dowrite(sd, &header, sizeof(header)) < 0 ||
637 11a5e2cf 2024-01-16 benni dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) {
638 11a5e2cf 2024-01-16 benni warn("Score file %s: write", _PATH_SCOREFILE);
639 11a5e2cf 2024-01-16 benni goto fail;
643 11a5e2cf 2024-01-16 benni warnx("high scores may be damaged");
646 11a5e2cf 2024-01-16 benni #endif /* ALLOW_SCORE_UPDATES */
650 11a5e2cf 2024-01-16 benni * Close the score file.
652 11a5e2cf 2024-01-16 benni static void
653 11a5e2cf 2024-01-16 benni closescores(int sd)
655 11a5e2cf 2024-01-16 benni flock(sd, LOCK_UN);
656 11a5e2cf 2024-01-16 benni close(sd);
660 11a5e2cf 2024-01-16 benni * Read and update the scores file with the current reults.
663 11a5e2cf 2024-01-16 benni savescore(int level)
665 11a5e2cf 2024-01-16 benni struct highscore *sp;
667 11a5e2cf 2024-01-16 benni int change;
669 11a5e2cf 2024-01-16 benni const char *me;
671 11a5e2cf 2024-01-16 benni getscores(&sd);
672 11a5e2cf 2024-01-16 benni gotscores = 1;
673 11a5e2cf 2024-01-16 benni (void)time(&now);
676 11a5e2cf 2024-01-16 benni * Allow at most one score per person per level -- see if we
677 11a5e2cf 2024-01-16 benni * can replace an existing score, or (easiest) do nothing.
678 11a5e2cf 2024-01-16 benni * Otherwise add new score at end (there is always room).
680 11a5e2cf 2024-01-16 benni change = 0;
681 11a5e2cf 2024-01-16 benni me = thisuser();
682 11a5e2cf 2024-01-16 benni for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) {
683 11a5e2cf 2024-01-16 benni if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0)
685 11a5e2cf 2024-01-16 benni if (score > sp->hs_score) {
686 11a5e2cf 2024-01-16 benni (void)printf("%s bettered %s %d score of %d!\n",
687 11a5e2cf 2024-01-16 benni "\nYou", "your old level", level,
688 11a5e2cf 2024-01-16 benni sp->hs_score * sp->hs_level);
689 11a5e2cf 2024-01-16 benni sp->hs_score = score; /* new score */
690 11a5e2cf 2024-01-16 benni sp->hs_time = now; /* and time */
691 11a5e2cf 2024-01-16 benni change = 1;
692 11a5e2cf 2024-01-16 benni } else if (score == sp->hs_score) {
693 11a5e2cf 2024-01-16 benni (void)printf("%s tied %s %d high score.\n",
694 11a5e2cf 2024-01-16 benni "\nYou", "your old level", level);
695 11a5e2cf 2024-01-16 benni sp->hs_time = now; /* renew it */
696 11a5e2cf 2024-01-16 benni change = 1; /* gotta rewrite, sigh */
697 11a5e2cf 2024-01-16 benni } /* else new score < old score: do nothing */
700 11a5e2cf 2024-01-16 benni if (i >= nscores) {
701 11a5e2cf 2024-01-16 benni strcpy(sp->hs_name, me);
702 11a5e2cf 2024-01-16 benni sp->hs_level = level;
703 11a5e2cf 2024-01-16 benni sp->hs_score = score;
704 11a5e2cf 2024-01-16 benni sp->hs_time = now;
705 11a5e2cf 2024-01-16 benni nscores++;
706 11a5e2cf 2024-01-16 benni change = 1;
709 11a5e2cf 2024-01-16 benni if (change) {
711 11a5e2cf 2024-01-16 benni * Sort & clean the scores, then rewrite.
713 11a5e2cf 2024-01-16 benni nscores = checkscores(scores, nscores);
714 11a5e2cf 2024-01-16 benni putscores(sd);
716 11a5e2cf 2024-01-16 benni closescores(sd);
720 11a5e2cf 2024-01-16 benni * Get login name, or if that fails, get something suitable.
721 11a5e2cf 2024-01-16 benni * The result is always trimmed to fit in a score.
723 11a5e2cf 2024-01-16 benni static char *
724 11a5e2cf 2024-01-16 benni thisuser(void)
726 11a5e2cf 2024-01-16 benni const char *p;
727 11a5e2cf 2024-01-16 benni struct passwd *pw;
729 11a5e2cf 2024-01-16 benni static char u[sizeof(scores[0].hs_name)];
732 11a5e2cf 2024-01-16 benni return (u);
733 11a5e2cf 2024-01-16 benni p = getlogin();
734 11a5e2cf 2024-01-16 benni if (p == NULL || *p == '\0') {
735 11a5e2cf 2024-01-16 benni pw = getpwuid(getuid());
736 11a5e2cf 2024-01-16 benni if (pw != NULL)
737 11a5e2cf 2024-01-16 benni p = pw->pw_name;
739 11a5e2cf 2024-01-16 benni p = " ???";
741 11a5e2cf 2024-01-16 benni l = strlen(p);
742 11a5e2cf 2024-01-16 benni if (l >= sizeof(u))
743 11a5e2cf 2024-01-16 benni l = sizeof(u) - 1;
744 11a5e2cf 2024-01-16 benni memcpy(u, p, l);
745 11a5e2cf 2024-01-16 benni u[l] = '\0';
746 11a5e2cf 2024-01-16 benni return (u);
750 11a5e2cf 2024-01-16 benni * Score comparison function for qsort.
752 11a5e2cf 2024-01-16 benni * If two scores are equal, the person who had the score first is
753 11a5e2cf 2024-01-16 benni * listed first in the highscore file.
755 11a5e2cf 2024-01-16 benni static int
756 11a5e2cf 2024-01-16 benni cmpscores(const void *x, const void *y)
758 11a5e2cf 2024-01-16 benni const struct highscore *a, *b;
763 11a5e2cf 2024-01-16 benni l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score;
764 11a5e2cf 2024-01-16 benni if (l < 0)
765 11a5e2cf 2024-01-16 benni return (-1);
766 11a5e2cf 2024-01-16 benni if (l > 0)
767 11a5e2cf 2024-01-16 benni return (1);
768 11a5e2cf 2024-01-16 benni if (a->hs_time < b->hs_time)
769 11a5e2cf 2024-01-16 benni return (-1);
770 11a5e2cf 2024-01-16 benni if (a->hs_time > b->hs_time)
771 11a5e2cf 2024-01-16 benni return (1);
772 11a5e2cf 2024-01-16 benni return (0);
776 11a5e2cf 2024-01-16 benni * If we've added a score to the file, we need to check the file and ensure
777 11a5e2cf 2024-01-16 benni * that this player has only a few entries. The number of entries is
778 11a5e2cf 2024-01-16 benni * controlled by MAXSCORES, and is to ensure that the highscore file is not
779 11a5e2cf 2024-01-16 benni * monopolised by just a few people. People who no longer have accounts are
780 11a5e2cf 2024-01-16 benni * only allowed the highest score. Scores older than EXPIRATION seconds are
781 11a5e2cf 2024-01-16 benni * removed, unless they are someone's personal best.
782 11a5e2cf 2024-01-16 benni * Caveat: the highest score on each level is always kept.
784 11a5e2cf 2024-01-16 benni static int
785 11a5e2cf 2024-01-16 benni checkscores(struct highscore *hs, int num)
787 11a5e2cf 2024-01-16 benni struct highscore *sp;
788 11a5e2cf 2024-01-16 benni int i, j, k, numnames;
789 11a5e2cf 2024-01-16 benni int levelfound[NLEVELS];
790 11a5e2cf 2024-01-16 benni struct peruser {
791 11a5e2cf 2024-01-16 benni char *name;
792 11a5e2cf 2024-01-16 benni int times;
793 11a5e2cf 2024-01-16 benni } count[NUMSPOTS];
794 11a5e2cf 2024-01-16 benni struct peruser *pu;
797 11a5e2cf 2024-01-16 benni * Sort so that highest totals come first.
799 11a5e2cf 2024-01-16 benni * levelfound[i] becomes set when the first high score for that
800 11a5e2cf 2024-01-16 benni * level is encountered. By definition this is the highest score.
802 11a5e2cf 2024-01-16 benni qsort((void *)hs, nscores, sizeof(*hs), cmpscores);
803 11a5e2cf 2024-01-16 benni for (i = MINLEVEL; i < NLEVELS; i++)
804 11a5e2cf 2024-01-16 benni levelfound[i] = 0;
805 11a5e2cf 2024-01-16 benni numnames = 0;
806 11a5e2cf 2024-01-16 benni for (i = 0, sp = hs; i < num;) {
808 11a5e2cf 2024-01-16 benni * This is O(n^2), but do you think we care?
810 11a5e2cf 2024-01-16 benni for (j = 0, pu = count; j < numnames; j++, pu++)
811 11a5e2cf 2024-01-16 benni if (strcmp(sp->hs_name, pu->name) == 0)
813 11a5e2cf 2024-01-16 benni if (j == numnames) {
815 11a5e2cf 2024-01-16 benni * Add new user, set per-user count to 1.
817 11a5e2cf 2024-01-16 benni pu->name = sp->hs_name;
818 11a5e2cf 2024-01-16 benni pu->times = 1;
819 11a5e2cf 2024-01-16 benni numnames++;
822 11a5e2cf 2024-01-16 benni * Two ways to keep this score:
823 11a5e2cf 2024-01-16 benni * - Not too many (per user), still has acct, &
824 11a5e2cf 2024-01-16 benni * score not dated; or
825 11a5e2cf 2024-01-16 benni * - High score on this level.
827 11a5e2cf 2024-01-16 benni if ((pu->times < MAXSCORES &&
828 11a5e2cf 2024-01-16 benni getpwnam(sp->hs_name) != NULL &&
829 11a5e2cf 2024-01-16 benni sp->hs_time + EXPIRATION >= now) ||
830 11a5e2cf 2024-01-16 benni levelfound[sp->hs_level] == 0)
831 11a5e2cf 2024-01-16 benni pu->times++;
834 11a5e2cf 2024-01-16 benni * Delete this score, do not count it,
835 11a5e2cf 2024-01-16 benni * do not pass go, do not collect $200.
838 11a5e2cf 2024-01-16 benni for (k = i; k < num; k++)
839 11a5e2cf 2024-01-16 benni hs[k] = hs[k + 1];
843 11a5e2cf 2024-01-16 benni if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
844 11a5e2cf 2024-01-16 benni levelfound[sp->hs_level] = 1;
845 11a5e2cf 2024-01-16 benni i++, sp++;
847 11a5e2cf 2024-01-16 benni return (num > MAXHISCORES ? MAXHISCORES : num);
851 11a5e2cf 2024-01-16 benni * Show current scores. This must be called after savescore, if
852 11a5e2cf 2024-01-16 benni * savescore is called at all, for two reasons:
853 11a5e2cf 2024-01-16 benni * - Showscores munches the time field.
854 11a5e2cf 2024-01-16 benni * - Even if that were not the case, a new score must be recorded
855 11a5e2cf 2024-01-16 benni * before it can be shown anyway.
858 11a5e2cf 2024-01-16 benni showscores(int level)
860 11a5e2cf 2024-01-16 benni struct highscore *sp;
861 11a5e2cf 2024-01-16 benni int i, n, c;
862 11a5e2cf 2024-01-16 benni const char *me;
863 11a5e2cf 2024-01-16 benni int levelfound[NLEVELS];
865 11a5e2cf 2024-01-16 benni if (!gotscores)
866 11a5e2cf 2024-01-16 benni getscores(NULL);
867 11a5e2cf 2024-01-16 benni (void)printf("\n\t\t\t Tetris High Scores\n");
870 11a5e2cf 2024-01-16 benni * If level == 0, the person has not played a game but just asked for
871 11a5e2cf 2024-01-16 benni * the high scores; we do not need to check for printing in highlight
872 11a5e2cf 2024-01-16 benni * mode. If SOstr is null, we can't do highlighting anyway.
874 11a5e2cf 2024-01-16 benni me = level && enter_standout_mode ? thisuser() : NULL;
877 11a5e2cf 2024-01-16 benni * Set times to 0 except for high score on each level.
879 11a5e2cf 2024-01-16 benni for (i = MINLEVEL; i < NLEVELS; i++)
880 11a5e2cf 2024-01-16 benni levelfound[i] = 0;
881 11a5e2cf 2024-01-16 benni for (i = 0, sp = scores; i < nscores; i++, sp++) {
882 11a5e2cf 2024-01-16 benni if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
883 11a5e2cf 2024-01-16 benni if (levelfound[sp->hs_level])
884 11a5e2cf 2024-01-16 benni sp->hs_time = 0;
886 11a5e2cf 2024-01-16 benni sp->hs_time = 1;
887 11a5e2cf 2024-01-16 benni levelfound[sp->hs_level] = 1;
893 11a5e2cf 2024-01-16 benni * Page each screenful of scores.
895 11a5e2cf 2024-01-16 benni for (i = 0, sp = scores; i < nscores; sp += n) {
897 11a5e2cf 2024-01-16 benni if (i + n > nscores)
898 11a5e2cf 2024-01-16 benni n = nscores - i;
899 11a5e2cf 2024-01-16 benni printem(level, i + 1, sp, n, me);
900 11a5e2cf 2024-01-16 benni if ((i += n) < nscores) {
901 11a5e2cf 2024-01-16 benni (void)printf("\nHit RETURN to continue.");
902 11a5e2cf 2024-01-16 benni (void)fflush(stdout);
903 11a5e2cf 2024-01-16 benni while ((c = getchar()) != '\n')
904 11a5e2cf 2024-01-16 benni if (c == EOF)
906 11a5e2cf 2024-01-16 benni (void)printf("\n");
911 11a5e2cf 2024-01-16 benni static void
912 11a5e2cf 2024-01-16 benni printem(int level, int offset, struct highscore *hs, int n, const char *me)
914 11a5e2cf 2024-01-16 benni struct highscore *sp;
915 11a5e2cf 2024-01-16 benni int nrows, row, col, item, i, highlight;
916 11a5e2cf 2024-01-16 benni char buf[100];
917 11a5e2cf 2024-01-16 benni #define TITLE "Rank Score Name (points/level)"
920 11a5e2cf 2024-01-16 benni * This makes a nice two-column sort with headers, but it's a bit
921 11a5e2cf 2024-01-16 benni * convoluted...
923 11a5e2cf 2024-01-16 benni printf("%s %s\n", TITLE, n > 1 ? TITLE : "");
925 11a5e2cf 2024-01-16 benni highlight = 0;
926 11a5e2cf 2024-01-16 benni nrows = (n + 1) / 2;
928 11a5e2cf 2024-01-16 benni for (row = 0; row < nrows; row++) {
929 11a5e2cf 2024-01-16 benni for (col = 0; col < 2; col++) {
930 11a5e2cf 2024-01-16 benni item = col * nrows + row;
931 11a5e2cf 2024-01-16 benni if (item >= n) {
933 11a5e2cf 2024-01-16 benni * Can only occur on trailing columns.
935 11a5e2cf 2024-01-16 benni (void)putchar('\n');
938 11a5e2cf 2024-01-16 benni sp = &hs[item];
939 11a5e2cf 2024-01-16 benni (void)snprintf(buf, sizeof(buf),
940 11a5e2cf 2024-01-16 benni "%3d%c %6d %-11s (%6d on %d)",
941 11a5e2cf 2024-01-16 benni item + offset, sp->hs_time ? '*' : ' ',
942 11a5e2cf 2024-01-16 benni sp->hs_score * sp->hs_level,
943 11a5e2cf 2024-01-16 benni sp->hs_name, sp->hs_score, sp->hs_level);
945 11a5e2cf 2024-01-16 benni * Highlight if appropriate. This works because
946 11a5e2cf 2024-01-16 benni * we only get one score per level.
948 11a5e2cf 2024-01-16 benni if (me != NULL &&
949 11a5e2cf 2024-01-16 benni sp->hs_level == level &&
950 11a5e2cf 2024-01-16 benni sp->hs_score == score &&
951 11a5e2cf 2024-01-16 benni strcmp(sp->hs_name, me) == 0) {
952 11a5e2cf 2024-01-16 benni putpad(enter_standout_mode);
953 11a5e2cf 2024-01-16 benni highlight = 1;
955 11a5e2cf 2024-01-16 benni (void)printf("%s", buf);
956 11a5e2cf 2024-01-16 benni if (highlight) {
957 11a5e2cf 2024-01-16 benni putpad(exit_standout_mode);
958 11a5e2cf 2024-01-16 benni highlight = 0;
961 11a5e2cf 2024-01-16 benni /* fill in spaces so column 1 lines up */
962 11a5e2cf 2024-01-16 benni if (col == 0)
963 11a5e2cf 2024-01-16 benni for (i = 40 - strlen(buf); --i >= 0;)
964 11a5e2cf 2024-01-16 benni (void)putchar(' ');
965 11a5e2cf 2024-01-16 benni else /* col == 1 */
966 11a5e2cf 2024-01-16 benni (void)putchar('\n');