|
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: 18633 (0x48c9) Types: TextFile Names: »cubeserv2.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Cubes/cubeserv2.c«
/* vi:set sw=4 ts=4: */ #ifndef lint static char sccsid[] = "@(#)cubeserv2.c 5.1 (G.M. Paris) 89/04/25"; #endif lint /* ** ** cubes 5.1 Copyright 1989 Gregory M. Paris ** Permission granted to redistribute on a no charge basis. ** All other rights are reserved. ** */ #include <stdio.h> #include <ctype.h> #include <strings.h> #include <syslog.h> #include <sys/types.h> #include <sys/time.h> #include "cubes.h" extern long gamenum; extern char *optarg; extern char *fgets(); extern char *gets(); extern char *malloc(); extern history *histbyname(); extern char *moniker(); extern time_t time(); struct tm *localtime(); extern boolean nameinuse(); extern boolean idinuse(); extern boolean isproxy(); extern winpref workhourstype(); extern unsigned nhist; /* number of entries in history */ extern unsigned neq; /* number of equivalent hosts */ extern char **equiv; /* equivalent hosts */ extern int active; /* number of Active humans */ extern int waiting; /* number of Waiting humans */ extern int watching; /* number of Watching humans */ extern int spiders; /* number of Spiders */ extern int turnnum; /* current turn number */ extern boolean enajokers; /* enable jokers */ extern boolean jokermode; /* dice do not have joker faces */ extern boolean inprogress; /* True when game in progress */ extern boolean clearobs; /* Observers are showing in roster */ extern blitzmode blitzwhen; /* Blitz mode enforced during work */ extern winpref gametype; /* specifies winscore */ extern int winscore; /* points needed to win */ extern player plr[]; /* player/connection list */ /* ** prescreen: does this person really want to play? */ boolean prescreen(c) register int c; { char *reason; /* ** If there's a game in progress, we'll return False if the ** game mode is incompatible with the player's preference. ** If no game is in progress, we check to see if the prevailing ** mode is enough to decide that the person doesn't want to play. */ reason = 0; switch(plr[c].p_pref) { case Fstand: /* only wants to play in a Standard game */ if(inprogress == True && gametype != Standard) reason = "Sorry, the current game is not Standard."; else if(blitzwhen == Enforced && workhourstype() == Blitz) reason = "Sorry, only Blitz games may be played now."; break; case Fblitz: /* only wants to play in a Blitz game */ if(inprogress == True && gametype != Blitz) reason = "Sorry, the current game is not Blitz."; else if(blitzwhen == Noblitz) reason = "Sorry, this server doesn't play Blitz."; break; } /* ** Tell them why we're saying goodbye and hang up. */ if(reason != 0) { (void) simp(c, M_DOWN, reason); oldplayer(c); return False; } return True; } /* ** oldplayer: remove a player from the game */ oldplayer(c) register int c; { /* ** Close the socket if one is open. */ if(plr[c].p_fd != -1) { (void) close(plr[c].p_fd); plr[c].p_fd = -1, plr[c].p_timeouts = 0; } /* ** Change the player's status. */ switch(plr[c].p_stat) { case Inactive: break; case Active: /* ** If game in progress and there are other humans playing and this ** player is on board, then there's a good chance that a Computer ** proxy will be assigned to take over. The proxy keeps the original ** name so that other players are not made aware. The id is also ** left unchanged so that a single player can't flood the game with ** proxies. We do this substitution, more rarely, when a player ** is not yet on board, to penalize players that repeatedly start ** and quit games looking for an initial big advantage. */ if(inprogress == True && active > 1) { if( isproxy(c) == True /* already a temporary proxy */ || (plr[c].p_onboard == True && randint(5) > 2) /* 3/5ths */ || (plr[c].p_onboard == False && randint(5) == 1) /* 1/5th */ ) { pickproxy(c); /* set up proxy personality */ plr[c].p_stat = Computer, --active; /* permanent proxy */ return; /* all done */ } } /* ** If the player wasn't replaced, then we remove. ** If a game is in progress adjust player's history. */ if(inprogress == True) histpoints(c); plr[c].p_stat = Inactive, --active; if(active < 0) { syslog(LOG_DEBUG, "oldplayer: active=%d (shouldn't be negative)", active); active = 0; } if(active == 0) inprogress = False; /* XXX: update Computer points history? */ annfarewell(c); break; case Waiting: /* ** If observers are showing in the roster, and this is not a ** Waiting Computer, then announce farewell. */ plr[c].p_stat = Inactive, --waiting; if(waiting < 0) { syslog(LOG_DEBUG, "oldplayer: waiting=%d (shouldn't be negative)", waiting); waiting = 0; } if(clearobs == True && plr[c].p_computer == 0) annfarewell(c); break; case Watching: plr[c].p_stat = Inactive, --watching; if(watching < 0) { syslog(LOG_DEBUG, "oldplayer: watching=%d (shouldn't be negative)", watching); watching = 0; } if(clearobs == True) /* observers are showing in roster */ annfarewell(c); /* clear this one */ break; case Spider: plr[c].p_stat = Inactive, --spiders; if(spiders < 0) { syslog(LOG_DEBUG, "oldplayer: spiders=%d (shouldn't be negative)", spiders); spiders = 0; } break; case Computer: plr[c].p_stat = Inactive; if(inprogress == True) histpoints(c); annfarewell(c); break; } zeroplayer(c); (void) observers(-1); } /* ** zeroplayer: initialize a player */ zeroplayer(c) int c; { register player *p = &plr[c]; p->p_stat = Inactive, p->p_computer = 0; p->p_score = p->p_squander = 0, p->p_onboard = False; p->p_fd = -1, p->p_timeouts = 0; p->p_pref = Nopref, p->p_mood = 0; p->p_name[0] = p->p_id[0] = '\0'; } /* ** fillroster: add computers to fill in roster */ fillroster(pplr, pcomp) int *pplr, *pcomp; { register int c, nplr, ncomp; /* ** Count all players. We assume that all types of humans will ** play in the next game. We add Computers in Waiting mode ** so we have to count them specially. */ ncomp = nplr = 0; for(c = 0;c < PLAYERS;++c) { switch(plr[c].p_stat) { case Inactive: break; case Waiting: if(plr[c].p_computer != 0) ++ncomp; ++nplr; break; case Computer: ++ncomp, ++nplr; break; default: ++nplr; break; } } /* ** Add enough Computer players to make at least MINCOMP. ** Add enough Computer players to make MINPLR players total. ** Add Computer players randomly with probability that ** decreases with increasing number of players. */ while(ncomp < MINCOMP || nplr < MINPLR || randint(nplr+2) == 1) { for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Inactive) { pickcomputer(c); /* sets status to Computer */ plr[c].p_stat = Waiting, ++waiting; /* no Waiting Computer mode */ plr[c].p_fd = -1, plr[c].p_timeouts = 0; plr[c].p_score = plr[c].p_squander = 0, plr[c].p_onboard = False; ++ncomp, ++nplr; break; } } *pplr = nplr, *pcomp = ncomp; } /* ** workhourstype: calculate default game type for this time of day ** Working hours: 8:00 - 11:59 AM, 12:45 - 4:45 PM Mon - Fri. XXX: Sorry, there's no accounting for holidays. */ winpref workhourstype() { time_t now; struct tm *tm; if(blitzwhen != Workhours && blitzwhen != Enforced) return Standard; (void) time(&now); tm = localtime(&now); if(tm->tm_wday >= 1 && tm->tm_wday <= 5) { /* Mon thru Fri? */ if(tm->tm_hour >= 8 && tm->tm_hour <= 16) { /* 8:00 to 4:59 */ if(tm->tm_hour == 12 && tm->tm_min <= 45) /* Noon to 12:45 */ ; /* lunch */ else if(tm->tm_hour == 16 && tm->tm_min > 45) /* 4:45 to 4:59 */ ; /* end of day */ else return Blitz; /* work hours */ } } return Standard; } /* ** votegametype: try to find a consensus amongst the players */ winpref votegametype() { winpref pref; register int c; pref = Nopref; for(c = 0;c < PLAYERS;++c) { if(plr[c].p_stat == Inactive || plr[c].p_pref == Nopref) continue; switch(pref) { case Standard: if(plr[c].p_pref != Standard && plr[c].p_pref != Fstand) return Nopref; break; case Blitz: if(plr[c].p_pref != Blitz && plr[c].p_pref != Fblitz) return Nopref; break; default: switch(plr[c].p_pref) { case Standard: case Fstand: pref = Standard; break; case Blitz: case Fblitz: pref = Blitz; break; } break; } } return pref; } /* ** pickgametype: select gametype, winscore, and jokermode */ pickgametype() { winpref temp; switch(blitzwhen) { case Noblitz: gametype = Standard; break; case Onrequest: if((gametype = votegametype()) == Nopref) gametype = Standard; break; case Workhours: if((gametype = votegametype()) == Nopref) gametype = workhourstype(); break; case Enforced: if((gametype = workhourstype()) != Blitz) if((temp = votegametype()) != Nopref) gametype = temp; break; } winscore = (gametype == Blitz) ? BLITZSCORE : WINSCORE; if(enajokers == False) jokermode = False; else jokermode = randint(6) == 6 ? True : False; } /* ** getequiv: determine equivalent hosts. */ getequiv() { register FILE *fp; register int n; char buf[512]; /* ** Open the hosts.equiv file and count the entries. ** Allocate memory for them and this host. */ neq = 1; if((fp = fopen("/etc/hosts.equiv", "r")) != 0) { while(fgets(buf, sizeof buf, fp) != 0) ++neq; clearerr(fp); rewind(fp); } if((equiv = (char **)malloc(neq * sizeof *equiv)) == 0) { syslog(LOG_ERR, "malloc: no memory for equivalent hosts"); exit(1); } /* ** This host is first in the list. */ (void) gethostname(buf, sizeof buf); if((equiv[0] = malloc((unsigned)(strlen(buf) + 1))) == 0) { syslog(LOG_ERR, "malloc: no memory for this host"); exit(1); } strcpy(equiv[0], buf); /* ** Add the hosts from hosts.equiv. */ if(fp != 0) { for(n = 1;n < neq;++n) { if(gets(buf) == 0) { neq = n; break; } if((equiv[n] = malloc((unsigned)(strlen(buf) + 1))) == 0) { syslog(LOG_ERR, "malloc: no memory for an equivalent host"); exit(1); } strcpy(equiv[n], buf); } (void) fclose(fp); } } /* ** equivalence: strip "@thishost" or "@equivhost" from an id */ equivalence(id) char *id; { register char *at; register int n; if((at = index(id, '@')) == 0) return; if(neq == 0) getequiv(); for(n = 0;n < neq;++n) { if(strcmp(at+1, equiv[n]) == 0) { *at = '\0'; return; } } } /* ** idinuse: return True if id is in use by a human ** or is in use by more than one Computer player ** side effect: converts user@thishost or user@equivhost to just user */ boolean idinuse(c, id) int c; char *id; { register int cc; int humanuse; int computeruse; equivalence(id); /* strip @equivhost */ humanuse = computeruse = 0; for(cc = 0;cc < PLAYERS;++cc) if(cc != c) { if(strncmp(plr[cc].p_id, id, IDLEN-1) == 0) { switch(plr[cc].p_stat) { case Active: case Waiting: case Watching: case Spider: ++humanuse; break; case Computer: ++computeruse; break; default: break; } } } if(humanuse > 0 || computeruse > 1) return True; return False; } /* ** getplayername: get the player's name and check it */ getplayername(c) int c; { register char *aka; char msgbuf[MESGLEN]; /* ** Prompt for and read name. */ sprintf(msgbuf, "%d Hello, I'm the Cube daemon. Who are you?\r\n", M_HELO); if(dialogue(c, msgbuf, sizeof msgbuf) < 0) { if(plr[c].p_stat != Inactive) { /* in case of timeout */ (void) simp(c, M_DOWN, "Server closing connection."); oldplayer(c); } return -1; } /* ** Skip past any leading spaces or tabs in the response. */ for(aka = msgbuf;*aka == ' ' || *aka == '\t';++aka) ; /* ** If a non-null name was supplied, check that it is not in use. ** If not, save the name in the list and we're done. */ if(*aka != '\0') { strncpy(plr[c].p_name, aka, NAMELEN); plr[c].p_name[NAMELEN-1] = '\0'; if(nameinuse(c, plr[c].p_name) == False) { savename(plr[c].p_name); return 0; } (void) simp(c, M_ARGE, "Your chosen moniker is already in use."); } /* ** We were supplied with a null name, or one already in use. ** Pick one from the monikers list rather than dumping the player. */ while(nameinuse(c, (aka = moniker())) == True) ; strncpy(plr[c].p_name, aka, NAMELEN); plr[c].p_name[NAMELEN-1] = '\0'; return 0; } /* ** getplayerid: get player's unique id (user@host) ** Allow setting of winscore preference. */ getplayerid(c) int c; { register char *s, *id; char msgbuf[MESGLEN]; /* ** Prompt for and read the id. */ sprintf(msgbuf, "%d Please supply a unique id.\r\n", M_RQID); if(dialogue(c, msgbuf, sizeof msgbuf) < 0) { if(plr[c].p_stat != Inactive) { /* in case of timeout */ sprintf(msgbuf, "%d No response taken to mean `No.'\r\n", M_ARGE); goto iderror; } return -1; } /* ** A null id is not valid. */ if(msgbuf[0] == '\0') { sprintf(msgbuf, "%d Sorry, that's not unique.\r\n", M_ARGE); goto iderror; } /* ** Check for winscore preference. Ignore bogus values. */ if(msgbuf[1] != ';') { plr[c].p_pref = Nopref; id = &msgbuf[0]; } else { switch(msgbuf[0]) { case 's': plr[c].p_pref = Standard; break; case 'S': plr[c].p_pref = Fstand; break; case 'b': plr[c].p_pref = Blitz; break; case 'B': plr[c].p_pref = Fblitz; break; default: plr[c].p_pref = Nopref; break; } id = &msgbuf[2]; } /* ** Id's are alphanumerics plus a few other characters. */ for(s = id;*s != '\0';++s) { if(isalnum(*s) || index("@._-+=", *s) != 0) continue; sprintf(msgbuf, "%d `%c' is not allowed in an id.\r\n", M_ARGE, *s); goto iderror; } /* ** Check to see that the id is not already in use. */ strncpy(plr[c].p_id, id, IDLEN); plr[c].p_id[IDLEN-1] = '\0'; if(idinuse(c, plr[c].p_id) == True) { sprintf(msgbuf, "%d You're already playing!\r\n", M_ARGE); goto iderror; } /* ** Success. */ return 0; /* ** Common error handling code. */ iderror: if(message(c, msgbuf) < 0) return -1; (void) simp(c, M_DOWN, "Server closing connection."); oldplayer(c); return -1; } /* ** getplayerintent: get the player's intent ** If a game is inprogress, the question is watch or play? ** Otherwise, the question is wait or play? ** Assumes that player status is already set to Watching. */ getplayerintent(c) int c; { history *ph; char msgbuf[MESGLEN]; /* ** Prompt for and read intent. */ sprintf(msgbuf, "%d Do you want to %s or play? [wp]\r\n", inprogress == True ? M_WORP : M_SORP, inprogress == True ? "watch" : "wait"); if(dialogue(c, msgbuf, sizeof msgbuf) < 0) { if(plr[c].p_stat != Inactive) { /* in case of timeout */ (void) simp(c, M_DOWN, "Server closing connection."); oldplayer(c); } return -1; } /* ** Check it for validity. */ switch(msgbuf[0]) { case '\0': /* default */ case 'p': case 'P': /* Play */ plr[c].p_stat = Waiting, ++waiting, --watching; return 0; case 'w': case 'W': /* Watch or Wait */ if(inprogress == False) { plr[c].p_stat = Spider, ++spiders, --watching; return 0; } if(active <= 0 && waiting <= 0) { if(simp(c, M_ARGE, "Sorry, no game in progress.") < 0) return -1; break; } /* ** We let new players watch, but we don't let ancient ones do so. ** It seems that some people would rather watch than play, but ** that's no fun for the regular players. */ if((ph = histbyname(plr[c].p_id)) != 0) { if(ph->h_lastgame > 0 && gamenum - ph->h_lastgame >= ANCIENT) { syslog(LOG_NOTICE, "getplayerintent: excluded ancient voyeur %s", plr[c].p_id); if(simp(c, M_ARGE, "Sorry, you're too ancient to watch!") < 0) return -1; break; } } return 0; default: if(simp(c, M_ARGE, "Sorry, but that's nonsense.") < 0) return -1; break; } (void) simp(c, M_DOWN, "Server closing connection."); oldplayer(c); return -1; } /* ** fixroster: do any necessary roster adjustment ** If add is true, we can add Waiting players to the front of ** the roster. If false, we will move them to the end. ** Collapses out Inactive slots. */ fixroster(add) boolean add; { int c, cc, low, squ; player tmp[PLAYERS]; /* ** No reason to do this except during a game. */ if(inprogress == False) return; /* ** If there are no Waiting players, or we're not allowed to add them, ** check for "empty" slots in the roster. We define empty here as ** Inactive, Watching, or Waiting, since to Active players, slots held ** by Watching or Waiting players appear empty. */ if(waiting == 0 || add == False) { for(c = 0;c < PLAYERS;++c) /* find first empty slot */ if(plr[c].p_stat != Computer && plr[c].p_stat != Active) break; for(++c;c < PLAYERS;++c) /* look for a non-empty slot */ if(plr[c].p_stat == Computer || plr[c].p_stat == Active) break; if(c >= PLAYERS) /* didn't find any */ return; } /* ** If we are allowed to add Waiting players, put them first, ** otherwise put them last with the Watching players. In either ** case, this algorithm removes empty slots. */ cc = 0; if(add == True) { for(c = 0;c < PLAYERS;++c) /* first pass, Waiting players */ if(plr[c].p_stat == Waiting) tmp[cc++] = plr[c]; } for(c = 0;c < PLAYERS;++c) /* second pass, Active players */ if(plr[c].p_stat == Computer || plr[c].p_stat == Active) tmp[cc++] = plr[c]; for(c = 0;c < PLAYERS;++c) /* third pass, Watching players */ if( plr[c].p_stat == Watching || plr[c].p_stat == Spider || (add == False && plr[c].p_stat == Waiting)) tmp[cc++] = plr[c]; if(cc == 0) { syslog(LOG_DEBUG, "fixroster: no players!"); active = waiting = watching = 0; return; } /* ** Calculate a suitable low score for any new players. */ if(waiting == 0 || add == False || (low = lowscore(&squ) - ONBOARD) < 0) low = 0; /* ** Copy the reordered roster into the real one while at the ** same time adding waiting players (if allowed). */ for(c = 0;c < cc;++c) { plr[c] = tmp[c]; if(add == True && plr[c].p_stat == Waiting) { plr[c].p_score = low; plr[c].p_squander = squ; plr[c].p_onboard = False; if(plr[c].p_computer != 0) plr[c].p_stat = Computer, --waiting; else { plr[c].p_stat = Active, --waiting, ++active; (void) tellstatus(c); /* XXX */ } } } for(;c < PLAYERS;++c) zeroplayer(c); if(waiting != 0) { syslog(LOG_DEBUG, "fixroster: waiting=%d (should be zero)", waiting); waiting = 0; } /* ** Broadcast the new roster to all players. */ roster(-1, False); }