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

⟦46d9387fc⟧ TextFile

    Length: 44192 (0xaca0)
    Types: TextFile
    Names: »cubeserv1.c«

Derivation

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

TextFile

/*	vi:set sw=4 ts=4: */
#ifndef	lint
static char	sccsid[] = "@(#)cubeserv1.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.
**
*/

/*
**	Some #defines to make linting easier.
*/
#ifdef	lint
#ifndef	PATHNAME
#define	PATHNAME	"dummy-pathname"
#endif	PATHNAME
#ifndef	HISTFILE
#define	HISTFILE	"dummy-histfile"
#endif	HISTFILE
#ifndef	UNIXSOCK
#define	UNIXSOCK	"dummy-unixsock"
#endif	UNIXSOCK
#ifndef	INETSOCK
#define	INETSOCK	/* dummy-inetsock */
#endif	INETSOCK
#endif	lint

#include	<stdio.h>
#include	<ctype.h>
#include	<strings.h>
#include	<errno.h>
#include	<syslog.h>
#include	<signal.h>
#include	<setjmp.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/file.h>
#include	<sys/ioctl.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<netdb.h>
#ifdef	UNIXSOCK
#include	<sys/un.h>
#endif	UNIXSOCK
#include	"cubes.h"

extern int		errno;
extern int		optind;
extern int		opterr;
extern long		gamenum;
extern char	   *optarg;
extern char	   *histfmtplr();
extern history *histbyname();
extern char	   *ctime();
extern time_t	time();
extern boolean	wantin();
extern boolean	prescreen();
extern boolean	iscomp();
extern boolean	isproxy();
extern boolean	iskamelion();
extern boolean	newplayers();
extern boolean	pendshutdown();
extern winpref	workhourstype();
static int		srvshutdown();
static int		sysshutdown();
static int		delshutdown();

static int		usock	= -1;			/* UNIX server socket */
static int		isock	= -1;			/* INET server socket */
static int		qsock	= -1;			/* INET query socket */
int				active	= 0;			/* number of Active humans */
int				waiting	= 0;			/* number of Waiting humans */
int				watching= 0;			/* number of Watching humans */
int				spiders	= 0;			/* number of Spiders */
int				turnnum	= 0;			/* current turn number */
int				currup	= -1;			/* player currently up */
unsigned		neq		= 0;			/* number of equivalent hosts */
char		  **equiv;					/* equivalent hosts */
boolean			enajokers	= False;	/* enable jokers */
boolean			jokermode	= False;	/* dice do not have joker faces */
boolean			inprogress	= False;	/* True when game in progress */
boolean			firstgame	= True;		/* first game with set of players */
boolean			saygoodbye	= False;	/* True if delayed shutdown requested */
boolean			adjroster	= False;	/* Adjust roster */
boolean			clearobs	= False;	/* Observers are showing in roster */
blitzmode		blitzwhen	= Enforced;	/* Blitz mode enforced during work */
winpref			gametype	= Standard;	/* specifies winscore */
int				winscore	= WINSCORE;	/* points needed to win */
#define	CLOSED	(3 * winscore / 4)		/* no more new players */
extern player	plr[];					/* player/connection list */
struct timeval	poll	= { 0L, 0L };	/* for select polling */
struct timeval	onesec	= { 1L, 0L };	/* for select polling */

#define	PSLEN	55						/* length of "ps" status line */
char		   *statusline;				/* really av[0] */

main(ac, av)
char   *av[];
{
	int			c;

	/*
	**	Make sure we have enough room to display our status.
	*/
	if(strlen(av[0]) < PSLEN) {
		char   *newav0;
		char   *malloc();

		if((newav0 = malloc(PSLEN+1)) == 0) {
			syslog(LOG_ERR, "cubeserver: no memory for new argv zero");
			exit(1);
		}
		sprintf(newav0, "cubes %-*s.", PSLEN-7, "startup");
		av[0] = newav0;
		execv(PATHNAME, av);
		perror(PATHNAME);	/* OK */
		exit(1);
	}

	(void) setuid(geteuid());
	statusline = av[0];
	updstat(-1);		/* server startup */

	initcomptbl();

	opterr = 0;
	while((c = getopt(ac, av, "jb:")) >= 0) {
		switch(c) {
		case 'j':
			enajokers = (enajokers == True) ? False : True;
			break;
		case 'b':
			switch(*optarg) {
			case 'n': case 'N': blitzwhen = Noblitz;   continue;
			case 'r': case 'R': blitzwhen = Onrequest; continue;
			case 'w': case 'W': blitzwhen = Workhours; continue;
			case 'e': case 'E': blitzwhen = Enforced;  continue;
			}
			/* fall to usage error */
		default:
			fprintf(stderr, "usage -- cubeserver [-j] [-b{nrwe}]\n");	/*OK*/
			exit(1);
		}
	}

	/*
	**	Blank arguments one through ac.
	*/
	for(c = 1;c < ac;++c) {
		char   *s;
		for(s = av[c];*s != '\0';++s)
			*s = ' ';
	}

	/*
	**	Fork.
	*/
	switch(c = fork()) {
	case -1:	/* error */
		perror("cubeserver: fork");	/* OK */
		exit(1);
	case 0:		/* child */
		break;
	default:	/* parent */
		printf("%d\n", c);
		exit(0);
	}

	/*
	**	Disconnect from controlling tty.
	*/
	if((c = open("/dev/tty", O_RDWR)) >= 0) {
		(void) ioctl(c, TIOCNOTTY, (char *)0);
		(void) close(c);
	}

	/*
	**	Ignore keybord signals.  Catch TERM for controlled shutdown.
	**	We catch HUP and take it to mean delayed shutdown.
	*/
	(void) signal(SIGINT,  SIG_IGN);
	(void) signal(SIGQUIT, SIG_IGN);
	(void) signal(SIGTSTP, SIG_IGN);
	(void) signal(SIGTTIN, SIG_IGN);
	(void) signal(SIGTTOU, SIG_IGN);
	(void) signal(SIGPIPE, SIG_IGN);
	(void) signal(SIGHUP,  delshutdown);
	(void) signal(SIGTERM, srvshutdown);

	/*
	**	Close std out and std err.  Initialize syslog.
	**	Provide a startup message.
	*/
	(void) fclose(stdout);
	(void) fclose(stderr);
#ifdef	LOG_DAEMON
	openlog("cubeserver", LOG_PID, LOG_DAEMON);
	(void) setlogmask(LOG_UPTO(LOG_ERR));
#else	LOG_DAEMON
	openlog("cubeserver", LOG_PID);
#endif	LOG_DAEMON

	{
		char   *bname;

		switch(blitzwhen) {
		case Noblitz:	bname = "never"; break;
		case Onrequest: bname = "on request"; break;
		case Workhours:	bname = "optional during peak"; break;
		case Enforced:	bname = "enforced during peak"; break;
		default:		bname = "unknown"; break;
		}
		syslog(LOG_INFO, "startup: jokers %s, blitz %s",
			enajokers == True ? "enabled" : "disabled", bname);
	}
	
	/*
	**	Close stdin and open server socket.
	**	Initialize random number generator.
	**	Read history file.
	*/
	(void) fclose(stdin);
	if(openservsock() < 0)
		exit(1);
	irandom();
	if(histread(HISTFILE) < 0)
		exit(1);

	/*
	**	Initialize player/connection list.
	*/
	active = waiting = watching = spiders = 0;
	for(c = 0;c < PLAYERS;++c)
		zeroplayer(c);
	addcomp(COMP);	/* add COMP computer */

	for(firstgame = True;saygoodbye == False;) {
		inprogress = False;
		do {
			(void) newplayers(True);
		} while(active == 0 && waiting == 0 && watching == 0);
		if(pendshutdown() == True) {
			sysshutdown();
			continue;
		}
		begingame();
		while(active > 0 && winner() < 0)
			gameturn();
		endgame();
	}

	srvshutdown();
	exit(0);
}

/*
**	pendshutdown: return True if system shutdown is pending
*/
boolean
pendshutdown()
{
	/*
	**	Check for the existence of the nologin file.  This gives
	**	a warning of five minutes or less.  Not really good enough.
	*/
	return access("/etc/nologin", F_OK) < 0 ? False : True;
}
	
/*
**	sysshutdown: hang up on all players saying that the system is going down
**		We don't shut down the server because of "shutdown -k".
*/
static int
sysshutdown()
{
	register int	c;

	/*
	**	Send the shutdown message to all humans.
	XXX	Could use contents of /etc/nologin here.
	*/
	if(active > 0 || waiting > 0 || watching > 0 || spiders > 0) {
		for(c = 0;c < PLAYERS;++c)
			if(plr[c].p_stat != Inactive && plr[c].p_stat != Computer)
				(void) simp(c, M_DOWN, "System shutdown in progress -- bye.");
	}

	/*
	**	Hang up on everybody, then reinstall the COMP computer.
	*/
	for(c = 0;c < PLAYERS;++c)
		if(plr[c].p_stat != Inactive)
			oldplayer(c);
	addcomp(COMP);

	updstat(-1);
}

/*
**	openservsock: open, bind, and begin listening on server socket(s)
**		also opens the status query socket
*/
openservsock()
{
	/*
	**	We shouldn't be called more than once, but make sure anyway.
	*/
	if(isock >= 0) {
		(void) close(isock);
		isock = -1;
	}
	if(usock >= 0) {
		(void) close(usock);
		usock = -1;
	}
	if(qsock >= 0) {
		(void) close(qsock);
		qsock = -1;
	}

#ifdef	UNIXSOCK
	/*
	**	If UNIXSOCK is defined, we open a server socket in the UNIX domain.
	**	This socket is used for transactions with players on the local system.
	*/
	{
		struct sockaddr_un	userver;
		int					len;

		(void) unlink(UNIXSOCK);
		if((usock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
			syslog(LOG_ERR, "server unix socket: %m");
			goto nounix;
		}
		bzero((char *)&userver, sizeof userver);
		userver.sun_family = AF_UNIX;
		strcpy(userver.sun_path, UNIXSOCK);
		len = sizeof userver.sun_family + strlen(userver.sun_path);
		if(bind(usock, &userver, len) < 0) {
			syslog(LOG_ERR, "server unix socket bind: %m");
			(void) close(usock);
			usock = -1;
			goto nounix;
		}
		if(listen(usock, 5) < 0) {
			syslog(LOG_ERR, "server unix socket listen: %m");
			(void) close(usock);
			usock = -1;
			goto nounix;
		}

nounix:	;
	}
#endif	UNIXSOCK

#ifdef	INETSOCK
	/*
	**	If INETSOCK is defined, we open a server socket in the INET domain.
	**	This socket is used for transactions with players on remote systems,
	**	and if UNIXSOCK is not defined, for players on the local system.
	*/
	{
		struct servent	   *ps;
		struct sockaddr_in	server;

		if((ps = getservbyname("cube", "tcp")) == 0) {
			syslog(LOG_ERR, "no cube/tcp service listed");
			goto noinet;
		}
		if((isock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			syslog(LOG_ERR, "server inet socket: %m");
			goto noinet;
		}
		bzero((char *)&server, sizeof server);
		server.sin_family = AF_INET;
		server.sin_port = ps->s_port;
		server.sin_addr.s_addr = INADDR_ANY;
		if(bind(isock, &server, sizeof server) < 0) {
			syslog(LOG_ERR, "server inet socket bind: %m");
			(void) close(isock);
			isock = -1;
			goto noinet;
		}
		if(listen(isock, 5) < 0) {
			syslog(LOG_ERR, "server inet socket listen: %m");
			(void) close(isock);
			isock = -1;
			goto noinet;
		}
noinet:	;
	}
#endif	INETSOCK

	/*
	**	Neither socket was opened.  Report this situation
	**	and return with failure status.
	*/
	if(usock < 0 && isock < 0) {
		syslog(LOG_ERR, "no server sockets opened");
		return -1;
	}

	/*
	**	Now open the status query socket.  This socket is used to respond to
	**	queries about the server's status.  Since it is not critical to the
	**	operation of the game, we'll make any failure here a non-fatal error.
	*/
	{
		struct sockaddr_in	server;
		struct servent	   *ps;

		if((ps = getservbyname("cubestat", "udp")) == 0) {
			syslog(LOG_WARNING, "no cubestat/udp service listed");
			goto noquery;
		}
		if((qsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
			syslog(LOG_WARNING, "query socket: %m");
			goto noquery;
		}
		/*
		XXX	If INETSOCK is not defined, this is pretty silly, since queries
		XXX	from remote systems can be responded to, but the game cannot be
		XXX	played from remote systems.  Maybe we could fix this for many
		XXX	configurations by fiddling with server.sin_addr.  Another way
		XXX	would be to open a UNIX domain query socket.  Both approaches
		XXX	seem like more trouble than they're worth.
		*/
		bzero((char *)&server, sizeof server);
		server.sin_family = AF_INET;
		server.sin_port = ps->s_port;
		server.sin_addr.s_addr = INADDR_ANY;
		if(bind(qsock, &server, sizeof server) < 0) {
			syslog(LOG_WARNING, "query socket bind: %m");
			(void) close(qsock);
			qsock = -1;
			goto noquery;
		}
noquery:;
	}

	return 0;
}

/*
**	asynchelp: long help message for asynchronous requests
*/
static char	   *asynchelp[]	= {
  "g       Go         begin playing (voyeurs and spiders only)",
  "x       Autopilot  enter autopilot mode (active players only)",
  "m       Myrank     display your ranking info",
  "a       Above      display ranking info for player ranked just above you",
  "b       Below      display ranking info for player ranked just below you",
  "p{num}  Player     display ranking info for player {num} in roster",
  (char *)0	/* end marker */
};

/*
**	async: check for and deal with asynchronous requests during game
**		Ignore the current player.
*/
async(cur)
int		cur;
{
	register int	c, n;
	char			msgbuf[MESGLEN];
	fd_set			rdy;
	int				num;

	if(inprogress == False)
		return;

	/*
	**	Select on all human connected sockets except for the
	**	one connected to the current player.  If the current
	**	player is temporarily proxied, select on that socket too.
	*/
	FD_ZERO(&rdy);
	n = -1;
	for(c = 0;c < PLAYERS;++c) {
		if(c == cur && isproxy(c) == False)
			continue;
		switch(plr[c].p_stat) {
		case Inactive:
		case Computer:
			continue;
		default:
			if(plr[c].p_fd >= 0) {
				FD_SET(plr[c].p_fd, &rdy);
				if(plr[c].p_fd > n)
					n = plr[c].p_fd;
			}
			break;
		}
	}
	if(n == -1 || select(n+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
		return;

	/*
	**	Check the sockets and handle any requests.
	*/
	for(c = 0;c < PLAYERS;++c) {
		if(c == cur && isproxy(c) == False)
			continue;
		if(plr[c].p_fd < 0 || !FD_ISSET(plr[c].p_fd, &rdy))
			continue;
		if(reply(c, msgbuf, sizeof msgbuf) <= 0)
			continue;
		if(msgbuf[0] != ASYNCMARK)	/* asynchronous marker */
			continue;				/* XXX: ignore silently */
		switch(msgbuf[1]) {
		case 'g': case 'G':		/* Go! */
			if(plr[c].p_stat == Active && isproxy(c) == True) {
				plr[c].p_computer = 0;	/* become human again */
				(void) tellstatus(c);
				break;
			}
			if(plr[c].p_stat != Watching) {
				(void) simp(c, M_ARGE, "The go command makes no sense now.");
				break;
			}
			if(highscore(-1, (int *)0) >= CLOSED) {
				(void) simp(c, M_ARGE, "It's too late to enter this game.");
				break;
			}
			plr[c].p_stat = Waiting, ++waiting, --watching;
			(void) tellstatus(c);
			adjroster = True;
			break;
		case 'x': case 'X':		/* toggle autopilot mode */
			if(plr[c].p_stat != Active) {
				(void) simp(c, M_ARGE, "Autopilot is for active players only.");
				break;
			}
			if(isproxy(c) == False)
				pickproxy(c);			/* temporary proxy */
			else
				plr[c].p_computer = 0;	/* become human again */
			(void) tellstatus(c);
			break;
		case 'm': case 'M': (void) myrank(c,  True); break;
		case 'a': case 'A': (void) aboveme(c, True); break;
		case 'b': case 'B': (void) belowme(c, True); break;
		case 'p': case 'P':
			if(sscanf(msgbuf, "%*[^0123456789]%d", &num) != 1) {
				(void) simp(c, M_ARGE, "Usage: player <number>");
				break;
			}
			(void) plrrank(c, num - 1, True);
			break;
		case 'q':	/* quit (decision already made) */
			/*
			**	If we send this message, we're more than likely to
			**	get a dead pipe entry in syslog.  By commenting it
			**	out, we assume that the client will exit on its own.
			*/
/*			(void) simp(c, M_DOWN, "So long, quitter...");	/* make sure */
			oldplayer(c);
			break;
		case '?':	/* help */
			(void) multimesg(c, M_HELP, asynchelp);
			break;
		default:
			(void) invalid(c);
			break;
		}
	}
}

/*
**	wantinjmp, wantintimo: wantin timeout handler
*/
static jmp_buf	wantinjmp;
static int
wantintimo()
{
	longjmp(wantinjmp, 1);
}

/*
**	wantin: return True if ready to do accept on server socket
**		If hang equals HANG, hang answering queries and providing heartbeat
*/
boolean
wantin(hang)
struct timeval *hang;
{
	fd_set		rdy;
	int			n, c;
	int		  (*oldalrm)();
	unsigned	alarmv, hold;
	char		msgbuf[MESGLEN];
	boolean		ready;

	/*
	**	If we're not supposed to hang, just do a poll of
	**	the server socket(s) using the requested hang time.
	*/
	if(hang != HANG) {
		FD_ZERO(&rdy);
		n = -1;
		if(usock >= 0) {
			FD_SET(usock, &rdy);
			if(usock > n)
				n = usock;
		}
		if(isock >= 0) {
			FD_SET(isock, &rdy);
			if(isock > n)
				n = isock;
		}

		if(select(n+1, &rdy, NOSEL, NOSEL, hang) > 0) {
			if(usock >= 0 && FD_ISSET(usock, &rdy))
				return True;	/* server unix socket ready */
			if(isock >= 0 && FD_ISSET(isock, &rdy))
				return True;	/* server inet socket ready */
		}

		return False;			/* server socket(s) not ready */
	}

	/*
	**	If there are spiders, set up a regular heartbeat.  We can't
	**	call heartbeat as an alarm signal handler because it calls
	**	message which uses alarms to timeout socket writes.  We check
	**	for pending shutdowns and get rid of spiders if True.
	*/
	alarmv = alarm(0);
	oldalrm = signal(SIGALRM, SIG_IGN);
	if(spiders > 0) {
		(void) signal(SIGALRM, wantintimo);
		if(setjmp(wantinjmp) != 0) {
			if(saygoodbye == True || pendshutdown() == True)
				sysshutdown();
			else
				heartbeat();
		}
		(void) alarm(READTIMO);
	}

	/*
	**	Hang answering queries.  Leave loop upon connection request.
	**	If there are Spiders, monitor the corresponding sockets
	**	in case they want to quit being Spiders and begin play.
	**	When any data is sent from a Spider client, we change that
	**	player's status to Waiting and return True.
	*/
	ready = False;
	for(;;) {
		FD_ZERO(&rdy);
		n = -1;
		if(usock >= 0) {
			FD_SET(usock, &rdy);
			if(usock > n)
				n = usock;
		}
		if(isock >= 0) {
			FD_SET(isock, &rdy);
			if(isock > n)
				n = isock;
		}
		if(qsock >= 0) {
			FD_SET(qsock, &rdy);
			if(qsock > n)
				n = qsock;
		}
		if(spiders > 0) {
			for(c = 0;c < PLAYERS;++c) {
				if(plr[c].p_stat == Spider && plr[c].p_fd >= 0) {
					FD_SET(plr[c].p_fd, &rdy);
					if(plr[c].p_fd > n)
						n = plr[c].p_fd;
				}
			}
		}

		if(n < 0 || select(n+1, &rdy, NOSEL, NOSEL, HANG) <= 0)
			continue;

		if(qsock >= 0 && FD_ISSET(qsock, &rdy))	/* query */
			answer();
		if(usock >= 0 && FD_ISSET(usock, &rdy)) { /* local conn. req. */
			ready = True;						/* server socket ready */
			break;
		}
		if(isock >= 0 && FD_ISSET(isock, &rdy)) { /* net conn. req. */
			ready = True;						/* server socket ready */
			break;
		}
		if(spiders > 0) {						/* anxious spiders? */
			hold = alarm(0);					/* suspend heartbeat */
			for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Spider) {
				if(!FD_ISSET(plr[c].p_fd, &rdy))
					continue;
				if(reply(c, msgbuf, sizeof msgbuf) <= 0)
					continue;
				if(msgbuf[0] != ASYNCMARK)		/* asynchronous marker */
					continue;					/* XXX: ignore silently */
				switch(msgbuf[1]) {
				case 'g': case 'G':		/* Go! */
					plr[c].p_stat = Waiting, ++waiting, --spiders;
					(void) tellstatus(c);
					break;
				case 'x': case 'X':		/* not appropriate */
					(void) simp(c, M_ARGE,
						"Using the autopilot now would be silly.");
					break;
				case 'm': case 'M': (void) myrank(c,  True); break;
				case 'a': case 'A': (void) aboveme(c, True); break;
				case 'b': case 'B': (void) belowme(c, True); break;
				case 'p': case 'P':
					(void) simp(c, M_ARGE, "There aren't any players!");
					break;
				case 'q':	/* quit (decision already made) */
					(void) simp(c, M_DOWN, "So long, quitter...");
					oldplayer(c);
					break;
				case '?':	/* help */
					(void) multimesg(c, M_HELP, asynchelp);
					break;
				default:
					(void) invalid(c);
					break;
				}
			}
			if(waiting > 0)						/* converted spiders */
				break;							/* keep ready false */
			(void) alarm(hold);					/* resume heartbeat */
		}
	}

	/*
	**	Turn off heartbeat.
	*/
	(void) alarm(0);
	(void) signal(SIGALRM, oldalrm);
	(void) alarm(alarmv);

	return ready;
}

/*
**	answer: check for and answer any pending status queries
**		For now, we support only the query Q_ROSTER
**		and return only S_IDLE and S_ROSTER.
*/
answer()
{
	register int		c, n, h;
	fd_set				rdy;
	char				status[STATLEN];
	char				query;
	int					flen;
	struct sockaddr		from;
	static char			unrecog[]	= { S_UNRECOG, '\0' };

	/*
	**	If no query socket, there's nothing to do.
	*/
	if(qsock < 0)
		return;

	/*
	**	Check for any pending requests.  If none, we're done.
	*/
	FD_ZERO(&rdy);
	FD_SET(qsock, &rdy);
	if(select(qsock+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
		return;
	
	/*
	**	We've got at least one request, so let's format a response
	**	and send it to any and all that have asked for it.  (This,
	**	of course, assumes that we're going to get a Q_ROSTER query.)
	*/
	if(active == 0 && waiting == 0) {
		if(saygoodbye == True) {
			static char		down[]	= "XThe cubeserver is shutting down.\r\n";

			strcpy(status, down);
			n = sizeof down - 1;
			status[0] = S_IDLE;
		} else {
			char		   *type;
			time_t			last;
			extern char	   *ctime();

			switch(workhourstype()) {
			case Blitz:
				type = blitzwhen == Enforced ?
					"Blitz only" : "Blitz by default";
				break;
			case Standard:
				type = blitzwhen == Noblitz ?
					"Standard only" : "Standard by default";
				break;
			default:
				type = "unknown";
				break;
			}
			last = 0;
			(void) histtime(HISTFILE, &last);
			sprintf(status,
"%cThe cubeserver is idle since %12.12s.\r\nPrevailing game type is %s.\r\n",
				S_IDLE, ctime(&last) + 4, type);
			n = strlen(status);
		}
	} else if(active == 0 && waiting > 0) {
		static char	about[]	= "XA game is about to begin.\r\n";
		strcpy(status, about);
		status[0] = S_ROSTER;	/* to provide success indication */
		n = sizeof about - 1;
	} else {
		status[0] = S_ROSTER;
		n = 1;
		strcpy(&status[n], "Up  Player ");
		n += 11;

		if((c = watching + waiting) == 0)
			strcpy(&status[n], "    ");
		else
			sprintf(&status[n], "%2dw ", c);
		n += 4;

		if(currup < 0)
			strcpy(&status[n], "        Sqndr Score\r\n");
		else if(turnnum <= 1)
			sprintf(&status[n], "%2d    * Sqndr Score\r\n", turnnum);
		else
			sprintf(&status[n], "%2d %4d Sqndr Score\r\n",
				turnnum, plr[currup].p_score / (turnnum-1));
		n += 21;

		strcpy(&status[n], "--- ------------------ ----- -----\r\n");
		n += 36;

		for(h = 0, c = 0;c < PLAYERS;++c) {
			switch(plr[c].p_stat) {
			case Active:
			case Computer:
				if(plr[c].p_score > h)
					h = plr[c].p_score;
				break;
			}
		}

		for(c = 0;c < PLAYERS && n < STATLEN-37;++c) {
			switch(plr[c].p_stat) {
			case Active:
			case Computer:
				sprintf(&status[n], "%3s %-18.18s %5d %5d%c\r\n",
					(c == currup) ? "=->" : "",
					plr[c].p_name, plr[c].p_squander, plr[c].p_score,
					(h == 0 || plr[c].p_score < h) ? ' ' : '*'
				);
				n += 37;
				break;
			}
		}

		/*
		**	Some additional explanation.
		*/
		if(inprogress == True) {
			if(n <= STATLEN - 36) {
				sprintf(&status[n], "(%s%s game %s.)\r\n",
					gametype == Standard ? "Standard" : "Blitz",
					jokermode == True ? " joker" : "",
					turnnum == 0 ? "has begun" : "in progress");
				n = n + strlen(&status[n]);
			}
		} else if(turnnum == 0) {
			static char	waitfor[] = "(Preparing for the next game.)\r\n";
			if(n <= STATLEN - sizeof waitfor) {
				strcpy(&status[n], waitfor);
				n += sizeof waitfor - 1;
			}
		} else {
			static char	gameover[] = "(The game has ended.)\r\n";
			if(n <= STATLEN - sizeof gameover) {
				strcpy(&status[n], gameover);
				n += sizeof gameover - 1;
			}
		}
	}

	/*
	**	Loop, reading each request and responding to it.  For now,
	**	there's only one type of request, so just read a single byte
	**	and ignore it.  When we run out of requests, we're done.
	*/
	for(;;) {
		FD_ZERO(&rdy);
		FD_SET(qsock, &rdy);
		if(select(qsock+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
			return;		/* no more requests */

		flen = sizeof from;
		if(recvfrom(qsock, &query, sizeof query, 0, &from, &flen) <= 0) {
			syslog(LOG_DEBUG, "answer: recvfrom: %m");
			continue;
		}

		switch(query) {
		case Q_ROSTER:
			if(sendto(qsock, status, n+1, 0, &from, flen) < 0)
				syslog(LOG_DEBUG, "answer: sendto: %m");
			break;
		default:
			syslog(LOG_NOTICE, "answer: got unrecognized query");
			if(sendto(qsock, unrecog, sizeof unrecog, 0, &from, flen) < 0)
				syslog(LOG_DEBUG, "answer: sendto: %m");
			break;
		}
	}
}

/*
**	newplayers: check for and add new players
*/
boolean
newplayers(hang)
boolean					hang;
{
	register int		c;
	int					csock;
	int					tries;
	int					len, n;
	char				msgbuf[BUFSIZ];
	int					off	= 0;
	int					added;
	fd_set				rdy;

	/*
	**	Is this a new batch of players?
	*/
	if(hang == True && active == 0)
		firstgame = True;

	/*
	**	If there are no humans that want to play, we can hang waiting for some.
	**	If wantin returns False, we have converted Spiders.
	*/
	if(hang == True && active == 0 && waiting == 0 && watching == 0)
		if(wantin(HANG) == False)
			return waiting > 0 ? True : False;	/* False? */

	/*
	**	Keep looping until we run out of connection requests.
	*/
	added = 0;
	for(;;) {
		/*
		**	If we are supposed to hang, then wait a while for
		**	another connection request, elsewise, just return.
		*/
#define	MAXTRIES	5
		for(tries = 0;wantin(&onesec) == False;++tries) {
			if(hang == False || tries > MAXTRIES)
				return added > 0 ? True : False;
			updstat(-1);	/* about to begin notification */
/*			sleep(1);		/* wantin hangs for one second */
		}

		/*
		**	Check server socket(s).
		*/
		FD_ZERO(&rdy);
		n = -1;
		if(usock >= 0) {
			FD_SET(usock, &rdy);
			if(usock > n)
				n = usock;
		}
		if(isock >= 0) {
			FD_SET(isock, &rdy);
			if(isock > n)
				n = isock;
		}
		if(n < 0 || select(n+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
			continue;

		/*
		**	No client socket open yet.
		*/
		csock = -1;

#ifdef	UNIXSOCK
		/*
		**	If we don't have an open client socket yet, accept a connection
		**	from a potential player via the UNIX domain socket.
		*/
		if(csock < 0 && usock >= 0 && FD_ISSET(usock, &rdy)) {
			struct sockaddr_un	uclient;

			len = sizeof uclient;
			if((csock = accept(usock, &uclient, &len)) < 0)
				syslog(LOG_DEBUG, "unix accept: %m");
		}
#endif	UNIXSOCK

#ifdef	INETSOCK
		/*
		**	If we don't have an open client socket yet, accept a connection
		**	from a potential player via the INET domain socket.
		*/
		if(csock < 0 && isock >= 0 && FD_ISSET(isock, &rdy)) {
			struct sockaddr_in	client;

			len = sizeof client;
			if((csock = accept(isock, &client, &len)) < 0)
				syslog(LOG_DEBUG, "inet accept: %m");
		}
#endif	INETSOCK

		/*
		**	If we reach here, it means that select returned indicating
		**	activity, but neither server socket had anything to accept.
		**	We log this and continue.
		*/
		if(csock < 0) {
			syslog(LOG_DEBUG, "newplayers: nothing to accept");
			continue;
		}

		/*
		**	Make sure non-blocking I/O is turned off.
		*/
		(void) ioctl(csock, FIONBIO, (char *)&off);

		/*
		**	If the system is about to go down, don't allow new players.
		*/
		if(pendshutdown() == True) {
			sprintf(msgbuf,
				"%d Sorry, the system is about to shut down.\r\n", M_ARGE);
			(void) write(csock, msgbuf, strlen(msgbuf));
			sprintf(msgbuf, "%d Server closing connection.\r\n", M_DOWN);
			(void) write(csock, msgbuf, strlen(msgbuf));
			(void) close(csock);
			continue;
		}

		/*
		**	Find an open slot.
		*/
		for(c = 0;c < PLAYERS;++c)
			if(plr[c].p_stat == Inactive)
				break;
		if(c == PLAYERS) {
			sprintf(msgbuf, "%d Sorry, full house.\r\n", M_ARGE);
			(void) write(csock, msgbuf, strlen(msgbuf));
			sprintf(msgbuf, "%d Server closing connection.\r\n", M_DOWN);
			(void) write(csock, msgbuf, strlen(msgbuf));
			(void) close(csock);
			continue;
		}

		/*
		**	Fill in player parameters.  We must initially give the player
		**	a status of Waiting or Watching so that we can send messages.
		**	We initially choose Watching and adjust later.
		*/
		zeroplayer(c);
		plr[c].p_fd = csock;
		plr[c].p_stat = Watching, ++watching;
		if(getplayername(c) < 0 || getplayerid(c) < 0)
			continue;

		/*
		**	If we're not inprogress, check to see if the system's about
		**	to go down.  If so, hang up on this player (and all others).
		*/
		if(inprogress == False && pendshutdown() == True) {
			sysshutdown();
			continue;
		}

		/*
		**	Give the welcome message.  If a game is in progress, display the
		**	roster and, if it matters, ask if the human wants to play or watch.
		**	If no game is in progress and there's no other Active, Waiting,
		**	or Watching humans, we ask if the human would prefer to be a
		**	Spider.  If so, we change status from Watching to Spider.
		**	Otherwise just change Watching to Waiting.
		*/
		sprintf(msgbuf, "%d %s, welcome to Cubes.\r\n", M_INFO, plr[c].p_name);
		if(message(c, msgbuf) < 0)
			continue;
		if(inprogress == True) {
			tellwinscore(c);	/* tell winscore to this player */
			roster(c, False);	/* roster for this player only */
			if(highscore(-1, (int *)0) < CLOSED)
				if(getplayerintent(c) < 0)	/* watch or play? */
					continue;
			if(plr[c].p_stat != Watching)	/* not watching */
				if(prescreen(c) == False)	/* doesn't like game type */
					continue;
			adjroster = True;
		} else if(active == 0 && waiting == 0 && watching == 1) {
			if(getplayerintent(c) < 0)		/* wait or play? */
				continue;
			if(plr[c].p_stat != Spider)		/* not waiting */
				if(prescreen(c) == False)	/* doesn't like prevailing mode */
					continue;
/*			adjroster = True;				/* no game in progress */
		} else {
			if(prescreen(c) == False)		/* doesn't like prevailing mode */
				continue;
			plr[c].p_stat = Waiting, ++waiting, --watching;
/*			adjroster = True;				/* no game in progress */
		}

		if(tellstatus(c) < 0)
			continue;

		++added;
		(void) observers(-1);
/*		updstat(-1);	/* new (temporary?) observer; don't report here */
	}
}

/*
**	begingame: setup prior to a game
*/
begingame()
{
	register int	c;
	int				nplr, ncomp;

	inprogress = False;
	turnnum = 0;
	setgamenum();
	updstat(-1);		/* pregame setup */

	/*
	**	Fill the roster, pick the game type, throw out those that can't
	**	live with the choice.  Repeat until we have enough players.
	**	Only complication is that Computers are added with Waiting 
	**	status, so we have to blow them away with special code.
	*/
	do {
		fillroster(&nplr, &ncomp);
		pickgametype();
		for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat != Inactive) {
			switch(plr[c].p_pref) {
			case Fstand: if(gametype == Standard) continue; break;
			case Fblitz: if(gametype == Blitz) continue; break;
			default: continue;
			}
			if( plr[c].p_stat == Computer
			|| (plr[c].p_stat == Waiting && plr[c].p_computer != 0)) {
				oldplayer(c);
				--nplr, --ncomp;
				continue;
			}
			(void) simp(c, M_DOWN, "This isn't your kind of game.");
			oldplayer(c);
			--nplr;
		}
	} while(nplr < MINPLR || ncomp < MINCOMP);

	/*
	**	Add Waiting and Watching here, though there shouldn't be any Watching.
	**	We do Spiders in the next loop in case all the humans got bumped out.
	**	We have to be careful of Waiting Computers.
	*/
	for(c = 0;c < PLAYERS;++c) {
		switch(plr[c].p_stat) {
		case Waiting:
			if(plr[c].p_computer == 0) {
				plr[c].p_stat = Active, ++active, --waiting;
				(void) tellstatus(c);
			} else	/* waiting computer */
				plr[c].p_stat = Computer, --waiting;
			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
			break;
		case Watching:
			plr[c].p_stat = Active, ++active, --watching;
			(void) tellstatus(c);
			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
			break;
		}
	}
	if(waiting != 0) {
		syslog(LOG_DEBUG, "begingame: waiting=%d (should be zero)", waiting);
		waiting = 0;
	}
	if(watching != 0) {
		syslog(LOG_DEBUG, "begingame: watching=%d (should be zero)", watching);
		watching = 0;
	}

	/*
	**	If no Active at this point, all the non-Spiders must have disagreed
	**	with the game type selection and were bumped out.  Give up.
	*/
	if(active == 0) {
		updstat(-1);
		return;
	}

	/*
	**	We're committed to a game, so awaken the Spiders.
	**	If the Spider's forced preference doesn't match, say goodbye.
	*/
	if(spiders > 0) {
		for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Spider) {
			if((plr[c].p_pref == Fstand && gametype != Standard)
			|| (plr[c].p_pref == Fblitz && gametype != Blitz)) {
				(void) simp(c,M_DOWN,"They aren't playing your kind of game.");
				oldplayer(c);
				continue;
			}
			plr[c].p_stat = Active, ++active, --spiders;
			(void) tellstatus(c);
			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
		}
		if(spiders != 0) {
			syslog(LOG_DEBUG,
				"begingame: spiders=%d (should be zero)", spiders);
			spiders = 0;
		}
	}

	/*
	**	Initialize and sort the history data.
	**	Reorder the player roster based on history and/or previous score.
	*/
	histsort();
	historder(firstgame);

	/*
	**	Zero scores and distribute new roster.
	*/
	tellwinscore(-1);
	for(c = 0;c < PLAYERS;++c)
		plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
	roster(-1, False);

	inprogress = True;
	updstat(-1);			/* ready to begin game */
	hesitate(2000L, 3000L);	/* give players a chance to assess */
}

/*
**	latejoincomp: add a computer player if it looks not-too-unfavorable
**		returns True if a player was added
*/
boolean
latejoincomp(high)
int		high;
{
	register int	c, low;
	int				nplr;

#define	SPREAD	(winscore / 30)

	/*
	**	Don't add a computer if the game is no longer open.
	**	Don't add a computer while the low score is less than ONBOARD.
	**	Don't add a computer if the point spread is too large.
	*/
	if(high >= CLOSED
	|| (low = lowscore((int *)0)) < ONBOARD
	|| high - low > SPREAD)
		return False;
	
	/*
	**	Count the number of players and find the first empty slot.
	*/
	low = -1, nplr = 0;
	for(c = 0;c < PLAYERS;++c) {
		switch(plr[c].p_stat) {
		case Active:
		case Computer:
		case Waiting:
		case Spider:	/* shouldn't happen */
			++nplr;
			break;
		case Inactive:
			if(low == -1)
				low = c;
			break;
		}
	}

	/*
	**	If there's an empty slot and not too many players, add a computer.
	*/
	if((c = low) != -1 && nplr < 6) {
		pickcomputer(c);						/* sets status to Computer */
		plr[c].p_stat = Waiting, ++waiting;		/* no Waiting Computer status */
		plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
		plr[c].p_fd = -1, plr[c].p_timeouts = 0;
		adjroster = True;
		return True;
	}

	return False;

#undef	SPREAD
}

/*
**	gameturn: do a single turn for each Active player
*/
gameturn()
{
	register int	c;
	int				about, wasabout;
	int				high;
	char			msgbuf[MESGLEN];

	++turnnum;

	wasabout = -1;
	for(c = 0;active > 0 && c < PLAYERS;++c) {
		switch(plr[c].p_stat) {
		case Computer:
		case Active:
			updstat(c);
			/*
			**	The turn function returns -d_pts_max when no points
			**	are gained on the turn, or d_pts_turn when points
			**	are gained on the turn.  We have no use for this
			**	return value (anymore), since turn (now) updates
			**	p_score, p_squander, and p_onboard.
			*/
			(void) turn(c);
			annscore(c);

			/*
			**	Tell about players about to win and players no longer
			**	about to win.
			*/
			if((about = winner()) == c) {
				sprintf(msgbuf, "%d %s is about to win...\r\n",
					M_INFO, plr[c].p_name);
				announce(c, msgbuf);
				if(plr[c].p_stat == Active)
					(void) simp(c, M_INFO, "You are about to win...");
				hesitate(1500L, 2000L);
			} else if(wasabout != about && wasabout >= 0) {
				sprintf(msgbuf, "%d %s is no longer about to win.\r\n",
					M_INFO, plr[wasabout].p_name);
				announce(wasabout, msgbuf);
				if(plr[wasabout].p_stat == Active)
					(void) simp(c, M_INFO, "You are no longer about to win.");
				hesitate(1500L, 2000L);
			}
			wasabout = about;

			/*
			**	Accept new players to Waiting or Watching status.
			**	Check for asyncrhronous requests.
			*/
			if(inprogress == True && wantin(&poll) == True)
				(void) newplayers(False);
			async(-1);	/* nobody's turn at the moment */
			hesitate(1500L, 1800L);
			break;
		}
	}

	/*
	**	If a game is inprogress, we may wish to adjust the roster.
	*/
	if(active > 0 && inprogress == True) {
		high = highscore(-1, (int *)0);

		/*
		**	Possibly add a computer player.  (More probable early in game.)
		*/
		if(randint(CLOSED) > high)
			(void) latejoincomp(high);

		/*
		**	Call fixroster when necessary to collapse out blank slots
		**	and to add new players to the proper place in the turn order.
		*/
		if(adjroster == True)
			fixroster(high < CLOSED ? True : False);
	}
}

/*
**	anoghelp: long help message at M_ANOG prompt
*/
static char	   *anoghelp[] = {
	"y       Yes:    play in next game (synonyms=[,])",
	"n       No:     don't play in next game (synonyms=[.eqx])",
	"m       Myrank: display your ranking info",
	"a       Above:  display ranking info for player ranked just above you",
	"b       Below:  display ranking info for player ranked just below you",
	"p<num>  Player: display ranking info for player <num> in roster",
	(char *)0	/* end marker */
};

/*
**	endgame: cleanup after game finish
*/
endgame()
{
	register int	c, highc;
	boolean			again;
	int				num, rankq, errs;
	time_t			now;
	char		   *date;
	history		   *ph;
	char			msgbuf[BUFSIZ];

	inprogress = False;
	firstgame = False;
/*	turnnum = 0;		/* don't zero; needed in histpoints */
	updstat(-1);		/* game over */

	/*
	**	If there's a winner, update player score histories.
	*/
	if((highc = winner()) >= 0) {
		for(c = 0;c < PLAYERS;++c) {
			switch(plr[c].p_stat) {
			case Computer:
			case Active:
				(void) histpoints(c);
				break;
			}
		}
		(void) histwins(highc);
	}

	/*
	**	Write history whether or not there was a winner.
	*/
	(void) histwrite(HISTFILE);

	/*
	**	If there's a winner, send each active player a copy
	**	of their (new) ranking info, for recordkeeping purposes.
	*/
	if(highc >= 0) {
		(void) time(&now);
		date = ctime(&now) + 4;
		for(c = 0;c < PLAYERS;++c) {
			if(plr[c].p_stat == Active) {
				if((ph = histbyname(plr[c].p_id)) == 0)
					continue;
				sprintf(msgbuf, "%d %.16s %ld %.12s %s\r\n", M_RANK,
					equiv[0], ph->h_lastgame, date, histfmtplr(c, 0, True));
				(void) message(c, msgbuf);
			}
		}
	}

	/*
	**	Tell everybody who won.
	*/
	if(highc < 0)
		sprintf(msgbuf, "%d The game has concluded with no winner.\r\n",
			M_NWIN);
	else
		sprintf(msgbuf, "%d Player %d %s has won the game!\r\n",
			M_OVER, highc+1, plr[highc].p_name);
	announce(-1, msgbuf);
	hesitate(2000L, 3000L);

	/*
	**	If the system's about to shut down, don't waste any more time.
	**	We delay checking saygoodbye until play again query.
	*/
	if(pendshutdown() == True) {
		sysshutdown();
		return;
	}

	/*
	**	Distribute a new roster that shows those Watching and Waiting.
	*/
	if(watching > 0 || waiting > 0)
		roster(-1, True);
	
	/*
	**	Get rid of all proxies.  Computers quit on occasion.  Kamelion
	**	quits more often.  Ask humans if they'd like to play.
	**	If they say no, dump them out of the game.
	*/
	for(c = 0;c < PLAYERS;++c) {
		switch(plr[c].p_stat) {
		case Active:
			if(isproxy(c) == True) {
				if(annturn(c) < 0)
					continue;
				(void) simp(c, M_DOWN, "You've chosen not to play...");
				hesitate(1500L, 3000L);
				oldplayer(c);
				continue;
			}
			/* fall */
		case Watching:
		case Waiting:
		case Spider:	/* shouldn't happen */
			if(annturn(c) < 0)
				continue;
			if(saygoodbye == True) {
				(void) simp(c, M_DOWN,
					"The cubeserver is shutting down -- bye.");
				oldplayer(c);
				continue;
			}
			if(pendshutdown() == True) {
				(void) simp(c, M_DOWN,
					"System shutdown in progress -- goodbye.");
				oldplayer(c);
				continue;
			}
			break;
		case Computer:
			(void) annturn(c);
			if(saygoodbye == True || pendshutdown() == True) {
				hesitate(500L, 1000L);
				oldplayer(c);
				continue;
			}
			if(iscomp(c) == True) {
				hesitate(750L, 1250L);
				continue;
			}
			hesitate(1000L, 3000L);
			if(isproxy(c) == True) {
				oldplayer(c);
				continue;
			}
			if(iskamelion(c) == True) {
				hesitate(100L, 1000L);
				if(randint(3) == 3)
					oldplayer(c);
				continue;
			}
			if(randint(9) == 9)
				oldplayer(c);
			continue;
		default:
			continue;
		}

#define	MAXRANKQ	3				/* limit rank query abuse */
#define	MAXERRS		(2*MAXRANKQ)	/* limit error recovery abuse */
		rankq = errs = 0;
		do {
			again = False;

			if(errs < MAXERRS) {
				sprintf(msgbuf, "%d Play %s game? [yn%s?]\r\n", M_ANOG,
					plr[c].p_stat == Active ? "another" : "in next",
					rankq < MAXRANKQ ? "mabp" : "");
				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);
					}
					break;
				}
			} else {
				if(simp(c, M_ARGE, "Too many errors, assuming No.") < 0)
					break;
				msgbuf[0] = 'n', msgbuf[1] = '\0';
			}

			switch(msgbuf[0]) {
/*			case '\0':				/* default -- too dangerous */
			case 'y': case 'Y':		/* Yes */
			case ',':				/* yes for numeric keypads */
				switch(plr[c].p_stat) {
				case Waiting:
					plr[c].p_stat = Active, ++active, --waiting;
					(void) tellstatus(c);
					break;
				case Watching:
					plr[c].p_stat = Active, ++active, --watching;
					(void) tellstatus(c);
					break;
				case Spider:
					plr[c].p_stat = Active, ++active, --spiders;
					(void) tellstatus(c);
					break;
				}
				break;
			case 'n': case 'N':		/* No */
			case 'q': case 'Q':		/* Quit */
			case 'e': case 'E':		/* Exit */
/*			case 'x': case 'X':		/* eXit -- now means autopilot */
			case '.':				/* hold (numeric keypad) */
				if(plr[c].p_stat == Active) {
					sprintf(msgbuf, c == highc
						?	"%d Good game %s.  Bye...\r\n"
						:	"%d Better luck next time, %s.\r\n",
						M_DOWN, plr[c].p_name);
					(void) message(c, msgbuf);
				} else
					(void) simp(c, M_DOWN, "See ya, wallflower...");
				oldplayer(c);
				break;
			case 'm': case 'M':		/* Myrank */
				if(rankq++ >= MAXRANKQ)
					goto toomany;
				if(myrank(c, False) < 0)
					break;
				again = True;
				break;
			case 'a': case 'A':		/* Aboveme */
				if(rankq++ >= MAXRANKQ)
					goto toomany;
				if(aboveme(c, False) < 0)
					break;
				again = True;
				break;
			case 'b': case 'B':		/* Belowme */
				if(rankq++ >= MAXRANKQ)
					goto toomany;
				if(belowme(c, False) < 0)
					break;
				again = True;
				break;
			case 'p': case 'P':		/* Player <num> */
				if(rankq++ >= MAXRANKQ)
					goto toomany;
				if(sscanf(msgbuf, "%*[^0123456789]%d", &num) != 1) {
					(void) simp(c, M_ARGE, "Usage: player <number>");
					again = True;
					break;
				}
				if(plrrank(c, num - 1, False) < 0)
					break;
				again = True;
				break;
			case '?':	/* help */
				if(multimesg(c, M_HELP, anoghelp) < 0)
					break;
				again = True;
				break;
toomany:
			default:
				if(invalid(c) < 0)
					break;
				again = True;
				break;
			}
			++errs;
		} while(again == True && plr[c].p_stat != Inactive);
	}

	/*
	**	Sanity checks.
	*/
	if(active < 0) {
		syslog(LOG_DEBUG, "endgame: active=%d (should non-negative)", active);
		waiting = 0;
	}
	if(waiting != 0) {
		syslog(LOG_DEBUG, "endgame: waiting=%d (should be zero)", waiting);
		waiting = 0;
	}
	if(watching != 0) {
		syslog(LOG_DEBUG, "endgame: watching=%d (should be zero)", watching);
		watching = 0;
	}
	if(spiders != 0) {
		syslog(LOG_DEBUG, "endgame: spiders=%d (should be zero)", spiders);
		spiders = 0;
	}

	/*
	**	If there are no Active players left, get rid of all Computer players.
	**	Reinstall the COMP computer.
	*/
	if(active == 0) {
		for(c = 0;c < PLAYERS;++c)
			if(plr[c].p_stat == Computer)
				oldplayer(c);
		addcomp(COMP);
	}

	clearroster();
	turnnum = 0;
	updstat(-1);		/* idle or pregame again */

#undef	MAXRANKQ
#undef	MAXERRS
}

/*
**	updstat: update status line
*/
updstat(cup)
int		cup;
{
	register int	c, n, h, nobs;
	char			buf[2*PSLEN+NAMELEN];

	currup = cup;	/* update global variable */

	nobs = waiting + watching;
	if(saygoodbye == True)
		strcpy(buf, "shutting down");
	else if(active == 0 && nobs == 0)
		strcpy(buf, "idle");
	else if(active == 0 && nobs != 0)
		sprintf(buf, "idle wt%d", nobs);
	else {
		for(h = n = c = 0;c < PLAYERS;++c) {
			if(plr[c].p_stat == Computer || plr[c].p_stat == Active) {
				if(plr[c].p_score > h)
					h = plr[c].p_score;
				++n;
			}
		}
		if(inprogress == False)
			sprintf(buf, "waiting pl%d", n);
		else if(cup < 0)
			sprintf(buf, "active pl%d hs%d tn%d", n, h, turnnum);
		else if(nobs == 0)
			sprintf(buf, "active pl%d hs%d tn%d %s",
				n, h, turnnum, plr[cup].p_name);
		else
			sprintf(buf, "active pl%d wt%d hs%d tn%d %s",
				n, nobs, h, turnnum, plr[cup].p_name);
	}

	sprintf(statusline, "cubes %-*s.", PSLEN-7, buf);

	/*
	**	Check for and answer any pending status queries.
	*/
	answer();
}

/*
**	srvshutdown: controlled shutdown on SIGTERM
*/
static int
srvshutdown()
{
	register int	c;

	for(c = 0;c < PLAYERS;++c) {
		switch(plr[c].p_stat) {
		case Active:
		case Waiting:
		case Watching:
		case Spider:
			(void) simp(c, M_DOWN, "The cubeserver is shutting down -- goodbye.");
			/* fall */
		case Computer:
			oldplayer(c);
			break;
		}
	}

#ifdef	UNIXSOCK
	(void) unlink(UNIXSOCK);
#endif	UNIXSOCK

	syslog(LOG_INFO, "shutdown");

	saygoodbye = True;
	updstat(-1);

	exit(0);
}

/*
**	delshutdown: delayed shutdown
**		if game is inprogress, just set saygoodbye
**		elsewise shutdown immediately
*/
static int
delshutdown()
{
	if(inprogress == True)
		saygoodbye = True;
	else
		srvshutdown();
}