|
|
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? */
}