|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T c
Length: 44192 (0xaca0) Types: TextFile Names: »cubeserv1.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Cubes/cubeserv1.c«
/* 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(); }