|
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: 21222 (0x52e6) Types: TextFile Names: »tempers.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Cubes/tempers.c«
/* vi:set sw=4 ts=4: */ #ifndef lint static char sccsid[] = "@(#)tempers.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 <syslog.h> #include "cubes.h" extern unsigned nhist; extern history *hist; extern int winscore; extern int turnnum; extern boolean jokermode; extern player plr[]; extern double ziprisk(); extern int highscore(); extern int closescore(); extern int winner(); extern long histavgplr(); #define P_MAXROLL (P_ACEMULT * P_AOKMULT) /* largest single roll */ /* ** atbreakeven: returns True if player has reached the quit-penalty ** break-even point (roughly half of player's point average) ** or can be expected to reach it before the game ends */ boolean atbreakeven(comp, mscore, hscore) int comp, mscore, hscore; { long half, need; if((half = histavgplr(comp)) == 0) /* no average */ return True; /* passed it, right? */ half = (half * winscore) / (2 * WINSCORE); /* derate and half */ if((need = half - mscore) <= 0) /* passed it? */ return True; if(winscore - hscore > 2 * need) /* probably can reach it? */ return True; return False; /* not yet */ } /* ** robot: just plays the expectations and ignores other players, etc. */ /*ARGSUSED*/ static boolean robot(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { /* ** Don't roll if there's no advantage. */ return (xpct <= stay) ? False : True; } /* ** xx_antsy: gets antsy when it gets behind; uses supplied tracking function ** a little bit more conservative than robot when ahead */ /*ARGSUSED*/ static boolean xx_antsy(comp, stay, xpct, pd, track) int comp, stay, xpct; diceset *pd; int (*track)(); { int mscore; int hscore, hc; double livefac; #define TOOLITTLE (2 * P_ACE) #define GAINFAC 1.1 #define WAYBEHIND ((P_MAXROLL * winscore) / WINSCORE) #define CLOSE WINMARGIN #define AVGTURN 400 /* empirically derived */ #define SANITY 0.40 /* ** Avoid starting out too far in the hole by not getting on board ** way behind -- the leave-the-game algorithm will eventually "save" ** us, but we pay a price. We know what the price is, so if we ** reach the break-even point, we hold. */ mscore = plr[comp].p_score + stay; if(plr[comp].p_onboard == False /* && stay >= ONBOARD */) { hscore = highscore(comp, (int *)0); /* not (*track) */ if(hscore - mscore > WAYBEHIND) { if(atbreakeven(comp, mscore, hscore) == True) return False; /* hold */ return True; /* desperation */ } } /* ** Never stop with too little points. */ if(stay <= TOOLITTLE) return True; /* ** If there's gain to be had, roll again. */ if(xpct > (int)(GAINFAC * stay)) return True; /* ** Find our score and the score we're tracking. If we're ahead ** or close, then don't reroll. Our definition of close depends ** on whether the leader has already taken its turn this round. */ hscore = (*track)(comp, &hc); if(hc < comp) { /* leader has taken turn */ if(hscore - mscore <= CLOSE) return False; } else { /* leader's turn still to come */ if(hscore - mscore <= CLOSE - AVGTURN) return False; } /* ** Calculate the expectation value we can live with. */ livefac = GAINFAC - (double)(hscore - mscore) / winscore; if(livefac < SANITY) livefac = SANITY; if(xpct > (int)(livefac * stay)) return True; return False; #undef TOOLITTLE #undef GAINFAC #undef WAYBEHIND #undef CLOSE #undef AVGTURN #undef SANITY } /* ** antsy: gets antsy when it gets behind; tracks leader */ static boolean antsy(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { return xx_antsy(comp, stay, xpct, pd, highscore); } /* ** tracker: same as antsy but tracks highest close score rather than higest */ static boolean tracker(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { return xx_antsy(comp, stay, xpct, pd, closescore); } /* ** chicken: gets scared when it has lots of points in hand ** doesn't care about expectation or other players ** also chickens out as soon as it thinks it has won ** won't get on board if high score is out of range */ /*ARGSUSED*/ static boolean chicken(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { int mscore, hscore, hc; int atrisk, maxrisk; int range, magic; #define OUTRNG ((P_STRAIGHT * winscore) / WINSCORE) #define AVGTURN 375 /* empirically, a bit low */ /* ** We have the opportunity to get on board if we quit. We hold ** if the high score is not out of range. We roll again if we ** have no average, thus cannot be penalized. We hold if we've ** reached the break-even point. Otherwise, roll again. */ if(plr[comp].p_onboard == False /* && stay >= ONBOARD */) { mscore = plr[comp].p_score + stay; hscore = highscore(comp, &hc); if(hc > comp) hscore += AVGTURN; if(hscore - mscore < OUTRNG) return False; /* close enough, hold! */ if(atbreakeven(comp, mscore, hscore) == True) return False; /* too risky to roll */ return True; /* desperation... */ } /* ** If the leader's turn has already been taken, then if we ** can win, hold. If the leader has yet to take its turn, ** if we can block it from winning, hold. Adjust hscore ** here for leader's predicted turn. */ if((mscore = plr[comp].p_score + stay) >= winscore) { hscore = highscore(comp, &hc); if(hc < comp) { /* leader had turn */ if(mscore - hscore >= WINMARGIN) /* can we win? */ return False; } else { /* leader's turn to come */ hscore += AVGTURN; /* predict future */ if(hscore - mscore < WINMARGIN) /* can we block? */ return False; } } /* ** Our behavior is dependent on our mood. */ switch(plr[comp].p_mood) { case 2: magic = P_FIVE / 2; break; case 3: magic = P_FIVE * 2; break; default:magic = P_FIVE; break; } /* ** We are conservative when close, getting more desperate ** as we get further behind. We get a little wild when ** we're ahead too. */ atrisk = (int)(stay * ziprisk(pd)); if((range = (mscore - hscore + 1) / WINMARGIN) <= 0) maxrisk = ((1 - range) * magic); /* not winning */ else maxrisk = (range * magic) / 3; /* winning */ if(maxrisk > 4 * magic) maxrisk = 4 * magic; if(atrisk > maxrisk) return False; return True; #undef OUTRNG #undef AVGTURN } /* ** gambler: cautious sometimes, not so others ** stops when it gets on board unless the risk is small or far behind ** stops when it has won or could block unless the risk is small ** rolls when the expectation is favorable ** rolls to try to stop a winner until too much at risk ** rolls when there's less than 50% risk and is behind and "lucky" */ /*ARGSUSED*/ static boolean gambler(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { int mscore, hscore, hc; double luck; #define SMALLRISK 0.15 #define EVENODDS 0.50 #define FARBEHIND (winscore / 5) #define AVGTURN (jokermode == True ? 350 : 450) /* ** Calculate our score. Calculate the other highest score ** and if the leader hasn't taken its turn yet, guess at ** what that turn might amount to. */ mscore = plr[comp].p_score + stay; hscore = highscore(comp, &hc); if(hc > comp) /* leader hasn't taken this turn */ hscore += AVGTURN; /* adjust for future earnings */ /* ** If we can get on board, we roll again anyway if we're ** far behind. Otherwise, we hold unless it's really tempting. */ if(plr[comp].p_onboard == False /* && stay >= ONBOARD */) { if(hscore - mscore > FARBEHIND) { if(atbreakeven(comp, mscore, hscore) == False) return True; /* too risky to quit */ } if(xpct > stay && ziprisk(pd) < SMALLRISK) return True; return False; } /* ** If we could win or block, hold unless it's really tempting. */ if((mscore = plr[comp].p_score + stay) >= winscore) { if( (hc < comp && mscore - hscore >= WINMARGIN) /* can win */ || (hc > comp && hscore - mscore < WINMARGIN)) { /* can block */ if(xpct > stay && ziprisk(pd) < SMALLRISK) return True; return False; } } /* ** If the expectation value is better than what we've got, go ahead. */ if(xpct > stay) return True; /* ** If another player is about to win, try to stop it. This isn't strictly ** corrrect code, since we may not be the second-best score, but we can't ** count on others to save us. We give up if we risk losing too much. */ if(hscore >= winscore && hscore - mscore >= WINMARGIN) return (int)(stay * ziprisk(pd)) > P_ASMSTR ? False : True; /* ** If we're behind, roll when the odds are favorable and we feel lucky. ** We feel lucky if we've been lucky -- if we've squandered less than ** half of what we've scored, then we have some luck. If our luck ** multiplied by our desperation is greater than what we'd have if ** we held, then we roll. */ if(hscore > mscore && mscore > 0 && ziprisk(pd) <= EVENODDS) if((luck = 1.0 - (2.0 * plr[comp].p_squander) / mscore) > 0) if(luck * (hscore - mscore) > stay) return True; /* ** Finally, don't hold with too few points. */ return (stay < 2 * P_ACE) ? True : False; #undef SMALLRISK #undef EVENODDS #undef FARBEHIND #undef AVGTURN } /* ** xx_crafty: ** Won't get on board if too far behind. ** Quits when it can win, unless really tempting. ** Tries to roll to beat a winner. ** Otherwise, tries to get and protect a safelead lead ** becoming more daring as it gets ahead or behind, ** but with a limit to its stupidity. */ /*ARGSUSED*/ static boolean xx_crafty(comp, stay, xpct, pd, safelead, maxgainfac, mingainfac, smallrisk) int comp, stay, xpct; diceset *pd; int safelead; double maxgainfac, mingainfac, smallrisk; { int mscore, hscore; int live, winc; double gainfac; #define TOOFAC 4 hscore = highscore(comp, (int *)0); mscore = plr[comp].p_score + stay; /* ** We can get on board here. If we're too far behind, and not ** yet at the quit-penalty break-even point, we keep rolling. ** If too risky, we hold. Otherwise, use the usual decision function. */ if(plr[comp].p_onboard == False /* && stay >= ONBOARD */) { if(hscore - mscore > TOOFAC * safelead) { /* too far behind */ if(atbreakeven(comp, mscore, hscore) == False) return True; /* must roll */ } if(ziprisk(pd) >= smallrisk) /* too risky */ return False; /* must hold */ } /* ** If we could win, hold unless it's really tempting. */ if(mscore >= winscore) if(mscore - hscore >= WINMARGIN && ziprisk(pd) >= smallrisk) return False; /* ** If we have a winner (other than us), try to beat them, but only ** if the expected gainfac is greater than or equal to mingainfac. ** We try to get within WINMARGIN of the player's score, ** but if their turn is after ours, we try to beat. ** Assumes that hscore is winner's score (but what else?). */ if((winc = winner()) != -1 && winc != comp) if(xpct >= stay * mingainfac) if(hscore - mscore >= WINMARGIN || winc > comp) return True; /* ** If there's enough gain to be had, roll again. */ if(xpct > (int)(maxgainfac * stay)) return True; /* ** Calculate the "instantaneous" gain factor. Changed gainfac ** calculation to use p_score when ahead, since this temperament ** used to routinely squander huge rolls. */ if(mscore < hscore) gainfac = (double)(mscore - hscore - safelead) / P_MAXROLL; else gainfac = (double)(plr[comp].p_score - hscore - safelead) / P_MAXROLL; if(gainfac < 0) gainfac = -gainfac; gainfac = maxgainfac - gainfac; if(gainfac < mingainfac) gainfac = mingainfac; /* ** Calculate the expectation value we can live with. */ live = (int)(stay * gainfac); if(xpct > live) return True; return False; #undef TOOFAC } /* ** crafty: call xx_crafty with original set of parameters */ static boolean crafty(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { return xx_crafty(comp, stay, xpct, pd, WINMARGIN, 1.20, 0.85, 0.20); } /* ** shakey: call xx_crafty with revised set of parameters */ static boolean shakey(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { /* ** We have four distinct moods. */ switch(plr[comp].p_mood) { case 2: return xx_crafty(comp,stay,xpct,pd, P_STRAIGHT, 1.30, 0.75, 0.10); case 3: return xx_crafty(comp,stay,xpct,pd, 2*OFFBOARD, 1.30, 0.80, 0.15); case 4: return xx_crafty(comp,stay,xpct,pd, OFFBOARD, 1.20, 0.85, 0.20); default:return xx_crafty(comp,stay,xpct,pd, OFFBOARD, 1.30, 0.75, 0.10); } } /* ** zeno: uses ratio of distance to finish over total distance */ /*ARGSUSED*/ static boolean zeno(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { #define MINWANT (WINMARGIN-1) #define SANITY (1.10) /* fudge factor for endgame sanity */ #define AVGTURN 400 /* empirical */ int hscore, mscore, hc; int lead, want; double zenofac, riskfac, wantfac; mscore = plr[comp].p_score; /* ignore this turn */ hscore = highscore(comp, &hc); /* of best competitor */ zenofac = (double)hscore / winscore; /* distance ratio */ if(hc < comp) { /* leader had turn */ zenofac = (double)hscore /* distance ratio */ / (winscore * SANITY); lead = (mscore-WINMARGIN) - hscore; /* positive when winning */ } else { /* leader's turn to come */ zenofac = (double)(hscore + AVGTURN) /* distance ratio */ / (winscore * SANITY); lead = (mscore-WINMARGIN)-(hscore+AVGTURN); /* positive when winning */ } if(lead > 0) { /* we're winning */ riskfac = 0.90 + 0.35 * zenofac; /* 0.90 - 1.25 */ if(xpct < (int)(riskfac * stay)) /* not willing to risk it */ return False; /* hold */ wantfac = 1.5 - 1.4 * zenofac; /* 1.5 - 0.1 */ if((want = lead * wantfac) < MINWANT) /* magic greed limit */ want = MINWANT; /* ... but keep moving */ if(stay > want) /* we're satisfied */ return False; /* hold */ } else if(lead > -WINMARGIN) { /* it's a dead heat */ riskfac = 1.0 + 0.35 * zenofac; /* 1.00 - 1.35 */ if(xpct < (int)(riskfac * stay)) /* not willing to risk it */ return False; /* hold */ /* no greed test */ } else { /* we're losing */ riskfac = 1.1 - 0.35 * zenofac; /* 1.1 - 0.75 */ if(xpct < (int)(riskfac * stay)) /* not willing to risk it */ return False; /* hold */ wantfac = 0.2 + 0.7 * zenofac; /* 0.2 - 0.9 */ if((want = -lead * wantfac) < MINWANT) /* magic greed limit */ want = MINWANT; /* ... but keep moving */ if(stay > want) /* we're satisfied */ return False; /* hold */ } return True; /* roll again */ #undef MINWANT #undef SANITY #undef AVGTURN } /* ** goalie: strives for four goals: ** winscore, 6/5ths average, 3/5ths average, and pace leader */ /*ARGSUSED*/ static boolean goalie(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { int cuscore, stscore, hiscore, hc; int turnsleft, avscore, totneed, perturn; int toolittle, tooclose; double gainfac, avgturn; #define WAYBEHIND ((P_MAXROLL * winscore) / WINSCORE) #define SANITY 0.50 #define MAXTURN (P_ACEMULT * P_3OKMULT) /* ** Avoid starting out too far in the hole by not getting on board ** way behind -- the leave-the-game algorithm will eventually "save" ** us, but we pay a price. We know what the price is, so if we ** reach the break-even point, we hold. */ cuscore = plr[comp].p_score; stscore = cuscore + stay; if(plr[comp].p_onboard == False /* && stay >= ONBOARD */) { hiscore = highscore(comp, (int *)0); if(hiscore - stscore > WAYBEHIND) { if(atbreakeven(comp, stscore, hiscore) == True) return False; /* hold */ return True; /* desperation */ } } /* ** Some parameters depend on our mood. */ switch(plr[comp].p_mood) { case 2: /* most agressive */ toolittle = 3 * P_ACE; tooclose = P_FIVE; gainfac = 1.025; break; case 3: /* more agressive */ toolittle = 2 * P_ACE + P_FIVE; tooclose = P_ACE; gainfac = 1.050; break; default: /* normal */ toolittle = 2 * P_ACE; tooclose = P_ACE + P_FIVE; gainfac = 1.075; break; } /* ** Never stop with too little points. If there's gain to be had, roll. ** If it would be insane to roll again, don't. */ if(stay <= toolittle) return True; if(xpct > (int)(gainfac * stay)) return True; if(xpct < (int)(SANITY * stay)) return False; /* ** If we're ahead or close to the high score, don't reroll. Definition ** of close depends on whether the leader has already taken its turn this ** round. We calculate the leader's average turn for use here or below. */ if((hiscore = highscore(comp, &hc)) == 0) return False; if(hc < comp) { if(hiscore - stscore <= tooclose) return False; avgturn = (double)hiscore / (turnnum + 1); } else { avgturn = (double)hiscore / turnnum; if(hiscore - stscore <= tooclose - (int)avgturn) return False; } if(avgturn < P_ACE) /* too low to believe */ avgturn = P_ACE; else if(avgturn > P_STRAIGHT) /* too high to believe */ avgturn = P_STRAIGHT; /* ** We're behind, so it's a race to one of our goals. We estimate how many ** turns left in the game based on the leader's average turn. From that, ** we figure out what we need per turn to reach a reasonable goal. ** If we've got that much, we hold, otherwise we roll again. */ if((turnsleft = (winscore - hiscore) / avgturn) <= 0) turnsleft = 1; /* ** Primary goal: strive for winscore. */ if((totneed = winscore - cuscore) <= 0) goto pace; if((perturn = totneed / turnsleft) <= MAXTURN) return (stay >= perturn) ? False: True; /* ** Calculate our historical average score. */ if((avscore = histavgplr(comp)) == 0) /* no record */ avscore = (3 * WINSCORE) / 4; /* fake one */ avscore = (avscore * winscore) / WINSCORE; /* derate */ /* ** Secondary goal: strive for 6/5ths of average. */ if((totneed = (6 * avscore) / 5 - cuscore) <= 0) goto pace; if((perturn = totneed / turnsleft) <= MAXTURN) return (stay >= perturn) ? False: True; /* ** Tertiary goal: strive for 3/5ths of average. */ if((totneed = (3 * avscore) / 5 - cuscore) <= 0) goto pace; if((perturn = totneed / turnsleft) <= MAXTURN) return (stay >= perturn) ? False: True; /* ** Last resort: try to keep ahead of leader. ** Else, keep moving at an arbitrary pace. */ pace: if((totneed = hiscore + WINMARGIN - cuscore) > 0) if((perturn = totneed / turnsleft) <= MAXTURN) return (stay >= perturn) ? False: True; return (stay >= 2 * P_ACE) ? False : True; #undef WAYBEHIND #undef SANITY #undef MAXTURN } /* ** best: use the temperament currently ranked best */ static boolean best(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { static history *phist = 0; static computer *last = 0; register int n; extern temper te_best; extern computer *pkamelion; /* ** If this is our first time through, or if we're not ** pointing to the same computer as last time, look ** for the top ranked computer. On occasion we might ** not re-search for the top when we should, but this ** should happen only rarely. */ if(phist == 0 || last == 0 || phist->h_computer != last) { last = 0; for(phist = hist, n = 0;n < nhist;++n, ++phist) { if(phist->h_computer == 0 || phist->h_computer == pkamelion) continue; if(phist->h_computer->c_temper == &te_best) continue; last = phist->h_computer; break; } } /* ** If we've got a best computer, use its temperament, ** else we default to using the robot temperament. */ if(last != 0) return (last->c_temper->t_func)(comp, stay, xpct, pd); return robot(comp, stay, xpct, pd); } /* ** kamelion: should never be called */ static boolean kamelion(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { syslog(LOG_WARNING, "kamelion temperament called!"); return best(comp, stay, xpct, pd); } /* ** schizo: pick another temperament randomly! ** intended for proxy players only */ static boolean schizo(comp, stay, xpct, pd) int comp, stay, xpct; diceset *pd; { int n; extern int tempers; extern temper *temptbl[]; hesitate(100L, 1000L); plr[comp].p_mood = randint(MAXMOOD); /* new mood every time */ n = randint(tempers - 1) - 1; /* schizo last -- don't pick it */ return (*temptbl[n]->t_func)(comp, stay, xpct, pd); } /* ** temptbl: table of available temperaments */ temper te_robot = { robot, "robot" }; temper te_antsy = { antsy, "antsy" }; temper te_tracker = { tracker, "tracker" }; temper te_chicken = { chicken, "chicken" }; temper te_gambler = { gambler, "gambler" }; temper te_crafty = { crafty, "crafty" }; temper te_shakey = { shakey, "shakey" }; temper te_zeno = { zeno, "zeno" }; temper te_goalie = { goalie, "goalie" }; temper te_best = { best, "best" }; temper te_schizo = { schizo, "schizo" }; temper te_kamelion = { kamelion, "kamelion" }; /* semi-dummy */ temper *temptbl[] = { &te_robot, &te_antsy, &te_tracker, &te_chicken, &te_gambler, &te_crafty, &te_shakey, &te_zeno, &te_goalie, &te_best, &te_schizo /* te_schizo must be last */ }; int tempers = sizeof temptbl / sizeof temptbl[0];