[comp.sources.games] v06i060: 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 60
Archive-name: cubes2/Part02



#! /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 2 (of 8)."
# Contents:  cubeserv1.c cubestat.6 tactics.c
# Wrapped by billr@saab on Thu Apr 27 12:13:35 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'cubeserv1.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cubeserv1.c'\"
else
echo shar: Extracting \"'cubeserv1.c'\" \(44192 characters\)
sed "s/^X//" >'cubeserv1.c' <<'END_OF_FILE'
X/*	vi:set sw=4 ts=4: */
X#ifndef	lint
Xstatic char	sccsid[] = "@(#)cubeserv1.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/*
X**	Some #defines to make linting easier.
X*/
X#ifdef	lint
X#ifndef	PATHNAME
X#define	PATHNAME	"dummy-pathname"
X#endif	PATHNAME
X#ifndef	HISTFILE
X#define	HISTFILE	"dummy-histfile"
X#endif	HISTFILE
X#ifndef	UNIXSOCK
X#define	UNIXSOCK	"dummy-unixsock"
X#endif	UNIXSOCK
X#ifndef	INETSOCK
X#define	INETSOCK	/* dummy-inetsock */
X#endif	INETSOCK
X#endif	lint
X
X#include	<stdio.h>
X#include	<ctype.h>
X#include	<strings.h>
X#include	<errno.h>
X#include	<syslog.h>
X#include	<signal.h>
X#include	<setjmp.h>
X#include	<sys/types.h>
X#include	<sys/time.h>
X#include	<sys/file.h>
X#include	<sys/ioctl.h>
X#include	<sys/socket.h>
X#include	<netinet/in.h>
X#include	<netdb.h>
X#ifdef	UNIXSOCK
X#include	<sys/un.h>
X#endif	UNIXSOCK
X#include	"cubes.h"
X
Xextern int		errno;
Xextern int		optind;
Xextern int		opterr;
Xextern long		gamenum;
Xextern char	   *optarg;
Xextern char	   *histfmtplr();
Xextern history *histbyname();
Xextern char	   *ctime();
Xextern time_t	time();
Xextern boolean	wantin();
Xextern boolean	prescreen();
Xextern boolean	iscomp();
Xextern boolean	isproxy();
Xextern boolean	iskamelion();
Xextern boolean	newplayers();
Xextern boolean	pendshutdown();
Xextern winpref	workhourstype();
Xstatic int		srvshutdown();
Xstatic int		sysshutdown();
Xstatic int		delshutdown();
X
Xstatic int		usock	= -1;			/* UNIX server socket */
Xstatic int		isock	= -1;			/* INET server socket */
Xstatic int		qsock	= -1;			/* INET query socket */
Xint				active	= 0;			/* number of Active humans */
Xint				waiting	= 0;			/* number of Waiting humans */
Xint				watching= 0;			/* number of Watching humans */
Xint				spiders	= 0;			/* number of Spiders */
Xint				turnnum	= 0;			/* current turn number */
Xint				currup	= -1;			/* player currently up */
Xunsigned		neq		= 0;			/* number of equivalent hosts */
Xchar		  **equiv;					/* equivalent hosts */
Xboolean			enajokers	= False;	/* enable jokers */
Xboolean			jokermode	= False;	/* dice do not have joker faces */
Xboolean			inprogress	= False;	/* True when game in progress */
Xboolean			firstgame	= True;		/* first game with set of players */
Xboolean			saygoodbye	= False;	/* True if delayed shutdown requested */
Xboolean			adjroster	= False;	/* Adjust roster */
Xboolean			clearobs	= False;	/* Observers are showing in roster */
Xblitzmode		blitzwhen	= Enforced;	/* Blitz mode enforced during work */
Xwinpref			gametype	= Standard;	/* specifies winscore */
Xint				winscore	= WINSCORE;	/* points needed to win */
X#define	CLOSED	(3 * winscore / 4)		/* no more new players */
Xextern player	plr[];					/* player/connection list */
Xstruct timeval	poll	= { 0L, 0L };	/* for select polling */
Xstruct timeval	onesec	= { 1L, 0L };	/* for select polling */
X
X#define	PSLEN	55						/* length of "ps" status line */
Xchar		   *statusline;				/* really av[0] */
X
Xmain(ac, av)
Xchar   *av[];
X{
X	int			c;
X
X	/*
X	**	Make sure we have enough room to display our status.
X	*/
X	if(strlen(av[0]) < PSLEN) {
X		char   *newav0;
X		char   *malloc();
X
X		if((newav0 = malloc(PSLEN+1)) == 0) {
X			syslog(LOG_ERR, "cubeserver: no memory for new argv zero");
X			exit(1);
X		}
X		sprintf(newav0, "cubes %-*s.", PSLEN-7, "startup");
X		av[0] = newav0;
X		execv(PATHNAME, av);
X		perror(PATHNAME);	/* OK */
X		exit(1);
X	}
X
X	(void) setuid(geteuid());
X	statusline = av[0];
X	updstat(-1);		/* server startup */
X
X	initcomptbl();
X
X	opterr = 0;
X	while((c = getopt(ac, av, "jb:")) >= 0) {
X		switch(c) {
X		case 'j':
X			enajokers = (enajokers == True) ? False : True;
X			break;
X		case 'b':
X			switch(*optarg) {
X			case 'n': case 'N': blitzwhen = Noblitz;   continue;
X			case 'r': case 'R': blitzwhen = Onrequest; continue;
X			case 'w': case 'W': blitzwhen = Workhours; continue;
X			case 'e': case 'E': blitzwhen = Enforced;  continue;
X			}
X			/* fall to usage error */
X		default:
X			fprintf(stderr, "usage -- cubeserver [-j] [-b{nrwe}]\n");	/*OK*/
X			exit(1);
X		}
X	}
X
X	/*
X	**	Blank arguments one through ac.
X	*/
X	for(c = 1;c < ac;++c) {
X		char   *s;
X		for(s = av[c];*s != '\0';++s)
X			*s = ' ';
X	}
X
X	/*
X	**	Fork.
X	*/
X	switch(c = fork()) {
X	case -1:	/* error */
X		perror("cubeserver: fork");	/* OK */
X		exit(1);
X	case 0:		/* child */
X		break;
X	default:	/* parent */
X		printf("%d\n", c);
X		exit(0);
X	}
X
X	/*
X	**	Disconnect from controlling tty.
X	*/
X	if((c = open("/dev/tty", O_RDWR)) >= 0) {
X		(void) ioctl(c, TIOCNOTTY, (char *)0);
X		(void) close(c);
X	}
X
X	/*
X	**	Ignore keybord signals.  Catch TERM for controlled shutdown.
X	**	We catch HUP and take it to mean delayed shutdown.
X	*/
X	(void) signal(SIGINT,  SIG_IGN);
X	(void) signal(SIGQUIT, SIG_IGN);
X	(void) signal(SIGTSTP, SIG_IGN);
X	(void) signal(SIGTTIN, SIG_IGN);
X	(void) signal(SIGTTOU, SIG_IGN);
X	(void) signal(SIGPIPE, SIG_IGN);
X	(void) signal(SIGHUP,  delshutdown);
X	(void) signal(SIGTERM, srvshutdown);
X
X	/*
X	**	Close std out and std err.  Initialize syslog.
X	**	Provide a startup message.
X	*/
X	(void) fclose(stdout);
X	(void) fclose(stderr);
X#ifdef	LOG_DAEMON
X	openlog("cubeserver", LOG_PID, LOG_DAEMON);
X	(void) setlogmask(LOG_UPTO(LOG_ERR));
X#else	LOG_DAEMON
X	openlog("cubeserver", LOG_PID);
X#endif	LOG_DAEMON
X
X	{
X		char   *bname;
X
X		switch(blitzwhen) {
X		case Noblitz:	bname = "never"; break;
X		case Onrequest: bname = "on request"; break;
X		case Workhours:	bname = "optional during peak"; break;
X		case Enforced:	bname = "enforced during peak"; break;
X		default:		bname = "unknown"; break;
X		}
X		syslog(LOG_INFO, "startup: jokers %s, blitz %s",
X			enajokers == True ? "enabled" : "disabled", bname);
X	}
X	
X	/*
X	**	Close stdin and open server socket.
X	**	Initialize random number generator.
X	**	Read history file.
X	*/
X	(void) fclose(stdin);
X	if(openservsock() < 0)
X		exit(1);
X	irandom();
X	if(histread(HISTFILE) < 0)
X		exit(1);
X
X	/*
X	**	Initialize player/connection list.
X	*/
X	active = waiting = watching = spiders = 0;
X	for(c = 0;c < PLAYERS;++c)
X		zeroplayer(c);
X	addcomp(COMP);	/* add COMP computer */
X
X	for(firstgame = True;saygoodbye == False;) {
X		inprogress = False;
X		do {
X			(void) newplayers(True);
X		} while(active == 0 && waiting == 0 && watching == 0);
X		if(pendshutdown() == True) {
X			sysshutdown();
X			continue;
X		}
X		begingame();
X		while(active > 0 && winner() < 0)
X			gameturn();
X		endgame();
X	}
X
X	srvshutdown();
X	exit(0);
X}
X
X/*
X**	pendshutdown: return True if system shutdown is pending
X*/
Xboolean
Xpendshutdown()
X{
X	/*
X	**	Check for the existence of the nologin file.  This gives
X	**	a warning of five minutes or less.  Not really good enough.
X	*/
X	return access("/etc/nologin", F_OK) < 0 ? False : True;
X}
X	
X/*
X**	sysshutdown: hang up on all players saying that the system is going down
X**		We don't shut down the server because of "shutdown -k".
X*/
Xstatic int
Xsysshutdown()
X{
X	register int	c;
X
X	/*
X	**	Send the shutdown message to all humans.
X	XXX	Could use contents of /etc/nologin here.
X	*/
X	if(active > 0 || waiting > 0 || watching > 0 || spiders > 0) {
X		for(c = 0;c < PLAYERS;++c)
X			if(plr[c].p_stat != Inactive && plr[c].p_stat != Computer)
X				(void) simp(c, M_DOWN, "System shutdown in progress -- bye.");
X	}
X
X	/*
X	**	Hang up on everybody, then reinstall the COMP computer.
X	*/
X	for(c = 0;c < PLAYERS;++c)
X		if(plr[c].p_stat != Inactive)
X			oldplayer(c);
X	addcomp(COMP);
X
X	updstat(-1);
X}
X
X/*
X**	openservsock: open, bind, and begin listening on server socket(s)
X**		also opens the status query socket
X*/
Xopenservsock()
X{
X	/*
X	**	We shouldn't be called more than once, but make sure anyway.
X	*/
X	if(isock >= 0) {
X		(void) close(isock);
X		isock = -1;
X	}
X	if(usock >= 0) {
X		(void) close(usock);
X		usock = -1;
X	}
X	if(qsock >= 0) {
X		(void) close(qsock);
X		qsock = -1;
X	}
X
X#ifdef	UNIXSOCK
X	/*
X	**	If UNIXSOCK is defined, we open a server socket in the UNIX domain.
X	**	This socket is used for transactions with players on the local system.
X	*/
X	{
X		struct sockaddr_un	userver;
X		int					len;
X
X		(void) unlink(UNIXSOCK);
X		if((usock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
X			syslog(LOG_ERR, "server unix socket: %m");
X			goto nounix;
X		}
X		bzero((char *)&userver, sizeof userver);
X		userver.sun_family = AF_UNIX;
X		strcpy(userver.sun_path, UNIXSOCK);
X		len = sizeof userver.sun_family + strlen(userver.sun_path);
X		if(bind(usock, &userver, len) < 0) {
X			syslog(LOG_ERR, "server unix socket bind: %m");
X			(void) close(usock);
X			usock = -1;
X			goto nounix;
X		}
X		if(listen(usock, 5) < 0) {
X			syslog(LOG_ERR, "server unix socket listen: %m");
X			(void) close(usock);
X			usock = -1;
X			goto nounix;
X		}
X
Xnounix:	;
X	}
X#endif	UNIXSOCK
X
X#ifdef	INETSOCK
X	/*
X	**	If INETSOCK is defined, we open a server socket in the INET domain.
X	**	This socket is used for transactions with players on remote systems,
X	**	and if UNIXSOCK is not defined, for players on the local system.
X	*/
X	{
X		struct servent	   *ps;
X		struct sockaddr_in	server;
X
X		if((ps = getservbyname("cube", "tcp")) == 0) {
X			syslog(LOG_ERR, "no cube/tcp service listed");
X			goto noinet;
X		}
X		if((isock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
X			syslog(LOG_ERR, "server inet socket: %m");
X			goto noinet;
X		}
X		bzero((char *)&server, sizeof server);
X		server.sin_family = AF_INET;
X		server.sin_port = ps->s_port;
X		server.sin_addr.s_addr = INADDR_ANY;
X		if(bind(isock, &server, sizeof server) < 0) {
X			syslog(LOG_ERR, "server inet socket bind: %m");
X			(void) close(isock);
X			isock = -1;
X			goto noinet;
X		}
X		if(listen(isock, 5) < 0) {
X			syslog(LOG_ERR, "server inet socket listen: %m");
X			(void) close(isock);
X			isock = -1;
X			goto noinet;
X		}
Xnoinet:	;
X	}
X#endif	INETSOCK
X
X	/*
X	**	Neither socket was opened.  Report this situation
X	**	and return with failure status.
X	*/
X	if(usock < 0 && isock < 0) {
X		syslog(LOG_ERR, "no server sockets opened");
X		return -1;
X	}
X
X	/*
X	**	Now open the status query socket.  This socket is used to respond to
X	**	queries about the server's status.  Since it is not critical to the
X	**	operation of the game, we'll make any failure here a non-fatal error.
X	*/
X	{
X		struct sockaddr_in	server;
X		struct servent	   *ps;
X
X		if((ps = getservbyname("cubestat", "udp")) == 0) {
X			syslog(LOG_WARNING, "no cubestat/udp service listed");
X			goto noquery;
X		}
X		if((qsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
X			syslog(LOG_WARNING, "query socket: %m");
X			goto noquery;
X		}
X		/*
X		XXX	If INETSOCK is not defined, this is pretty silly, since queries
X		XXX	from remote systems can be responded to, but the game cannot be
X		XXX	played from remote systems.  Maybe we could fix this for many
X		XXX	configurations by fiddling with server.sin_addr.  Another way
X		XXX	would be to open a UNIX domain query socket.  Both approaches
X		XXX	seem like more trouble than they're worth.
X		*/
X		bzero((char *)&server, sizeof server);
X		server.sin_family = AF_INET;
X		server.sin_port = ps->s_port;
X		server.sin_addr.s_addr = INADDR_ANY;
X		if(bind(qsock, &server, sizeof server) < 0) {
X			syslog(LOG_WARNING, "query socket bind: %m");
X			(void) close(qsock);
X			qsock = -1;
X			goto noquery;
X		}
Xnoquery:;
X	}
X
X	return 0;
X}
X
X/*
X**	asynchelp: long help message for asynchronous requests
X*/
Xstatic char	   *asynchelp[]	= {
X  "g       Go         begin playing (voyeurs and spiders only)",
X  "x       Autopilot  enter autopilot mode (active players only)",
X  "m       Myrank     display your ranking info",
X  "a       Above      display ranking info for player ranked just above you",
X  "b       Below      display ranking info for player ranked just below you",
X  "p{num}  Player     display ranking info for player {num} in roster",
X  (char *)0	/* end marker */
X};
X
X/*
X**	async: check for and deal with asynchronous requests during game
X**		Ignore the current player.
X*/
Xasync(cur)
Xint		cur;
X{
X	register int	c, n;
X	char			msgbuf[MESGLEN];
X	fd_set			rdy;
X	int				num;
X
X	if(inprogress == False)
X		return;
X
X	/*
X	**	Select on all human connected sockets except for the
X	**	one connected to the current player.  If the current
X	**	player is temporarily proxied, select on that socket too.
X	*/
X	FD_ZERO(&rdy);
X	n = -1;
X	for(c = 0;c < PLAYERS;++c) {
X		if(c == cur && isproxy(c) == False)
X			continue;
X		switch(plr[c].p_stat) {
X		case Inactive:
X		case Computer:
X			continue;
X		default:
X			if(plr[c].p_fd >= 0) {
X				FD_SET(plr[c].p_fd, &rdy);
X				if(plr[c].p_fd > n)
X					n = plr[c].p_fd;
X			}
X			break;
X		}
X	}
X	if(n == -1 || select(n+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
X		return;
X
X	/*
X	**	Check the sockets and handle any requests.
X	*/
X	for(c = 0;c < PLAYERS;++c) {
X		if(c == cur && isproxy(c) == False)
X			continue;
X		if(plr[c].p_fd < 0 || !FD_ISSET(plr[c].p_fd, &rdy))
X			continue;
X		if(reply(c, msgbuf, sizeof msgbuf) <= 0)
X			continue;
X		if(msgbuf[0] != ASYNCMARK)	/* asynchronous marker */
X			continue;				/* XXX: ignore silently */
X		switch(msgbuf[1]) {
X		case 'g': case 'G':		/* Go! */
X			if(plr[c].p_stat == Active && isproxy(c) == True) {
X				plr[c].p_computer = 0;	/* become human again */
X				(void) tellstatus(c);
X				break;
X			}
X			if(plr[c].p_stat != Watching) {
X				(void) simp(c, M_ARGE, "The go command makes no sense now.");
X				break;
X			}
X			if(highscore(-1, (int *)0) >= CLOSED) {
X				(void) simp(c, M_ARGE, "It's too late to enter this game.");
X				break;
X			}
X			plr[c].p_stat = Waiting, ++waiting, --watching;
X			(void) tellstatus(c);
X			adjroster = True;
X			break;
X		case 'x': case 'X':		/* toggle autopilot mode */
X			if(plr[c].p_stat != Active) {
X				(void) simp(c, M_ARGE, "Autopilot is for active players only.");
X				break;
X			}
X			if(isproxy(c) == False)
X				pickproxy(c);			/* temporary proxy */
X			else
X				plr[c].p_computer = 0;	/* become human again */
X			(void) tellstatus(c);
X			break;
X		case 'm': case 'M': (void) myrank(c,  True); break;
X		case 'a': case 'A': (void) aboveme(c, True); break;
X		case 'b': case 'B': (void) belowme(c, True); break;
X		case 'p': case 'P':
X			if(sscanf(msgbuf, "%*[^0123456789]%d", &num) != 1) {
X				(void) simp(c, M_ARGE, "Usage: player <number>");
X				break;
X			}
X			(void) plrrank(c, num - 1, True);
X			break;
X		case 'q':	/* quit (decision already made) */
X			/*
X			**	If we send this message, we're more than likely to
X			**	get a dead pipe entry in syslog.  By commenting it
X			**	out, we assume that the client will exit on its own.
X			*/
X/*			(void) simp(c, M_DOWN, "So long, quitter...");	/* make sure */
X			oldplayer(c);
X			break;
X		case '?':	/* help */
X			(void) multimesg(c, M_HELP, asynchelp);
X			break;
X		default:
X			(void) invalid(c);
X			break;
X		}
X	}
X}
X
X/*
X**	wantinjmp, wantintimo: wantin timeout handler
X*/
Xstatic jmp_buf	wantinjmp;
Xstatic int
Xwantintimo()
X{
X	longjmp(wantinjmp, 1);
X}
X
X/*
X**	wantin: return True if ready to do accept on server socket
X**		If hang equals HANG, hang answering queries and providing heartbeat
X*/
Xboolean
Xwantin(hang)
Xstruct timeval *hang;
X{
X	fd_set		rdy;
X	int			n, c;
X	int		  (*oldalrm)();
X	unsigned	alarmv, hold;
X	char		msgbuf[MESGLEN];
X	boolean		ready;
X
X	/*
X	**	If we're not supposed to hang, just do a poll of
X	**	the server socket(s) using the requested hang time.
X	*/
X	if(hang != HANG) {
X		FD_ZERO(&rdy);
X		n = -1;
X		if(usock >= 0) {
X			FD_SET(usock, &rdy);
X			if(usock > n)
X				n = usock;
X		}
X		if(isock >= 0) {
X			FD_SET(isock, &rdy);
X			if(isock > n)
X				n = isock;
X		}
X
X		if(select(n+1, &rdy, NOSEL, NOSEL, hang) > 0) {
X			if(usock >= 0 && FD_ISSET(usock, &rdy))
X				return True;	/* server unix socket ready */
X			if(isock >= 0 && FD_ISSET(isock, &rdy))
X				return True;	/* server inet socket ready */
X		}
X
X		return False;			/* server socket(s) not ready */
X	}
X
X	/*
X	**	If there are spiders, set up a regular heartbeat.  We can't
X	**	call heartbeat as an alarm signal handler because it calls
X	**	message which uses alarms to timeout socket writes.  We check
X	**	for pending shutdowns and get rid of spiders if True.
X	*/
X	alarmv = alarm(0);
X	oldalrm = signal(SIGALRM, SIG_IGN);
X	if(spiders > 0) {
X		(void) signal(SIGALRM, wantintimo);
X		if(setjmp(wantinjmp) != 0) {
X			if(saygoodbye == True || pendshutdown() == True)
X				sysshutdown();
X			else
X				heartbeat();
X		}
X		(void) alarm(READTIMO);
X	}
X
X	/*
X	**	Hang answering queries.  Leave loop upon connection request.
X	**	If there are Spiders, monitor the corresponding sockets
X	**	in case they want to quit being Spiders and begin play.
X	**	When any data is sent from a Spider client, we change that
X	**	player's status to Waiting and return True.
X	*/
X	ready = False;
X	for(;;) {
X		FD_ZERO(&rdy);
X		n = -1;
X		if(usock >= 0) {
X			FD_SET(usock, &rdy);
X			if(usock > n)
X				n = usock;
X		}
X		if(isock >= 0) {
X			FD_SET(isock, &rdy);
X			if(isock > n)
X				n = isock;
X		}
X		if(qsock >= 0) {
X			FD_SET(qsock, &rdy);
X			if(qsock > n)
X				n = qsock;
X		}
X		if(spiders > 0) {
X			for(c = 0;c < PLAYERS;++c) {
X				if(plr[c].p_stat == Spider && plr[c].p_fd >= 0) {
X					FD_SET(plr[c].p_fd, &rdy);
X					if(plr[c].p_fd > n)
X						n = plr[c].p_fd;
X				}
X			}
X		}
X
X		if(n < 0 || select(n+1, &rdy, NOSEL, NOSEL, HANG) <= 0)
X			continue;
X
X		if(qsock >= 0 && FD_ISSET(qsock, &rdy))	/* query */
X			answer();
X		if(usock >= 0 && FD_ISSET(usock, &rdy)) { /* local conn. req. */
X			ready = True;						/* server socket ready */
X			break;
X		}
X		if(isock >= 0 && FD_ISSET(isock, &rdy)) { /* net conn. req. */
X			ready = True;						/* server socket ready */
X			break;
X		}
X		if(spiders > 0) {						/* anxious spiders? */
X			hold = alarm(0);					/* suspend heartbeat */
X			for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Spider) {
X				if(!FD_ISSET(plr[c].p_fd, &rdy))
X					continue;
X				if(reply(c, msgbuf, sizeof msgbuf) <= 0)
X					continue;
X				if(msgbuf[0] != ASYNCMARK)		/* asynchronous marker */
X					continue;					/* XXX: ignore silently */
X				switch(msgbuf[1]) {
X				case 'g': case 'G':		/* Go! */
X					plr[c].p_stat = Waiting, ++waiting, --spiders;
X					(void) tellstatus(c);
X					break;
X				case 'x': case 'X':		/* not appropriate */
X					(void) simp(c, M_ARGE,
X						"Using the autopilot now would be silly.");
X					break;
X				case 'm': case 'M': (void) myrank(c,  True); break;
X				case 'a': case 'A': (void) aboveme(c, True); break;
X				case 'b': case 'B': (void) belowme(c, True); break;
X				case 'p': case 'P':
X					(void) simp(c, M_ARGE, "There aren't any players!");
X					break;
X				case 'q':	/* quit (decision already made) */
X					(void) simp(c, M_DOWN, "So long, quitter...");
X					oldplayer(c);
X					break;
X				case '?':	/* help */
X					(void) multimesg(c, M_HELP, asynchelp);
X					break;
X				default:
X					(void) invalid(c);
X					break;
X				}
X			}
X			if(waiting > 0)						/* converted spiders */
X				break;							/* keep ready false */
X			(void) alarm(hold);					/* resume heartbeat */
X		}
X	}
X
X	/*
X	**	Turn off heartbeat.
X	*/
X	(void) alarm(0);
X	(void) signal(SIGALRM, oldalrm);
X	(void) alarm(alarmv);
X
X	return ready;
X}
X
X/*
X**	answer: check for and answer any pending status queries
X**		For now, we support only the query Q_ROSTER
X**		and return only S_IDLE and S_ROSTER.
X*/
Xanswer()
X{
X	register int		c, n, h;
X	fd_set				rdy;
X	char				status[STATLEN];
X	char				query;
X	int					flen;
X	struct sockaddr		from;
X	static char			unrecog[]	= { S_UNRECOG, '\0' };
X
X	/*
X	**	If no query socket, there's nothing to do.
X	*/
X	if(qsock < 0)
X		return;
X
X	/*
X	**	Check for any pending requests.  If none, we're done.
X	*/
X	FD_ZERO(&rdy);
X	FD_SET(qsock, &rdy);
X	if(select(qsock+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
X		return;
X	
X	/*
X	**	We've got at least one request, so let's format a response
X	**	and send it to any and all that have asked for it.  (This,
X	**	of course, assumes that we're going to get a Q_ROSTER query.)
X	*/
X	if(active == 0 && waiting == 0) {
X		if(saygoodbye == True) {
X			static char		down[]	= "XThe cubeserver is shutting down.\r\n";
X
X			strcpy(status, down);
X			n = sizeof down - 1;
X			status[0] = S_IDLE;
X		} else {
X			char		   *type;
X			time_t			last;
X			extern char	   *ctime();
X
X			switch(workhourstype()) {
X			case Blitz:
X				type = blitzwhen == Enforced ?
X					"Blitz only" : "Blitz by default";
X				break;
X			case Standard:
X				type = blitzwhen == Noblitz ?
X					"Standard only" : "Standard by default";
X				break;
X			default:
X				type = "unknown";
X				break;
X			}
X			last = 0;
X			(void) histtime(HISTFILE, &last);
X			sprintf(status,
X"%cThe cubeserver is idle since %12.12s.\r\nPrevailing game type is %s.\r\n",
X				S_IDLE, ctime(&last) + 4, type);
X			n = strlen(status);
X		}
X	} else if(active == 0 && waiting > 0) {
X		static char	about[]	= "XA game is about to begin.\r\n";
X		strcpy(status, about);
X		status[0] = S_ROSTER;	/* to provide success indication */
X		n = sizeof about - 1;
X	} else {
X		status[0] = S_ROSTER;
X		n = 1;
X		strcpy(&status[n], "Up  Player ");
X		n += 11;
X
X		if((c = watching + waiting) == 0)
X			strcpy(&status[n], "    ");
X		else
X			sprintf(&status[n], "%2dw ", c);
X		n += 4;
X
X		if(currup < 0)
X			strcpy(&status[n], "        Sqndr Score\r\n");
X		else if(turnnum <= 1)
X			sprintf(&status[n], "%2d    * Sqndr Score\r\n", turnnum);
X		else
X			sprintf(&status[n], "%2d %4d Sqndr Score\r\n",
X				turnnum, plr[currup].p_score / (turnnum-1));
X		n += 21;
X
X		strcpy(&status[n], "--- ------------------ ----- -----\r\n");
X		n += 36;
X
X		for(h = 0, c = 0;c < PLAYERS;++c) {
X			switch(plr[c].p_stat) {
X			case Active:
X			case Computer:
X				if(plr[c].p_score > h)
X					h = plr[c].p_score;
X				break;
X			}
X		}
X
X		for(c = 0;c < PLAYERS && n < STATLEN-37;++c) {
X			switch(plr[c].p_stat) {
X			case Active:
X			case Computer:
X				sprintf(&status[n], "%3s %-18.18s %5d %5d%c\r\n",
X					(c == currup) ? "=->" : "",
X					plr[c].p_name, plr[c].p_squander, plr[c].p_score,
X					(h == 0 || plr[c].p_score < h) ? ' ' : '*'
X				);
X				n += 37;
X				break;
X			}
X		}
X
X		/*
X		**	Some additional explanation.
X		*/
X		if(inprogress == True) {
X			if(n <= STATLEN - 36) {
X				sprintf(&status[n], "(%s%s game %s.)\r\n",
X					gametype == Standard ? "Standard" : "Blitz",
X					jokermode == True ? " joker" : "",
X					turnnum == 0 ? "has begun" : "in progress");
X				n = n + strlen(&status[n]);
X			}
X		} else if(turnnum == 0) {
X			static char	waitfor[] = "(Preparing for the next game.)\r\n";
X			if(n <= STATLEN - sizeof waitfor) {
X				strcpy(&status[n], waitfor);
X				n += sizeof waitfor - 1;
X			}
X		} else {
X			static char	gameover[] = "(The game has ended.)\r\n";
X			if(n <= STATLEN - sizeof gameover) {
X				strcpy(&status[n], gameover);
X				n += sizeof gameover - 1;
X			}
X		}
X	}
X
X	/*
X	**	Loop, reading each request and responding to it.  For now,
X	**	there's only one type of request, so just read a single byte
X	**	and ignore it.  When we run out of requests, we're done.
X	*/
X	for(;;) {
X		FD_ZERO(&rdy);
X		FD_SET(qsock, &rdy);
X		if(select(qsock+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
X			return;		/* no more requests */
X
X		flen = sizeof from;
X		if(recvfrom(qsock, &query, sizeof query, 0, &from, &flen) <= 0) {
X			syslog(LOG_DEBUG, "answer: recvfrom: %m");
X			continue;
X		}
X
X		switch(query) {
X		case Q_ROSTER:
X			if(sendto(qsock, status, n+1, 0, &from, flen) < 0)
X				syslog(LOG_DEBUG, "answer: sendto: %m");
X			break;
X		default:
X			syslog(LOG_NOTICE, "answer: got unrecognized query");
X			if(sendto(qsock, unrecog, sizeof unrecog, 0, &from, flen) < 0)
X				syslog(LOG_DEBUG, "answer: sendto: %m");
X			break;
X		}
X	}
X}
X
X/*
X**	newplayers: check for and add new players
X*/
Xboolean
Xnewplayers(hang)
Xboolean					hang;
X{
X	register int		c;
X	int					csock;
X	int					tries;
X	int					len, n;
X	char				msgbuf[BUFSIZ];
X	int					off	= 0;
X	int					added;
X	fd_set				rdy;
X
X	/*
X	**	Is this a new batch of players?
X	*/
X	if(hang == True && active == 0)
X		firstgame = True;
X
X	/*
X	**	If there are no humans that want to play, we can hang waiting for some.
X	**	If wantin returns False, we have converted Spiders.
X	*/
X	if(hang == True && active == 0 && waiting == 0 && watching == 0)
X		if(wantin(HANG) == False)
X			return waiting > 0 ? True : False;	/* False? */
X
X	/*
X	**	Keep looping until we run out of connection requests.
X	*/
X	added = 0;
X	for(;;) {
X		/*
X		**	If we are supposed to hang, then wait a while for
X		**	another connection request, elsewise, just return.
X		*/
X#define	MAXTRIES	5
X		for(tries = 0;wantin(&onesec) == False;++tries) {
X			if(hang == False || tries > MAXTRIES)
X				return added > 0 ? True : False;
X			updstat(-1);	/* about to begin notification */
X/*			sleep(1);		/* wantin hangs for one second */
X		}
X
X		/*
X		**	Check server socket(s).
X		*/
X		FD_ZERO(&rdy);
X		n = -1;
X		if(usock >= 0) {
X			FD_SET(usock, &rdy);
X			if(usock > n)
X				n = usock;
X		}
X		if(isock >= 0) {
X			FD_SET(isock, &rdy);
X			if(isock > n)
X				n = isock;
X		}
X		if(n < 0 || select(n+1, &rdy, NOSEL, NOSEL, &poll) <= 0)
X			continue;
X
X		/*
X		**	No client socket open yet.
X		*/
X		csock = -1;
X
X#ifdef	UNIXSOCK
X		/*
X		**	If we don't have an open client socket yet, accept a connection
X		**	from a potential player via the UNIX domain socket.
X		*/
X		if(csock < 0 && usock >= 0 && FD_ISSET(usock, &rdy)) {
X			struct sockaddr_un	uclient;
X
X			len = sizeof uclient;
X			if((csock = accept(usock, &uclient, &len)) < 0)
X				syslog(LOG_DEBUG, "unix accept: %m");
X		}
X#endif	UNIXSOCK
X
X#ifdef	INETSOCK
X		/*
X		**	If we don't have an open client socket yet, accept a connection
X		**	from a potential player via the INET domain socket.
X		*/
X		if(csock < 0 && isock >= 0 && FD_ISSET(isock, &rdy)) {
X			struct sockaddr_in	client;
X
X			len = sizeof client;
X			if((csock = accept(isock, &client, &len)) < 0)
X				syslog(LOG_DEBUG, "inet accept: %m");
X		}
X#endif	INETSOCK
X
X		/*
X		**	If we reach here, it means that select returned indicating
X		**	activity, but neither server socket had anything to accept.
X		**	We log this and continue.
X		*/
X		if(csock < 0) {
X			syslog(LOG_DEBUG, "newplayers: nothing to accept");
X			continue;
X		}
X
X		/*
X		**	Make sure non-blocking I/O is turned off.
X		*/
X		(void) ioctl(csock, FIONBIO, (char *)&off);
X
X		/*
X		**	If the system is about to go down, don't allow new players.
X		*/
X		if(pendshutdown() == True) {
X			sprintf(msgbuf,
X				"%d Sorry, the system is about to shut down.\r\n", M_ARGE);
X			(void) write(csock, msgbuf, strlen(msgbuf));
X			sprintf(msgbuf, "%d Server closing connection.\r\n", M_DOWN);
X			(void) write(csock, msgbuf, strlen(msgbuf));
X			(void) close(csock);
X			continue;
X		}
X
X		/*
X		**	Find an open slot.
X		*/
X		for(c = 0;c < PLAYERS;++c)
X			if(plr[c].p_stat == Inactive)
X				break;
X		if(c == PLAYERS) {
X			sprintf(msgbuf, "%d Sorry, full house.\r\n", M_ARGE);
X			(void) write(csock, msgbuf, strlen(msgbuf));
X			sprintf(msgbuf, "%d Server closing connection.\r\n", M_DOWN);
X			(void) write(csock, msgbuf, strlen(msgbuf));
X			(void) close(csock);
X			continue;
X		}
X
X		/*
X		**	Fill in player parameters.  We must initially give the player
X		**	a status of Waiting or Watching so that we can send messages.
X		**	We initially choose Watching and adjust later.
X		*/
X		zeroplayer(c);
X		plr[c].p_fd = csock;
X		plr[c].p_stat = Watching, ++watching;
X		if(getplayername(c) < 0 || getplayerid(c) < 0)
X			continue;
X
X		/*
X		**	If we're not inprogress, check to see if the system's about
X		**	to go down.  If so, hang up on this player (and all others).
X		*/
X		if(inprogress == False && pendshutdown() == True) {
X			sysshutdown();
X			continue;
X		}
X
X		/*
X		**	Give the welcome message.  If a game is in progress, display the
X		**	roster and, if it matters, ask if the human wants to play or watch.
X		**	If no game is in progress and there's no other Active, Waiting,
X		**	or Watching humans, we ask if the human would prefer to be a
X		**	Spider.  If so, we change status from Watching to Spider.
X		**	Otherwise just change Watching to Waiting.
X		*/
X		sprintf(msgbuf, "%d %s, welcome to Cubes.\r\n", M_INFO, plr[c].p_name);
X		if(message(c, msgbuf) < 0)
X			continue;
X		if(inprogress == True) {
X			tellwinscore(c);	/* tell winscore to this player */
X			roster(c, False);	/* roster for this player only */
X			if(highscore(-1, (int *)0) < CLOSED)
X				if(getplayerintent(c) < 0)	/* watch or play? */
X					continue;
X			if(plr[c].p_stat != Watching)	/* not watching */
X				if(prescreen(c) == False)	/* doesn't like game type */
X					continue;
X			adjroster = True;
X		} else if(active == 0 && waiting == 0 && watching == 1) {
X			if(getplayerintent(c) < 0)		/* wait or play? */
X				continue;
X			if(plr[c].p_stat != Spider)		/* not waiting */
X				if(prescreen(c) == False)	/* doesn't like prevailing mode */
X					continue;
X/*			adjroster = True;				/* no game in progress */
X		} else {
X			if(prescreen(c) == False)		/* doesn't like prevailing mode */
X				continue;
X			plr[c].p_stat = Waiting, ++waiting, --watching;
X/*			adjroster = True;				/* no game in progress */
X		}
X
X		if(tellstatus(c) < 0)
X			continue;
X
X		++added;
X		(void) observers(-1);
X/*		updstat(-1);	/* new (temporary?) observer; don't report here */
X	}
X}
X
X/*
X**	begingame: setup prior to a game
X*/
Xbegingame()
X{
X	register int	c;
X	int				nplr, ncomp;
X
X	inprogress = False;
X	turnnum = 0;
X	setgamenum();
X	updstat(-1);		/* pregame setup */
X
X	/*
X	**	Fill the roster, pick the game type, throw out those that can't
X	**	live with the choice.  Repeat until we have enough players.
X	**	Only complication is that Computers are added with Waiting 
X	**	status, so we have to blow them away with special code.
X	*/
X	do {
X		fillroster(&nplr, &ncomp);
X		pickgametype();
X		for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat != Inactive) {
X			switch(plr[c].p_pref) {
X			case Fstand: if(gametype == Standard) continue; break;
X			case Fblitz: if(gametype == Blitz) continue; break;
X			default: continue;
X			}
X			if( plr[c].p_stat == Computer
X			|| (plr[c].p_stat == Waiting && plr[c].p_computer != 0)) {
X				oldplayer(c);
X				--nplr, --ncomp;
X				continue;
X			}
X			(void) simp(c, M_DOWN, "This isn't your kind of game.");
X			oldplayer(c);
X			--nplr;
X		}
X	} while(nplr < MINPLR || ncomp < MINCOMP);
X
X	/*
X	**	Add Waiting and Watching here, though there shouldn't be any Watching.
X	**	We do Spiders in the next loop in case all the humans got bumped out.
X	**	We have to be careful of Waiting Computers.
X	*/
X	for(c = 0;c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Waiting:
X			if(plr[c].p_computer == 0) {
X				plr[c].p_stat = Active, ++active, --waiting;
X				(void) tellstatus(c);
X			} else	/* waiting computer */
X				plr[c].p_stat = Computer, --waiting;
X			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
X			break;
X		case Watching:
X			plr[c].p_stat = Active, ++active, --watching;
X			(void) tellstatus(c);
X			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
X			break;
X		}
X	}
X	if(waiting != 0) {
X		syslog(LOG_DEBUG, "begingame: waiting=%d (should be zero)", waiting);
X		waiting = 0;
X	}
X	if(watching != 0) {
X		syslog(LOG_DEBUG, "begingame: watching=%d (should be zero)", watching);
X		watching = 0;
X	}
X
X	/*
X	**	If no Active at this point, all the non-Spiders must have disagreed
X	**	with the game type selection and were bumped out.  Give up.
X	*/
X	if(active == 0) {
X		updstat(-1);
X		return;
X	}
X
X	/*
X	**	We're committed to a game, so awaken the Spiders.
X	**	If the Spider's forced preference doesn't match, say goodbye.
X	*/
X	if(spiders > 0) {
X		for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Spider) {
X			if((plr[c].p_pref == Fstand && gametype != Standard)
X			|| (plr[c].p_pref == Fblitz && gametype != Blitz)) {
X				(void) simp(c,M_DOWN,"They aren't playing your kind of game.");
X				oldplayer(c);
X				continue;
X			}
X			plr[c].p_stat = Active, ++active, --spiders;
X			(void) tellstatus(c);
X			plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
X		}
X		if(spiders != 0) {
X			syslog(LOG_DEBUG,
X				"begingame: spiders=%d (should be zero)", spiders);
X			spiders = 0;
X		}
X	}
X
X	/*
X	**	Initialize and sort the history data.
X	**	Reorder the player roster based on history and/or previous score.
X	*/
X	histsort();
X	historder(firstgame);
X
X	/*
X	**	Zero scores and distribute new roster.
X	*/
X	tellwinscore(-1);
X	for(c = 0;c < PLAYERS;++c)
X		plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
X	roster(-1, False);
X
X	inprogress = True;
X	updstat(-1);			/* ready to begin game */
X	hesitate(2000L, 3000L);	/* give players a chance to assess */
X}
X
X/*
X**	latejoincomp: add a computer player if it looks not-too-unfavorable
X**		returns True if a player was added
X*/
Xboolean
Xlatejoincomp(high)
Xint		high;
X{
X	register int	c, low;
X	int				nplr;
X
X#define	SPREAD	(winscore / 30)
X
X	/*
X	**	Don't add a computer if the game is no longer open.
X	**	Don't add a computer while the low score is less than ONBOARD.
X	**	Don't add a computer if the point spread is too large.
X	*/
X	if(high >= CLOSED
X	|| (low = lowscore((int *)0)) < ONBOARD
X	|| high - low > SPREAD)
X		return False;
X	
X	/*
X	**	Count the number of players and find the first empty slot.
X	*/
X	low = -1, nplr = 0;
X	for(c = 0;c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Active:
X		case Computer:
X		case Waiting:
X		case Spider:	/* shouldn't happen */
X			++nplr;
X			break;
X		case Inactive:
X			if(low == -1)
X				low = c;
X			break;
X		}
X	}
X
X	/*
X	**	If there's an empty slot and not too many players, add a computer.
X	*/
X	if((c = low) != -1 && nplr < 6) {
X		pickcomputer(c);						/* sets status to Computer */
X		plr[c].p_stat = Waiting, ++waiting;		/* no Waiting Computer status */
X		plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False;
X		plr[c].p_fd = -1, plr[c].p_timeouts = 0;
X		adjroster = True;
X		return True;
X	}
X
X	return False;
X
X#undef	SPREAD
X}
X
X/*
X**	gameturn: do a single turn for each Active player
X*/
Xgameturn()
X{
X	register int	c;
X	int				about, wasabout;
X	int				high;
X	char			msgbuf[MESGLEN];
X
X	++turnnum;
X
X	wasabout = -1;
X	for(c = 0;active > 0 && c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Computer:
X		case Active:
X			updstat(c);
X			/*
X			**	The turn function returns -d_pts_max when no points
X			**	are gained on the turn, or d_pts_turn when points
X			**	are gained on the turn.  We have no use for this
X			**	return value (anymore), since turn (now) updates
X			**	p_score, p_squander, and p_onboard.
X			*/
X			(void) turn(c);
X			annscore(c);
X
X			/*
X			**	Tell about players about to win and players no longer
X			**	about to win.
X			*/
X			if((about = winner()) == c) {
X				sprintf(msgbuf, "%d %s is about to win...\r\n",
X					M_INFO, plr[c].p_name);
X				announce(c, msgbuf);
X				if(plr[c].p_stat == Active)
X					(void) simp(c, M_INFO, "You are about to win...");
X				hesitate(1500L, 2000L);
X			} else if(wasabout != about && wasabout >= 0) {
X				sprintf(msgbuf, "%d %s is no longer about to win.\r\n",
X					M_INFO, plr[wasabout].p_name);
X				announce(wasabout, msgbuf);
X				if(plr[wasabout].p_stat == Active)
X					(void) simp(c, M_INFO, "You are no longer about to win.");
X				hesitate(1500L, 2000L);
X			}
X			wasabout = about;
X
X			/*
X			**	Accept new players to Waiting or Watching status.
X			**	Check for asyncrhronous requests.
X			*/
X			if(inprogress == True && wantin(&poll) == True)
X				(void) newplayers(False);
X			async(-1);	/* nobody's turn at the moment */
X			hesitate(1500L, 1800L);
X			break;
X		}
X	}
X
X	/*
X	**	If a game is inprogress, we may wish to adjust the roster.
X	*/
X	if(active > 0 && inprogress == True) {
X		high = highscore(-1, (int *)0);
X
X		/*
X		**	Possibly add a computer player.  (More probable early in game.)
X		*/
X		if(randint(CLOSED) > high)
X			(void) latejoincomp(high);
X
X		/*
X		**	Call fixroster when necessary to collapse out blank slots
X		**	and to add new players to the proper place in the turn order.
X		*/
X		if(adjroster == True)
X			fixroster(high < CLOSED ? True : False);
X	}
X}
X
X/*
X**	anoghelp: long help message at M_ANOG prompt
X*/
Xstatic char	   *anoghelp[] = {
X	"y       Yes:    play in next game (synonyms=[,])",
X	"n       No:     don't play in next game (synonyms=[.eqx])",
X	"m       Myrank: display your ranking info",
X	"a       Above:  display ranking info for player ranked just above you",
X	"b       Below:  display ranking info for player ranked just below you",
X	"p<num>  Player: display ranking info for player <num> in roster",
X	(char *)0	/* end marker */
X};
X
X/*
X**	endgame: cleanup after game finish
X*/
Xendgame()
X{
X	register int	c, highc;
X	boolean			again;
X	int				num, rankq, errs;
X	time_t			now;
X	char		   *date;
X	history		   *ph;
X	char			msgbuf[BUFSIZ];
X
X	inprogress = False;
X	firstgame = False;
X/*	turnnum = 0;		/* don't zero; needed in histpoints */
X	updstat(-1);		/* game over */
X
X	/*
X	**	If there's a winner, update player score histories.
X	*/
X	if((highc = winner()) >= 0) {
X		for(c = 0;c < PLAYERS;++c) {
X			switch(plr[c].p_stat) {
X			case Computer:
X			case Active:
X				(void) histpoints(c);
X				break;
X			}
X		}
X		(void) histwins(highc);
X	}
X
X	/*
X	**	Write history whether or not there was a winner.
X	*/
X	(void) histwrite(HISTFILE);
X
X	/*
X	**	If there's a winner, send each active player a copy
X	**	of their (new) ranking info, for recordkeeping purposes.
X	*/
X	if(highc >= 0) {
X		(void) time(&now);
X		date = ctime(&now) + 4;
X		for(c = 0;c < PLAYERS;++c) {
X			if(plr[c].p_stat == Active) {
X				if((ph = histbyname(plr[c].p_id)) == 0)
X					continue;
X				sprintf(msgbuf, "%d %.16s %ld %.12s %s\r\n", M_RANK,
X					equiv[0], ph->h_lastgame, date, histfmtplr(c, 0, True));
X				(void) message(c, msgbuf);
X			}
X		}
X	}
X
X	/*
X	**	Tell everybody who won.
X	*/
X	if(highc < 0)
X		sprintf(msgbuf, "%d The game has concluded with no winner.\r\n",
X			M_NWIN);
X	else
X		sprintf(msgbuf, "%d Player %d %s has won the game!\r\n",
X			M_OVER, highc+1, plr[highc].p_name);
X	announce(-1, msgbuf);
X	hesitate(2000L, 3000L);
X
X	/*
X	**	If the system's about to shut down, don't waste any more time.
X	**	We delay checking saygoodbye until play again query.
X	*/
X	if(pendshutdown() == True) {
X		sysshutdown();
X		return;
X	}
X
X	/*
X	**	Distribute a new roster that shows those Watching and Waiting.
X	*/
X	if(watching > 0 || waiting > 0)
X		roster(-1, True);
X	
X	/*
X	**	Get rid of all proxies.  Computers quit on occasion.  Kamelion
X	**	quits more often.  Ask humans if they'd like to play.
X	**	If they say no, dump them out of the game.
X	*/
X	for(c = 0;c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Active:
X			if(isproxy(c) == True) {
X				if(annturn(c) < 0)
X					continue;
X				(void) simp(c, M_DOWN, "You've chosen not to play...");
X				hesitate(1500L, 3000L);
X				oldplayer(c);
X				continue;
X			}
X			/* fall */
X		case Watching:
X		case Waiting:
X		case Spider:	/* shouldn't happen */
X			if(annturn(c) < 0)
X				continue;
X			if(saygoodbye == True) {
X				(void) simp(c, M_DOWN,
X					"The cubeserver is shutting down -- bye.");
X				oldplayer(c);
X				continue;
X			}
X			if(pendshutdown() == True) {
X				(void) simp(c, M_DOWN,
X					"System shutdown in progress -- goodbye.");
X				oldplayer(c);
X				continue;
X			}
X			break;
X		case Computer:
X			(void) annturn(c);
X			if(saygoodbye == True || pendshutdown() == True) {
X				hesitate(500L, 1000L);
X				oldplayer(c);
X				continue;
X			}
X			if(iscomp(c) == True) {
X				hesitate(750L, 1250L);
X				continue;
X			}
X			hesitate(1000L, 3000L);
X			if(isproxy(c) == True) {
X				oldplayer(c);
X				continue;
X			}
X			if(iskamelion(c) == True) {
X				hesitate(100L, 1000L);
X				if(randint(3) == 3)
X					oldplayer(c);
X				continue;
X			}
X			if(randint(9) == 9)
X				oldplayer(c);
X			continue;
X		default:
X			continue;
X		}
X
X#define	MAXRANKQ	3				/* limit rank query abuse */
X#define	MAXERRS		(2*MAXRANKQ)	/* limit error recovery abuse */
X		rankq = errs = 0;
X		do {
X			again = False;
X
X			if(errs < MAXERRS) {
X				sprintf(msgbuf, "%d Play %s game? [yn%s?]\r\n", M_ANOG,
X					plr[c].p_stat == Active ? "another" : "in next",
X					rankq < MAXRANKQ ? "mabp" : "");
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					break;
X				}
X			} else {
X				if(simp(c, M_ARGE, "Too many errors, assuming No.") < 0)
X					break;
X				msgbuf[0] = 'n', msgbuf[1] = '\0';
X			}
X
X			switch(msgbuf[0]) {
X/*			case '\0':				/* default -- too dangerous */
X			case 'y': case 'Y':		/* Yes */
X			case ',':				/* yes for numeric keypads */
X				switch(plr[c].p_stat) {
X				case Waiting:
X					plr[c].p_stat = Active, ++active, --waiting;
X					(void) tellstatus(c);
X					break;
X				case Watching:
X					plr[c].p_stat = Active, ++active, --watching;
X					(void) tellstatus(c);
X					break;
X				case Spider:
X					plr[c].p_stat = Active, ++active, --spiders;
X					(void) tellstatus(c);
X					break;
X				}
X				break;
X			case 'n': case 'N':		/* No */
X			case 'q': case 'Q':		/* Quit */
X			case 'e': case 'E':		/* Exit */
X/*			case 'x': case 'X':		/* eXit -- now means autopilot */
X			case '.':				/* hold (numeric keypad) */
X				if(plr[c].p_stat == Active) {
X					sprintf(msgbuf, c == highc
X						?	"%d Good game %s.  Bye...\r\n"
X						:	"%d Better luck next time, %s.\r\n",
X						M_DOWN, plr[c].p_name);
X					(void) message(c, msgbuf);
X				} else
X					(void) simp(c, M_DOWN, "See ya, wallflower...");
X				oldplayer(c);
X				break;
X			case 'm': case 'M':		/* Myrank */
X				if(rankq++ >= MAXRANKQ)
X					goto toomany;
X				if(myrank(c, False) < 0)
X					break;
X				again = True;
X				break;
X			case 'a': case 'A':		/* Aboveme */
X				if(rankq++ >= MAXRANKQ)
X					goto toomany;
X				if(aboveme(c, False) < 0)
X					break;
X				again = True;
X				break;
X			case 'b': case 'B':		/* Belowme */
X				if(rankq++ >= MAXRANKQ)
X					goto toomany;
X				if(belowme(c, False) < 0)
X					break;
X				again = True;
X				break;
X			case 'p': case 'P':		/* Player <num> */
X				if(rankq++ >= MAXRANKQ)
X					goto toomany;
X				if(sscanf(msgbuf, "%*[^0123456789]%d", &num) != 1) {
X					(void) simp(c, M_ARGE, "Usage: player <number>");
X					again = True;
X					break;
X				}
X				if(plrrank(c, num - 1, False) < 0)
X					break;
X				again = True;
X				break;
X			case '?':	/* help */
X				if(multimesg(c, M_HELP, anoghelp) < 0)
X					break;
X				again = True;
X				break;
Xtoomany:
X			default:
X				if(invalid(c) < 0)
X					break;
X				again = True;
X				break;
X			}
X			++errs;
X		} while(again == True && plr[c].p_stat != Inactive);
X	}
X
X	/*
X	**	Sanity checks.
X	*/
X	if(active < 0) {
X		syslog(LOG_DEBUG, "endgame: active=%d (should non-negative)", active);
X		waiting = 0;
X	}
X	if(waiting != 0) {
X		syslog(LOG_DEBUG, "endgame: waiting=%d (should be zero)", waiting);
X		waiting = 0;
X	}
X	if(watching != 0) {
X		syslog(LOG_DEBUG, "endgame: watching=%d (should be zero)", watching);
X		watching = 0;
X	}
X	if(spiders != 0) {
X		syslog(LOG_DEBUG, "endgame: spiders=%d (should be zero)", spiders);
X		spiders = 0;
X	}
X
X	/*
X	**	If there are no Active players left, get rid of all Computer players.
X	**	Reinstall the COMP computer.
X	*/
X	if(active == 0) {
X		for(c = 0;c < PLAYERS;++c)
X			if(plr[c].p_stat == Computer)
X				oldplayer(c);
X		addcomp(COMP);
X	}
X
X	clearroster();
X	turnnum = 0;
X	updstat(-1);		/* idle or pregame again */
X
X#undef	MAXRANKQ
X#undef	MAXERRS
X}
X
X/*
X**	updstat: update status line
X*/
Xupdstat(cup)
Xint		cup;
X{
X	register int	c, n, h, nobs;
X	char			buf[2*PSLEN+NAMELEN];
X
X	currup = cup;	/* update global variable */
X
X	nobs = waiting + watching;
X	if(saygoodbye == True)
X		strcpy(buf, "shutting down");
X	else if(active == 0 && nobs == 0)
X		strcpy(buf, "idle");
X	else if(active == 0 && nobs != 0)
X		sprintf(buf, "idle wt%d", nobs);
X	else {
X		for(h = n = c = 0;c < PLAYERS;++c) {
X			if(plr[c].p_stat == Computer || plr[c].p_stat == Active) {
X				if(plr[c].p_score > h)
X					h = plr[c].p_score;
X				++n;
X			}
X		}
X		if(inprogress == False)
X			sprintf(buf, "waiting pl%d", n);
X		else if(cup < 0)
X			sprintf(buf, "active pl%d hs%d tn%d", n, h, turnnum);
X		else if(nobs == 0)
X			sprintf(buf, "active pl%d hs%d tn%d %s",
X				n, h, turnnum, plr[cup].p_name);
X		else
X			sprintf(buf, "active pl%d wt%d hs%d tn%d %s",
X				n, nobs, h, turnnum, plr[cup].p_name);
X	}
X
X	sprintf(statusline, "cubes %-*s.", PSLEN-7, buf);
X
X	/*
X	**	Check for and answer any pending status queries.
X	*/
X	answer();
X}
X
X/*
X**	srvshutdown: controlled shutdown on SIGTERM
X*/
Xstatic int
Xsrvshutdown()
X{
X	register int	c;
X
X	for(c = 0;c < PLAYERS;++c) {
X		switch(plr[c].p_stat) {
X		case Active:
X		case Waiting:
X		case Watching:
X		case Spider:
X			(void) simp(c, M_DOWN, "The cubeserver is shutting down -- goodbye.");
X			/* fall */
X		case Computer:
X			oldplayer(c);
X			break;
X		}
X	}
X
X#ifdef	UNIXSOCK
X	(void) unlink(UNIXSOCK);
X#endif	UNIXSOCK
X
X	syslog(LOG_INFO, "shutdown");
X
X	saygoodbye = True;
X	updstat(-1);
X
X	exit(0);
X}
X
X/*
X**	delshutdown: delayed shutdown
X**		if game is inprogress, just set saygoodbye
X**		elsewise shutdown immediately
X*/
Xstatic int
Xdelshutdown()
X{
X	if(inprogress == True)
X		saygoodbye = True;
X	else
X		srvshutdown();
X}
END_OF_FILE
if test 44192 -ne `wc -c <'cubeserv1.c'`; then
    echo shar: \"'cubeserv1.c'\" unpacked with wrong size!
fi
# end of 'cubeserv1.c'
fi
if test -f 'cubestat.6' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cubestat.6'\"
else
echo shar: Extracting \"'cubestat.6'\" \(1342 characters\)
sed "s/^X//" >'cubestat.6' <<'END_OF_FILE'
X.TH CUBESTAT 6 "cubes 5.1" GMP "UNIX Gaming Manual"
X.SH NAME
Xcubestat \- check status of the cube server
X.\"
X.\" sccsid: @(#)cubestat.6 5.1 (G.M. Paris) 89/01/22
X.\"
X.\"
X.\"
X.\"	cubes 5.1  Copyright 1988 Gregory M. Paris
X.\"		Permission granted to redistribute on a no charge basis.
X.\"		All other rights are reserved.
X.\"
X.\"
X.SH SYNOPSIS
X.B cubestat
X[
X.BR \- { be }
X] [
X.B \-h host
X]
X.SH OVERVIEW
XThe
X.I cubestat
Xprogram allows users to query the status of the
X.IR cubes (6)
Xserver.
XIf the server to be queried is on a remote system,
Xthe
X.B \-h host
Xoption can be used to specify which system's server to query.
X.PP
XCurrently, the behavior of
X.I cubestat
Xis defined as follows.
XIf there is a game in progress,
X.I cubestat
Xwill output a player roster similar to the one displayed by the
X.I cubes
Xprogram.
XOtherwise,
X.I cubestat
Xwill output an informative message relating to the current status
Xof the server.
XIf the
X.B \-b
Xoption is specified,
X.I cubestat
Xwill loop until a game is about to begin.
XIf the
X.B \-e
Xoption is specified,
X.I cubestat
Xwill loop until any game that's in progress ends.
X.SH "EXIT STATUS"
XAn exit status of zero is returned if a game is in progress.
XA non-zero exit status is returned if no game is in progress
Xor if there was an error of some type.
X.SH AUTHOR
XGreg Paris <gmp@rayssd.ray.com>
X.SH "SEE ALSO"
Xcubes(6)
END_OF_FILE
if test 1342 -ne `wc -c <'cubestat.6'`; then
    echo shar: \"'cubestat.6'\" unpacked with wrong size!
fi
# end of 'cubestat.6'
fi
if test -f 'tactics.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tactics.c'\"
else
echo shar: Extracting \"'tactics.c'\" \(5405 characters\)
sed "s/^X//" >'tactics.c' <<'END_OF_FILE'
X/*	vi:set sw=4 ts=4: */
X#ifndef	lint
Xstatic char	sccsid[] = "@(#)tactics.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	"cubes.h"
X
X/*
X**	nosingle: discard excess singles of a certain variety
X*/
Xnosingles(pd, comb)
Xregister diceset   *pd;
Xcombination			comb;
X{
X	register int	d;
X	register int	singles;
X	register int	aces	= 0;
X	register int	fives	= 0;
X	register int	jokers	= 0;
X
X	if(pd->d_best == Nothing)
X		return;
X
X	for(d = 0;d < NDICE;++d) {
X		switch(pd->d_comb[d]) {
X		case Ace:	++aces; break;
X		case Five:	++fives; break;
X		case Joker:	++jokers; break;
X		default:	break;
X		}
X	}
X
X	switch(comb) {
X	case Ace:	if((singles = aces) == 0) return; break;
X	case Five:	if((singles = fives) == 0) return; break;
X	case Joker:	if((singles = jokers) == 0) return; break;
X	default:	return;
X	}
X
X	if(pd->d_best == comb) {
X		switch(comb) {
X		case Ace:	if(fives > 0) { pd->d_best = Five; break; }		/* fall */
X		case Five:	if(jokers > 0) { pd->d_best = Joker; break; }	/* fall */
X		case Joker:	if(--singles == 0) return; break;
X		}
X	}
X
X	/*
X	**	Alternate zapping LtoR and RtoL.
X	*/
X	if(randint(2) == 1) {
X		for(d = 0;singles > 0 && d < NDICE;++d) {
X			if(pd->d_comb[d] == comb) {
X				pd->d_comb[d] = Nothing;
X				pd->d_stat[d] = Free;
X				++pd->d_rolling;
X				--singles;
X			}
X		}
X	} else {
X		for(d = NDICE-1;singles > 0 && d >= 0;--d) {
X			if(pd->d_comb[d] == comb) {
X				pd->d_comb[d] = Nothing;
X				pd->d_stat[d] = Free;
X				++pd->d_rolling;
X				--singles;
X			}
X		}
X	}
X}
X
X/*
X**	noaces, nofives, nojokers: calls to nosingles routine
X*/
Xnoaces(pd)		diceset *pd; { nosingles(pd, Ace); }
Xnofives(pd)		diceset *pd; { nosingles(pd, Five); }
Xnojokers(pd)	diceset *pd; { nosingles(pd, Joker); }
X
X/*
X**	no3okind: zap 3o'kind in specified denomination
X**		If face is BADFACE, zaps any denomination 3o'kind found.
X**		Assumes only Aces, Fives, and Jokers will fit in
X**		the same hand as a Three_of_a_kind.
X*/
Xno3okind(pd, face)
Xregister diceset   *pd;
Xint					face;
X{
X	register int	d;
X	boolean			present;
X	combination		nextbest;
X
X	if(pd->d_best != Three_of_a_kind)
X		return;
X
X	/*
X	**	Look for the 3o'kind and the highest single.
X	*/
X	present = False;
X	nextbest = Nothing;
X	for(d = 0;d < NDICE;++d) {
X		switch(pd->d_comb[d]) {
X		case Three_of_a_kind:
X			if(face != BADFACE && pd->d_face[d] != face)
X				return;
X			present = True;
X			switch(pd->d_face[d]) {
X			case ACE:
X				nextbest = Ace;
X				break;
X			case FIVE:
X				if(nextbest == Nothing || nextbest == Joker)
X					nextbest = Five;
X				break;
X			case JOKER:
X				if(nextbest == Nothing)
X					nextbest = Joker;
X				break;
X			default:
X				break;
X			}
X			break;
X		case Ace:
X			nextbest = Ace;
X			break;
X		case Five:
X			if(nextbest == Nothing || nextbest == Joker)
X				nextbest = Five;
X			break;
X		case Joker:
X			if(nextbest == Nothing)
X				nextbest = Joker;
X			break;
X		default:
X			break;
X		}
X	}
X
X	/*
X	**	If we didn't find an appropriate 3o'kind or if there
X	**	would be no scoring dice left if we zapped it, we're done.
X	*/
X	if(present == False || nextbest == Nothing)
X		return;
X	
X	/*
X	**	Obliterate 3o'kind.  If it's in a scoring denomination,
X	**	do the appropriate conversion and count on a later call
X	**	to noaces, nofives, or nojokers to finish the job.
X	*/
X	for(d = 0;d < NDICE;++d) {
X		if(pd->d_comb[d] != Three_of_a_kind)
X			continue;
X		switch(pd->d_face[d]) {
X		case ACE:	pd->d_comb[d] = Ace; break;
X		case FIVE:	pd->d_comb[d] = Five; break;
X		case JOKER:	pd->d_comb[d] = Joker; break;
X		default:
X			pd->d_comb[d] = Nothing;
X			pd->d_stat[d] = Free;
X			++pd->d_rolling;
X			break;
X		}
X	}
X	pd->d_best = nextbest;
X}
X
X/*
X**	no3deuce, no3three, no3any: calls to no3okind routine
X*/
Xno3deuce(pd)	diceset *pd; { no3okind(pd, DEUCE); }
Xno3three(pd)	diceset *pd; { no3okind(pd, THREE); }
Xno3any(pd)		diceset *pd; { no3okind(pd, BADFACE); }
X
X/*
X**	nosmall: discard Small_straight, retaining imbeded ace or five
X**		Assumes only singles fit in a hand with a Small_straight.
X*/
Xnosmall(pd)
Xregister diceset   *pd;
X{
X	register int	d;
X	boolean			present;
X	combination		nextbest;
X
X	if(pd->d_best != Small_straight)
X		return;
X
X	present = False;
X	nextbest = Nothing;
X	for(d = 0;d < NDICE;++d) {
X		switch(pd->d_comb[d]) {
X		case Small_straight:
X			present = True;
X			switch(pd->d_face[d]) {
X			case ACE:
X				pd->d_comb[d] = Ace;
X				nextbest = Ace;
X				break;
X			case FIVE:
X				pd->d_comb[d] = Five;
X				if(nextbest == Nothing || nextbest == Joker)
X					nextbest = Five;
X				break;
X			default:
X				pd->d_comb[d] = Nothing;
X				pd->d_stat[d] = Free;
X				++pd->d_rolling;
X				break;
X			}
X			break;
X		case Ace:
X			nextbest = Ace;
X			break;
X		case Five:
X			if(nextbest == Nothing || nextbest == Joker)
X				nextbest = Five;
X			break;
X		case Joker:
X			if(nextbest == Nothing)
X				nextbest = Joker;
X			break;
X		default:
X			break;
X		}
X	}
X	if(present == True)
X		pd->d_best = nextbest;
X}
X
X/*
X**	noacesmall: discard only Small_straights that contain an imbedded ace
X*/
Xnoacesmall(pd)
Xregister diceset   *pd;
X{
X	register int	d;
X
X	if(pd->d_best != Small_straight)
X		return;
X	
X	for(d = 0;d < NDICE;++d) {
X		if(pd->d_comb[d] == Small_straight) {
X			switch(pd->d_face[d]) {
X			case ACE:			/* imbedded ace */
X				nosmall(pd);	/* dump it */
X				return;			/* all done */
X			case FIVE:			/* imbedded five */
X				return;			/* keep it */
X			}
X		}
X	}
X}
END_OF_FILE
if test 5405 -ne `wc -c <'tactics.c'`; then
    echo shar: \"'tactics.c'\" unpacked with wrong size!
fi
# end of 'tactics.c'
fi
echo shar: End of archive 2 \(of 8\).
cp /dev/null ark2isdone
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