|
|
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);
}