|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T h
Length: 17309 (0x439d) Types: TextFile Names: »history.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Cubes/history.c«
/* vi:set sw=4 ts=4: */ #ifndef lint static char sccsid[] = "@(#)history.c 5.1 (G.M. Paris) 89/01/22"; #endif lint /* ** ** cubes 5.1 Copyright 1989 Gregory M. Paris ** Permission granted to redistribute on a no charge basis. ** All other rights are reserved. ** */ #include <stdio.h> #include <syslog.h> #include <signal.h> #include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <strings.h> #include "cubes.h" #define MVGAMES 25 /* number of games for moving avg */ history *hist; /* history array */ unsigned nhist = 0; /* entries in history array */ long gamenum; /* current game number */ ptstype ptsmode = Combined; /* history points used in ranking */ extern int winscore; extern int turnnum; extern player plr[]; extern int plrcmp(); extern long histwgt(); extern long histwgtplr(); static history *histadd(); extern history *histbyname(); extern computer comptbl[]; extern computer *compbyname(); extern boolean iscomp(); extern boolean iskamelion(); extern char *fgets(); extern char *malloc(); extern char *realloc(); /* ** histread: read history file ** Any format conversions must be handled by a separate utility. */ histread(histfile) char *histfile; { register history *ph; register int n; register FILE *fp; char line[BUFSIZ]; char dupid[IDLEN]; long duplastgame; int omask; omask = sigsetmask(~0); if(hist != 0) { free((char *)hist); hist = 0, nhist = 0; } /* ** Open the file for reading. The file must exist. */ if((fp = fopen(histfile, "r")) == 0) { syslog(LOG_ERR, "histread: fopen(%s,r): %m", histfile); (void) sigsetmask(omask); return -1; } /* ** Count the lines in the file. If none, we're all done. */ for(n = 0;fgets(line, sizeof line, fp) != 0;++n) ; if(n == 0) { (void) fclose(fp); (void) sigsetmask(omask); return 0; } /* ** Rewind the file, allocate space, and read in the entries. ** There are now two lines per player. */ clearerr(fp); rewind(fp); nhist = n / 2; if((hist = (history *)malloc(nhist * sizeof *hist)) == 0) { syslog(LOG_ERR, "histread: malloc: can't allocate space for history"); (void) fclose(fp); (void) sigsetmask(omask); return -1; } for(n = 0, ph = hist;;++n, ++ph) { if(fgets(line, sizeof line, fp) == 0) break; if(sscanf(line, "L %ld %ld %ld %ld %ld%*[ ]%[^\n]", &ph->h_games, &ph->h_wins, &ph->h_points, &ph->h_avgturn, &ph->h_lastgame, ph->h_id) != 6) { syslog(LOG_DEBUG, "histread: got bad lifetime in %s", histfile); continue; } if(fgets(line, sizeof line, fp) == 0) { syslog(LOG_DEBUG, "histread: missing last moving in %s", histfile); break; } if(sscanf(line, "M %ld %ld %ld %ld %ld%*[ ]%[^\n]", &ph->h_mvgames, &ph->h_mvwins, &ph->h_mvpoints, &ph->h_mvavgturn, &duplastgame, dupid) != 6) { syslog(LOG_DEBUG, "histread: got bad moving in %s", histfile); continue; } if(strcmp(ph->h_id, dupid) != 0) { syslog(LOG_DEBUG, "histread: name mismatch %ls vs %ls in %s", ph->h_id, dupid, histfile); continue; } if(ph->h_lastgame != duplastgame) { syslog(LOG_DEBUG, "histread: lastgame mismatch %ld vs %ld for %s in %s", ph->h_lastgame, duplastgame, ph->h_id, histfile); continue; } ph->h_computer = compbyname(ph->h_id); } (void) fclose(fp); (void) sigsetmask(omask); histsort(); return 0; } /* ** histprune: remove ancient records from history ** assumes gamenum has been set */ histprune(gpcredit) int gpcredit; { register int h; register history *pt, *pf; register long span; unsigned nnhist; int omask; omask = sigsetmask(~0); /* ** Look for ancient records and delete them. */ pt = pf = hist; for(h = 0;h < nhist;++h, ++pf) { span = gamenum - pf->h_lastgame; if(gpcredit != 0) span -= gpcredit * pf->h_games; if(span >= ANCIENT) continue; if(pt != pf) *pt = *pf; ++pt; } /* ** Shrink memory used by history to account for pruned entries. */ nnhist = (unsigned)(pt - hist); if(nnhist != nhist) { if((hist = (history *)realloc((char *)hist, nnhist * sizeof *hist)) == 0) { syslog(LOG_WARNING, "histprune: realloc failed"); nnhist = 0; } nhist = nnhist; } (void) sigsetmask(omask); } /* ** histcmp: history comparison function for history file ordering */ histcmp(h1, h2) history *h1, *h2; { return (int)(h2->h_weight - h1->h_weight); } /* ** histsort: sort the history array */ histsort() { register int n; int omask; if(nhist != 0 && hist != 0) { for(n = 0;n < nhist;++n) (void) histwgt(n); omask = sigsetmask(~0); qsort((char *)hist, (int)nhist, sizeof *hist, histcmp); (void) sigsetmask(omask); for(n = 0;n < nhist;++n) (hist+n)->h_rank = n + 1; } } /* ** histwrite: write the history file, pruning ancient records */ histwrite(histfile) char *histfile; { register history *ph; register FILE *fp; register int n; int omask; omask = sigsetmask(~0); /* block all signals */ (void) umask(0644); if((fp = fopen(histfile, "w")) == 0) { syslog(LOG_ERR, "histwrite: fopen(%s,w): %m", histfile); (void) sigsetmask(omask); return -1; } if(nhist != 0) { histprune(WRITECREDIT); histsort(); for(n = 0, ph = hist;n < nhist;++n, ++ph) { fprintf(fp, "L %5ld %5ld %10ld %10ld %6ld %s\n", ph->h_games, ph->h_wins, ph->h_points, ph->h_avgturn, ph->h_lastgame, ph->h_id); fprintf(fp, "M %5ld %5ld %10ld %10ld %6ld %s\n", ph->h_mvgames, ph->h_mvwins, ph->h_mvpoints, ph->h_mvavgturn, ph->h_lastgame, ph->h_id); } } (void) fsync(fileno(fp)); /* commit to disk */ (void) fclose(fp); (void) sigsetmask(omask); /* restore signal mask */ return 0; } /* ** histtime: return modification time of history file */ histtime(histfile, ptime) char *histfile; time_t *ptime; { struct stat st; if(stat(histfile, &st) < 0) { syslog(LOG_NOTICE, "histtime: stat(%s): %m", histfile); return -1; } *ptime = st.st_mtime; return 0; } /* ** histadd: add a player to the history array */ static history * histadd(c) int c; { register history *ph; char *id; int omask; omask = sigsetmask(~0); if(nhist == 0) hist = (history *)malloc(sizeof *hist), ++nhist; else hist = (history *)realloc((char *)hist, ++nhist * sizeof *hist); if(hist == 0) { syslog(LOG_ERR, "histadd: no memory for more history"); nhist = 0; (void) sigsetmask(omask); return (history *)0; } /* ** Initialize the new entry. */ ph = hist + nhist - 1; if(*(id = plr[c].p_id) == '\0') id = plr[c].p_name; strncpy(ph->h_id, id, IDLEN); ph->h_id[IDLEN-1] = '\0'; ph->h_computer = compbyname(ph->h_id); ph->h_rank = nhist, ph->h_weight = 0; ph->h_points = ph->h_avgturn = ph->h_wins = ph->h_games = 0; ph->h_mvpoints = ph->h_mvavgturn = ph->h_mvwins = ph->h_mvgames = 0; ph->h_lastgame = 0; (void) sigsetmask(omask); return ph; } /* ** histpoints: add player's points to history ** updates game count and lastgame and adds new entries as needed ** uses a fifty-percent rule to penalize players not onboard */ histpoints(c) int c; { history *ph; char *id; long scale, fifty; long avgturn; double winrate; /* ** Locate player's entry in history array. ** If player doesn't have an entry, call histadd ** to add one. Histadd returns a pointer to the ** new entry or zero on error. */ if(*(id = plr[c].p_id) == '\0') id = plr[c].p_name; if((ph = histbyname(id)) == 0) { if((ph = histadd(c)) == 0) { syslog(LOG_DEBUG, "histpoints: histadd failed"); return -1; } } /* ** If this non-COMP player was not onboard, it was probably a quit. ** In this case, we penalize the player's point total to get the ** same ptsmode average that would result if the game was played to ** finish and the player got 50% of its ptsmode average. We must not ** update game count or lastgame. We do adjust the moving average ** using the same half-average score, though the updating is a ** bit more complex due to the nature of the recent point total. */ if(plr[c].p_onboard == False && iscomp(c) == False) { if(ph->h_games == 0) return 0; histcalc(ph, &winrate, &fifty, &avgturn); fifty /= 2; /* ** The next calculation done purely as integer math is likely to ** overflow once the player has about 500 games. */ #ifdef notdef ph->h_points = (ph->h_games * ph->h_points + fifty) / (ph->h_games + 1); #else notdef { double rat, gam; gam = (double)(ph->h_games + 1); rat = (double)ph->h_games / gam; ph->h_points = (long)(ph->h_points * rat + fifty / gam); } #endif notdef if(ph->h_mvgames == MVGAMES) ph->h_mvpoints += fifty - ph->h_mvpoints / MVGAMES; else /* next line not strictly correct if h_mvgames > MVGAMES */ ph->h_mvpoints = (ph->h_mvgames * ph->h_mvpoints + fifty) / (ph->h_mvgames + 1); return 0; } /* ** Update points and avgturn and increment game count. History points ** are based on games of WINSCORE points, so scale points here before ** adding to history. Note that avgturn does not need scaling. ** Game count is incremented and lastgame is set only here. Mvwins ** is updated here as if the game was a loss -- corrected in histwin. */ scale = (WINSCORE * (long)plr[c].p_score) / winscore; avgturn = turnnum > 0 ? (long)plr[c].p_score / turnnum : 0L; if(ph->h_mvgames == MVGAMES) { ph->h_mvpoints += scale - ph->h_mvpoints / MVGAMES; ph->h_mvavgturn += avgturn - ph->h_mvavgturn / MVGAMES; ph->h_mvwins -= ph->h_mvwins / MVGAMES; } else if(ph->h_mvgames < MVGAMES) { ph->h_mvpoints += scale; ph->h_mvavgturn += avgturn; /* no adjustment of h_mvwins necessary */ ++ph->h_mvgames; } else { /* over! */ ph->h_mvpoints = (MVGAMES * ph->h_mvpoints) / ph->h_mvgames; ph->h_mvavgturn = (MVGAMES * ph->h_mvavgturn) / ph->h_mvgames; ph->h_mvwins = (MVGAMES * ph->h_mvwins) / ph->h_mvgames; ph->h_mvgames = MVGAMES; ph->h_mvpoints += scale - ph->h_mvpoints / MVGAMES; ph->h_mvavgturn += avgturn - ph->h_mvavgturn / MVGAMES; ph->h_mvwins -= ph->h_mvwins / MVGAMES; } ph->h_games++; ph->h_points += scale; ph->h_avgturn += avgturn; ph->h_lastgame = gamenum; return 0; } /* ** histwins: update win count for game winner ** assumes that histpoints has been called to update game count ** assumes that histpoints adjusts mvwins as if game was lost ** doesn't add new history entries as needed */ histwins(c) int c; { register char *id; register history *ph; if(*(id = plr[c].p_id) == '\0') id = plr[c].p_name; if((ph = histbyname(id)) == 0) { syslog(LOG_DEBUG, "histwins: can't find <%s> in history", id); return -1; } ph->h_wins++; ph->h_mvwins += H_MVWINMULT; return 0; } /* ** histwgt: return weighted value of history item n */ long histwgt(n) int n; { register history *ph; long avgpoints, avgturn; double winrate; if(n >= nhist || (ph = hist + n)->h_games == 0) { ph->h_weight = 0; return 0L; } histcalc(ph, &winrate, &avgpoints, &avgturn); ph->h_weight = (long)(1000L * winrate) + avgpoints + avgturn; return ph->h_weight; } /* ** histavgplr: return player's historical average game points */ long histavgplr(c) int c; { history *ph; char *id; long avgmpt, avtnpt; double winrt; if(*(id = plr[c].p_id) == '\0') id = plr[c].p_name; if((ph = histbyname(id)) == 0 || ph->h_games == 0) return 0L; /* no average */ histcalc(ph, &winrt, &avgmpt, &avtnpt); return avgmpt; } /* ** histwgtplr: return player's historical wins/points weighting */ long histwgtplr(c) int c; { history *ph; char *id; if(*(id = plr[c].p_id) == '\0') id = plr[c].p_name; if((ph = histbyname(id)) == 0) return 0L; return histwgt((int)(ph - hist)); } /* ** humanplaying: returns true if the human is playing or watching this game */ boolean humanplaying(c, id) int c; char *id; { register int cc; for(cc = 0;cc < PLAYERS;++cc) if(cc != c) { switch(plr[cc].p_stat) { case Active: case Waiting: case Watching: if(strncmp(plr[cc].p_id, id, IDLEN) == 0) return True; break; } } return False; } /* ** histfmtplr: return formatted history entry for player ** or entry in relation to player ** Note: this routine is used by the server to let players check ** their own and other players' histories during a game. ** Some deception is used when Kamelion is the subject of inquiry. */ char * histfmtplr(c, rel, useid) int c, rel; boolean useid; { register history *ph; register int n, d; register char *id; static char fmt[MESGLEN]; /* ** Sorry, but we don't want to have to do a lookup. ** What's the right thing to do in this case anyway? */ if(useid == False && rel != 0) useid = True; /* ** We have to let Waiting and Watching pass here, or a such a ** player can't use the related commands at the "next game?" ** prompt. This can be used to spy on observers. */ if(c < 0 || c >= PLAYERS || plr[c].p_stat == Inactive) { sprintf(fmt, "No player number %d.", c+1); return fmt; } if(*(id = plr[c].p_id) == '\0') id = plr[c].p_name; if((ph = histbyname(id)) == 0) { sprintf(fmt, "%s is not ranked.", useid == True ? plr[c].p_id : plr[c].p_name); return fmt; } n = (int)(ph - hist); if(rel != 0) { ph += rel, n += rel; if(n < 0 || n >= nhist) { sprintf(fmt, "No player is ranked %d.", n+1); return fmt; } } /* ** When rel is zero and useid is False, this is a query about one player ** by another. In this case, we try to protect Kamelion's identity. ** What we do is look for the nearest ranked human that's not playing. ** If there isn't one, we tell the truth. */ if(rel == 0 && useid == False && iskamelion(c) == True) { for(d = 1;d < nhist;++d) { if(n - d >= 0 && (ph-d)->h_computer == 0 && humanplaying(c, (ph-d)->h_id) == False) { ph -= d, n -= d; break; } if(n + d < nhist && (ph+d)->h_computer == 0 && humanplaying(c, (ph+d)->h_id) == False) { ph += d, n += d; break; } } } if(ph->h_games == 0) sprintf(fmt, "%ld %.18s 0 * * * %ld", ph->h_rank, useid == True ? ph->h_id : plr[c].p_name, ph->h_weight); else { long avgpoints, avgturn; double winrate; histcalc(ph, &winrate, &avgpoints, &avgturn); sprintf(fmt, "%ld %.18s %ld %5.3f %ld %ld %ld", ph->h_rank, useid == True ? ph->h_id : plr[c].p_name, ph->h_games, winrate, avgpoints, avgturn, ph->h_weight); } return fmt; } /* ** historder: reorder players by historical wins/points weighting ** and/or score from previous game */ historder(firstgame) boolean firstgame; { register int c; int omask; #define SORTBONUS 15000 /* ** Give active players a score based on their history unless they ** played in the previous game. In that case, award them a bonus ** that guarantees preferential treatment. */ for(c = 0;c < PLAYERS;++c) { switch(plr[c].p_stat) { case Computer: case Active: if(firstgame == True || plr[c].p_score == 0) plr[c].p_score = (int)histwgtplr(c); else plr[c].p_score += SORTBONUS; break; default: plr[c].p_score = 0; break; } } omask = sigsetmask(~0); qsort((char *)plr, PLAYERS, sizeof plr[0], plrcmp); (void) sigsetmask(omask); #undef SORTBONUS } /* ** histbyname: return pointer to player history or NULL */ history * histbyname(id) register char *id; { register int h; for(h = 0;h < nhist;++h) if(strncmp(id, (hist+h)->h_id, IDLEN) == 0) return hist+h; return (history *)0; } /* ** setgamenum: set current game number using COMP's game count */ setgamenum() { register int h; /* ** Search for the COMP computer and set the gamenum to one more ** than the number of games played. This works because the COMP ** computer plays in every game. */ for(h = 0;h < nhist;++h) { if((hist+h)->h_computer == &comptbl[0]) { gamenum = (hist+h)->h_games + 1; return; } } /* ** This really may be the first game! */ if(nhist == 0) { gamenum = 1; return; } /* ** Don't know what happened to cause this, but let's try to handle it by ** setting the gamenum to one more than the highest played by any player. */ syslog(LOG_DEBUG, "setgamenum: can't find COMP computer"); gamenum = 0; for(h = 0;h < nhist;++h) if((hist+h)->h_lastgame > gamenum) gamenum = (hist+h)->h_lastgame; ++gamenum; } /* ** histcalc: calculate winrt, avgmpt, and avtnpt from history */ histcalc(ph, pwinrt, pavgmpt, pavtnpt) register history *ph; double *pwinrt; long *pavgmpt, *pavtnpt; { if(ph->h_games == 0) { *pwinrt = 0.0; *pavgmpt = *pavtnpt = 0; return; } switch(ptsmode) { case Lifetime: *pwinrt = (double)ph->h_wins / ph->h_games; *pavgmpt = ph->h_points / ph->h_games; *pavtnpt = ph->h_avgturn / ph->h_games; break; case Recent: *pwinrt = (double)ph->h_mvwins / (H_MVWINMULT * ph->h_mvgames); *pavgmpt = ph->h_mvpoints / ph->h_mvgames; *pavtnpt = ph->h_mvavgturn / ph->h_mvgames; break; case Combined: *pwinrt = (double)ph->h_wins / ph->h_games; *pavgmpt = ph->h_points / ph->h_games; *pavtnpt = ph->h_avgturn / ph->h_games; *pwinrt += (double)ph->h_mvwins / (H_MVWINMULT * ph->h_mvgames); *pavgmpt += ph->h_mvpoints / ph->h_mvgames; *pavtnpt += ph->h_mvavgturn / ph->h_mvgames; *pwinrt *= 0.5; *pavgmpt /= 2; *pavtnpt /= 2; break; } }