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