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