source: mainline/tetris/scores.c@ 854387b

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 854387b was e9a3c52, checked in by Jakub Jermar <jakub@…>, 19 years ago

Import BSD tetris from OpenBSD.
Needs to be integrated and ported.

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