]>
git.cameronkatri.com Git - bsdgames-darwin.git/blob - tetris/scores.c
1 /* $NetBSD: scores.c,v 1.4 1997/10/14 01:14:20 lukem Exp $ */
4 * Copyright (c) 1992, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
8 * Chris Torek and Darren F. Provine.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * @(#)scores.c 8.1 (Berkeley) 5/31/93
42 * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu)
43 * modified 22 January 1992, to limit the number of entries any one
46 * Major whacks since then.
58 #include "pathnames.h"
64 * Within this code, we can hang onto one extra "high score", leaving
65 * room for our current score (whether or not it is high).
67 * We also sometimes keep tabs on the "highest" score on each level.
68 * As long as the scores are kept sorted, this is simply the first one at
71 #define NUMSPOTS (MAXHISCORES + 1)
72 #define NLEVELS (MAXLEVEL + 1)
77 static struct highscore scores
[NUMSPOTS
];
79 static int checkscores
__P((struct highscore
*, int));
80 static int cmpscores
__P((const void *, const void *));
81 static void getscores
__P((FILE **));
82 static void printem
__P((int, int, struct highscore
*, int, const char *));
83 static char *thisuser
__P((void));
86 * Read the score file. Can be called from savescore (before showscores)
87 * or showscores (if savescore will not be called). If the given pointer
88 * is not NULL, sets *fpp to an open file pointer that corresponds to a
89 * read/write score file that is locked with LOCK_EX. Otherwise, the
90 * file is locked with LOCK_SH for the read and closed before return.
92 * Note, we assume closing the stdio file releases the lock.
103 mint
= O_RDWR
| O_CREAT
;
105 human
= "read/write";
113 sd
= open(_PATH_SCOREFILE
, mint
, 0666);
119 (void)fprintf(stderr
, "tetris: cannot open %s for %s: %s\n",
120 _PATH_SCOREFILE
, human
, strerror(errno
));
123 if ((sf
= fdopen(sd
, mstr
)) == NULL
) {
124 (void)fprintf(stderr
, "tetris: cannot fdopen %s for %s: %s\n",
125 _PATH_SCOREFILE
, human
, strerror(errno
));
133 (void)fprintf(stderr
,
134 "tetris: warning: score file %s cannot be locked: %s\n",
135 _PATH_SCOREFILE
, strerror(errno
));
137 nscores
= fread(scores
, sizeof(scores
[0]), MAXHISCORES
, sf
);
139 (void)fprintf(stderr
, "tetris: error reading %s: %s\n",
140 _PATH_SCOREFILE
, strerror(errno
));
154 register struct highscore
*sp
;
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).
171 for (i
= 0, sp
= &scores
[0]; i
< nscores
; i
++, sp
++) {
172 if (sp
->hs_level
!= level
|| strcmp(sp
->hs_name
, me
) != 0)
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 */
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 */
190 strcpy(sp
->hs_name
, me
);
191 sp
->hs_level
= level
;
192 sp
->hs_score
= score
;
200 * Sort & clean the scores, then rewrite.
202 nscores
= checkscores(scores
, nscores
);
204 if (fwrite(scores
, sizeof(*sp
), nscores
, sf
) != nscores
||
206 (void)fprintf(stderr
,
207 "tetris: error writing %s: %s -- %s\n",
208 _PATH_SCOREFILE
, strerror(errno
),
209 "high scores may be damaged");
211 (void)fclose(sf
); /* releases lock */
215 * Get login name, or if that fails, get something suitable.
216 * The result is always trimmed to fit in a score.
221 register const char *p
;
222 register struct passwd
*pw
;
224 static char u
[sizeof(scores
[0].hs_name
)];
229 if (p
== NULL
|| *p
== '\0') {
230 pw
= getpwuid(getuid());
245 * Score comparison function for qsort.
247 * If two scores are equal, the person who had the score first is
248 * listed first in the highscore file.
254 register const struct highscore
*a
, *b
;
259 l
= (long)b
->hs_level
* b
->hs_score
- (long)a
->hs_level
* a
->hs_score
;
264 if (a
->hs_time
< b
->hs_time
)
266 if (a
->hs_time
> b
->hs_time
)
272 * If we've added a score to the file, we need to check the file and ensure
273 * that this player has only a few entries. The number of entries is
274 * controlled by MAXSCORES, and is to ensure that the highscore file is not
275 * monopolised by just a few people. People who no longer have accounts are
276 * only allowed the highest score. Scores older than EXPIRATION seconds are
277 * removed, unless they are someone's personal best.
278 * Caveat: the highest score on each level is always kept.
282 register struct highscore
*hs
;
285 register struct highscore
*sp
;
286 register int i
, j
, k
, numnames
;
287 int levelfound
[NLEVELS
];
292 register struct peruser
*pu
;
295 * Sort so that highest totals come first.
297 * levelfound[i] becomes set when the first high score for that
298 * level is encountered. By definition this is the highest score.
300 qsort((void *)hs
, nscores
, sizeof(*hs
), cmpscores
);
301 for (i
= MINLEVEL
; i
< NLEVELS
; i
++)
304 for (i
= 0, sp
= hs
; i
< num
;) {
306 * This is O(n^2), but do you think we care?
308 for (j
= 0, pu
= count
; j
< numnames
; j
++, pu
++)
309 if (strcmp(sp
->hs_name
, pu
->name
) == 0)
313 * Add new user, set per-user count to 1.
315 pu
->name
= sp
->hs_name
;
320 * Two ways to keep this score:
321 * - Not too many (per user), still has acct, &
322 * score not dated; or
323 * - High score on this level.
325 if ((pu
->times
< MAXSCORES
&&
326 getpwnam(sp
->hs_name
) != NULL
&&
327 sp
->hs_time
+ EXPIRATION
>= now
) ||
328 levelfound
[sp
->hs_level
] == 0)
332 * Delete this score, do not count it,
333 * do not pass go, do not collect $200.
336 for (k
= i
; k
< num
; k
++)
341 levelfound
[sp
->hs_level
] = 1;
344 return (num
> MAXHISCORES
? MAXHISCORES
: num
);
348 * Show current scores. This must be called after savescore, if
349 * savescore is called at all, for two reasons:
350 * - Showscores munches the time field.
351 * - Even if that were not the case, a new score must be recorded
352 * before it can be shown anyway.
358 register struct highscore
*sp
;
359 register int i
, n
, c
;
361 int levelfound
[NLEVELS
];
364 getscores((FILE **)NULL
);
365 (void)printf("\n\t\t\t Tetris High Scores\n");
368 * If level == 0, the person has not played a game but just asked for
369 * the high scores; we do not need to check for printing in highlight
370 * mode. If SOstr is null, we can't do highlighting anyway.
372 me
= level
&& SOstr
? thisuser() : NULL
;
375 * Set times to 0 except for high score on each level.
377 for (i
= MINLEVEL
; i
< NLEVELS
; i
++)
379 for (i
= 0, sp
= scores
; i
< nscores
; i
++, sp
++) {
380 if (levelfound
[sp
->hs_level
])
384 levelfound
[sp
->hs_level
] = 1;
389 * Page each screenful of scores.
391 for (i
= 0, sp
= scores
; i
< nscores
; sp
+= n
) {
395 printem(level
, i
+ 1, sp
, n
, me
);
396 if ((i
+= n
) < nscores
) {
397 (void)printf("\nHit RETURN to continue.");
398 (void)fflush(stdout
);
399 while ((c
= getchar()) != '\n')
408 printem(level
, offset
, hs
, n
, me
)
410 register struct highscore
*hs
;
414 register struct highscore
*sp
;
415 int nrows
, row
, col
, item
, i
, highlight
;
417 #define TITLE "Rank Score Name (points/level)"
420 * This makes a nice two-column sort with headers, but it's a bit
423 printf("%s %s\n", TITLE
, n
> 1 ? TITLE
: "");
428 for (row
= 0; row
< nrows
; row
++) {
429 for (col
= 0; col
< 2; col
++) {
430 item
= col
* nrows
+ row
;
433 * Can only occur on trailing columns.
438 (void)printf(item
+ offset
< 10 ? " " : " ");
441 "%d%c %6d %-11s (%d on %d)",
442 item
+ offset
, sp
->hs_time
? '*' : ' ',
443 sp
->hs_score
* sp
->hs_level
,
444 sp
->hs_name
, sp
->hs_score
, sp
->hs_level
);
446 * Highlight if appropriate. This works because
447 * we only get one score per level.
450 sp
->hs_level
== level
&&
451 sp
->hs_score
== score
&&
452 strcmp(sp
->hs_name
, me
) == 0) {
456 (void)printf("%s", buf
);
462 /* fill in spaces so column 1 lines up */
464 for (i
= 40 - strlen(buf
); --i
>= 0;)