[comp.sources.games] v06i064: cubes2 - a networked dice game

games@tekred.CNA.TEK.COM (04/28/89)

Submitted-by: gmp@rayssdb.RAY.COM (Gregory M. Paris)
Posting-number: Volume 6, Issue 64
Archive-name: cubes2/Part06



#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 6 (of 8)."
# Contents:  actions.c cubeserv2.c history.c
# Wrapped by billr@saab on Thu Apr 27 12:13:39 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'actions.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'actions.c'\"
else
echo shar: Extracting \"'actions.c'\" \(15195 characters\)
sed "s/^X//" >'actions.c' <<'END_OF_FILE'
X/*	vi:set sw=4 ts=4: */
X#ifndef	lint
Xstatic char	sccsid[] = "@(#)actions.c 5.1 (G.M. Paris) 89/01/22";
X#endif	lint
X
X/*
X**
X**	cubes 5.1  Copyright 1989 Gregory M. Paris
X**		Permission granted to redistribute on a no charge basis.
X**		All other rights are reserved.
X**
X*/
X
X#include	<stdio.h>
X#include	<signal.h>
X#include	<strings.h>
X#include	<ctype.h>
X#include	<sys/types.h>
X#include	<sys/time.h>
X#include	<sys/file.h>
X#include	<sys/socket.h>
X#include	<sys/ioctl.h>
X#include	<netdb.h>
X#include	<netinet/in.h>
X#include	<pwd.h>
X#include	"cubes.h"
X
Xplayer				plr[PLAYERS];		/* player status */
Xdiceset				dice;				/* the status of the dice */
Xcstat				mystat	= Inactive;	/* our playing status */
Xboolean				jokermode= False;	/* jokers on the dice */
Xboolean				inprogress= False;	/* game in progress */
Xint					winscore= 0;		/* game win score */
Xint					mypnum	= -1;		/* my player number */
Xint					turnnum	= 0;		/* current turn number */
Xint					nobs	= 0;		/* number of observers */
Xint					beats	= 0;		/* number of heartbeats */
Xextern boolean		quiet;				/* in background */
Xextern enterlate	watch;				/* what to do about late entry */
Xextern enterlate	spider;				/* what to do about waiting */
Xextern winpref		preference;			/* gametype/winscore preference */
Xextern char		   *cubename;			/* player name */
Xextern char		   *cubehist;			/* personal ranking history file */
Xextern int			ssock;				/* server socket */
X
Xextern int			errno;
Xextern char		   *sys_errlist[];
Xextern boolean		active;				/* True while active */
Xextern char		   *index();
Xextern char		   *getenv();
Xstruct passwd	   *getpwuid();
X
X/*
X**	respond: send a reply to the server adding "\r\n"
X*/
Xrespond(reply)
Xchar   *reply;
X{
X	char	repbuf[BUFSIZ];
X
X	strcpy(repbuf, reply);
X	strcat(repbuf, "\r\n");
X
X	return write(ssock, repbuf, strlen(repbuf)) < 0 ? -1 : 0;
X}
X
X/*
X**	dispact: display an informative message to the player
X*/
X/*ARGSUSED*/
Xdispact(t,m)
Xchar *m;
X{
X	return infomesg(m+4);
X}
X
X/*
X**	heloact: respond to the M_HELO message by giving player name.
X*/
X/*ARGSUSED*/
Xheloact(t,m)
Xchar *m;
X{
X	return respond(cubename);
X}
X
X/*
X**	rqidact: respond to the M_RQID message by giving player identity
X**		and gametype preference.
X*/
X/*ARGSUSED*/
Xrqidact(t,m)
Xchar *m;
X{
X	char			idbuf[IDLEN];
X	char			host[256];
X	int				uid;
X	struct passwd  *pw;
X	char			pchar;
X
X	(void) gethostname(host, sizeof host);
X
X	switch(preference) {
X	case Standard:	pchar = 's'; break;
X	case Fstand:	pchar = 'S'; break;
X	case Blitz:		pchar = 'b'; break;
X	case Fblitz:	pchar = 'B'; break;
X	default:		pchar = 'n'; break;
X	}
X
X	/*
X	**	Look up login name in the password file.  If it's not there,
X	**	just use the user id number.
X	*/
X	uid = getuid();
X	if((pw = getpwuid(uid)) == 0)
X		sprintf(idbuf, "%c;%d@%.*s", pchar, uid, IDLEN-12, host);
X	else
X		sprintf(idbuf, "%c;%s@%.*s", pchar, pw->pw_name, IDLEN-12, host);
X
X	return respond(idbuf);
X}
X
X/*
X**	worpact: respond to the M_WORP message by saying play or watch
X*/
Xworpact(t,m)
Xchar *m;
X{
X	switch(watch) {
X	case Watch:	return respond("watch");
X	case Play:	return respond("play");
X	default:	return rfstact(t,m);
X	}
X}
X
X/*
X**	sorpact: respond to the M_SORP message by saying wait or play
X*/
Xsorpact(t,m)
Xchar *m;
X{
X	switch(spider) {
X	case Wait:	return respond("wait");	/* be a spider */
X	case Play:	return respond("play");
X	default:	return rfstact(t,m);
X	}
X}
X
X/*
X**	actvact: respond to the M_ACTV message by alerting the player
X*/
X/*ARGSUSED*/
Xactvact(t,m)
Xchar *m;
X{
X	cstat	oldstat;
X
X	oldstat = mystat;
X	mystat = Active, beats = 0;
X	if(quiet == True)
X		mybeep(2);
X
X	switch(oldstat) {
X	case Computer:
X		(void) infomesg("You've regained control of the dice.");
X		break;
X	case Watching:
X		if(turnnum > 1)
X			(void) infomesg("You've joined the game in progress.");
X		break;
X	default:
X/*		(void) infomesg(m+4);	/* don't need this chatter */
X		break;
X	}
X
X	return 0;
X}
X
X/*
X**	autoact: respond to the M_AUTO message by alerting the player
X*/
X/*ARGSUSED*/
Xautoact(t,m)
Xchar *m;
X{
X	mystat = Computer, beats = 0;
X	(void) infomesg(m+4);
X	return 0;
X}
X
X/*
X**	waitact: respond to the M_WAIT message by notifying the player
X*/
X/*ARGSUSED*/
Xwaitact(t,m)
Xchar *m;
X{
X	if(mystat == Spider || mystat == Watching)
X		(void) infomesg(m+4);
X	mystat = Waiting, beats = 0;
X	return 0;
X}
X
X/*
X**	voyract: respond to the M_VOYR message by notifying the player
X*/
X/*ARGSUSED*/
Xvoyract(t,m)
Xchar *m;
X{
X	mystat = Watching, beats = 0;
X	return infomesg(m+4);
X}
X
X/*
X**	spdract: respond to the M_SPDR message by entering spider mode
X**		We could suspend here, but that makes this mode
X**		unusable in shells without job control.
X*/
X/*ARGSUSED*/
Xspdract(t,m)
Xchar *m;
X{
X	mystat = Spider, beats = 0;
X	(void) infomesg(m+4);
X#ifdef	notdef
X	sleep(2);
X	(void) kill(0, SIGTSTP);	/* suspend process group */
X#endif	notdef
X	return 0;
X}
X
X/*
X**	noopact: no operation (really heartbeat action)
X**		Assumes that heartbeats come one per minute.
X*/
X/*ARGSUSED*/
Xnoopact(t,m)
Xchar *m;
X{
X	char	msgbuf[MESGLEN];
X
X	++beats;
X	sprintf(msgbuf, "You've been a spider for %d minute%s...",
X		beats, beats == 1 ? "" : "s");
X	return infomesg(msgbuf);
X}
X
X/*
X**	infoact: display an informational message
X*/
X/*ARGSUSED*/
Xinfoact(t,m)
Xchar *m;
X{
X	return infomesg(m+4);
X}
X
X/*
X**	helpact: display a help message
X*/
X/*ARGSUSED*/
Xhelpact(t,m)
Xchar *m;
X{
X	return helpmesg(m+4);
X}
X
X/*
X**	playact: display a play-by-play message (not used)
X*/
X/*ARGSUSED*/
Xplayact(t,m)
Xchar *m;
X{
X	return 0;
X}
X
X/*
X**	rfstact: respond to "roll first?" message
X*/
X/*ARGSUSED*/
Xrfstact(t,m)
Xchar *m;
X{
X	char	answer[BUFSIZ];
X
X	switch(prompt(m+4, answer, False)) {
X	case -2:	/* timeout */
X		return 0;
X	case -1:	/* error */
X		return -1;
X	}
X	if(respond(answer) < 0)
X		return -1;
X	return 0;
X}
X
X/*
X**	keepact: respond to "%d points:" message
X*/
Xkeepact(t,m)
Xchar *m;
X{
X	return rfstact(t,m);
X}
X
X/*
X**	anogact: respond to "another game?" message
X*/
Xanogact(t,m)
Xchar *m;
X{
X	return rfstact(t,m);
X}
X
X/*
X**	downact: print the shutdown message and exit gracefully
X*/
X/*ARGSUSED*/
Xdownact(t,m)
Xchar *m;
X{
X	for(m += 4;*m == ' ';++m)
X		;
X	if(*m == '\0')
X		m = "Server said goodbye (no reason given).";
X	(void) infomesg(m);
X	sleep(2);
X	cleanup(0, "");
X	return -1;
X}
X
X/*
X**	turnact: cause turn indicator to advance
X*/
X/*ARGSUSED*/
Xturnact(t,m)
Xchar *m;
X{
X	int		pnum;
X
X	if(sscanf(m+4, "Turn %d: player %d", &turnnum, &pnum) != 2)
X		return -1;
X	--pnum;
X
X	return nowup(pnum);
X}
X
X/*
X**	cplract: clear player roster
X*/
X/*ARGSUSED*/
Xcplract(t,m)
Xchar *m;
X{
X	register int	c;
X
X	mypnum = -1;
X	for(c = 0;c < PLAYERS;++c) {
X		plr[c].p_stat = Inactive;
X		plr[c].p_name[0] = '\0';
X		plr[c].p_score = plr[c].p_squander = 0;
X	}
X
X	return clearscores();
X}
X
X/*
X**	uareact: setup this player
X*/
X/*ARGSUSED*/
Xuareact(t,m)
Xchar *m;
X{
X	int		pnum;
X	char   *s;
X
X	if(sscanf(m+4, "You are player %d", &pnum) < 1)
X		return -1;
X	--pnum;
X
X	mypnum = pnum;
X	plr[pnum].p_score = plr[pnum].p_squander = 0;
X	plr[pnum].p_stat = Active;
X	plr[pnum].p_name[0] = '\0';
X	if((s = index(m+20, ' ')) == 0)	/* mmm You are player n */
X		return -1;
X	strncat(plr[pnum].p_name, s+1, sizeof plr[pnum].p_name);
X
X	return showplr(pnum);
X}
X
X/*
X**	pnumact: setup another player
X*/
X/*ARGSUSED*/
Xpnumact(t,m)
Xchar *m;
X{
X	int		pnum;
X	char   *s;
X
X	if(sscanf(m+4, "player %d", &pnum) < 1)
X		return -1;
X	--pnum;
X
X	plr[pnum].p_score = plr[pnum].p_squander = 0;
X	plr[pnum].p_stat = Active;
X	plr[pnum].p_name[0] = '\0';
X	if((s = index(m+12, ' ')) == 0)	/* mmm player n */
X		return -1;
X	strncat(plr[pnum].p_name, s+1, sizeof plr[pnum].p_name);
X
X	return showplr(pnum);
X}
X
X/*
X**	fareact: clean up when a player leaves
X*/
X/*ARGSUSED*/
Xfareact(t,m)
Xchar *m;
X{
X	static struct timeval	halfsec	= { 0L, 500000L };
X	int						pnum;
X	char					msgbuf[MESGLEN];
X	char					name[NAMELEN];
X
X	if(sscanf(m+4, "farewell %d %[^\r\n]", &pnum, name) < 2)
X		return -1;
X	--pnum;
X
X	sprintf(msgbuf, "%s has left the game.", name);
X	plr[pnum].p_score = plr[pnum].p_squander = 0;
X	plr[pnum].p_stat = Inactive;
X	plr[pnum].p_name[0] = '\0';
X	(void) clearplr(pnum);
X	(void) infomesg(msgbuf);
X	(void) select(32, NOSEL, NOSEL, NOSEL, &halfsec);
X
X	return 0;
X}
X
X/*
X**	nobsact: update number of observers
X*/
X/*ARGSUSED*/
Xnobsact(t,m)
Xchar *m;
X{
X	if(sscanf(m+4, "%d", &nobs) < 1)
X		return -1;
X	return 0;
X}
X
X/*
X**	mscoact: update my score
X*/
X/*ARGSUSED*/
Xmscoact(t,m)
Xchar *m;
X{
X	int		score, squander;
X
X	if(sscanf(m+4,
X		"You now have %d points (squandered %d).", &score, &squander) < 2)
X		return -1;
X	if(mypnum < 0)
X		return -1;
X
X	plr[mypnum].p_score = score, plr[mypnum].p_squander = squander;
X	return showplr(mypnum);
X}
X
X/*
X**	oscoact: update other player score
X*/
X/*ARGSUSED*/
Xoscoact(t,m)
Xchar *m;
X{
X	int		pnum, score, squander;
X
X	if(sscanf(m+4,
X	  "Player %d now has %d points (squandered %d).",
X	  &pnum, &score, &squander) < 3)
X		return -1;
X	--pnum;
X
X	plr[pnum].p_score = score, plr[pnum].p_squander = squander;
X	return showplr(pnum);
X}
X
X/*
X**	nwin: no winner message
X*/
X/*ARGSUSED*/
Xnwinact(t,m)
Xchar *m;
X{
X	inprogress = False;
X	(void) infomesg(m+4);
X	return cleardice();
X}
X
X/*
X**	overact: game over with winner
X*/
X/*ARGSUSED*/
Xoveract(t,m)
Xchar *m;
X{
X	inprogress = False;
X	(void) infomesg(m+4);
X	return cleardice();
X}
X
X/*
X**	rankact: save ranking info
X*/
X/*ARGSUSED*/
Xrankact(t,m)
Xchar *m;
X{
X	static FILE	   *frec	= 0;
X	int				omask;
X
X	/*
X	**	If cubehist is null then we aren't recording history.
X	*/
X	if(cubehist == 0 || *cubehist == '\0')
X		return 0;
X
X	/*
X	**	Open the cubehist file for append.
X	*/
X	if(frec == 0) {
X		omask = umask(022);
X		(void) umask(omask|022);	/* ensure write protection */
X		if((frec = fopen(cubehist, "a")) == 0) {
X			char	err[MESGLEN];
X			sprintf(err, "Can't open %s: %s.", cubehist, sys_errlist[errno]);
X			(void) umask(omask);
X			return infomesg(err);
X		}
X		(void) umask(omask);
X	}
X
X	/*
X	**	Write the info supplied by the server.
X	*/
X	fprintf(frec, "%s\n", m+4);
X	(void) fflush(frec);
X
X	return 0;
X}
X
X/*
X**	diceact: parse the dice status message and redisplay the dice
X*/
X/*ARGSUSED*/
Xdiceact(t,m)
Xchar *m;
X{
X	register int	d;
X	char			stats[NDICE+1];
X	char			faces[NDICE+1];
X	char			combs[NDICE+1];
X	int				c;
X
X	dice.d_mesg[0] = '\0';
X	if(sscanf(m+4, "%d %s %s %s %d %d %d %d%*1c%[^\n]",
X	  &c, stats, faces, combs,
X	  &dice.d_pts_roll, &dice.d_pts_dice, &dice.d_pts_turn, &dice.d_pts_max,
X	  dice.d_mesg) < 8) {
X		fprintf(stderr, "dieact: couldn't parse dice status\n");
X		return -1;
X	}
X	
X	for(d = 0;d < NDICE;++d) {
X		switch(stats[d]) {
X		case 'f':	dice.d_stat[d] = Free; break;
X		case 'h':	dice.d_stat[d] = Held; break;
X		case 't':	dice.d_stat[d] = Taken; break;
X		case 'r':	dice.d_stat[d] = Rolled; break;
X		default:	dice.d_stat[d] = Free; break;
X		}
X		dice.d_face[d] = isdigit(faces[d]) ? (faces[d] - '0') : BADFACE;
X		switch(combs[d]) {
X		case 'p':	dice.d_comb[d] = Previous; break;
X		case 'n':	dice.d_comb[d] = Nothing; break;
X		case 'j':	dice.d_comb[d] = Joker; break;
X		case '5':	dice.d_comb[d] = Five; break;
X		case '1':	dice.d_comb[d] = Ace; break;
X		case 't':	dice.d_comb[d] = Three_of_a_kind; break;
X		case 'l':	dice.d_comb[d] = Small_straight; break;
X		case 'f':	dice.d_comb[d] = Four_of_a_kind; break;
X		case 'b':	dice.d_comb[d] = Asm_straight; break;
X		case 's':	dice.d_comb[d] = Straight; break;
X		case 'a':	dice.d_comb[d] = All_of_a_kind; break;
X		default:	dice.d_comb[d] = Nothing; break;
X		}
X	}
X
X	return drawdice();
X}
X
X/*
X**	parmact: read game parameters winscore and jokermode
X*/
X/*ARGSUSED*/
Xparmact(t,m)
Xchar *m;
X{
X	int		n;
X	char	word[64];
X
X	if((n = sscanf(m+4, "This is a %d point game with %s",
X	  &winscore, word)) < 1)
X		return -1;
X	
X	/*
X	**	This message happens at game startup.  If we're suspended
X	**	we want to alert the player, so send a bunch of beeps.
X	*/
X	if(quiet == True)
X		mybeep(3);
X	
X	inprogress = True;
X	jokermode = (n == 2 && word[0] == 'j') ? True : False;
X
X	(void) infomesg(m+4);
X	(void) defhelpmesg();
X	return drawdice();
X}
X
X/*
X**	argeact: argument error, display error message
X*/
X/*ARGSUSED*/
Xargeact(t,m)
Xchar *m;
X{
X	if(quiet != True)
X		mybeep(1);
X	(void) infomesg(m+4);
X	sleep(2);
X	return 0;
X}
X
X/*
X**	badmact: alert player to bad message
X*/
Xbadmact(t,m)
Xchar *m;
X{
X	return (dispact(t,"Bad message follows:") < 0 || dispact(t,m) < 0) ? -1 : 0;
X}
X
X/*
X**	unktact: alert player to unknown type message
X*/
Xunktact(t,m)
Xchar *m;
X{
X	return (dispact(t,"Unknown message follows:") < 0 || dispact(t,m) < 0)
X		? -1 : 0;
X}
X
X/*
X**	actmap: relates message types to actions
X*/
Xtypedef struct {
X	m_num	a_type;			/* message type M_???? */
X	int	  (*a_action)();	/* action to take */
X} actmap;
X
X/*
X**	atable: action table
X*/
Xstatic actmap	atable[]	= {
X	{ M_NOOP,	noopact },			/* no operation (usually heartbeat) */
X	{ M_INFO,	infoact },			/* informational or status messages */
X	{ M_HELP,	helpact },			/* help messages */
X	{ M_DICE,	diceact },			/* dice status */
X	{ M_PARM,	parmact },			/* game parameters */
X	{ M_PLAY,	playact },			/* play-by-play on other players */
X	{ M_KEEP,	keepact },			/* %d points: */
X	{ M_HELO,	heloact },			/* hello message (and name query) */
X	{ M_DOWN,	downact },			/* shutdown message */
X	{ M_RQID,	rqidact },			/* id request */
X	{ M_WORP,	worpact },			/* play or watch? */
X	{ M_SORP,	sorpact },			/* wait (spider) or play? */
X	{ M_ACTV,	actvact },			/* you are now an active player */
X	{ M_AUTO,	autoact },			/* you are on autopilot */
X	{ M_WAIT,	waitact },			/* you are waiting to be added */
X	{ M_VOYR,	voyract },			/* you are now a voyeur */
X	{ M_SPDR,	spdract },			/* you are now a spider */
X	{ M_TURN,	turnact },			/* Player %d, ... up with %d points. */
X	{ M_NWIN,	nwinact },			/* game over, no winner */
X	{ M_OVER,	overact },			/* Player %d has won the game! */
X	{ M_RANK,	rankact },			/* player ranking info after game */
X	{ M_CPLR,	cplract },			/* clear player roster */
X	{ M_UARE,	uareact },			/* you are player %d */
X	{ M_PNUM,	pnumact },			/* player %d %s */
X	{ M_FARE,	fareact },			/* farewell %d %s */
X	{ M_NOBS,	nobsact },			/* %d observer%s */
X	{ M_MSCO,	mscoact },			/* You now have %d points. */
X	{ M_OSCO,	oscoact },			/* Player %d now has %d points. */
X	{ M_ANOG,	anogact },			/* play another game? */
X	{ M_RFST,	rfstact },			/* 0 points, roll first? */
X	{ M_ARGE,	argeact },			/* argument error, try again */		
X	{ M_BADM,	badmact },			/* bad message */
X};
Xstatic int		nactions	= (sizeof atable / sizeof atable[0]);
X
X/*
X**	actlist: array of pointers to actions
X*/
Xstatic int		 (**actlist)()	= 0;
X
X/*
X**	initactions: build an array of actions from atable
X*/
Xinitactions()
X{
X	register int	m;
X	unsigned		size;
X	char		   *malloc();
X
X	/*
X	**	Just in case we're called more than once.
X	*/
X	if(actlist != 0) {
X		free((char *)actlist);
X		actlist = 0;
X	}
X
X	/*
X	**	Get memory for action list.  Initialize each pointer.
X	*/
X	size = (unsigned)(M_LAST - M_BASE + 1);
X	if((actlist = (int (**)())malloc(size * sizeof *actlist)) == 0)
X		cleanup(1, "no memory for action list");
X	for(m = 0;m < size;++m)
X		*(actlist+m) = unktact;	/* un-mapped action */
X	
X	/*
X	**	Install the values mapped in atable.
X	*/
X	for(m = 0;m < nactions;++m)
X		actlist[(int)atable[m].a_type-M_BASE] = atable[m].a_action;
X}
X
X/*
X**	action: a dispatching function
X*/
Xaction(type, mesg)
Xint		type;
Xchar   *mesg;
X{
X	int		r;
X
X	if(type < M_BASE || type > M_LAST)
X		r = badmact(type, mesg);
X	else
X		r = (*actlist[type-M_BASE])(type, mesg);
X
X	neutral();
X	return r;
X}
END_OF_FILE
if test 15195 -ne `wc -c <'actions.c'`; then
    echo shar: \"'actions.c'\" unpacked with wrong size!
fi
# end of 'actions.c'
fi
if test -f 'cubeserv2.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cubeserv2.c'\"
else
echo shar: Extracting \"'cubeserv2.c'\" \(18633 characters\)
sed "s/^X//" >'cubeserv2.c' <<'END_OF_FILE'
X/*	vi:set sw=4 ts=4: */
X#ifndef	lint
Xstatic char	sccsid[] = "@(#)cubeserv2.c 5.1 (G.M. Paris) 89/04/25";
X#endif	lint
X
X/*
X**
X**	cubes 5.1  Copyright 1989 Gregory M. Paris
X**		Permission granted to redistribute on a no charge basis.
X**		All other rights are reserved.
X**
X*/
X
X#include	<stdio.h>
X#include	<ctype.h>
X#include	<strings.h>
X#include	<syslog.h>
X#include	<sys/types.h>
X#include	<sys/time.h>
X#include	"cubes.h"
X
Xextern long		gamenum;
Xextern char	   *optarg;
Xextern char	   *fgets();
Xextern char	   *gets();
Xextern char	   *malloc();
Xextern history *histbyname();
Xextern char	   *moniker();
Xextern time_t	time();
Xstruct tm	   *localtime();
Xextern boolean	nameinuse();
Xextern boolean	idinuse();
Xextern boolean	isproxy();
Xextern winpref	workhourstype();
X
Xextern unsigned	nhist;					/* number of entries in history */
Xextern unsigned	neq;					/* number of equivalent hosts */
Xextern char	  **equiv;					/* equivalent hosts */
Xextern int		active;					/* number of Active humans */
Xextern int		waiting;				/* number of Waiting humans */
Xextern int		watching;				/* number of Watching humans */
Xextern int		spiders;				/* number of Spiders */
Xextern int		turnnum;				/* current turn number */
Xextern boolean	enajokers;				/* enable jokers */
Xextern boolean	jokermode;				/* dice do not have joker faces */
Xextern boolean	inprogress;				/* True when game in progress */
Xextern boolean	clearobs;				/* Observers are showing in roster */
Xextern blitzmode blitzwhen;				/* Blitz mode enforced during work */
Xextern winpref	gametype;				/* specifies winscore */
Xextern int		winscore;				/* points needed to win */
Xextern player	plr[];					/* player/connection list */
X
X/*
X**	prescreen: does this person really want to play?
X*/
Xboolean
Xprescreen(c)
Xregister int	c;
X{
X	char   *reason;
X
X	/*
X	**	If there's a game in progress, we'll return False if the
X	**	game mode is incompatible with the player's preference.
X	**	If no game is in progress, we check to see if the prevailing
X	**	mode is enough to decide that the person doesn't want to play.
X	*/
X	reason = 0;
X	switch(plr[c].p_pref) {
X	case Fstand:	/* only wants to play in a Standard game */
X		if(inprogress == True && gametype != Standard)
X			reason = "Sorry, the current game is not Standard.";
X		else if(blitzwhen == Enforced && workhourstype() == Blitz)
X			reason = "Sorry, only Blitz games may be played now.";
X		break;
X	case Fblitz:	/* only wants to play in a Blitz game */
X		if(inprogress == True && gametype != Blitz)
X			reason = "Sorry, the current game is not Blitz.";
X		else if(blitzwhen == Noblitz)
X			reason = "Sorry, this server doesn't play Blitz.";
X		break;
X	}
X
X	/*
X	**	Tell them why we're saying goodbye and hang up.
X	*/
X	if(reason != 0) {
X		(void) simp(c, M_DOWN, reason);
X		oldplayer(c);
X		return False;
X	}
X
X	return True;
X}
X
X/*
X**	oldplayer: remove a player from the game
X*/
Xoldplayer(c)
Xregister int	c;
X{
X	/*
X	**	Close the socket if one is open.
X	*/
X	if(plr[c].p_fd != -1) {
X		(void) close(plr[c].p_fd);
X		plr[c].p_fd = -1, plr[c].p_timeouts = 0;
X	}
X
X	/*
X	**	Change the player's status.
X	*/
X	switch(plr[c].p_stat) {
X	case Inactive:
X		break;
X
X	case Active:
X		/*
X		**	If game in progress and there are other humans playing and this
X		**	player is on board, then there's a good chance that a Computer
X		**	proxy will be assigned to take over.  The proxy keeps the original
X		**	name so that other players are not made aware.  The id is also
X		**	left unchanged so that a single player can't flood the game with
X		**	proxies.  We do this substitution, more rarely, when a player
X		**	is not yet on board, to penalize players that repeatedly start
X		**	and quit games looking for an initial big advantage.
X		*/
X		if(inprogress == True && active > 1) {
X			if(	isproxy(c) == True			/* already a temporary proxy */
X			||	(plr[c].p_onboard == True && randint(5) > 2)	/* 3/5ths */
X			||	(plr[c].p_onboard == False && randint(5) == 1)	/* 1/5th */
X			) {
X				pickproxy(c);	/* set up proxy personality */
X				plr[c].p_stat = Computer, --active;	/* permanent proxy */
X				return;			/* all done */
X			}
X		}
X
X		/*
X		**	If the player wasn't replaced, then we remove.
X		**	If a game is in progress adjust player's history.
X		*/
X		if(inprogress == True)
X			histpoints(c);
X		plr[c].p_stat = Inactive, --active;
X		if(active < 0) {
X			syslog(LOG_DEBUG,
X				"oldplayer: active=%d (shouldn't be negative)", active);
X			active = 0;
X		}
X		if(active == 0)
X			inprogress = False;	/* XXX: update Computer points history? */
X		annfarewell(c);
X		break;
X
X	case Waiting:
X		/*
X		**	If observers are showing in the roster, and this is not a
X		**	Waiting Computer, then announce farewell.
X		*/
X		plr[c].p_stat = Inactive, --waiting;
X		if(waiting < 0) {
X			syslog(LOG_DEBUG,
X				"oldplayer: waiting=%d (shouldn't be negative)", waiting);
X			waiting = 0;
X		}
X		if(clearobs == True && plr[c].p_computer == 0)
X			annfarewell(c);
X		break;
X
X	case Watching:
X		plr[c].p_stat = Inactive, --watching;
X		if(watching < 0) {
X			syslog(LOG_DEBUG,
X				"oldplayer: watching=%d (shouldn't be negative)", watching);
X			watching = 0;
X		}
X		if(clearobs == True)		/* observers are showing in roster */
X			annfarewell(c);			/* clear this one */
X		break;
X
X	case Spider:
X		plr[c].p_stat = Inactive, --spiders;
X		if(spiders < 0) {
X			syslog(LOG_DEBUG,
X				"oldplayer: spiders=%d (shouldn't be negative)", spiders);
X			spiders = 0;
X		}
X		break;
X
X	case Computer:
X		plr[c].p_stat = Inactive;
X		if(inprogress == True)
X			histpoints(c);
X		annfarewell(c);
X		break;
X	}
X
X	zeroplayer(c);
X	(void) observers(-1);
X}
X
X/*
X**	zeroplayer: initialize a player
X*/
Xzeroplayer(c)
Xint		c;
X{
X	register player	   *p = &plr[c];
X
X	p->p_stat = Inactive, p->p_computer = 0;
X	p->p_score = p->p_squander = 0, p->p_onboard = False;
X	p->p_fd = -1, p->p_timeouts = 0;
X	p->p_pref = Nopref, p->p_mood = 0;
X	p->p_name[0] = p->p_id[0] = '\0';
X}
X
X/*
X**	fillroster: add computers to fill in roster
X*/
Xfillroster(pplr, pcomp)
Xint	   *pplr, *pcomp;
X{
X	register int	c, nplr, ncomp;
X
X	/*
X	**	Count all players.  We assume that all types of humans will
X	**	play in the next game.  We add Computers in Waiting mode
X	**	so we have to count them specially.
X	*/
X	ncomp = nplr = 0;
X	for(c = 0;c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Inactive:
X			break;
X		case Waiting:
X			if(plr[c].p_computer != 0)
X				++ncomp;
X			++nplr;
X			break;
X		case Computer:
X			++ncomp, ++nplr;
X			break;
X		default:
X			++nplr;
X			break;
X		}
X	}
X	
X	/*
X	**	Add enough Computer players to make at least MINCOMP.
X	**	Add enough Computer players to make MINPLR players total.
X	**	Add Computer players randomly with probability that
X	**		decreases with increasing number of players.
X	*/
X	while(ncomp < MINCOMP || nplr < MINPLR || randint(nplr+2) == 1) {
X		for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Inactive) {
X			pickcomputer(c);					/* sets status to Computer */
X			plr[c].p_stat = Waiting, ++waiting;	/* no Waiting Computer mode */
X			plr[c].p_fd = -1, plr[c].p_timeouts = 0;
X			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
X			++ncomp, ++nplr;
X			break;
X		}
X	}
X
X	*pplr = nplr, *pcomp = ncomp;
X}
X
X/*
X**	workhourstype: calculate default game type for this time of day
X**		Working hours: 8:00 - 11:59 AM, 12:45 - 4:45 PM Mon - Fri.
XXXX:	Sorry, there's no accounting for holidays.
X*/
Xwinpref
Xworkhourstype()
X{
X	time_t		now;
X	struct tm  *tm;
X
X	if(blitzwhen != Workhours && blitzwhen != Enforced)
X		return Standard;
X
X	(void) time(&now);
X	tm = localtime(&now);
X
X	if(tm->tm_wday >= 1 && tm->tm_wday <= 5) {				/* Mon thru Fri? */
X		if(tm->tm_hour >= 8 && tm->tm_hour <= 16) {			/* 8:00 to 4:59 */
X			if(tm->tm_hour == 12 && tm->tm_min <= 45)		/* Noon to 12:45 */
X				;											/* lunch */
X			else if(tm->tm_hour == 16 && tm->tm_min > 45)	/* 4:45 to 4:59 */
X				;											/* end of day */
X			else return Blitz;								/* work hours */
X		}
X	}
X
X	return Standard;
X}
X
X/*
X**	votegametype: try to find a consensus amongst the players
X*/
Xwinpref
Xvotegametype()
X{
X	winpref			pref;
X	register int	c;
X
X	pref = Nopref;
X	for(c = 0;c < PLAYERS;++c) {
X		if(plr[c].p_stat == Inactive || plr[c].p_pref == Nopref)
X			continue;
X		switch(pref) {
X		case Standard:
X			if(plr[c].p_pref != Standard && plr[c].p_pref != Fstand)
X				return Nopref;
X			break;
X		case Blitz:
X			if(plr[c].p_pref != Blitz && plr[c].p_pref != Fblitz)
X				return Nopref;
X			break;
X		default:
X			switch(plr[c].p_pref) {
X			case Standard: case Fstand: pref = Standard; break;
X			case Blitz:    case Fblitz: pref = Blitz; break;
X			}
X			break;
X		}
X	}
X
X	return pref;
X}
X
X/*
X**	pickgametype: select gametype, winscore, and jokermode
X*/
Xpickgametype()
X{
X	winpref		temp;
X
X	switch(blitzwhen) {
X	case Noblitz:
X		gametype = Standard;
X		break;
X	case Onrequest:
X		if((gametype = votegametype()) == Nopref)
X			gametype = Standard;
X		break;
X	case Workhours:
X		if((gametype = votegametype()) == Nopref)
X			gametype = workhourstype();
X		break;
X	case Enforced:
X		if((gametype = workhourstype()) != Blitz)
X			if((temp = votegametype()) != Nopref)
X				gametype = temp;
X		break;
X	}
X
X	winscore = (gametype == Blitz) ? BLITZSCORE : WINSCORE;
X
X	if(enajokers == False)
X		jokermode = False;
X	else
X		jokermode = randint(6) == 6 ? True : False;
X}
X
X/*
X**	getequiv: determine equivalent hosts.
X*/
Xgetequiv()
X{
X	register FILE  *fp;
X	register int	n;
X	char			buf[512];
X
X	/*
X	**	Open the hosts.equiv file and count the entries.
X	**	Allocate memory for them and this host.
X	*/
X	neq = 1;
X	if((fp = fopen("/etc/hosts.equiv", "r")) != 0) {
X		while(fgets(buf, sizeof buf, fp) != 0)
X			++neq;
X		clearerr(fp);
X		rewind(fp);
X	}
X	if((equiv = (char **)malloc(neq * sizeof *equiv)) == 0) {
X		syslog(LOG_ERR, "malloc: no memory for equivalent hosts");
X		exit(1);
X	}
X
X	/*
X	**	This host is first in the list.
X	*/
X	(void) gethostname(buf, sizeof buf);
X	if((equiv[0] = malloc((unsigned)(strlen(buf) + 1))) == 0) {
X		syslog(LOG_ERR, "malloc: no memory for this host");
X		exit(1);
X	}
X	strcpy(equiv[0], buf);
X
X	/*
X	**	Add the hosts from hosts.equiv.
X	*/
X	if(fp != 0) {
X		for(n = 1;n < neq;++n) {
X			if(gets(buf) == 0) {
X				neq = n;
X				break;
X			}
X			if((equiv[n] = malloc((unsigned)(strlen(buf) + 1))) == 0) {
X				syslog(LOG_ERR, "malloc: no memory for an equivalent host");
X				exit(1);
X			}
X			strcpy(equiv[n], buf);
X		}
X		(void) fclose(fp);
X	}
X}
X
X/*
X**	equivalence: strip "@thishost" or "@equivhost" from an id
X*/
Xequivalence(id)
Xchar   *id;
X{
X	register char  *at;
X	register int	n;
X
X	if((at = index(id, '@')) == 0)
X		return;
X	if(neq == 0)
X		getequiv();
X	for(n = 0;n < neq;++n) {
X		if(strcmp(at+1, equiv[n]) == 0) {
X			*at = '\0';
X			return;
X		}
X	}
X}
X
X/*
X**	idinuse: return True if id is in use by a human
X**		or is in use by more than one Computer player
X**	side effect: converts user@thishost or user@equivhost to just user
X*/
Xboolean
Xidinuse(c, id)
Xint		c;
Xchar   *id;
X{
X	register int	cc;
X	int				humanuse;
X	int				computeruse;
X
X	equivalence(id);	/* strip @equivhost */
X
X	humanuse = computeruse = 0;
X	for(cc = 0;cc < PLAYERS;++cc) if(cc != c) {
X		if(strncmp(plr[cc].p_id, id, IDLEN-1) == 0) {
X			switch(plr[cc].p_stat) {
X			case Active:
X			case Waiting:
X			case Watching:
X			case Spider:
X				++humanuse;
X				break;
X			case Computer:
X				++computeruse;
X				break;
X			default:
X				break;
X			}
X		}
X	}
X
X	if(humanuse > 0 || computeruse > 1)
X		return True;
X
X	return False;
X}
X
X/*
X**	getplayername: get the player's name and check it
X*/
Xgetplayername(c)
Xint			c;
X{
X	register char  *aka;
X	char			msgbuf[MESGLEN];
X
X	/*
X	**	Prompt for and read name.
X	*/
X	sprintf(msgbuf,
X		"%d Hello, I'm the Cube daemon.  Who are you?\r\n", M_HELO);
X	if(dialogue(c, msgbuf, sizeof msgbuf) < 0) {
X		if(plr[c].p_stat != Inactive) {	/* in case of timeout */
X			(void) simp(c, M_DOWN, "Server closing connection.");
X			oldplayer(c);
X		}
X		return -1;
X	}
X
X	/*
X	**	Skip past any leading spaces or tabs in the response.
X	*/
X	for(aka = msgbuf;*aka == ' ' || *aka == '\t';++aka)
X		;
X
X	/*
X	**	If a non-null name was supplied, check that it is not in use.
X	**	If not, save the name in the list and we're done.
X	*/
X	if(*aka != '\0') {
X		strncpy(plr[c].p_name, aka, NAMELEN);
X		plr[c].p_name[NAMELEN-1] = '\0';
X		if(nameinuse(c, plr[c].p_name) == False) {
X			savename(plr[c].p_name);
X			return 0;
X		}
X		(void) simp(c, M_ARGE, "Your chosen moniker is already in use.");
X	}
X
X	/*
X	**	We were supplied with a null name, or one already in use.
X	**	Pick one from the monikers list rather than dumping the player.
X	*/
X	while(nameinuse(c, (aka = moniker())) == True)
X		;
X	strncpy(plr[c].p_name, aka, NAMELEN);
X	plr[c].p_name[NAMELEN-1] = '\0';
X
X	return 0;
X}
X
X/*
X**	getplayerid: get player's unique id (user@host)
X**		Allow setting of winscore preference.
X*/
Xgetplayerid(c)
Xint			c;
X{
X	register char  *s, *id;
X	char			msgbuf[MESGLEN];
X
X	/*
X	**	Prompt for and read the id.
X	*/
X	sprintf(msgbuf, "%d Please supply a unique id.\r\n", M_RQID);
X	if(dialogue(c, msgbuf, sizeof msgbuf) < 0) {
X		if(plr[c].p_stat != Inactive) {	/* in case of timeout */
X			sprintf(msgbuf, "%d No response taken to mean `No.'\r\n", M_ARGE);
X			goto iderror;
X		}
X		return -1;
X	}
X
X	/*
X	**	A null id is not valid.
X	*/
X	if(msgbuf[0] == '\0') {
X		sprintf(msgbuf, "%d Sorry, that's not unique.\r\n", M_ARGE);
X		goto iderror;
X	}
X
X	/*
X	**	Check for winscore preference.  Ignore bogus values.
X	*/
X	if(msgbuf[1] != ';') {
X		plr[c].p_pref = Nopref;
X		id = &msgbuf[0];
X	} else {
X		switch(msgbuf[0]) {
X		case 's': plr[c].p_pref = Standard; break;
X		case 'S': plr[c].p_pref = Fstand; break;
X		case 'b': plr[c].p_pref = Blitz; break;
X		case 'B': plr[c].p_pref = Fblitz; break;
X		default:  plr[c].p_pref = Nopref; break;
X		}
X		id = &msgbuf[2];
X	}
X
X	/*
X	**	Id's are alphanumerics plus a few other characters.
X	*/
X	for(s = id;*s != '\0';++s) {
X		if(isalnum(*s) || index("@._-+=", *s) != 0)
X			continue;
X		sprintf(msgbuf, "%d `%c' is not allowed in an id.\r\n", M_ARGE, *s);
X		goto iderror;
X	}
X
X	/*
X	**	Check to see that the id is not already in use.
X	*/
X	strncpy(plr[c].p_id, id, IDLEN);
X	plr[c].p_id[IDLEN-1] = '\0';
X	if(idinuse(c, plr[c].p_id) == True) {
X		sprintf(msgbuf, "%d You're already playing!\r\n", M_ARGE);
X		goto iderror;
X	}
X
X	/*
X	**	Success.
X	*/
X	return 0;
X
X	/*
X	**	Common error handling code.
X	*/
Xiderror:
X	if(message(c, msgbuf) < 0)
X		return -1;
X	(void) simp(c, M_DOWN, "Server closing connection.");
X	oldplayer(c);
X	return -1;
X}
X
X/*
X**	getplayerintent: get the player's intent
X**		If a game is inprogress, the question is watch or play?
X**		Otherwise, the question is wait or play?
X**	Assumes that player status is already set to Watching.
X*/
Xgetplayerintent(c)
Xint			c;
X{
X	history	   *ph;
X	char		msgbuf[MESGLEN];
X
X	/*
X	**	Prompt for and read intent.
X	*/
X	sprintf(msgbuf, "%d Do you want to %s or play? [wp]\r\n",
X		inprogress == True ? M_WORP : M_SORP,
X		inprogress == True ? "watch" : "wait");
X	if(dialogue(c, msgbuf, sizeof msgbuf) < 0) {
X		if(plr[c].p_stat != Inactive) {	/* in case of timeout */
X			(void) simp(c, M_DOWN, "Server closing connection.");
X			oldplayer(c);
X		}
X		return -1;
X	}
X	
X	/*
X	**	Check it for validity.
X	*/
X	switch(msgbuf[0]) {
X	case '\0':				/* default */
X	case 'p': case 'P':		/* Play */
X		plr[c].p_stat = Waiting, ++waiting, --watching;
X		return 0;
X	case 'w': case 'W':		/* Watch or Wait */
X		if(inprogress == False) {
X			plr[c].p_stat = Spider, ++spiders, --watching;
X			return 0;
X		}
X		if(active <= 0 && waiting <= 0) {
X			if(simp(c, M_ARGE, "Sorry, no game in progress.") < 0)
X				return -1;
X			break;
X		}
X		/*
X		**	We let new players watch, but we don't let ancient ones do so.
X		**	It seems that some people would rather watch than play, but
X		**	that's no fun for the regular players.
X		*/
X		if((ph = histbyname(plr[c].p_id)) != 0) {
X			if(ph->h_lastgame > 0 && gamenum - ph->h_lastgame >= ANCIENT) {
X				syslog(LOG_NOTICE,
X					"getplayerintent: excluded ancient voyeur %s", plr[c].p_id);
X				if(simp(c, M_ARGE, "Sorry, you're too ancient to watch!") < 0)
X					return -1;
X				break;
X			}
X		}
X		return 0;
X	default:
X		if(simp(c, M_ARGE, "Sorry, but that's nonsense.") < 0)
X			return -1;
X		break;
X	}
X
X	(void) simp(c, M_DOWN, "Server closing connection.");
X	oldplayer(c);
X	return -1;
X}
X
X/*
X**	fixroster: do any necessary roster adjustment
X**		If add is true, we can add Waiting players to the front of
X**		the roster.  If false, we will move them to the end.
X**		Collapses out Inactive slots.
X*/
Xfixroster(add)
Xboolean		add;
X{
X	int		c, cc, low, squ;
X	player	tmp[PLAYERS];
X
X	/*
X	**	No reason to do this except during a game.
X	*/
X	if(inprogress == False)
X		return;
X
X	/*
X	**	If there are no Waiting players, or we're not allowed to add them,
X	**	check for "empty" slots in the roster.  We define empty here as
X	**	Inactive, Watching, or Waiting, since to Active players, slots held
X	**	by Watching or Waiting players appear empty.
X	*/
X	if(waiting == 0 || add == False) {
X		for(c = 0;c < PLAYERS;++c)		/* find first empty slot */
X			if(plr[c].p_stat != Computer && plr[c].p_stat != Active)
X				break;
X		for(++c;c < PLAYERS;++c)		/* look for a non-empty slot */
X			if(plr[c].p_stat == Computer || plr[c].p_stat == Active)
X				break;
X		if(c >= PLAYERS)				/* didn't find any */
X			return;
X	}
X
X	/*
X	**	If we are allowed to add Waiting players, put them first,
X	**	otherwise put them last with the Watching players.  In either
X	**	case, this algorithm removes empty slots.
X	*/
X	cc = 0;
X	if(add == True) {
X		for(c = 0;c < PLAYERS;++c)	/* first pass, Waiting players */
X			if(plr[c].p_stat == Waiting)
X				tmp[cc++] = plr[c];
X	}
X	for(c = 0;c < PLAYERS;++c)		/* second pass, Active players */
X		if(plr[c].p_stat == Computer || plr[c].p_stat == Active)
X			tmp[cc++] = plr[c];
X	for(c = 0;c < PLAYERS;++c)		/* third pass, Watching players */
X		if(	plr[c].p_stat == Watching
X		|| plr[c].p_stat == Spider
X		||	(add == False && plr[c].p_stat == Waiting))
X			tmp[cc++] = plr[c];
X	if(cc == 0) {
X		syslog(LOG_DEBUG, "fixroster: no players!");
X		active = waiting = watching = 0;
X		return;
X	}
X
X	/*
X	**	Calculate a suitable low score for any new players.
X	*/
X	if(waiting == 0 || add == False || (low = lowscore(&squ) - ONBOARD) < 0)
X		low = 0;
X
X	/*
X	**	Copy the reordered roster into the real one while at the
X	**	same time adding waiting players (if allowed).
X	*/
X	for(c = 0;c < cc;++c) {
X		plr[c] = tmp[c];
X		if(add == True && plr[c].p_stat == Waiting) {
X			plr[c].p_score = low;
X			plr[c].p_squander = squ;
X			plr[c].p_onboard = False;
X			if(plr[c].p_computer != 0)
X				plr[c].p_stat = Computer, --waiting;
X			else {
X				plr[c].p_stat = Active, --waiting, ++active;
X				(void) tellstatus(c);	/* XXX */
X			}
X		}
X	}
X	for(;c < PLAYERS;++c)
X		zeroplayer(c);
X	if(waiting != 0) {
X		syslog(LOG_DEBUG, "fixroster: waiting=%d (should be zero)", waiting);
X		waiting = 0;
X	}
X
X	/*
X	**	Broadcast the new roster to all players.
X	*/
X	roster(-1, False);
X}
END_OF_FILE
if test 18633 -ne `wc -c <'cubeserv2.c'`; then
    echo shar: \"'cubeserv2.c'\" unpacked with wrong size!
fi
# end of 'cubeserv2.c'
fi
if test -f 'history.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'history.c'\"
else
echo shar: Extracting \"'history.c'\" \(17309 characters\)
sed "s/^X//" >'history.c' <<'END_OF_FILE'
X/*	vi:set sw=4 ts=4: */
X#ifndef	lint
Xstatic char	sccsid[] = "@(#)history.c 5.1 (G.M. Paris) 89/01/22";
X#endif	lint
X
X/*
X**
X**	cubes 5.1  Copyright 1989 Gregory M. Paris
X**		Permission granted to redistribute on a no charge basis.
X**		All other rights are reserved.
X**
X*/
X
X#include	<stdio.h>
X#include	<syslog.h>
X#include	<signal.h>
X#include	<sys/file.h>
X#include	<sys/types.h>
X#include	<sys/stat.h>
X#include	<strings.h>
X#include	"cubes.h"
X
X#define		MVGAMES		25			/* number of games for moving avg */
X
Xhistory			   *hist;			/* history array */
Xunsigned			nhist	= 0;	/* entries in history array */
Xlong				gamenum;		/* current game number */
Xptstype				ptsmode	= Combined;	/* history points used in ranking */
X
Xextern int			winscore;
Xextern int			turnnum;
Xextern player		plr[];
Xextern int			plrcmp();
Xextern long			histwgt();
Xextern long			histwgtplr();
Xstatic history	   *histadd();
Xextern history	   *histbyname();
Xextern computer		comptbl[];
Xextern computer	   *compbyname();
Xextern boolean		iscomp();
Xextern boolean		iskamelion();
X
Xextern char		   *fgets();
Xextern char		   *malloc();
Xextern char		   *realloc();
X
X/*
X**	histread: read history file
X**		Any format conversions must be handled by a separate utility.
X*/
Xhistread(histfile)
Xchar   *histfile;
X{
X	register history   *ph;
X	register int		n;
X	register FILE	   *fp;
X	char				line[BUFSIZ];
X	char				dupid[IDLEN];
X	long				duplastgame;
X	int					omask;
X
X	omask = sigsetmask(~0);
X
X	if(hist != 0) {
X		free((char *)hist);
X		hist = 0, nhist = 0;
X	}
X
X	/*
X	**	Open the file for reading.  The file must exist.
X	*/
X	if((fp = fopen(histfile, "r")) == 0) {
X		syslog(LOG_ERR, "histread: fopen(%s,r): %m", histfile);
X		(void) sigsetmask(omask);
X		return -1;
X	}
X	/*
X	**	Count the lines in the file.  If none, we're all done.
X	*/
X	for(n = 0;fgets(line, sizeof line, fp) != 0;++n)
X		;
X	if(n == 0) {
X		(void) fclose(fp);
X		(void) sigsetmask(omask);
X		return 0;
X	}
X
X	/*
X	**	Rewind the file, allocate space, and read in the entries.
X	**	There are now two lines per player.
X	*/
X	clearerr(fp);
X	rewind(fp);
X	nhist = n / 2;
X	if((hist = (history *)malloc(nhist * sizeof *hist)) == 0) {
X		syslog(LOG_ERR, "histread: malloc: can't allocate space for history");
X		(void) fclose(fp);
X		(void) sigsetmask(omask);
X		return -1;
X	}
X
X	for(n = 0, ph = hist;;++n, ++ph) {
X		if(fgets(line, sizeof line, fp) == 0)
X			break;
X		if(sscanf(line, "L %ld %ld %ld %ld %ld%*[ ]%[^\n]",
X			&ph->h_games, &ph->h_wins, &ph->h_points, &ph->h_avgturn,
X			&ph->h_lastgame, ph->h_id) != 6) {
X			syslog(LOG_DEBUG, "histread: got bad lifetime in %s", histfile);
X			continue;
X		}
X		if(fgets(line, sizeof line, fp) == 0) {
X			syslog(LOG_DEBUG, "histread: missing last moving in %s", histfile);
X			break;
X		}
X		if(sscanf(line, "M %ld %ld %ld %ld %ld%*[ ]%[^\n]",
X			&ph->h_mvgames, &ph->h_mvwins, &ph->h_mvpoints, &ph->h_mvavgturn,
X			&duplastgame, dupid) != 6) {
X			syslog(LOG_DEBUG, "histread: got bad moving in %s", histfile);
X			continue;
X		}
X		if(strcmp(ph->h_id, dupid) != 0) {
X			syslog(LOG_DEBUG, "histread: name mismatch %ls vs %ls in %s",
X				ph->h_id, dupid, histfile);
X			continue;
X		}
X		if(ph->h_lastgame != duplastgame) {
X			syslog(LOG_DEBUG,
X				"histread: lastgame mismatch %ld vs %ld for %s in %s",
X				ph->h_lastgame, duplastgame, ph->h_id, histfile);
X			continue;
X		}
X		ph->h_computer = compbyname(ph->h_id);
X	}
X
X	(void) fclose(fp);
X
X	(void) sigsetmask(omask);
X	histsort();
X
X	return 0;
X}
X
X/*
X**	histprune: remove ancient records from history
X**		assumes gamenum has been set
X*/
Xhistprune(gpcredit)
Xint		gpcredit;
X{
X	register int		h;
X	register history   *pt, *pf;
X	register long		span;
X	unsigned			nnhist;
X	int					omask;
X
X	omask = sigsetmask(~0);
X
X	/*
X	**	Look for ancient records and delete them.
X	*/
X	pt = pf = hist;
X	for(h = 0;h < nhist;++h, ++pf) {
X		span = gamenum - pf->h_lastgame;
X		if(gpcredit != 0)
X			span -= gpcredit * pf->h_games;
X		if(span >= ANCIENT)
X			continue;
X		if(pt != pf)
X			*pt = *pf;
X		++pt;
X	}
X
X	/*
X	**	Shrink memory used by history to account for pruned entries.
X	*/
X	nnhist = (unsigned)(pt - hist);
X	if(nnhist != nhist) {
X		if((hist =
X			(history *)realloc((char *)hist, nnhist * sizeof *hist)) == 0) {
X			syslog(LOG_WARNING, "histprune: realloc failed");
X			nnhist = 0;
X		}
X		nhist = nnhist;
X	}
X
X	(void) sigsetmask(omask);
X}
X
X/*
X**	histcmp: history comparison function for history file ordering
X*/
Xhistcmp(h1, h2)
Xhistory	   *h1, *h2;
X{
X	return (int)(h2->h_weight - h1->h_weight);
X}
X
X/*
X**	histsort: sort the history array
X*/
Xhistsort()
X{
X	register int	n;
X	int				omask;
X
X	if(nhist != 0 && hist != 0) {
X		for(n = 0;n < nhist;++n)
X			(void) histwgt(n);
X		omask = sigsetmask(~0);
X		qsort((char *)hist, (int)nhist, sizeof *hist, histcmp);
X		(void) sigsetmask(omask);
X		for(n = 0;n < nhist;++n)
X			(hist+n)->h_rank = n + 1;
X	}
X}
X
X/*
X**	histwrite: write the history file, pruning ancient records
X*/
Xhistwrite(histfile)
Xchar   *histfile;
X{
X	register history   *ph;
X	register FILE	   *fp;
X	register int		n;
X	int					omask;
X
X	omask = sigsetmask(~0);		/* block all signals */
X
X	(void) umask(0644);
X	if((fp = fopen(histfile, "w")) == 0) {
X		syslog(LOG_ERR, "histwrite: fopen(%s,w): %m", histfile);
X		(void) sigsetmask(omask);
X		return -1;
X	}
X
X	if(nhist != 0) {
X		histprune(WRITECREDIT);
X		histsort();
X		for(n = 0, ph = hist;n < nhist;++n, ++ph) {
X			fprintf(fp, "L %5ld %5ld %10ld %10ld %6ld %s\n",
X				ph->h_games, ph->h_wins, ph->h_points, ph->h_avgturn,
X				ph->h_lastgame, ph->h_id);
X			fprintf(fp, "M %5ld %5ld %10ld %10ld %6ld %s\n",
X				ph->h_mvgames, ph->h_mvwins, ph->h_mvpoints, ph->h_mvavgturn,
X				ph->h_lastgame, ph->h_id);
X		}
X	}
X
X	(void) fsync(fileno(fp));	/* commit to disk */
X	(void) fclose(fp);
X
X	(void) sigsetmask(omask);	/* restore signal mask */
X
X	return 0;
X}
X
X/*
X**	histtime: return modification time of history file
X*/
Xhisttime(histfile, ptime)
Xchar   *histfile;
Xtime_t *ptime;
X{
X	struct stat	st;
X
X	if(stat(histfile, &st) < 0) {
X		syslog(LOG_NOTICE, "histtime: stat(%s): %m", histfile);
X		return -1;
X	}
X	*ptime = st.st_mtime;
X
X	return 0;
X}
X
X/*
X**	histadd: add a player to the history array
X*/
Xstatic history *
Xhistadd(c)
Xint		c;
X{
X	register history   *ph;
X	char			   *id;
X	int					omask;
X
X	omask = sigsetmask(~0);
X
X	if(nhist == 0)
X		hist = (history *)malloc(sizeof *hist), ++nhist;
X	else
X		hist = (history *)realloc((char *)hist, ++nhist * sizeof *hist);
X
X	if(hist == 0) {
X		syslog(LOG_ERR, "histadd: no memory for more history");
X		nhist = 0;
X		(void) sigsetmask(omask);
X		return (history *)0;
X	}
X
X	/*
X	**	Initialize the new entry.
X	*/
X	ph = hist + nhist - 1;
X	if(*(id = plr[c].p_id) == '\0')
X		id = plr[c].p_name;
X	strncpy(ph->h_id, id, IDLEN);
X	ph->h_id[IDLEN-1] = '\0';
X	ph->h_computer = compbyname(ph->h_id);
X	ph->h_rank = nhist, ph->h_weight = 0;
X	ph->h_points = ph->h_avgturn = ph->h_wins = ph->h_games = 0;
X	ph->h_mvpoints = ph->h_mvavgturn = ph->h_mvwins = ph->h_mvgames = 0;
X	ph->h_lastgame = 0;
X
X	(void) sigsetmask(omask);
X
X	return ph;
X}
X
X/*
X**	histpoints: add player's points to history
X**		updates game count and lastgame and adds new entries as needed
X**		uses a fifty-percent rule to penalize players not onboard
X*/
Xhistpoints(c)
Xint		c;
X{
X	history	   *ph;
X	char	   *id;
X	long		scale, fifty;
X	long		avgturn;
X	double		winrate;
X
X	/*
X	**	Locate player's entry in history array.
X	**	If player doesn't have an entry, call histadd
X	**	to add one.  Histadd returns a pointer to the
X	**	new entry or zero on error.
X	*/
X	if(*(id = plr[c].p_id) == '\0')
X		id = plr[c].p_name;
X	if((ph = histbyname(id)) == 0) {
X		if((ph = histadd(c)) == 0) {
X			syslog(LOG_DEBUG, "histpoints: histadd failed");
X			return -1;
X		}
X	}
X
X	/*
X	**	If this non-COMP player was not onboard, it was probably a quit.
X	**	In this case, we penalize the player's point total to get the
X	**	same ptsmode average that would result if the game was played to
X	**	finish and the player got 50% of its ptsmode average.  We must not
X	**	update game count or lastgame.  We do adjust the moving average
X	**	using the same half-average score, though the updating is a
X	**	bit more complex due to the nature of the recent point total.
X	*/
X	if(plr[c].p_onboard == False && iscomp(c) == False) {
X		if(ph->h_games == 0)
X			return 0;
X
X		histcalc(ph, &winrate, &fifty, &avgturn);
X		fifty /= 2;
X
X		/*
X		**	The next calculation done purely as integer math is likely to
X		**	overflow once the player has about 500 games.
X		*/
X#ifdef	notdef
X		ph->h_points = (ph->h_games * ph->h_points + fifty) / (ph->h_games + 1);
X#else	notdef
X		{
X			double	rat, gam;
X
X			gam = (double)(ph->h_games + 1);
X			rat = (double)ph->h_games / gam;
X			ph->h_points = (long)(ph->h_points * rat + fifty / gam);
X		}
X#endif	notdef
X
X		if(ph->h_mvgames == MVGAMES)
X			ph->h_mvpoints += fifty - ph->h_mvpoints / MVGAMES;
X		else	/* next line not strictly correct if h_mvgames > MVGAMES */
X			ph->h_mvpoints = (ph->h_mvgames * ph->h_mvpoints + fifty)
X				/ (ph->h_mvgames + 1);
X
X		return 0;
X	}
X
X	/*
X	**	Update points and avgturn and increment game count.  History points
X	**	are based on games of WINSCORE points, so scale points here before
X	**	adding to history.  Note that avgturn does not need scaling.
X	**	Game count is incremented and lastgame is set only here.  Mvwins
X	**	is updated here as if the game was a loss -- corrected in histwin.
X	*/
X	scale = (WINSCORE * (long)plr[c].p_score) / winscore;
X	avgturn = turnnum > 0 ? (long)plr[c].p_score / turnnum : 0L;
X
X	if(ph->h_mvgames == MVGAMES) {
X		ph->h_mvpoints += scale - ph->h_mvpoints / MVGAMES;
X		ph->h_mvavgturn += avgturn - ph->h_mvavgturn / MVGAMES;
X		ph->h_mvwins -= ph->h_mvwins / MVGAMES;
X	} else if(ph->h_mvgames < MVGAMES) {
X		ph->h_mvpoints += scale;
X		ph->h_mvavgturn += avgturn;
X		/* no adjustment of h_mvwins necessary */
X		++ph->h_mvgames;
X	} else {	/* over! */
X		ph->h_mvpoints = (MVGAMES * ph->h_mvpoints) / ph->h_mvgames;
X		ph->h_mvavgturn = (MVGAMES * ph->h_mvavgturn) / ph->h_mvgames;
X		ph->h_mvwins = (MVGAMES * ph->h_mvwins) / ph->h_mvgames;
X		ph->h_mvgames = MVGAMES;
X		ph->h_mvpoints += scale - ph->h_mvpoints / MVGAMES;
X		ph->h_mvavgturn += avgturn - ph->h_mvavgturn / MVGAMES;
X		ph->h_mvwins -= ph->h_mvwins / MVGAMES;
X	}
X
X	ph->h_games++;
X	ph->h_points += scale;
X	ph->h_avgturn += avgturn;
X	ph->h_lastgame = gamenum;
X
X	return 0;
X}
X
X/*
X**	histwins: update win count for game winner
X**		assumes that histpoints has been called to update game count
X**		assumes that histpoints adjusts mvwins as if game was lost
X**		doesn't add new history entries as needed
X*/
Xhistwins(c)
Xint		c;
X{
X	register char	   *id;
X	register history   *ph;
X
X	if(*(id = plr[c].p_id) == '\0')
X		id = plr[c].p_name;
X
X	if((ph = histbyname(id)) == 0) {
X		syslog(LOG_DEBUG, "histwins: can't find <%s> in history", id);
X		return -1;
X	}
X
X	ph->h_wins++;
X	ph->h_mvwins += H_MVWINMULT;
X	return 0;
X}
X
X/*
X**	histwgt: return weighted value of history item n
X*/
Xlong
Xhistwgt(n)
Xint		n;
X{
X	register history   *ph;
X	long				avgpoints, avgturn;
X	double				winrate;
X
X	if(n >= nhist || (ph = hist + n)->h_games == 0) {
X		ph->h_weight = 0;
X		return 0L;
X	}
X
X	histcalc(ph, &winrate, &avgpoints, &avgturn);
X	ph->h_weight = (long)(1000L * winrate) + avgpoints + avgturn;
X
X	return ph->h_weight;
X}
X
X/*
X**	histavgplr: return player's historical average game points
X*/
Xlong
Xhistavgplr(c)
Xint		c;
X{
X	history	   *ph;
X	char	   *id;
X	long		avgmpt, avtnpt;
X	double		winrt;
X
X	if(*(id = plr[c].p_id) == '\0')
X		id = plr[c].p_name;
X	
X	if((ph = histbyname(id)) == 0 || ph->h_games == 0)
X		return 0L;	/* no average */
X
X	histcalc(ph, &winrt, &avgmpt, &avtnpt);
X	return avgmpt;
X}
X
X/*
X**	histwgtplr: return player's historical wins/points weighting
X*/
Xlong
Xhistwgtplr(c)
Xint		c;
X{
X	history	   *ph;
X	char	   *id;
X
X	if(*(id = plr[c].p_id) == '\0')
X		id = plr[c].p_name;
X	
X	if((ph = histbyname(id)) == 0)
X		return 0L;
X
X	return histwgt((int)(ph - hist));
X}
X
X/*
X**	humanplaying: returns true if the human is playing or watching this game
X*/
Xboolean
Xhumanplaying(c, id)
Xint		c;
Xchar   *id;
X{
X	register int	cc;
X
X	for(cc = 0;cc < PLAYERS;++cc) if(cc != c) {
X		switch(plr[cc].p_stat) {
X		case Active:
X		case Waiting:
X		case Watching:
X			if(strncmp(plr[cc].p_id, id, IDLEN) == 0)
X				return True;
X			break;
X		}
X	}
X
X	return False;
X}
X
X/*
X**	histfmtplr: return formatted history entry for player
X**		or entry in relation to player
X**	Note: this routine is used by the server to let players check
X**		their own and other players' histories during a game.
X**		Some deception is used when Kamelion is the subject of inquiry.
X*/
Xchar *
Xhistfmtplr(c, rel, useid)
Xint		c, rel;
Xboolean	useid;
X{
X	register history   *ph;
X	register int		n, d;
X	register char	   *id;
X	static char			fmt[MESGLEN];
X
X	/*
X	**	Sorry, but we don't want to have to do a lookup.
X	**	What's the right thing to do in this case anyway?
X	*/
X	if(useid == False && rel != 0)
X		useid = True;
X
X	/*
X	**	We have to let Waiting and Watching pass here, or a such a
X	**	player can't use the related commands at the "next game?"
X	**	prompt.  This can be used to spy on observers.
X	*/
X	if(c < 0 || c >= PLAYERS || plr[c].p_stat == Inactive) {
X		sprintf(fmt, "No player number %d.", c+1);
X		return fmt;
X	}
X
X	if(*(id = plr[c].p_id) == '\0')
X		id = plr[c].p_name;
X	if((ph = histbyname(id)) == 0) {
X		sprintf(fmt, "%s is not ranked.",
X			useid == True ? plr[c].p_id : plr[c].p_name);
X		return fmt;
X	}
X	n = (int)(ph - hist);
X
X	if(rel != 0) {
X		ph += rel, n += rel;
X		if(n < 0 || n >= nhist) {
X			sprintf(fmt, "No player is ranked %d.", n+1);
X			return fmt;
X		}
X	}
X
X	/*
X	**	When rel is zero and useid is False, this is a query about one player
X	**	by another.  In this case, we try to protect Kamelion's identity.
X	**	What we do is look for the nearest ranked human that's not playing.
X	**	If there isn't one, we tell the truth.
X	*/
X	if(rel == 0 && useid == False && iskamelion(c) == True) {
X		for(d = 1;d < nhist;++d) {
X			if(n - d >= 0 && (ph-d)->h_computer == 0
X			&& humanplaying(c, (ph-d)->h_id) == False) {
X				ph -= d, n -= d;
X				break;
X			}
X			if(n + d < nhist && (ph+d)->h_computer == 0
X			&& humanplaying(c, (ph+d)->h_id) == False) {
X				ph += d, n += d;
X				break;
X			}
X		}
X	}
X
X	if(ph->h_games == 0)
X		sprintf(fmt, "%ld %.18s 0 * * * %ld",
X			ph->h_rank, useid == True ? ph->h_id : plr[c].p_name, ph->h_weight);
X	else {
X		long	avgpoints, avgturn;
X		double	winrate;
X
X		histcalc(ph, &winrate, &avgpoints, &avgturn);
X		sprintf(fmt, "%ld %.18s %ld %5.3f %ld %ld %ld",
X			ph->h_rank, useid == True ? ph->h_id : plr[c].p_name,
X			ph->h_games, winrate, avgpoints, avgturn, ph->h_weight);
X	}
X	
X	return fmt;
X}
X
X/*
X**	historder: reorder players by historical wins/points weighting
X**		and/or score from previous game
X*/
Xhistorder(firstgame)
Xboolean	firstgame;
X{
X	register int	c;
X	int				omask;
X#define	SORTBONUS	15000
X
X	/*
X	**	Give active players a score based on their history unless they
X	**	played in the previous game.  In that case, award them a bonus
X	**	that guarantees preferential treatment.
X	*/
X	for(c = 0;c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Computer:
X		case Active:
X			if(firstgame == True || plr[c].p_score == 0)
X				plr[c].p_score = (int)histwgtplr(c);
X			else
X				plr[c].p_score += SORTBONUS;
X			break;
X		default:
X			plr[c].p_score = 0;
X			break;
X		}
X	}
X
X	omask = sigsetmask(~0);
X	qsort((char *)plr, PLAYERS, sizeof plr[0], plrcmp);
X	(void) sigsetmask(omask);
X
X#undef	SORTBONUS
X}
X
X/*
X**	histbyname: return pointer to player history or NULL
X*/
Xhistory *
Xhistbyname(id)
Xregister char  *id;
X{
X	register int	h;
X
X	for(h = 0;h < nhist;++h)
X		if(strncmp(id, (hist+h)->h_id, IDLEN) == 0)
X			return hist+h;
X	
X	return (history *)0;
X}
X
X/*
X**	setgamenum: set current game number using COMP's game count
X*/
Xsetgamenum()
X{
X	register int	h;
X
X	/*
X	**	Search for the COMP computer and set the gamenum to one more
X	**	than the number of games played.  This works because the COMP
X	**	computer plays in every game.
X	*/
X	for(h = 0;h < nhist;++h) {
X		if((hist+h)->h_computer == &comptbl[0]) {
X			gamenum = (hist+h)->h_games + 1;
X			return;
X		}
X	}
X
X	/*
X	**	This really may be the first game!
X	*/
X	if(nhist == 0) {
X		gamenum = 1;
X		return;
X	}
X
X	/*
X	**	Don't know what happened to cause this, but let's try to handle it by
X	**	setting the gamenum to one more than the highest played by any player.
X	*/
X	syslog(LOG_DEBUG, "setgamenum: can't find COMP computer");
X	gamenum = 0;
X	for(h = 0;h < nhist;++h)
X		if((hist+h)->h_lastgame > gamenum)
X			gamenum = (hist+h)->h_lastgame;
X	++gamenum;
X}
X
X/*
X**	histcalc: calculate winrt, avgmpt, and avtnpt from history
X*/
Xhistcalc(ph, pwinrt, pavgmpt, pavtnpt)
Xregister history   *ph;
Xdouble			   *pwinrt;
Xlong			   *pavgmpt, *pavtnpt;
X{
X	if(ph->h_games == 0) {
X		*pwinrt = 0.0;
X		*pavgmpt = *pavtnpt = 0;
X		return;
X	}
X
X	switch(ptsmode) {
X	case Lifetime:
X		*pwinrt = (double)ph->h_wins / ph->h_games;
X		*pavgmpt = ph->h_points / ph->h_games;
X		*pavtnpt = ph->h_avgturn / ph->h_games;
X		break;
X	case Recent:
X		*pwinrt = (double)ph->h_mvwins / (H_MVWINMULT * ph->h_mvgames);
X		*pavgmpt = ph->h_mvpoints / ph->h_mvgames;
X		*pavtnpt = ph->h_mvavgturn / ph->h_mvgames;
X		break;
X	case Combined:
X		*pwinrt = (double)ph->h_wins / ph->h_games;
X		*pavgmpt = ph->h_points / ph->h_games;
X		*pavtnpt = ph->h_avgturn / ph->h_games;
X		*pwinrt += (double)ph->h_mvwins / (H_MVWINMULT * ph->h_mvgames);
X		*pavgmpt += ph->h_mvpoints / ph->h_mvgames;
X		*pavtnpt += ph->h_mvavgturn / ph->h_mvgames;
X		*pwinrt *= 0.5;
X		*pavgmpt /= 2;
X		*pavtnpt /= 2;
X		break;
X	}
X}
END_OF_FILE
if test 17309 -ne `wc -c <'history.c'`; then
    echo shar: \"'history.c'\" unpacked with wrong size!
fi
# end of 'history.c'
fi
echo shar: End of archive 6 \(of 8\).
cp /dev/null ark6isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 8 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0