|
|
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 t
Length: 19603 (0x4c93)
Types: TextFile
Names: »turn.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
└─⟦this⟧ »EUUGD18/General/Cubes/turn.c«
/* vi:set sw=4 ts=4: */
#ifndef lint
static char sccsid[] = "@(#)turn.c 5.1 (G.M. Paris) 89/01/22";
#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 <syslog.h>
#include <strings.h>
#include <ctype.h>
#include "cubes.h"
extern boolean inprogress;
extern int winscore;
extern int turnnum;
extern player plr[];
extern char *histfmtplr();
extern long histavgplr();
static boolean reallyquit();
extern boolean isproxy();
/*
** turn: do a single player's turn
** Return turn score or 0 if player left game.
** If points were thrown away, return them as negative.
*/
turn(c)
register int c;
{
diceset dice, hold;
int winsqu;
/*
** Announce who's up to the world.
*/
if(annturn(c) < 0)
return 0; /* left game */
/*
** Reset the dice to their initial state.
*/
initdice(&dice);
showdice(c, &dice);
if(plr[c].p_stat == Active && isproxy(c) == False) {
if(pfirst(c) == -2) { /* timeout */
pickproxy(c); /* auto-autopilot */
(void) tellstatus(c); /* inform player */
}
}
if(plr[c].p_stat == Computer || isproxy(c) == True)
if(cfirst(c) < 0)
return 0; /* left game */
if(plr[c].p_stat == Inactive)
return 0; /* quit */
winsqu = 0;
for(;;) {
/*
** Check for asynchronous requests from other players.
** Then roll the dice and evaluate the roll.
*/
async(c);
rolldice(&dice);
evaluate(&dice);
/*
** If Nothing rolled, this turn is over with no points scored.
** We jump to the end of the routine for final processing.
*/
if(dice.d_best == Nothing)
goto gotnothing;
/*
** Display the current state of the dice.
** Score the points we'd have were we to hold now.
*/
showdice(c, &dice);
hold = dice;
scoredice(&hold);
/*
** Trying to properly assess when points have been squandered,
** we adjust the value of winsqu here. This value represents
** points that could have been held with before crossing the
** winscore threshold. If we've already crossed winscore,
** we don't adjust this value.
*/
if(plr[c].p_score + hold.d_pts_turn < winscore)
if(hold.d_pts_turn > winsqu)
winsqu = hold.d_pts_turn;
/*
** If something was scored on the roll, then we ask the player what
** to do about it. We must re-evaluate and redisplay the roll after
** asking since the scoring combinations may have changed. We check
** for Nothing scored as a precaution, but that situation is not
** allowed by cquery or pquery. Adjust point values.
*/
if(plr[c].p_stat == Active && isproxy(c) == False) {
if(pquery(c, &dice) == -2) { /* timeout */
pickproxy(c); /* auto-autopilot */
(void) tellstatus(c); /* inform player */
}
}
if(plr[c].p_stat == Computer || isproxy(c) == True)
cquery(c, &dice);
if(plr[c].p_stat == Inactive)
return 0; /* quit */
evaluate(&dice);
scoredice(&dice);
if(hold.d_pts_max > dice.d_pts_max) /* discarded some points */
dice.d_pts_max = hold.d_pts_max; /* reflect that fact */
if(dice.d_best == Nothing) {
syslog(LOG_WARNING, "turn: %s `%s' tried to roll all scoring dice",
plr[c].p_stat == Computer ? "Computer" : "Player",
plr[c].p_name);
goto gotnothing;
}
/*
** If d_again is False, the player has chosen to hold.
** Leave the loop and do the hold processing at routine end.
*/
if(dice.d_again == False)
break;
/*
** Otherwise, reset and redisplay dice prior to next roll.
*/
pickup(&dice);
showdice(c, &dice);
}
/*
** We get here when dice.d_again is False. Don't sleep or hesistate
** here because it disrupts the rhythm of the game. There's not much
** new information at this point anyway.
*/
strcpy(dice.d_mesg, "held");
showdice(c, &dice);
/*
** If not onboard yet, mark p_onboard True. The [cp]query
** routines should enforce ONBOARD, but note any anomaly.
*/
if(plr[c].p_onboard == False) {
if(dice.d_pts_turn < ONBOARD)
syslog(LOG_WARNING, "turn: %s held %d getting onboard",
plr[c].p_name, dice.d_pts_turn);
plr[c].p_onboard = True;
}
/*
** If we reach winscore with less than OFFBOARD points,
** note the condition. OFFBOARD should be enforced by
** the [cp]query routines.
*/
if(plr[c].p_score < winscore && dice.d_pts_turn < OFFBOARD)
if(plr[c].p_score + dice.d_pts_turn >= winscore)
syslog(LOG_WARNING, "turn: %s held %d getting offboard",
plr[c].p_name, dice.d_pts_turn);
/*
** Turn points are added to the player's score.
** The player may have discarded some points and not
** won them back. If so, add those points to p_squander.
*/
plr[c].p_score += dice.d_pts_turn;
if(dice.d_pts_turn < dice.d_pts_max)
plr[c].p_squander += dice.d_pts_max - dice.d_pts_turn;
return dice.d_pts_turn; /* normal */
gotnothing:
/*
** We get here if we rolled a nothing. Score the dice.
*/
scoredice(&dice);
/*
** If we aren't on board and didn't get ONBOARD points
** during the turn, we don't count any points as squandered,
** since the player had no choice.
*/
if(plr[c].p_onboard == False && dice.d_pts_max < ONBOARD)
dice.d_pts_max = 0;
/*
** If the max points during the turn would have put us over,
** but we didn't get OFFBOARD points, we don't count the
** points as squandered, since the player had little choice.
** This isn't always correct, since the player might have been
** able to hold before crossing winscore. We account for this
** situation by making use of the value of winsqu, which is
** the maximum number of points that could have been held
** before going over the winscore threshold.
*/
if(plr[c].p_score < winscore && dice.d_pts_max < OFFBOARD)
if(plr[c].p_score + dice.d_pts_max >= winscore)
dice.d_pts_max = winsqu;
/*
** Now that d_pts_max has been adjusted, we can show the dice.
*/
showdice(c, &dice);
sleep(2);
/*
** Squandered d_pts_max, no points gained.
*/
plr[c].p_squander += dice.d_pts_max;
return -dice.d_pts_max; /* normal */
}
/*
** keephelp: long help message for M_KEEP prompt
XXX We assume there's only seven lines of help window.
*/
static char *keephelp[] = {
"{list} Roll roll non-scoring and listed dice (synonyms=[rt]{list})",
"k{list} Keep roll all but the dice listed (synonyms=[,s]{list})",
"h Hold don't roll again, save points scored (synonyms=[.nq])",
"m,a,b Ranks display ranking info for you, or above or below you",
"p{num} Player display ranking info for player {num} in roster",
"x Autopilot engage the autopilot (in case of interruptions)",
"e Exit leave the game early (not recommended)",
(char *)0 /* end marker */
};
/*
** pquery: ask the player what to do next
**
** choices are:
** Exit leave the game
** Autopilot (x) engage the autopilot
** Hold/Quit end turn (no more rolling)
** Keep/Save score named dice and roll again
** Roll/Throw throw named dice (scoring those left)
** Myrank display player's ranking info
** Aboveme display ranking of next better player
** Belowme display ranking of next worse player
*/
pquery(c, pd)
int c;
register diceset *pd;
{
register int n, d;
int stay, rankq, errs;
boolean error;
char msgbuf[MESGLEN];
char cmd[32], digits[32];
diceset save, temp;
/*
** If we got passed a nothing roll then we wonder why we are here
** and return with a little comment.
*/
if(pd->d_best == Nothing) {
syslog(LOG_DEBUG, "pquery: got a nothing roll");
if(simp(c, M_INFO, "You rolled a big zip.") < 0)
return -1;
return 0;
}
/*
** Calculate the number of points if we hold now.
*/
temp = *pd;
scoredice(&temp);
stay = temp.d_pts_turn;
#define MAXRANKQ 3 /* limit abuse of rank queries */
#define MAXERRS (2 * MAXRANKQ) /* limit abuse of error recovery */
rankq = errs = 0;
save = *pd; /* save a copy of the dice */
do {
error = False; /* no error yet */
*pd = save; /* get back the saved version */
/*
** Send prompt. Read and parse response.
**
** We've got to prevent a player from causing everybody else
** to time out by giving three minutes worth of erroneous
** responses. If we've reached MAXERRS, give a message
** and do the default action.
*/
if(errs < MAXERRS) {
sprintf(msgbuf, "%d %d saved, %d showing; command? [rkh%sxe?]\r\n",
M_KEEP, pd->d_pts_turn, stay - pd->d_pts_turn,
rankq < MAXRANKQ ? "mabp" : "");
switch(dialogue(c, msgbuf, sizeof msgbuf)) {
case -2: msgbuf[0] = '\0'; break; /* timeout, do default */
case -1: return -1; /* left game */
}
} else {
if(++plr[c].p_timeouts >= MAXTIMO) {
(void) simp(c, M_DOWN, "Too many errors -- goodbye!");
oldplayer(c);
return -1;
}
if(simp(c, M_ARGE, "Too many errors -- doing default.") < 0)
return -1;
msgbuf[0] = '\0';
}
cmd[0] = '\0', digits[0] = '\0';
(void) sscanf(msgbuf, "%[^0123456789]%[0123456789]", cmd, digits);
switch(cmd[0]) {
case 'e': case 'E': /* Exit -- leave game */
if(reallyquit(c) == True)
return -1;
error = True;
break;
case 'x': case 'X': /* enter autopilot (temporary proxy) mode */
return -2; /* pretend timeout, let caller do work */
case 'm': case 'M': /* Myrank -- send player's ranking info */
if(rankq >= MAXRANKQ)
goto toomany;
if(myrank(c, False) < 0)
return -1;
error = True; /* not an error, but we must continue */
break;
case 'a': case 'A': /* Aboveme -- send better player's ranking */
if(rankq >= MAXRANKQ)
goto toomany;
if(aboveme(c, False) < 0)
return -1;
error = True; /* not an error, but we must continue */
break;
case 'b': case 'B': /* Belowme -- send worse player's ranking */
if(rankq >= MAXRANKQ)
goto toomany;
if(belowme(c, False) < 0)
return -1;
error = True; /* not an error, but we must continue */
break;
case 'p': case 'P': /* Player: send info about numbered player */
if(rankq >= MAXRANKQ)
goto toomany;
if(plrrank(c, atoi(digits)-1, False) < 0)
return -1;
error = True; /* maybe not an error, but we must continue */
break;
case 'n': case 'N': /* No -- don't roll again */
case 'h': case 'H': /* Hold -- hold at current score */
case 'q': case 'Q': /* Quit -- quit rolling */
case '.': /* Hold (for numeric keypads) */
pd->d_again = False; /* quit rolling */
keepall(pd); /* keep all scoring dice */
temp = *pd; /* temporary for score evaluation */
scoredice(&temp); /* calculate turn points */
if(plr[c].p_onboard == False) { /* not on board yet */
if(temp.d_pts_turn < ONBOARD) { /* threshold */
sprintf(msgbuf,
"%d You can't hold until you get %d points.\r\n",
M_ARGE, ONBOARD);
if(message(c, msgbuf) < 0)
return -1;
error = True;
break;
}
} else if(plr[c].p_score + temp.d_pts_turn >= winscore
&& plr[c].p_score < winscore) { /* about to go off board */
if(temp.d_pts_turn < OFFBOARD) { /* threshold */
sprintf(msgbuf,
"%d You can't hold until you get %d points.\r\n",
M_ARGE, OFFBOARD);
if(message(c, msgbuf) < 0)
return -1;
error = True;
break;
}
}
return 0; /* all done */
case 'k': case 'K': /* Keep -- keep only named dice */
case 's': case 'S': /* Save -- save only named dice */
case ',': /* Keep (komma, for numeric keypads) */
pd->d_again = True; /* roll again */
if(digits[0] == '\0') { /* no dice named */
keepall(pd); /* default is to keep all */
return 0; /* all done */
}
freeall(pd); /* free all rolled dice */
for(n = 0;digits[n] != '\0';++n) {
if((d = digits[n] - '1') < 0 || d >= NDICE) {
if(simp(c, M_ARGE, "Die number is out of range.") < 0)
return -1;
error = True;
break;
}
if(pd->d_comb[d] == Nothing) {
if(simp(c, M_ARGE, "You can keep only scoring dice.") < 0)
return -1;
error = True;
break;
}
if(pd->d_comb[d] == Previous || pd->d_stat[d] != Free) {
if(simp(c, M_ARGE, "You can keep only rolled dice.") < 0)
return -1;
error = True;
break;
}
pd->d_stat[d] = Taken;
}
break;
case '\0': /* default command */
/* case 'y': case 'Y': /* Yes -- roll again */
case 'r': case 'R': /* Roll -- roll named dice and non-scoring */
case 't': case 'T': /* Throw -- throw named dice and non-scoring */
pd->d_again = True; /* roll again */
if(digits[0] == '\0') { /* no dice named */
keepall(pd); /* default is to keep all */
return 0; /* all done */
}
keepall(pd); /* keep all rolled, scoring dice */
for(n = 0;digits[n] != '\0';++n) {
if((d = digits[n] - '1') < 0 || d >= NDICE) {
if(simp(c, M_ARGE, "Die number is out of range.") < 0)
return -1;
error = True;
break;
}
if(pd->d_comb[d] == Previous || pd->d_stat[d] == Held) {
if(simp(c,M_ARGE,"You can't roll previously held dice.")<0)
return -1;
error = True;
break;
}
if(pd->d_stat[d] != Free) {
pd->d_stat[d] = Free;
++pd->d_rolling;
}
}
break;
case '?': /* help */
if(multimesg(c, M_HELP, keephelp) < 0)
return -1;
error = True;
break;
toomany:
default:
if(invalid(c) > 0)
return -1;
error = True;
break;
}
/*
** If we've arrived here without error, then a subset of the rolled
** dice was chosen. Get a temporary copy of the roll as it stands
** and evaluate it. If nothing scored it's an error.
*/
if(error == False) {
temp = *pd;
evaluate(&temp);
if(temp.d_best == Nothing) {
if(simp(c, M_ARGE, "You must save a scoring combination.") < 0)
return -1;
error = True;
}
}
} while(++errs, error == True);
return 0;
#undef MAXRANKQ
#undef MAXERRS
}
/*
** annturn: announce who's turn it is
*/
annturn(c)
int c;
{
char msgbuf[MESGLEN];
if(plr[c].p_stat != Computer) {
sprintf(msgbuf,
"%d Turn %d: player %d, you are up.\r\n", M_TURN, turnnum, c+1);
if(message(c, msgbuf) < 0)
return -1;
}
sprintf(msgbuf, "%d Turn %d: player %d, %s, is up.\r\n",
M_TURN, turnnum, c+1, plr[c].p_name);
(void) announce(c, msgbuf);
return 0;
}
/*
** statchar: map die status to a single character
*/
statchar(s)
diestat s;
{
switch(s) {
case Free: return 'f';
case Held: return 'h';
case Taken: return 't';
case Rolled: return 'r';
default: return '?';
}
}
/*
** facechar: map die face to a single character
*/
facechar(f)
int f;
{
return (f == BADFACE) ? '*' : (f + '0');
}
/*
** combchar: map die combination to a single character
*/
combchar(c)
combination c;
{
switch(c) {
case Previous: return 'p';
case Nothing: return 'n';
case Joker: return 'j';
case Five: return '5';
case Ace: return '1';
case Three_of_a_kind: return 't';
case Small_straight: return 'l'; /* l for little */
case Four_of_a_kind: return 'f';
case Asm_straight: return 'b'; /* b for built */
case Straight: return 's';
case All_of_a_kind: return 'a';
default: return '?';
}
}
/*
** showdice: present the current status of the dice to all players
*/
showdice(c, pd)
int c;
register diceset *pd;
{
register int d, cc;
char stats[NDICE+1];
char faces[NDICE+1];
char combs[NDICE+1];
char msgbuf[MESGLEN+4*NDICE+32];
for(d = 0;d < NDICE;++d) {
stats[d] = statchar(pd->d_stat[d]);
faces[d] = facechar(pd->d_face[d]);
combs[d] = combchar(pd->d_comb[d]);
}
stats[d] = faces[d] = combs[d] = '\0';
sprintf(msgbuf, "%d %d %s %s %s %d %d %d %d %s\r\n",
M_DICE, c, stats, faces, combs,
pd->d_pts_roll, pd->d_pts_dice, pd->d_pts_turn, pd->d_pts_max,
pd->d_mesg);
for(cc = 0;cc < PLAYERS;++cc) {
switch(plr[cc].p_stat) {
case Active:
case Waiting:
case Watching:
(void) message(cc, msgbuf);
break;
}
}
}
/*
** rfsthelp: long help message for M_RFST prompt
*/
static char *rfsthelp[] = {
"<return> Roll roll the dice (synonyms=[yrt])",
"m Myrank display your ranking info",
"x Autopilot engage the autopilot (in case of interruptions)",
"e Exit leave the game early (not recommended)",
(char *)0 /* end marker */
};
/*
** pfirst: query player for first roll of dice
*/
pfirst(c)
int c;
{
char msgbuf[MESGLEN];
int n, errs;
#define MAXERRS 3 /* limit abuse */
for(errs = 0;;++errs) {
if(errs < MAXERRS) {
sprintf(msgbuf, "%d Ready to roll? [ymxe?]\r\n", M_RFST);
if((n = dialogue(c, msgbuf, sizeof msgbuf)) < 0)
return n;
} else {
if(++plr[c].p_timeouts >= MAXTIMO) {
(void) simp(c, M_ARGE, "Too many errors -- goodbye!");
oldplayer(c);
return -1;
}
if(simp(c, M_ARGE, "Too much tomfoolery -- rolling!") < 0)
return -1;
return 0; /* roll */
}
switch(msgbuf[0]) {
case '\0': /* default */
case 'y': case 'Y': /* Yes */
case 'r': case 'R': /* Roll */
return 0;
case 'e': case 'E': /* Exit */
if(reallyquit(c) == True)
return -1;
break;
case 'x': case 'X': /* autopilot */
return -2; /* pretend timeout */
case 'm': case 'M': /* Myrank */
if(myrank(c, False) < 0)
return -1;
break;
case '?': /* Help */
if(multimesg(c, M_HELP, rfsthelp) < 0)
return -1;
break;
default:
if(invalid(c) < 0)
return -1;
break;
}
}
#undef MAXERRS
}
/*
** reallyquit: does player really want to quit?
*/
static boolean
reallyquit(c)
int c;
{
int half;
boolean wise = True;
char msgbuf[128];
if(inprogress == True && plr[c].p_stat == Active) {
if(plr[c].p_onboard == True)
wise = False;
else if((half = histavgplr(c)) > 0) {
half = (half * winscore) / (2 * WINSCORE);
if(plr[c].p_score > half
|| winscore - highscore(c, (int *)0) > half - plr[c].p_score)
wise = False;
}
}
sprintf(msgbuf, "%d %s [ny]\r\n", M_RFST,
wise == True ? "Do you really want to quit?"
: "It seems unwise to quit now. Are you sure?");
if(dialogue(c, msgbuf, sizeof msgbuf) < 0)
return True;
if(msgbuf[0] == 'y' || msgbuf[0] == 'Y') {
(void) simp(c, M_DOWN, "Quitters never win.");
oldplayer(c);
return True;
}
return False;
}
/*
** myrank: look up player's rank in history then broadcast it
*/
myrank(c, isasync)
boolean isasync;
{
char msgbuf[MESGLEN];
if(isasync == False) {
sprintf(msgbuf, "%d rank: %s\r\n", M_INFO, histfmtplr(c, 0, False));
announce(c, msgbuf);
}
if(plr[c].p_stat == Computer)
return 0;
sprintf(msgbuf, "%d rank: %s\r\n", M_INFO, histfmtplr(c, 0, True));
return message(c, msgbuf);
}
/*
** aboveme: look up player's rank in history
** then broadcast the one directly below it
*/
aboveme(c, isasync)
boolean isasync;
{
char msgbuf[MESGLEN];
sprintf(msgbuf, "%d rank: %s\r\n", M_INFO, histfmtplr(c, -1, True));
if(isasync == True)
return message(c, msgbuf);
announce(-1, msgbuf);
return 0;
}
/*
** belowme: look up player's rank in history
** then broadcast the one directly below it
*/
belowme(c, isasync)
boolean isasync;
{
char msgbuf[MESGLEN];
sprintf(msgbuf, "%d rank: %s\r\n", M_INFO, histfmtplr(c, 1, True));
if(isasync == True)
return message(c, msgbuf);
announce(-1, msgbuf);
return 0;
}
/*
** plrrank: look up player p's rank in history
** then broadcast it, using the player's moniker
*/
/*ARGSUSED*/
plrrank(c, p, isasync)
boolean isasync;
{
char msgbuf[MESGLEN];
sprintf(msgbuf, "%d rank: %s\r\n", M_INFO, histfmtplr(p, 0, False));
if(isasync == True)
return message(c, msgbuf);
announce(-1, msgbuf);
return 0;
}