1 /* $Id: score.c,v 1.5 2002/03/02 21:02:21 sverrehu Exp $ */
2 /**************************************************************************
3 *
4 * FILE score.c
5 * MODULE OF Card game.
6 *
7 * WRITTEN BY Sverre H. Huseby <shh@thathost.com>
8 *
9 **************************************************************************/
10
11 #include <stdlib.h>
12 #include <string.h>
13 #include <time.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <pwd.h>
19
20 #include <shhmsg.h>
21 #include <xalloc.h>
22
23 #include "game.h"
24 #include "board.h"
25 #include "suid.h"
26 #include "score.h"
27
28 /**************************************************************************
29 * *
30 * P R I V A T E D A T A *
31 * *
32 **************************************************************************/
33
34 typedef struct {
35 char *userName;
36 char *realName;
37 long score;
38 char *dateTime;
39 } Highscore;
40
41 static Highscore *highscore[MAX_HIGHSCORES];
42 static int numHighscores;
43 static time_t lastUpdate = 0;
44 static void (*highscoresChangedCallback)(void) = NULL;
45
46
47 /**************************************************************************
48 * *
49 * P R I V A T E F U N C T I O N S *
50 * *
51 **************************************************************************/
52
53 static Highscore *
scoreNew(void)54 scoreNew(void)
55 {
56 Highscore *hs;
57
58 if ((hs = malloc(sizeof(Highscore))) == NULL)
59 msgFatal("out of memory\n");
60 hs->userName = NULL;
61 hs->realName = NULL;
62 hs->score = 0L;
63 hs->dateTime = NULL;
64 return hs;
65 }
66
67 static Highscore *
scoreNewInit(char * uname,char * rname,long score,char * dt)68 scoreNewInit(char *uname, char *rname, long score, char *dt)
69 {
70 Highscore *hs;
71
72 if ((hs = malloc(sizeof(Highscore))) == NULL)
73 msgFatal("out of memory\n");
74 hs->userName = xstrdup(uname);
75 hs->realName = xstrdup(rname);
76 hs->score = score;
77 hs->dateTime = xstrdup(dt);
78 return hs;
79 }
80
81 static Highscore *
scoreNewCurrent(void)82 scoreNewCurrent(void)
83 {
84 Highscore *hs;
85 struct passwd *pw;
86 char dateTime[20], *rname, *s;
87 struct tm *tm;
88 time_t tt;
89
90 if ((pw = getpwuid(getuid())) == NULL) {
91 msgError("unable to get password info\n");
92 return NULL;
93 }
94 rname = xstrdup(pw->pw_gecos);
95 if ((s = strchr(rname, ',')) != NULL)
96 *s = '\0';
97 time(&tt);
98 tm = localtime(&tt);
99 strftime(dateTime, sizeof(dateTime), "%Y-%m-%d %H:%M:%S", tm);
100 hs = scoreNewInit(pw->pw_name, rname, gameTime, dateTime);
101 free(rname);
102 return hs;
103 }
104
105 void
scoreFree(Highscore * hs)106 scoreFree(Highscore *hs)
107 {
108 if (!hs)
109 return;
110 free(hs->userName);
111 free(hs->realName);
112 free(hs->dateTime);
113 free(hs);
114 }
115
116 static time_t
scoreGetUpdateTime(void)117 scoreGetUpdateTime(void)
118 {
119 struct stat st;
120
121 if (stat(SCOREFILE, &st) < 0)
122 return 0;
123 return st.st_mtime;
124 }
125
126 static void
scoreDelete(int n)127 scoreDelete(int n)
128 {
129 int q;
130
131 if (n >= MAX_HIGHSCORES)
132 return;
133 scoreFree(highscore[n]);
134 for (q = n; q < numHighscores - 1; q++)
135 highscore[q] = highscore[q + 1];
136 highscore[numHighscores - 1] = scoreNew();
137 if (n < numHighscores)
138 --numHighscores;
139 }
140
141 static void
scoreInsert(int n,Highscore * hs)142 scoreInsert(int n, Highscore *hs)
143 {
144 int q;
145
146 if (numHighscores == MAX_HIGHSCORES) {
147 scoreFree(highscore[numHighscores - 1]);
148 --numHighscores;
149 }
150 for (q = numHighscores; q > n; q--)
151 highscore[q] = highscore[q - 1];
152 highscore[n] = hs;
153 ++numHighscores;
154 }
155
156 static int
scoreFindUser(const char * user)157 scoreFindUser(const char *user)
158 {
159 int q;
160
161 for (q = 0; q < numHighscores; q++)
162 if (strcmp(highscore[q]->userName, user) == 0)
163 return q;
164 return -1;
165 }
166
167 static void
scoreLockHelp(FILE * f,short type)168 scoreLockHelp(FILE *f, short type)
169 {
170 struct flock fl;
171
172 fl.l_type = type;
173 fl.l_start = 0;
174 fl.l_whence = SEEK_SET;
175 fl.l_len = 0; /* lock to EOF */
176 if (fcntl(fileno(f), F_SETLKW, &fl) < 0)
177 msgFatal("error locking highscore file\n");
178 }
179
180 static void
scoreReadLockFile(FILE * f)181 scoreReadLockFile(FILE *f)
182 {
183 scoreLockHelp(f, F_RDLCK);
184 }
185
186 static void
scoreWriteLockFile(FILE * f)187 scoreWriteLockFile(FILE *f)
188 {
189 scoreLockHelp(f, F_WRLCK);
190 }
191
192 static void
scoreUnlockFile(FILE * f)193 scoreUnlockFile(FILE *f)
194 {
195 fflush(f);
196 scoreLockHelp(f, F_UNLCK);
197 }
198
199 static Highscore *
scoreReadScoreLine(FILE * f)200 scoreReadScoreLine(FILE *f)
201 {
202 char line[100], *uname, *rname, *score, *dateTime;
203 Highscore *hs = NULL;
204
205 if (fgets(line, sizeof(line), f)) {
206 if (line[strlen(line) - 1] == '\n')
207 line[strlen(line) - 1] = '\0';
208 if ((uname = strtok(line, ",")) == NULL)
209 msgFatal("error in highscore file\n");
210 if ((rname = strtok(NULL, ",")) == NULL)
211 msgFatal("error in highscore file\n");
212 if ((score = strtok(NULL, ",")) == NULL)
213 msgFatal("error in highscore file\n");
214 if ((dateTime = strtok(NULL, ",")) == NULL)
215 msgFatal("error in highscore file\n");
216 hs = scoreNewInit(uname, rname, atol(score), dateTime);
217 }
218 return hs;
219 }
220
221 static void
scoreReadScoreLines(FILE * f)222 scoreReadScoreLines(FILE *f)
223 {
224 int n;
225 char line[81];
226 Highscore *hs;
227
228 numHighscores = 0;
229 if (fgets(line, sizeof(line), f) == NULL)
230 return;
231 n = atoi(line);
232 while (numHighscores < n && (hs = scoreReadScoreLine(f)) != NULL) {
233 scoreFree(highscore[numHighscores]);
234 highscore[numHighscores] = hs;
235 if (++numHighscores == MAX_HIGHSCORES)
236 break;
237 }
238 }
239
240 static void
scoreWriteScoreLines(FILE * f)241 scoreWriteScoreLines(FILE *f)
242 {
243 Highscore *hs;
244 int q;
245
246 fprintf(f, "%d\n", numHighscores);
247 for (q = 0; q < numHighscores; q++) {
248 hs = highscore[q];
249 /* strtok (used when reading score lines) doesn't like empty
250 * tokens, so we make sure no empty tokens are written out. */
251 fprintf(f, "%s,%s,%ld,%s\n",
252 strlen(hs->userName) ? hs->userName : "nobody",
253 strlen(hs->realName) ? hs->realName : "An Anonymous Gamer",
254 hs->score, hs->dateTime);
255 }
256 }
257
258 static void
scoreReadScoreFile(void)259 scoreReadScoreFile(void)
260 {
261 FILE *f;
262
263 suidStartPrivilegedAction();
264 numHighscores = 0;
265 if ((f = fopen(SCOREFILE, "r")) == NULL)
266 goto finish;
267 scoreReadLockFile(f);
268 scoreReadScoreLines(f);
269 scoreUnlockFile(f);
270 fclose(f);
271 finish:
272 suidEndPrivilegedAction();
273 }
274
275 static int
scorePossiblyAddEntry(Highscore * hs)276 scorePossiblyAddEntry(Highscore *hs)
277 {
278 int ret = 0, q, n;
279
280 if ((n = scoreFindUser(hs->userName)) >= 0) {
281 if (highscore[n]->score <= hs->score)
282 goto finish;
283 scoreDelete(n);
284 }
285 for (q = 0; q < numHighscores; q++)
286 if (highscore[q]->score > hs->score)
287 break;
288 if (q < MAX_HIGHSCORES) {
289 scoreInsert(q, hs);
290 ret = 1;
291 }
292 finish:
293 return ret;
294 }
295
296 static char *
scoreToTime(long score)297 scoreToTime(long score)
298 {
299 static char ret[40];
300 int h, m, s;
301
302 h = score / 3600;
303 m = (score % 3600) / 60;
304 s = score % 60;
305 if (h)
306 sprintf(ret, "%2d:%02d:%02d", h, m, s);
307 else
308 sprintf(ret, " %2d:%02d", m, s);
309 return ret;
310 }
311
312
313
314 /**************************************************************************
315 * *
316 * P U B L I C F U N C T I O N S *
317 * *
318 **************************************************************************/
319
320 void
scoreInit(void)321 scoreInit(void)
322 {
323 int q;
324
325 for (q = 0; q < MAX_HIGHSCORES; q++)
326 highscore[q] = scoreNew();
327 numHighscores = 0;
328 lastUpdate = scoreGetUpdateTime();
329 scoreReadScoreFile();
330 }
331
332 void
scoreFinish(void)333 scoreFinish(void)
334 {
335 int q;
336
337 for (q = 0; q < MAX_HIGHSCORES; q++)
338 scoreFree(highscore[q]);
339 }
340
341 void
scoreInitGame(void)342 scoreInitGame(void)
343 {
344 }
345
346 void
scoreFinishGame(void)347 scoreFinishGame(void)
348 {
349 }
350
351 void
scoreStatHighscoreFile(void)352 scoreStatHighscoreFile(void)
353 {
354 time_t ut;
355
356 if (!lastUpdate)
357 return;
358 if ((ut = scoreGetUpdateTime()) > lastUpdate) {
359 lastUpdate = ut;
360 scoreReadScoreFile();
361 if (highscoresChangedCallback)
362 highscoresChangedCallback();
363 }
364 }
365
366 int
scoreGetThisPlayerIndex(void)367 scoreGetThisPlayerIndex(void)
368 {
369 struct passwd *pw;
370
371 if ((pw = getpwuid(getuid())) == NULL) {
372 msgError("unable to get password info\n");
373 return -1;
374 }
375 return scoreFindUser(pw->pw_name);
376 }
377
378 char *
scoreGetHeadStr(void)379 scoreGetHeadStr(void)
380 {
381 static char line[81];
382
383 sprintf(line, " # %-10.10s %-25.25s %9.9s %s",
384 "user", "name", " how long", "date time");
385 return line;
386 }
387
388 char *
scoreGetHeadSepStr(void)389 scoreGetHeadSepStr(void)
390 {
391 static char line[81];
392 int q;
393
394 for (q = 0; q < 72; q++)
395 line[q] = '-';
396 line[q] = '\0';
397 return line;
398 }
399
400 char *
scoreGetEntryStr(int n)401 scoreGetEntryStr(int n)
402 {
403 static char line[81];
404 Highscore *hs;
405
406 if (n < 0 || n >= MAX_HIGHSCORES)
407 return NULL;
408 hs = highscore[n];
409 if (n >= numHighscores)
410 sprintf(line, "%3d.", n + 1);
411 else
412 sprintf(line, "%3d. %-10.10s %-25.25s %9.9s %s", n + 1,
413 hs->userName, hs->realName,
414 scoreToTime(hs->score), hs->dateTime);
415 return line;
416 }
417
418 void
scoreDumpHighscores(void)419 scoreDumpHighscores(void)
420 {
421 int q;
422
423 if (!numHighscores)
424 scoreReadScoreFile();
425 printf("%s\n", scoreGetHeadStr());
426 printf("%s\n", scoreGetHeadSepStr());
427 for (q = 0; q < numHighscores; q++)
428 printf("%s\n", scoreGetEntryStr(q));
429 printf("\n");
430 }
431
432 void
scorePossiblyUpdateScores(void)433 scorePossiblyUpdateScores(void)
434 {
435 FILE *f;
436 Highscore *hs;
437
438 suidStartPrivilegedAction();
439 if ((hs = scoreNewCurrent()) == NULL)
440 goto finish;
441 numHighscores = 0;
442 if ((f = fopen(SCOREFILE, "r+")) == NULL) {
443 msgPerror("unable to write `%s'", SCOREFILE);
444 goto finish;
445 }
446 scoreWriteLockFile(f);
447 scoreReadScoreLines(f);
448 if (scorePossiblyAddEntry(hs)) {
449 rewind(f);
450 scoreWriteScoreLines(f);
451 scoreStatHighscoreFile();
452 } else
453 scoreFree(hs);
454 scoreUnlockFile(f);
455 fclose(f);
456 finish:
457 suidEndPrivilegedAction();
458 }
459
460 void
scoreMergeScoreFile(char * file)461 scoreMergeScoreFile(char *file)
462 {
463 int q, n, changed = 0;
464 FILE *orig, *merge;
465 char line[81];
466 Highscore *hs;
467
468 suidStartPrivilegedAction();
469 if (getuid() != 0 && getuid() != geteuid())
470 msgFatal("scorefile merging only allowed for game owner or root\n");
471
472 if ((merge = fopen(file, "r")) == NULL)
473 msgFatalPerror("unable to open `%s'", file);
474 scoreReadLockFile(merge);
475
476 /* read current highscores */
477 numHighscores = 0;
478 if ((orig = fopen(SCOREFILE, "r+")) == NULL) {
479 msgFatalPerror("unable to write `%s'", SCOREFILE);
480 goto finish;
481 }
482 scoreWriteLockFile(orig);
483 scoreReadScoreLines(orig);
484
485 /* merge new entries */
486 if (fgets(line, sizeof(line), merge) == NULL)
487 goto unlock;
488 n = atoi(line);
489 for (q = 0; q < n; q++)
490 if ((hs = scoreReadScoreLine(merge)) != NULL)
491 if (scorePossiblyAddEntry(hs))
492 changed = 1;
493 else
494 scoreFree(hs);
495
496 /* possibly save new highscore table */
497 if (changed) {
498 rewind(orig);
499 scoreWriteScoreLines(orig);
500 }
501
502 unlock:
503 scoreUnlockFile(orig);
504 fclose(orig);
505 scoreUnlockFile(merge);
506 fclose(merge);
507 finish:
508 suidEndPrivilegedAction();
509 }
510
scoreSetHigscoresChangedCallback(void (* f)(void))511 void scoreSetHigscoresChangedCallback(void (*f)(void))
512 {
513 highscoresChangedCallback = f;
514 }
515