|
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 r
Length: 16470 (0x4056) Types: TextFile Names: »risk.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Cubes/risk.c«
/* vi:set sw=4 ts=4: */ #ifndef lint static char sccsid[] = "@(#)risk.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 "cubes.h" extern boolean jokermode; extern int winscore; extern player plr[]; extern boolean iscomp(); extern boolean isproxy(); extern boolean iskamelion(); extern long histavgplr(); /* ** risk: values derived empirically using the avg program ** ** Yes, you can calculate all this stuff, but what a pain. ** In any case, this table is by no means a complete listing ** of all possible conditions, probabilities, and expectations. ** You could come up with a much more complicated table, but ** to what end? Some of computer players using this table ** and quite simple strategies are able to best humans regularly. ** ** Notes: ** 1. p_aok values for rolling NDICE are nonsense. ** 2. p_aok values cannot be used with all aces, fives, or jokers ** since they already count as scoring dice. ** 3. p_any and p_all for rolling 3, 2, and 1 can be averaged. ** 4. Values for rolling 4 are special, since avg is able to ** detect asmstr in that case. Use only mix values. */ risk joker_risktbl[NDICE+1] = { /* any all aok mix aces fives jokers */ { 0.000000, 0.000000, 0.000000, 0, 0, 0, 0 }, /* roll 0 */ { 0.384615, 0.384615, 0.153846, 23, 406, 215, 715 }, /* roll 1 */ { 0.621656, 0.147983, 0.023669, 46, 105, 76, 99 }, /* roll 2 */ { 0.781163, 0.071125, 0.003641, 79, 86, 83, 83 }, /* roll 3 */ { 0.885755, 0.086400, 0.000560, 145, 145, 145, 145 }, /* roll 4 */ { 0.948547, 0.096702, 0.000086, 269, 269, 269, 269 }, /* roll 5 */ }; risk std_risktbl[NDICE+1] = { /* any all aok mix aces fives jokers */ { 0.000000, 0.000000, 0.000000, 0, 0, 0, 0 }, /* roll 0 */ { 0.333333, 0.333333, 0.166667, 25, 442, 233, 0 }, /* roll 1 */ { 0.555513, 0.110736, 0.027778, 50, 119, 84, 0 }, /* roll 2 */ { 0.722309, 0.055524, 0.004630, 86, 95, 91, 0 }, /* roll 3 */ { 0.842288, 0.095481, 0.000772, 161, 161, 161, 0 }, /* roll 4 */ { 0.922813, 0.099667, 0.000129, 309, 309, 309, 0 }, /* roll 5 */ }; #define risktbl (jokermode == True ? joker_risktbl : std_risktbl) /* ** cquery: computer rolling choice */ cquery(comp, pd) int comp; diceset *pd; { diceset modi; /* dice after re-rolling strategy */ diceset temp; /* a temporary copy of the dice */ int stay; /* points if we hold */ int need; /* points needed beyond kept so far */ int xpct; /* expectation value of next roll */ boolean canthold; /* true if can't hold */ /* ** Fake human-like hesitation for kamelion and proxies. ** Otherwise, just allow time for screen update. */ if(iskamelion(comp) == True || isproxy(comp) == True) hesitate(1800L, 3600L); else hesitate(1500L, 2000L); /* ** Mark Rolled dice as Taken, updating the value of d_rolling. */ keepall(pd); /* ** Determine any minimum number of points we need above those ** accumulated on previous rolls. For now, we just worry about XXX onboard and offboard conditions, though in the future we XXX could also worry about stopping winners and setting minimums. */ if(plr[comp].p_onboard == False && pd->d_pts_turn < ONBOARD) need = ONBOARD - pd->d_pts_turn; else if(pd->d_pts_turn < OFFBOARD && plr[comp].p_score < winscore) { if(plr[comp].p_score + pd->d_pts_turn >= winscore) need = OFFBOARD - pd->d_pts_turn; } else need = 0; /* ** We get a copy of the dice and then use the computer's re-rolling ** strategy on it. We can only do this once, because some players ** are using a fickle strategy that's different every time. Set ** this copy of the dice up to roll again. */ modi = *pd; (*plr[comp].p_computer->c_strategy->s_func)(&modi, need); modi.d_again = True; /* ** Evaluate the number of points we'd have if we held. ** Set this copy of the dice up to hold. */ pd->d_again = False; temp = *pd; scoredice(&temp); stay = temp.d_pts_turn; /* ** Take care of conditions where we can't hold. */ if(plr[comp].p_onboard == False && stay < ONBOARD) canthold = True; else if(stay < OFFBOARD && plr[comp].p_score < winscore) { if(plr[comp].p_score + stay >= winscore) canthold = True; } else canthold = False; /* ** If we can't hold, use the diceset with the best expectation value. ** Maybe this decision should be made by the temperament routine, ** but they aren't set up with that assumption. Still, this should ** be an improvement over the previous situation, where the modified ** diceset was always selected. */ if(canthold == True) { temp = *pd; stay = expect(&temp); /* stay is expectation of original */ temp = modi; xpct = expect(&temp); /* xpct is expectation of modified */ if(xpct >= stay) /* prefer modified version */ *pd = modi; /* use modified diceset */ pd->d_again = True; /* in case it's the orignal */ return; } /* ** Fake some indecision sometimes. More rarely, call snoop to ** pretend to be a human interested in rankings. The randint ** calls below work out to about one snoop per game for kamelion ** and proxies vs. about one snoop per two games for others ** (before checking value of stay, anyway). */ if(iscomp(comp) == False && randint(12) > 9) { int thresh; switch(modi.d_rolling) { case 1: thresh = P_SMSTR; break; case 2: thresh = P_ASMSTR; break; case 3: thresh = P_3OKMULT * P_ACEMULT; break; case 4: thresh = P_4OKMULT * P_ACEMULT; break; default:thresh = P_AOKMULT * P_ACEMULT; break; } if(stay >= thresh) { hesitate(500L, 2000L); switch(randint(56)) { case 21: if(iskamelion(comp) == True) snoop(comp); break; case 14: if(isproxy(comp) == True) snoop(comp); break; case 7: snoop(comp); break; } } } /* ** Calculate the expectation value of rolling again. */ temp = modi; xpct = expect(&temp); /* ** Player's temperament will chose which diceset to use. ** If c_temper->t_func returns True, it means roll again. */ if((*plr[comp].p_computer->c_temper->t_func)(comp,stay,xpct,&modi) == True) *pd = modi; /* modi is the roll-again diceset */ } /* ** sameface: all dice showing same face test ** if scoring is False, considers only held dice ** returns True if considered dice are all comb's ** returns False in the case of no comb's or mixed faces */ boolean sameface(pd, comb, scoring) diceset *pd; combination comb; boolean scoring; { register int d; register boolean all; int face; switch(comb) { case Joker: if(jokermode == False) return False; face = JOKER; break; case Five: face = FIVE; break; case Ace: face = ACE; break; default: face = BADFACE; break; /* non-scoring face */ } if(scoring == False) { all = False; for(d = 0;d < NDICE;++d) { if(pd->d_comb[d] == Previous) { if(face == BADFACE) { switch(pd->d_face[d]) { case ACE: case FIVE: case JOKER: return False; default: face = pd->d_face[d]; break; } } else if(pd->d_face[d] != face) return False; all = True; } } return all; } all = False; for(d = 0;d < NDICE;++d) { switch(pd->d_comb[d]) { case Previous: case Three_of_a_kind: case Four_of_a_kind: if(face == BADFACE) { /* comb == Nothing too */ switch(pd->d_face[d]) { case ACE: case FIVE: case JOKER: return False; default: face = pd->d_face[d]; break; } } else if(pd->d_face[d] != face) return False; break; case All_of_a_kind: if(comb == Nothing) { switch(pd->d_face[d]) { case ACE: case FIVE: case JOKER: return False; default: return True; } } return (pd->d_face[d] == face) ? True : False; case Ace: if(comb != Ace) return False; break; case Five: if(comb != Five) return False; break; case Joker: if(comb != Joker) return False; break; case Nothing: continue; default: return False; /* scored but mixed faces */ } all = True; } return all; } /* ** expect: return expectation value of rolling dice as they stand */ expect(pd) register diceset *pd; { int d; boolean allaces, allfives, alljokers, allofakind; risk *pr; diceset temp; double xpct; double any, all; /* ** Get a copy of the dice and forget the location of the original. */ temp = *pd; pd = &temp; /* ** Four special conditions: all Aces, all Fives, all Jokers, ** or all of a kind (not in Aces, Fives, or Jokers). */ allaces = allfives = alljokers = allofakind = False; if(pd->d_rolling != 0 && pd->d_rolling != NDICE) if((allaces = sameface(pd, Ace, True)) == False) if((allfives = sameface(pd, Five, True)) == False) if(jokermode == False || (alljokers = sameface(pd, Joker, True)) == False) allofakind = sameface(pd, Nothing, True); /* ** Point to the appropriate risk values. First get the expected ** value of the next roll, accounting for whether we have all Aces, ** all Fives, all Jokers or a mix. */ pr = &risktbl[pd->d_rolling == 0 ? NDICE : pd->d_rolling]; any = pr->r_p_any, all = pr->r_p_all; if(allaces == True) xpct = pr->r_e_aces; else if(allfives == True) xpct = pr->r_e_fives; else if(alljokers == True) xpct = pr->r_e_jokers; else xpct = pr->r_e_mix; scoredice(pd); /* sets d_pts values */ /* ** Add additional expectation due to getting All_of_a_kind ** in what normally would be considered non-scoring dice. */ if(allofakind == True) { any += pr->r_p_aok, all += pr->r_p_aok; for(d = 0;d < NDICE;++d) { if(pd->d_comb[d] != Nothing) { xpct += pr->r_p_aok * (P_AOKMULT * pd->d_face[d] - pd->d_pts_dice); break; } } } #ifdef ASMSTR /* ** Account for Asm_straight. There are three basic ways to ** complete an assembled straight. First is if an ace or five ** is held. Second is if an ace and five are held. Last is ** if a small straight is held, of which there are three varieties. */ switch(pd->d_rolling) { case ASMSTR-1: if(allaces == True) { /* ** To assemble here, what would be a Small_straight ** would need to be rolled. That probability is ** already accounted for, but the expectation value ** of this combination is higher. */ xpct += (jokermode == True) ? 3 : 4; } else if(allfives == True) { /* ** To assemble here, a Small_straight or a 2346 is needed. ** The probability of the first is accounted for, the ** second is not. Additional expectation from both. */ if(jokermode == True) any += 0.013445, all += 0.013445, xpct += 13; else any += 0.018519, all += 0.018519, xpct += 18; } break; case ASMSTR-2: if(allaces == False && allfives == False) { /* ** To assemble here, we need 234 which is unaccounted for ** and has additional expectation. */ if(jokermode == True) any += 0.021848, all += 0.021848, xpct += 13; else any += 0.027778, all += 0.027778, xpct += 16; } break; case 1: switch(heldsmall(pd)) { case ACE: /* low die is an ACE */ /* ** To assemble here, we need a FIVE. The probability ** is accounted for, but the expectation is not. */ xpct += (jokermode == True) ? 46 : 50; break; case 2: /* low die is a deuce */ /* ** To assemble here, we need an ACE or a 6. The ** first is accounted for, the second is not. Both ** carry additional expectation. */ if(jokermode == True) any += 0.153846, all += 0.153846, xpct += 92; else any += 0.166667, all += 0.166667, xpct += 100; break; case 3: /* low die is a three */ /* ** To assemble here, we need a 2. The probability ** and additional expectation are unaccounted for. */ if(jokermode == True) any += 0.153846, all += 0.153846, xpct += 53; else any += 0.166667, all += 0.166667, xpct += 58; break; default: /* not holding small straight */ break; } break; } #endif ASMSTR /* ** Add the value of keeping what we've got. */ xpct += any * pd->d_pts_turn; /* ** Add the value of getting to roll all dice again. ** This is probably the most faulty estimate due to the way ** we make use of r_e_mix and p_nothing * xpct. */ pr = &risktbl[NDICE]; xpct = xpct + all * (pr->r_e_mix - (1.0 - pr->r_p_any) * xpct); return (int)xpct; } /* ** ziprisk: return the probability of rolling nothing ** assumes that rolling zero really means rolling all */ double ziprisk(pd) register diceset *pd; { double any; /* ** Get the probability of anything assuming mixed dice held. */ any = risktbl[pd->d_rolling == 0 ? NDICE : pd->d_rolling].r_p_any; /* ** Check for all held/scoring dice having same (usu. non-scoring) face. ** We count both Previous and scoring dice. */ if(pd->d_rolling != 0 && pd->d_rolling != NDICE) if(sameface(pd, Nothing, True) == True) any += risktbl[pd->d_rolling].r_p_aok; return 1.0 - any; } /* ** mayendearly: return True if the game may end prematurely ** (no human player is onboard == True) */ boolean mayendearly() { register int c; for(c = 0;c < PLAYERS;++c) if(plr[c].p_stat == Active && plr[c].p_onboard == True) return False; return True; } /* ** cfirst: hesitation before first roll */ cfirst(comp) int comp; { int high, half; int winneed, halfneed; #define QUIT (winscore / 4) /* ** Fake human-like hesitation for kamelion and proxies. ** Otherwise, just allow time for screen update. */ if(iskamelion(comp) == True || isproxy(comp) == True) hesitate(1800L, 3600L); else hesitate(1500L, 2000L); /* ** If this player is not yet on board, and ** if this is not the COMP computer and also not a proxy player, and ** if this player is behind by more than QUIT points, and ** if the game is in little danger of ending prematurely, and ** if this player's score is less than half of its average score, and ** if this player needs more to reach half than the leader needs to win, ** then there is a probability of quitting that is initially 50% */ if(plr[comp].p_onboard == False) { if(iscomp(comp) == False && isproxy(comp) == False) { if((high = highscore(comp, (int *)0)) - plr[comp].p_score > QUIT) { hesitate(250L, 2000L); /* fake consideration of quit */ if(mayendearly() == False) { half = (winscore * histavgplr(comp)) / (2 * WINSCORE); if((halfneed = half - plr[comp].p_score) > 0) { if((winneed = winscore - high) < halfneed) { if(randint(2 * winneed) < halfneed) { oldplayer(comp); return -1; } } } } } } } /* ** If this is a proxy computer, sometimes do more hesitating. */ if(isproxy(comp) == True) hesitate(100L, 1500L); return 0; #undef QUIT } /* ** snoop: make it look like a computer player is interested in rankings ** It might be better to individualize this by putting calls ** to the snooping routines in the temperament routines. For now, ** we'll just make some decisions based on computer temperament. */ snoop(comp) { int r; temper *ctemp; extern temper te_robot, te_antsy, te_tracker, te_goalie; extern int highscore(), closescore(); ctemp = plr[comp].p_computer->c_temper; if(ctemp == &te_robot) return; if(ctemp == &te_antsy) r = peekhigh(comp, highscore); else if(ctemp == &te_tracker) r = peekhigh(comp, closescore); else if(ctemp == &te_goalie) { r = randint(3) == 1 ? myrank(comp, False) : peekhigh(comp, highscore); } else switch(randint(5)) { case 1: r = aboveme(comp, False); break; case 2: r = belowme(comp, False); break; case 3: r = peekhigh(comp, highscore); break; case 4: r = peekhuman(comp); break; default:r = myrank(comp, False); break; } /* ** Pretend to consider the ranking info. */ if(r >= 0) hesitate(1000L, 3000L); } /* ** peekhigh: look at the high scorer's history ** If there's a tie, don't bother. */ peekhigh(comp, track) int comp; int (*track)(); { int highc; (void) (*track)(comp, &highc); if(highc < 0) return -1; return plrrank(comp, highc, False); } /* ** peekhuman: look at a random human's history */ peekhuman(comp) { register int c, pick; extern int active, waiting, watching; pick = randint(active + waiting + watching); for(c = 0;c < PLAYERS;++c) { switch(plr[c].p_stat) { case Active: case Waiting: case Watching: if(--pick == 0) return plrrank(comp, c, False); break; } } return -1; /* huh? */ }