DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T h

⟦b1e4b2739⟧ TextFile

    Length: 17309 (0x439d)
    Types: TextFile
    Names: »history.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/General/Cubes/history.c« 

TextFile

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