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 c

⟦e51aad883⟧ TextFile

    Length: 18633 (0x48c9)
    Types: TextFile
    Names: »cubeserv2.c«

Derivation

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

TextFile

/*	vi:set sw=4 ts=4: */
#ifndef	lint
static char	sccsid[] = "@(#)cubeserv2.c 5.1 (G.M. Paris) 89/04/25";
#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	<ctype.h>
#include	<strings.h>
#include	<syslog.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	"cubes.h"

extern long		gamenum;
extern char	   *optarg;
extern char	   *fgets();
extern char	   *gets();
extern char	   *malloc();
extern history *histbyname();
extern char	   *moniker();
extern time_t	time();
struct tm	   *localtime();
extern boolean	nameinuse();
extern boolean	idinuse();
extern boolean	isproxy();
extern winpref	workhourstype();

extern unsigned	nhist;					/* number of entries in history */
extern unsigned	neq;					/* number of equivalent hosts */
extern char	  **equiv;					/* equivalent hosts */
extern int		active;					/* number of Active humans */
extern int		waiting;				/* number of Waiting humans */
extern int		watching;				/* number of Watching humans */
extern int		spiders;				/* number of Spiders */
extern int		turnnum;				/* current turn number */
extern boolean	enajokers;				/* enable jokers */
extern boolean	jokermode;				/* dice do not have joker faces */
extern boolean	inprogress;				/* True when game in progress */
extern boolean	clearobs;				/* Observers are showing in roster */
extern blitzmode blitzwhen;				/* Blitz mode enforced during work */
extern winpref	gametype;				/* specifies winscore */
extern int		winscore;				/* points needed to win */
extern player	plr[];					/* player/connection list */

/*
**	prescreen: does this person really want to play?
*/
boolean
prescreen(c)
register int	c;
{
	char   *reason;

	/*
	**	If there's a game in progress, we'll return False if the
	**	game mode is incompatible with the player's preference.
	**	If no game is in progress, we check to see if the prevailing
	**	mode is enough to decide that the person doesn't want to play.
	*/
	reason = 0;
	switch(plr[c].p_pref) {
	case Fstand:	/* only wants to play in a Standard game */
		if(inprogress == True && gametype != Standard)
			reason = "Sorry, the current game is not Standard.";
		else if(blitzwhen == Enforced && workhourstype() == Blitz)
			reason = "Sorry, only Blitz games may be played now.";
		break;
	case Fblitz:	/* only wants to play in a Blitz game */
		if(inprogress == True && gametype != Blitz)
			reason = "Sorry, the current game is not Blitz.";
		else if(blitzwhen == Noblitz)
			reason = "Sorry, this server doesn't play Blitz.";
		break;
	}

	/*
	**	Tell them why we're saying goodbye and hang up.
	*/
	if(reason != 0) {
		(void) simp(c, M_DOWN, reason);
		oldplayer(c);
		return False;
	}

	return True;
}

/*
**	oldplayer: remove a player from the game
*/
oldplayer(c)
register int	c;
{
	/*
	**	Close the socket if one is open.
	*/
	if(plr[c].p_fd != -1) {
		(void) close(plr[c].p_fd);
		plr[c].p_fd = -1, plr[c].p_timeouts = 0;
	}

	/*
	**	Change the player's status.
	*/
	switch(plr[c].p_stat) {
	case Inactive:
		break;

	case Active:
		/*
		**	If game in progress and there are other humans playing and this
		**	player is on board, then there's a good chance that a Computer
		**	proxy will be assigned to take over.  The proxy keeps the original
		**	name so that other players are not made aware.  The id is also
		**	left unchanged so that a single player can't flood the game with
		**	proxies.  We do this substitution, more rarely, when a player
		**	is not yet on board, to penalize players that repeatedly start
		**	and quit games looking for an initial big advantage.
		*/
		if(inprogress == True && active > 1) {
			if(	isproxy(c) == True			/* already a temporary proxy */
			||	(plr[c].p_onboard == True && randint(5) > 2)	/* 3/5ths */
			||	(plr[c].p_onboard == False && randint(5) == 1)	/* 1/5th */
			) {
				pickproxy(c);	/* set up proxy personality */
				plr[c].p_stat = Computer, --active;	/* permanent proxy */
				return;			/* all done */
			}
		}

		/*
		**	If the player wasn't replaced, then we remove.
		**	If a game is in progress adjust player's history.
		*/
		if(inprogress == True)
			histpoints(c);
		plr[c].p_stat = Inactive, --active;
		if(active < 0) {
			syslog(LOG_DEBUG,
				"oldplayer: active=%d (shouldn't be negative)", active);
			active = 0;
		}
		if(active == 0)
			inprogress = False;	/* XXX: update Computer points history? */
		annfarewell(c);
		break;

	case Waiting:
		/*
		**	If observers are showing in the roster, and this is not a
		**	Waiting Computer, then announce farewell.
		*/
		plr[c].p_stat = Inactive, --waiting;
		if(waiting < 0) {
			syslog(LOG_DEBUG,
				"oldplayer: waiting=%d (shouldn't be negative)", waiting);
			waiting = 0;
		}
		if(clearobs == True && plr[c].p_computer == 0)
			annfarewell(c);
		break;

	case Watching:
		plr[c].p_stat = Inactive, --watching;
		if(watching < 0) {
			syslog(LOG_DEBUG,
				"oldplayer: watching=%d (shouldn't be negative)", watching);
			watching = 0;
		}
		if(clearobs == True)		/* observers are showing in roster */
			annfarewell(c);			/* clear this one */
		break;

	case Spider:
		plr[c].p_stat = Inactive, --spiders;
		if(spiders < 0) {
			syslog(LOG_DEBUG,
				"oldplayer: spiders=%d (shouldn't be negative)", spiders);
			spiders = 0;
		}
		break;

	case Computer:
		plr[c].p_stat = Inactive;
		if(inprogress == True)
			histpoints(c);
		annfarewell(c);
		break;
	}

	zeroplayer(c);
	(void) observers(-1);
}

/*
**	zeroplayer: initialize a player
*/
zeroplayer(c)
int		c;
{
	register player	   *p = &plr[c];

	p->p_stat = Inactive, p->p_computer = 0;
	p->p_score = p->p_squander = 0, p->p_onboard = False;
	p->p_fd = -1, p->p_timeouts = 0;
	p->p_pref = Nopref, p->p_mood = 0;
	p->p_name[0] = p->p_id[0] = '\0';
}

/*
**	fillroster: add computers to fill in roster
*/
fillroster(pplr, pcomp)
int	   *pplr, *pcomp;
{
	register int	c, nplr, ncomp;

	/*
	**	Count all players.  We assume that all types of humans will
	**	play in the next game.  We add Computers in Waiting mode
	**	so we have to count them specially.
	*/
	ncomp = nplr = 0;
	for(c = 0;c < PLAYERS;++c) {
		switch(plr[c].p_stat) {
		case Inactive:
			break;
		case Waiting:
			if(plr[c].p_computer != 0)
				++ncomp;
			++nplr;
			break;
		case Computer:
			++ncomp, ++nplr;
			break;
		default:
			++nplr;
			break;
		}
	}
	
	/*
	**	Add enough Computer players to make at least MINCOMP.
	**	Add enough Computer players to make MINPLR players total.
	**	Add Computer players randomly with probability that
	**		decreases with increasing number of players.
	*/
	while(ncomp < MINCOMP || nplr < MINPLR || randint(nplr+2) == 1) {
		for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Inactive) {
			pickcomputer(c);					/* sets status to Computer */
			plr[c].p_stat = Waiting, ++waiting;	/* no Waiting Computer mode */
			plr[c].p_fd = -1, plr[c].p_timeouts = 0;
			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
			++ncomp, ++nplr;
			break;
		}
	}

	*pplr = nplr, *pcomp = ncomp;
}

/*
**	workhourstype: calculate default game type for this time of day
**		Working hours: 8:00 - 11:59 AM, 12:45 - 4:45 PM Mon - Fri.
XXX:	Sorry, there's no accounting for holidays.
*/
winpref
workhourstype()
{
	time_t		now;
	struct tm  *tm;

	if(blitzwhen != Workhours && blitzwhen != Enforced)
		return Standard;

	(void) time(&now);
	tm = localtime(&now);

	if(tm->tm_wday >= 1 && tm->tm_wday <= 5) {				/* Mon thru Fri? */
		if(tm->tm_hour >= 8 && tm->tm_hour <= 16) {			/* 8:00 to 4:59 */
			if(tm->tm_hour == 12 && tm->tm_min <= 45)		/* Noon to 12:45 */
				;											/* lunch */
			else if(tm->tm_hour == 16 && tm->tm_min > 45)	/* 4:45 to 4:59 */
				;											/* end of day */
			else return Blitz;								/* work hours */
		}
	}

	return Standard;
}

/*
**	votegametype: try to find a consensus amongst the players
*/
winpref
votegametype()
{
	winpref			pref;
	register int	c;

	pref = Nopref;
	for(c = 0;c < PLAYERS;++c) {
		if(plr[c].p_stat == Inactive || plr[c].p_pref == Nopref)
			continue;
		switch(pref) {
		case Standard:
			if(plr[c].p_pref != Standard && plr[c].p_pref != Fstand)
				return Nopref;
			break;
		case Blitz:
			if(plr[c].p_pref != Blitz && plr[c].p_pref != Fblitz)
				return Nopref;
			break;
		default:
			switch(plr[c].p_pref) {
			case Standard: case Fstand: pref = Standard; break;
			case Blitz:    case Fblitz: pref = Blitz; break;
			}
			break;
		}
	}

	return pref;
}

/*
**	pickgametype: select gametype, winscore, and jokermode
*/
pickgametype()
{
	winpref		temp;

	switch(blitzwhen) {
	case Noblitz:
		gametype = Standard;
		break;
	case Onrequest:
		if((gametype = votegametype()) == Nopref)
			gametype = Standard;
		break;
	case Workhours:
		if((gametype = votegametype()) == Nopref)
			gametype = workhourstype();
		break;
	case Enforced:
		if((gametype = workhourstype()) != Blitz)
			if((temp = votegametype()) != Nopref)
				gametype = temp;
		break;
	}

	winscore = (gametype == Blitz) ? BLITZSCORE : WINSCORE;

	if(enajokers == False)
		jokermode = False;
	else
		jokermode = randint(6) == 6 ? True : False;
}

/*
**	getequiv: determine equivalent hosts.
*/
getequiv()
{
	register FILE  *fp;
	register int	n;
	char			buf[512];

	/*
	**	Open the hosts.equiv file and count the entries.
	**	Allocate memory for them and this host.
	*/
	neq = 1;
	if((fp = fopen("/etc/hosts.equiv", "r")) != 0) {
		while(fgets(buf, sizeof buf, fp) != 0)
			++neq;
		clearerr(fp);
		rewind(fp);
	}
	if((equiv = (char **)malloc(neq * sizeof *equiv)) == 0) {
		syslog(LOG_ERR, "malloc: no memory for equivalent hosts");
		exit(1);
	}

	/*
	**	This host is first in the list.
	*/
	(void) gethostname(buf, sizeof buf);
	if((equiv[0] = malloc((unsigned)(strlen(buf) + 1))) == 0) {
		syslog(LOG_ERR, "malloc: no memory for this host");
		exit(1);
	}
	strcpy(equiv[0], buf);

	/*
	**	Add the hosts from hosts.equiv.
	*/
	if(fp != 0) {
		for(n = 1;n < neq;++n) {
			if(gets(buf) == 0) {
				neq = n;
				break;
			}
			if((equiv[n] = malloc((unsigned)(strlen(buf) + 1))) == 0) {
				syslog(LOG_ERR, "malloc: no memory for an equivalent host");
				exit(1);
			}
			strcpy(equiv[n], buf);
		}
		(void) fclose(fp);
	}
}

/*
**	equivalence: strip "@thishost" or "@equivhost" from an id
*/
equivalence(id)
char   *id;
{
	register char  *at;
	register int	n;

	if((at = index(id, '@')) == 0)
		return;
	if(neq == 0)
		getequiv();
	for(n = 0;n < neq;++n) {
		if(strcmp(at+1, equiv[n]) == 0) {
			*at = '\0';
			return;
		}
	}
}

/*
**	idinuse: return True if id is in use by a human
**		or is in use by more than one Computer player
**	side effect: converts user@thishost or user@equivhost to just user
*/
boolean
idinuse(c, id)
int		c;
char   *id;
{
	register int	cc;
	int				humanuse;
	int				computeruse;

	equivalence(id);	/* strip @equivhost */

	humanuse = computeruse = 0;
	for(cc = 0;cc < PLAYERS;++cc) if(cc != c) {
		if(strncmp(plr[cc].p_id, id, IDLEN-1) == 0) {
			switch(plr[cc].p_stat) {
			case Active:
			case Waiting:
			case Watching:
			case Spider:
				++humanuse;
				break;
			case Computer:
				++computeruse;
				break;
			default:
				break;
			}
		}
	}

	if(humanuse > 0 || computeruse > 1)
		return True;

	return False;
}

/*
**	getplayername: get the player's name and check it
*/
getplayername(c)
int			c;
{
	register char  *aka;
	char			msgbuf[MESGLEN];

	/*
	**	Prompt for and read name.
	*/
	sprintf(msgbuf,
		"%d Hello, I'm the Cube daemon.  Who are you?\r\n", M_HELO);
	if(dialogue(c, msgbuf, sizeof msgbuf) < 0) {
		if(plr[c].p_stat != Inactive) {	/* in case of timeout */
			(void) simp(c, M_DOWN, "Server closing connection.");
			oldplayer(c);
		}
		return -1;
	}

	/*
	**	Skip past any leading spaces or tabs in the response.
	*/
	for(aka = msgbuf;*aka == ' ' || *aka == '\t';++aka)
		;

	/*
	**	If a non-null name was supplied, check that it is not in use.
	**	If not, save the name in the list and we're done.
	*/
	if(*aka != '\0') {
		strncpy(plr[c].p_name, aka, NAMELEN);
		plr[c].p_name[NAMELEN-1] = '\0';
		if(nameinuse(c, plr[c].p_name) == False) {
			savename(plr[c].p_name);
			return 0;
		}
		(void) simp(c, M_ARGE, "Your chosen moniker is already in use.");
	}

	/*
	**	We were supplied with a null name, or one already in use.
	**	Pick one from the monikers list rather than dumping the player.
	*/
	while(nameinuse(c, (aka = moniker())) == True)
		;
	strncpy(plr[c].p_name, aka, NAMELEN);
	plr[c].p_name[NAMELEN-1] = '\0';

	return 0;
}

/*
**	getplayerid: get player's unique id (user@host)
**		Allow setting of winscore preference.
*/
getplayerid(c)
int			c;
{
	register char  *s, *id;
	char			msgbuf[MESGLEN];

	/*
	**	Prompt for and read the id.
	*/
	sprintf(msgbuf, "%d Please supply a unique id.\r\n", M_RQID);
	if(dialogue(c, msgbuf, sizeof msgbuf) < 0) {
		if(plr[c].p_stat != Inactive) {	/* in case of timeout */
			sprintf(msgbuf, "%d No response taken to mean `No.'\r\n", M_ARGE);
			goto iderror;
		}
		return -1;
	}

	/*
	**	A null id is not valid.
	*/
	if(msgbuf[0] == '\0') {
		sprintf(msgbuf, "%d Sorry, that's not unique.\r\n", M_ARGE);
		goto iderror;
	}

	/*
	**	Check for winscore preference.  Ignore bogus values.
	*/
	if(msgbuf[1] != ';') {
		plr[c].p_pref = Nopref;
		id = &msgbuf[0];
	} else {
		switch(msgbuf[0]) {
		case 's': plr[c].p_pref = Standard; break;
		case 'S': plr[c].p_pref = Fstand; break;
		case 'b': plr[c].p_pref = Blitz; break;
		case 'B': plr[c].p_pref = Fblitz; break;
		default:  plr[c].p_pref = Nopref; break;
		}
		id = &msgbuf[2];
	}

	/*
	**	Id's are alphanumerics plus a few other characters.
	*/
	for(s = id;*s != '\0';++s) {
		if(isalnum(*s) || index("@._-+=", *s) != 0)
			continue;
		sprintf(msgbuf, "%d `%c' is not allowed in an id.\r\n", M_ARGE, *s);
		goto iderror;
	}

	/*
	**	Check to see that the id is not already in use.
	*/
	strncpy(plr[c].p_id, id, IDLEN);
	plr[c].p_id[IDLEN-1] = '\0';
	if(idinuse(c, plr[c].p_id) == True) {
		sprintf(msgbuf, "%d You're already playing!\r\n", M_ARGE);
		goto iderror;
	}

	/*
	**	Success.
	*/
	return 0;

	/*
	**	Common error handling code.
	*/
iderror:
	if(message(c, msgbuf) < 0)
		return -1;
	(void) simp(c, M_DOWN, "Server closing connection.");
	oldplayer(c);
	return -1;
}

/*
**	getplayerintent: get the player's intent
**		If a game is inprogress, the question is watch or play?
**		Otherwise, the question is wait or play?
**	Assumes that player status is already set to Watching.
*/
getplayerintent(c)
int			c;
{
	history	   *ph;
	char		msgbuf[MESGLEN];

	/*
	**	Prompt for and read intent.
	*/
	sprintf(msgbuf, "%d Do you want to %s or play? [wp]\r\n",
		inprogress == True ? M_WORP : M_SORP,
		inprogress == True ? "watch" : "wait");
	if(dialogue(c, msgbuf, sizeof msgbuf) < 0) {
		if(plr[c].p_stat != Inactive) {	/* in case of timeout */
			(void) simp(c, M_DOWN, "Server closing connection.");
			oldplayer(c);
		}
		return -1;
	}
	
	/*
	**	Check it for validity.
	*/
	switch(msgbuf[0]) {
	case '\0':				/* default */
	case 'p': case 'P':		/* Play */
		plr[c].p_stat = Waiting, ++waiting, --watching;
		return 0;
	case 'w': case 'W':		/* Watch or Wait */
		if(inprogress == False) {
			plr[c].p_stat = Spider, ++spiders, --watching;
			return 0;
		}
		if(active <= 0 && waiting <= 0) {
			if(simp(c, M_ARGE, "Sorry, no game in progress.") < 0)
				return -1;
			break;
		}
		/*
		**	We let new players watch, but we don't let ancient ones do so.
		**	It seems that some people would rather watch than play, but
		**	that's no fun for the regular players.
		*/
		if((ph = histbyname(plr[c].p_id)) != 0) {
			if(ph->h_lastgame > 0 && gamenum - ph->h_lastgame >= ANCIENT) {
				syslog(LOG_NOTICE,
					"getplayerintent: excluded ancient voyeur %s", plr[c].p_id);
				if(simp(c, M_ARGE, "Sorry, you're too ancient to watch!") < 0)
					return -1;
				break;
			}
		}
		return 0;
	default:
		if(simp(c, M_ARGE, "Sorry, but that's nonsense.") < 0)
			return -1;
		break;
	}

	(void) simp(c, M_DOWN, "Server closing connection.");
	oldplayer(c);
	return -1;
}

/*
**	fixroster: do any necessary roster adjustment
**		If add is true, we can add Waiting players to the front of
**		the roster.  If false, we will move them to the end.
**		Collapses out Inactive slots.
*/
fixroster(add)
boolean		add;
{
	int		c, cc, low, squ;
	player	tmp[PLAYERS];

	/*
	**	No reason to do this except during a game.
	*/
	if(inprogress == False)
		return;

	/*
	**	If there are no Waiting players, or we're not allowed to add them,
	**	check for "empty" slots in the roster.  We define empty here as
	**	Inactive, Watching, or Waiting, since to Active players, slots held
	**	by Watching or Waiting players appear empty.
	*/
	if(waiting == 0 || add == False) {
		for(c = 0;c < PLAYERS;++c)		/* find first empty slot */
			if(plr[c].p_stat != Computer && plr[c].p_stat != Active)
				break;
		for(++c;c < PLAYERS;++c)		/* look for a non-empty slot */
			if(plr[c].p_stat == Computer || plr[c].p_stat == Active)
				break;
		if(c >= PLAYERS)				/* didn't find any */
			return;
	}

	/*
	**	If we are allowed to add Waiting players, put them first,
	**	otherwise put them last with the Watching players.  In either
	**	case, this algorithm removes empty slots.
	*/
	cc = 0;
	if(add == True) {
		for(c = 0;c < PLAYERS;++c)	/* first pass, Waiting players */
			if(plr[c].p_stat == Waiting)
				tmp[cc++] = plr[c];
	}
	for(c = 0;c < PLAYERS;++c)		/* second pass, Active players */
		if(plr[c].p_stat == Computer || plr[c].p_stat == Active)
			tmp[cc++] = plr[c];
	for(c = 0;c < PLAYERS;++c)		/* third pass, Watching players */
		if(	plr[c].p_stat == Watching
		|| plr[c].p_stat == Spider
		||	(add == False && plr[c].p_stat == Waiting))
			tmp[cc++] = plr[c];
	if(cc == 0) {
		syslog(LOG_DEBUG, "fixroster: no players!");
		active = waiting = watching = 0;
		return;
	}

	/*
	**	Calculate a suitable low score for any new players.
	*/
	if(waiting == 0 || add == False || (low = lowscore(&squ) - ONBOARD) < 0)
		low = 0;

	/*
	**	Copy the reordered roster into the real one while at the
	**	same time adding waiting players (if allowed).
	*/
	for(c = 0;c < cc;++c) {
		plr[c] = tmp[c];
		if(add == True && plr[c].p_stat == Waiting) {
			plr[c].p_score = low;
			plr[c].p_squander = squ;
			plr[c].p_onboard = False;
			if(plr[c].p_computer != 0)
				plr[c].p_stat = Computer, --waiting;
			else {
				plr[c].p_stat = Active, --waiting, ++active;
				(void) tellstatus(c);	/* XXX */
			}
		}
	}
	for(;c < PLAYERS;++c)
		zeroplayer(c);
	if(waiting != 0) {
		syslog(LOG_DEBUG, "fixroster: waiting=%d (should be zero)", waiting);
		waiting = 0;
	}

	/*
	**	Broadcast the new roster to all players.
	*/
	roster(-1, False);
}