|
|
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;
}
}