/* $Id: score.c,v 1.5 2002/03/02 21:02:21 sverrehu Exp $ */ /************************************************************************** * * FILE score.c * MODULE OF Card game. * * WRITTEN BY Sverre H. Huseby * **************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "game.h" #include "board.h" #include "suid.h" #include "score.h" /************************************************************************** * * * P R I V A T E D A T A * * * **************************************************************************/ typedef struct { char *userName; char *realName; long score; char *dateTime; } Highscore; static Highscore *highscore[MAX_HIGHSCORES]; static int numHighscores; static time_t lastUpdate = 0; static void (*highscoresChangedCallback)(void) = NULL; /************************************************************************** * * * P R I V A T E F U N C T I O N S * * * **************************************************************************/ static Highscore * scoreNew(void) { Highscore *hs; if ((hs = malloc(sizeof(Highscore))) == NULL) msgFatal("out of memory\n"); hs->userName = NULL; hs->realName = NULL; hs->score = 0L; hs->dateTime = NULL; return hs; } static Highscore * scoreNewInit(char *uname, char *rname, long score, char *dt) { Highscore *hs; if ((hs = malloc(sizeof(Highscore))) == NULL) msgFatal("out of memory\n"); hs->userName = xstrdup(uname); hs->realName = xstrdup(rname); hs->score = score; hs->dateTime = xstrdup(dt); return hs; } static Highscore * scoreNewCurrent(void) { Highscore *hs; struct passwd *pw; char dateTime[20], *rname, *s; struct tm *tm; time_t tt; if ((pw = getpwuid(getuid())) == NULL) { msgError("unable to get password info\n"); return NULL; } rname = xstrdup(pw->pw_gecos); if ((s = strchr(rname, ',')) != NULL) *s = '\0'; time(&tt); tm = localtime(&tt); strftime(dateTime, sizeof(dateTime), "%Y-%m-%d %H:%M:%S", tm); hs = scoreNewInit(pw->pw_name, rname, gameTime, dateTime); free(rname); return hs; } void scoreFree(Highscore *hs) { if (!hs) return; free(hs->userName); free(hs->realName); free(hs->dateTime); free(hs); } static time_t scoreGetUpdateTime(void) { struct stat st; if (stat(SCOREFILE, &st) < 0) return 0; return st.st_mtime; } static void scoreDelete(int n) { int q; if (n >= MAX_HIGHSCORES) return; scoreFree(highscore[n]); for (q = n; q < numHighscores - 1; q++) highscore[q] = highscore[q + 1]; highscore[numHighscores - 1] = scoreNew(); if (n < numHighscores) --numHighscores; } static void scoreInsert(int n, Highscore *hs) { int q; if (numHighscores == MAX_HIGHSCORES) { scoreFree(highscore[numHighscores - 1]); --numHighscores; } for (q = numHighscores; q > n; q--) highscore[q] = highscore[q - 1]; highscore[n] = hs; ++numHighscores; } static int scoreFindUser(const char *user) { int q; for (q = 0; q < numHighscores; q++) if (strcmp(highscore[q]->userName, user) == 0) return q; return -1; } static void scoreLockHelp(FILE *f, short type) { struct flock fl; fl.l_type = type; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; /* lock to EOF */ if (fcntl(fileno(f), F_SETLKW, &fl) < 0) msgFatal("error locking highscore file\n"); } static void scoreReadLockFile(FILE *f) { scoreLockHelp(f, F_RDLCK); } static void scoreWriteLockFile(FILE *f) { scoreLockHelp(f, F_WRLCK); } static void scoreUnlockFile(FILE *f) { fflush(f); scoreLockHelp(f, F_UNLCK); } static Highscore * scoreReadScoreLine(FILE *f) { char line[100], *uname, *rname, *score, *dateTime; Highscore *hs = NULL; if (fgets(line, sizeof(line), f)) { if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0'; if ((uname = strtok(line, ",")) == NULL) msgFatal("error in highscore file\n"); if ((rname = strtok(NULL, ",")) == NULL) msgFatal("error in highscore file\n"); if ((score = strtok(NULL, ",")) == NULL) msgFatal("error in highscore file\n"); if ((dateTime = strtok(NULL, ",")) == NULL) msgFatal("error in highscore file\n"); hs = scoreNewInit(uname, rname, atol(score), dateTime); } return hs; } static void scoreReadScoreLines(FILE *f) { int n; char line[81]; Highscore *hs; numHighscores = 0; if (fgets(line, sizeof(line), f) == NULL) return; n = atoi(line); while (numHighscores < n && (hs = scoreReadScoreLine(f)) != NULL) { scoreFree(highscore[numHighscores]); highscore[numHighscores] = hs; if (++numHighscores == MAX_HIGHSCORES) break; } } static void scoreWriteScoreLines(FILE *f) { Highscore *hs; int q; fprintf(f, "%d\n", numHighscores); for (q = 0; q < numHighscores; q++) { hs = highscore[q]; /* strtok (used when reading score lines) doesn't like empty * tokens, so we make sure no empty tokens are written out. */ fprintf(f, "%s,%s,%ld,%s\n", strlen(hs->userName) ? hs->userName : "nobody", strlen(hs->realName) ? hs->realName : "An Anonymous Gamer", hs->score, hs->dateTime); } } static void scoreReadScoreFile(void) { FILE *f; suidStartPrivilegedAction(); numHighscores = 0; if ((f = fopen(SCOREFILE, "r")) == NULL) goto finish; scoreReadLockFile(f); scoreReadScoreLines(f); scoreUnlockFile(f); fclose(f); finish: suidEndPrivilegedAction(); } static int scorePossiblyAddEntry(Highscore *hs) { int ret = 0, q, n; if ((n = scoreFindUser(hs->userName)) >= 0) { if (highscore[n]->score <= hs->score) goto finish; scoreDelete(n); } for (q = 0; q < numHighscores; q++) if (highscore[q]->score > hs->score) break; if (q < MAX_HIGHSCORES) { scoreInsert(q, hs); ret = 1; } finish: return ret; } static char * scoreToTime(long score) { static char ret[40]; int h, m, s; h = score / 3600; m = (score % 3600) / 60; s = score % 60; if (h) sprintf(ret, "%2d:%02d:%02d", h, m, s); else sprintf(ret, " %2d:%02d", m, s); return ret; } /************************************************************************** * * * P U B L I C F U N C T I O N S * * * **************************************************************************/ void scoreInit(void) { int q; for (q = 0; q < MAX_HIGHSCORES; q++) highscore[q] = scoreNew(); numHighscores = 0; lastUpdate = scoreGetUpdateTime(); scoreReadScoreFile(); } void scoreFinish(void) { int q; for (q = 0; q < MAX_HIGHSCORES; q++) scoreFree(highscore[q]); } void scoreInitGame(void) { } void scoreFinishGame(void) { } void scoreStatHighscoreFile(void) { time_t ut; if (!lastUpdate) return; if ((ut = scoreGetUpdateTime()) > lastUpdate) { lastUpdate = ut; scoreReadScoreFile(); if (highscoresChangedCallback) highscoresChangedCallback(); } } int scoreGetThisPlayerIndex(void) { struct passwd *pw; if ((pw = getpwuid(getuid())) == NULL) { msgError("unable to get password info\n"); return -1; } return scoreFindUser(pw->pw_name); } char * scoreGetHeadStr(void) { static char line[81]; sprintf(line, " # %-10.10s %-25.25s %9.9s %s", "user", "name", " how long", "date time"); return line; } char * scoreGetHeadSepStr(void) { static char line[81]; int q; for (q = 0; q < 72; q++) line[q] = '-'; line[q] = '\0'; return line; } char * scoreGetEntryStr(int n) { static char line[81]; Highscore *hs; if (n < 0 || n >= MAX_HIGHSCORES) return NULL; hs = highscore[n]; if (n >= numHighscores) sprintf(line, "%3d.", n + 1); else sprintf(line, "%3d. %-10.10s %-25.25s %9.9s %s", n + 1, hs->userName, hs->realName, scoreToTime(hs->score), hs->dateTime); return line; } void scoreDumpHighscores(void) { int q; if (!numHighscores) scoreReadScoreFile(); printf("%s\n", scoreGetHeadStr()); printf("%s\n", scoreGetHeadSepStr()); for (q = 0; q < numHighscores; q++) printf("%s\n", scoreGetEntryStr(q)); printf("\n"); } void scorePossiblyUpdateScores(void) { FILE *f; Highscore *hs; suidStartPrivilegedAction(); if ((hs = scoreNewCurrent()) == NULL) goto finish; numHighscores = 0; if ((f = fopen(SCOREFILE, "r+")) == NULL) { msgPerror("unable to write `%s'", SCOREFILE); goto finish; } scoreWriteLockFile(f); scoreReadScoreLines(f); if (scorePossiblyAddEntry(hs)) { rewind(f); scoreWriteScoreLines(f); scoreStatHighscoreFile(); } else scoreFree(hs); scoreUnlockFile(f); fclose(f); finish: suidEndPrivilegedAction(); } void scoreMergeScoreFile(char *file) { int q, n, changed = 0; FILE *orig, *merge; char line[81]; Highscore *hs; suidStartPrivilegedAction(); if (getuid() != 0 && getuid() != geteuid()) msgFatal("scorefile merging only allowed for game owner or root\n"); if ((merge = fopen(file, "r")) == NULL) msgFatalPerror("unable to open `%s'", file); scoreReadLockFile(merge); /* read current highscores */ numHighscores = 0; if ((orig = fopen(SCOREFILE, "r+")) == NULL) { msgFatalPerror("unable to write `%s'", SCOREFILE); goto finish; } scoreWriteLockFile(orig); scoreReadScoreLines(orig); /* merge new entries */ if (fgets(line, sizeof(line), merge) == NULL) goto unlock; n = atoi(line); for (q = 0; q < n; q++) if ((hs = scoreReadScoreLine(merge)) != NULL) if (scorePossiblyAddEntry(hs)) changed = 1; else scoreFree(hs); /* possibly save new highscore table */ if (changed) { rewind(orig); scoreWriteScoreLines(orig); } unlock: scoreUnlockFile(orig); fclose(orig); scoreUnlockFile(merge); fclose(merge); finish: suidEndPrivilegedAction(); } void scoreSetHigscoresChangedCallback(void (*f)(void)) { highscoresChangedCallback = f; }