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