]> git.cameronkatri.com Git - bsdgames-darwin.git/blob - tetris/scores.c
Both legs of the conditional operator must return the same type.
[bsdgames-darwin.git] / tetris / scores.c
1 /* $NetBSD: scores.c,v 1.14 2006/06/01 16:12:27 drochner 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 <termcap.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 * Within this code, we can hang onto one extra "high score", leaving
63 * room for our current score (whether or not it is high).
64 *
65 * We also sometimes keep tabs on the "highest" score on each level.
66 * As long as the scores are kept sorted, this is simply the first one at
67 * that level.
68 */
69 #define NUMSPOTS (MAXHISCORES + 1)
70 #define NLEVELS (MAXLEVEL + 1)
71
72 static time_t now;
73 static int nscores;
74 static int gotscores;
75 static struct highscore scores[NUMSPOTS];
76
77 static int checkscores(struct highscore *, int);
78 static int cmpscores(const void *, const void *);
79 static void getscores(FILE **);
80 static void printem(int, int, struct highscore *, int, const char *);
81 static char *thisuser(void);
82
83 /*
84 * Read the score file. Can be called from savescore (before showscores)
85 * or showscores (if savescore will not be called). If the given pointer
86 * is not NULL, sets *fpp to an open file pointer that corresponds to a
87 * read/write score file that is locked with LOCK_EX. Otherwise, the
88 * file is locked with LOCK_SH for the read and closed before return.
89 *
90 * Note, we assume closing the stdio file releases the lock.
91 */
92 static void
93 getscores(fpp)
94 FILE **fpp;
95 {
96 int sd, mint, lck;
97 mode_t mask;
98 const char *mstr, *human;
99 FILE *sf;
100
101 if (fpp != NULL) {
102 mint = O_RDWR | O_CREAT;
103 mstr = "r+";
104 human = "read/write";
105 lck = LOCK_EX;
106 } else {
107 mint = O_RDONLY;
108 mstr = "r";
109 human = "reading";
110 lck = LOCK_SH;
111 }
112 setegid(egid);
113 mask = umask(S_IWOTH);
114 sd = open(_PATH_SCOREFILE, mint, 0666);
115 (void)umask(mask);
116 if (sd < 0) {
117 if (fpp == NULL) {
118 nscores = 0;
119 setegid(gid);
120 return;
121 }
122 err(1, "cannot open %s for %s", _PATH_SCOREFILE, human);
123 }
124 if ((sf = fdopen(sd, mstr)) == NULL) {
125 err(1, "cannot fdopen %s for %s", _PATH_SCOREFILE, human);
126 }
127 setegid(gid);
128
129 /*
130 * Grab a lock.
131 */
132 if (flock(sd, lck))
133 warn("warning: score file %s cannot be locked",
134 _PATH_SCOREFILE);
135
136 nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf);
137 if (ferror(sf)) {
138 err(1, "error reading %s", _PATH_SCOREFILE);
139 }
140
141 if (fpp)
142 *fpp = sf;
143 else
144 (void)fclose(sf);
145 }
146
147 void
148 savescore(level)
149 int level;
150 {
151 struct highscore *sp;
152 int i;
153 int change;
154 FILE *sf;
155 const char *me;
156
157 getscores(&sf);
158 gotscores = 1;
159 (void)time(&now);
160
161 /*
162 * Allow at most one score per person per level -- see if we
163 * can replace an existing score, or (easiest) do nothing.
164 * Otherwise add new score at end (there is always room).
165 */
166 change = 0;
167 me = thisuser();
168 for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) {
169 if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0)
170 continue;
171 if (score > sp->hs_score) {
172 (void)printf("%s bettered %s %d score of %d!\n",
173 "\nYou", "your old level", level,
174 sp->hs_score * sp->hs_level);
175 sp->hs_score = score; /* new score */
176 sp->hs_time = now; /* and time */
177 change = 1;
178 } else if (score == sp->hs_score) {
179 (void)printf("%s tied %s %d high score.\n",
180 "\nYou", "your old level", level);
181 sp->hs_time = now; /* renew it */
182 change = 1; /* gotta rewrite, sigh */
183 } /* else new score < old score: do nothing */
184 break;
185 }
186 if (i >= nscores) {
187 strcpy(sp->hs_name, me);
188 sp->hs_level = level;
189 sp->hs_score = score;
190 sp->hs_time = now;
191 nscores++;
192 change = 1;
193 }
194
195 if (change) {
196 /*
197 * Sort & clean the scores, then rewrite.
198 */
199 nscores = checkscores(scores, nscores);
200 rewind(sf);
201 if (fwrite(scores, sizeof(*sp), nscores, sf) != (size_t)nscores ||
202 fflush(sf) == EOF)
203 warnx("error writing %s: %s -- %s",
204 _PATH_SCOREFILE, strerror(errno),
205 "high scores may be damaged");
206 }
207 (void)fclose(sf); /* releases lock */
208 }
209
210 /*
211 * Get login name, or if that fails, get something suitable.
212 * The result is always trimmed to fit in a score.
213 */
214 static char *
215 thisuser()
216 {
217 const char *p;
218 struct passwd *pw;
219 size_t l;
220 static char u[sizeof(scores[0].hs_name)];
221
222 if (u[0])
223 return (u);
224 p = getlogin();
225 if (p == NULL || *p == '\0') {
226 pw = getpwuid(getuid());
227 if (pw != NULL)
228 p = pw->pw_name;
229 else
230 p = " ???";
231 }
232 l = strlen(p);
233 if (l >= sizeof(u))
234 l = sizeof(u) - 1;
235 memcpy(u, p, l);
236 u[l] = '\0';
237 return (u);
238 }
239
240 /*
241 * Score comparison function for qsort.
242 *
243 * If two scores are equal, the person who had the score first is
244 * listed first in the highscore file.
245 */
246 static int
247 cmpscores(x, y)
248 const void *x, *y;
249 {
250 const struct highscore *a, *b;
251 long l;
252
253 a = x;
254 b = y;
255 l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score;
256 if (l < 0)
257 return (-1);
258 if (l > 0)
259 return (1);
260 if (a->hs_time < b->hs_time)
261 return (-1);
262 if (a->hs_time > b->hs_time)
263 return (1);
264 return (0);
265 }
266
267 /*
268 * If we've added a score to the file, we need to check the file and ensure
269 * that this player has only a few entries. The number of entries is
270 * controlled by MAXSCORES, and is to ensure that the highscore file is not
271 * monopolised by just a few people. People who no longer have accounts are
272 * only allowed the highest score. Scores older than EXPIRATION seconds are
273 * removed, unless they are someone's personal best.
274 * Caveat: the highest score on each level is always kept.
275 */
276 static int
277 checkscores(hs, num)
278 struct highscore *hs;
279 int num;
280 {
281 struct highscore *sp;
282 int i, j, k, numnames;
283 int levelfound[NLEVELS];
284 struct peruser {
285 char *name;
286 int times;
287 } count[NUMSPOTS];
288 struct peruser *pu;
289
290 /*
291 * Sort so that highest totals come first.
292 *
293 * levelfound[i] becomes set when the first high score for that
294 * level is encountered. By definition this is the highest score.
295 */
296 qsort((void *)hs, nscores, sizeof(*hs), cmpscores);
297 for (i = MINLEVEL; i < NLEVELS; i++)
298 levelfound[i] = 0;
299 numnames = 0;
300 for (i = 0, sp = hs; i < num;) {
301 /*
302 * This is O(n^2), but do you think we care?
303 */
304 for (j = 0, pu = count; j < numnames; j++, pu++)
305 if (strcmp(sp->hs_name, pu->name) == 0)
306 break;
307 if (j == numnames) {
308 /*
309 * Add new user, set per-user count to 1.
310 */
311 pu->name = sp->hs_name;
312 pu->times = 1;
313 numnames++;
314 } else {
315 /*
316 * Two ways to keep this score:
317 * - Not too many (per user), still has acct, &
318 * score not dated; or
319 * - High score on this level.
320 */
321 if ((pu->times < MAXSCORES &&
322 getpwnam(sp->hs_name) != NULL &&
323 sp->hs_time + EXPIRATION >= now) ||
324 levelfound[sp->hs_level] == 0)
325 pu->times++;
326 else {
327 /*
328 * Delete this score, do not count it,
329 * do not pass go, do not collect $200.
330 */
331 num--;
332 for (k = i; k < num; k++)
333 hs[k] = hs[k + 1];
334 continue;
335 }
336 }
337 if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
338 levelfound[sp->hs_level] = 1;
339 i++, sp++;
340 }
341 return (num > MAXHISCORES ? MAXHISCORES : num);
342 }
343
344 /*
345 * Show current scores. This must be called after savescore, if
346 * savescore is called at all, for two reasons:
347 * - Showscores munches the time field.
348 * - Even if that were not the case, a new score must be recorded
349 * before it can be shown anyway.
350 */
351 void
352 showscores(level)
353 int level;
354 {
355 struct highscore *sp;
356 int i, n, c;
357 const char *me;
358 int levelfound[NLEVELS];
359
360 if (!gotscores)
361 getscores((FILE **)NULL);
362 (void)printf("\n\t\t\t Tetris High Scores\n");
363
364 /*
365 * If level == 0, the person has not played a game but just asked for
366 * the high scores; we do not need to check for printing in highlight
367 * mode. If SOstr is null, we can't do highlighting anyway.
368 */
369 me = level && SOstr ? thisuser() : NULL;
370
371 /*
372 * Set times to 0 except for high score on each level.
373 */
374 for (i = MINLEVEL; i < NLEVELS; i++)
375 levelfound[i] = 0;
376 for (i = 0, sp = scores; i < nscores; i++, sp++) {
377 if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
378 if (levelfound[sp->hs_level])
379 sp->hs_time = 0;
380 else {
381 sp->hs_time = 1;
382 levelfound[sp->hs_level] = 1;
383 }
384 }
385 }
386
387 /*
388 * Page each screenful of scores.
389 */
390 for (i = 0, sp = scores; i < nscores; sp += n) {
391 n = 40;
392 if (i + n > nscores)
393 n = nscores - i;
394 printem(level, i + 1, sp, n, me);
395 if ((i += n) < nscores) {
396 (void)printf("\nHit RETURN to continue.");
397 (void)fflush(stdout);
398 while ((c = getchar()) != '\n')
399 if (c == EOF)
400 break;
401 (void)printf("\n");
402 }
403 }
404 }
405
406 static void
407 printem(level, offset, hs, n, me)
408 int level, offset;
409 struct highscore *hs;
410 int n;
411 const char *me;
412 {
413 struct highscore *sp;
414 int nrows, row, col, item, i, highlight;
415 char buf[100];
416 #define TITLE "Rank Score Name (points/level)"
417
418 /*
419 * This makes a nice two-column sort with headers, but it's a bit
420 * convoluted...
421 */
422 printf("%s %s\n", TITLE, n > 1 ? TITLE : "");
423
424 highlight = 0;
425 nrows = (n + 1) / 2;
426
427 for (row = 0; row < nrows; row++) {
428 for (col = 0; col < 2; col++) {
429 item = col * nrows + row;
430 if (item >= n) {
431 /*
432 * Can only occur on trailing columns.
433 */
434 (void)putchar('\n');
435 continue;
436 }
437 sp = &hs[item];
438 (void)snprintf(buf, sizeof(buf),
439 "%3d%c %6d %-11s (%6d on %d)",
440 item + offset, sp->hs_time ? '*' : ' ',
441 sp->hs_score * sp->hs_level,
442 sp->hs_name, sp->hs_score, sp->hs_level);
443 /*
444 * Highlight if appropriate. This works because
445 * we only get one score per level.
446 */
447 if (me != NULL &&
448 sp->hs_level == level &&
449 sp->hs_score == score &&
450 strcmp(sp->hs_name, me) == 0) {
451 putpad(SOstr);
452 highlight = 1;
453 }
454 (void)printf("%s", buf);
455 if (highlight) {
456 putpad(SEstr);
457 highlight = 0;
458 }
459
460 /* fill in spaces so column 1 lines up */
461 if (col == 0)
462 for (i = 40 - strlen(buf); --i >= 0;)
463 (void)putchar(' ');
464 else /* col == 1 */
465 (void)putchar('\n');
466 }
467 }
468 }