adopt github pull request #286 - rndmonst()
Eliminate the cache that was supporting rndmonst() and pick a random monster in a single pass through mons[] via "weighted reservoir sampling", a term I'm not familiar with. It had a couple of bugs: if the first monster examined happened to be given a weighting of 0, rn2() would divide by 0. I didn't try to figure out how to trigger that. But the second one was easy to trigger: if all eligible monsters were extinct or genocided, it would issue a warning even though the situation isn't impossible. Aside from fixing those, the rest is mostly as-is. I included a bit of formatting in decl.c, moved some declarations to not require C99, and changed a couple of macros to not hide and duplicate a call to level_difficulty(). Fixes #286
This commit is contained in:
@@ -464,8 +464,6 @@ const struct instance_globals g_init = {
|
||||
UNDEFINED_VALUES,
|
||||
|
||||
/* makemon.c */
|
||||
{ -1, /* choice_count */
|
||||
{ 0 } }, /* mchoices */
|
||||
|
||||
/* mhitm.c */
|
||||
UNDEFINED_VALUE, /* vis */
|
||||
|
||||
1
src/do.c
1
src/do.c
@@ -1453,7 +1453,6 @@ boolean at_stairs, falling, portal;
|
||||
|| dunlev(&u.uz) < dunlev_reached(&u.uz))
|
||||
dunlev_reached(&u.uz) = dunlev(&u.uz);
|
||||
}
|
||||
reset_rndmonst(NON_PM); /* u.uz change affects monster generation */
|
||||
|
||||
/* set default level change destination areas */
|
||||
/* the special level code may override these */
|
||||
|
||||
@@ -215,7 +215,6 @@ const char *drainer; /* cause of death, if drain should be fatal */
|
||||
pline("%s level %d.", Goodbye(), u.ulevel--);
|
||||
/* remove intrinsic abilities */
|
||||
adjabil(u.ulevel + 1, u.ulevel);
|
||||
reset_rndmonst(NON_PM); /* new monster selection */
|
||||
} else {
|
||||
if (drainer) {
|
||||
g.killer.format = KILLED_BY;
|
||||
@@ -315,7 +314,6 @@ boolean incr; /* true iff via incremental experience growth */
|
||||
if (u.ulevelmax < u.ulevel)
|
||||
u.ulevelmax = u.ulevel;
|
||||
adjabil(u.ulevel - 1, u.ulevel); /* give new intrinsics */
|
||||
reset_rndmonst(NON_PM); /* new monster selection */
|
||||
}
|
||||
g.context.botl = TRUE;
|
||||
}
|
||||
|
||||
138
src/makemon.c
138
src/makemon.c
@@ -27,8 +27,6 @@ static boolean FDECL(makemon_rnd_goodpos, (struct monst *,
|
||||
|
||||
#define m_initsgrp(mtmp, x, y, mmf) m_initgrp(mtmp, x, y, 3, mmf)
|
||||
#define m_initlgrp(mtmp, x, y, mmf) m_initgrp(mtmp, x, y, 10, mmf)
|
||||
#define toostrong(monindx, lev) (mons[monindx].difficulty > lev)
|
||||
#define tooweak(monindx, lev) (mons[monindx].difficulty < lev)
|
||||
|
||||
boolean
|
||||
is_home_elemental(ptr)
|
||||
@@ -945,7 +943,6 @@ boolean ghostly;
|
||||
makeplural(mons[mndx].mname));
|
||||
}
|
||||
g.mvitals[mndx].mvflags |= G_EXTINCT;
|
||||
reset_rndmonst(mndx);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1517,98 +1514,75 @@ struct permonst *
|
||||
rndmonst()
|
||||
{
|
||||
register struct permonst *ptr;
|
||||
register int mndx, ct;
|
||||
register int mndx;
|
||||
int weight, totalweight, selected_mndx, zlevel, minmlev, maxmlev;
|
||||
boolean elemlevel, upper;
|
||||
|
||||
if (u.uz.dnum == quest_dnum && rn2(7) && (ptr = qt_montype()) != 0)
|
||||
return ptr;
|
||||
|
||||
if (g.rndmonst_state.choice_count < 0) { /* need to recalculate */
|
||||
int zlevel, minmlev, maxmlev;
|
||||
boolean elemlevel;
|
||||
boolean upper;
|
||||
zlevel = level_difficulty();
|
||||
minmlev = monmin_difficulty(zlevel);
|
||||
maxmlev = monmax_difficulty(zlevel);
|
||||
upper = Is_rogue_level(&u.uz); /* prefer uppercase only on rogue level */
|
||||
elemlevel = In_endgame(&u.uz) && !Is_astralevel(&u.uz); /* elmntl plane */
|
||||
|
||||
g.rndmonst_state.choice_count = 0;
|
||||
/* look for first common monster */
|
||||
for (mndx = LOW_PM; mndx < SPECIAL_PM; mndx++) {
|
||||
if (!uncommon(mndx))
|
||||
break;
|
||||
g.rndmonst_state.mchoices[mndx] = 0;
|
||||
}
|
||||
if (mndx == SPECIAL_PM) {
|
||||
/* evidently they've all been exterminated */
|
||||
debugpline0("rndmonst: no common mons!");
|
||||
return (struct permonst *) 0;
|
||||
} /* else `mndx' now ready for use below */
|
||||
zlevel = level_difficulty();
|
||||
/* determine the level of the weakest monster to make. */
|
||||
minmlev = zlevel / 6;
|
||||
/* determine the level of the strongest monster to make. */
|
||||
maxmlev = (zlevel + u.ulevel) / 2;
|
||||
upper = Is_rogue_level(&u.uz);
|
||||
elemlevel = In_endgame(&u.uz) && !Is_astralevel(&u.uz);
|
||||
/* amount processed so far */
|
||||
totalweight = 0;
|
||||
selected_mndx = NON_PM;
|
||||
|
||||
for (mndx = LOW_PM; mndx < SPECIAL_PM; ++mndx) {
|
||||
ptr = &mons[mndx];
|
||||
|
||||
if (montooweak(mndx, minmlev) || montoostrong(mndx, maxmlev))
|
||||
continue;
|
||||
if (upper && !isupper((uchar) def_monsyms[(int) ptr->mlet].sym))
|
||||
continue;
|
||||
if (elemlevel && wrong_elem_type(ptr))
|
||||
continue;
|
||||
if (uncommon(mndx))
|
||||
continue;
|
||||
if (Inhell && (ptr->geno & G_NOHELL))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Find out how many monsters exist in the range we have selected.
|
||||
* Weighted reservoir sampling: select ptr with a
|
||||
* (ptr weight)/(total of all weights so far including ptr's)
|
||||
* probability. For example, if the previous total is 10, and
|
||||
* this is now looking at acid blobs with a frequency of 2, it
|
||||
* has a 2/12 chance of abandoning ptr's previous value in favor
|
||||
* of acid blobs, and 10/12 chance of keeping whatever it was.
|
||||
*
|
||||
* This does not bias results towards either the earlier or the
|
||||
* later monsters: the smaller pool and better odds from being
|
||||
* earlier are exactly canceled out by having more monsters to
|
||||
* potentially steal its spot.
|
||||
*/
|
||||
for ( ; mndx < SPECIAL_PM; mndx++) { /* (`mndx' initialized above) */
|
||||
ptr = &mons[mndx];
|
||||
g.rndmonst_state.mchoices[mndx] = 0;
|
||||
if (tooweak(mndx, minmlev) || toostrong(mndx, maxmlev))
|
||||
continue;
|
||||
if (upper && !isupper((uchar) def_monsyms[(int) ptr->mlet].sym))
|
||||
continue;
|
||||
if (elemlevel && wrong_elem_type(ptr))
|
||||
continue;
|
||||
if (uncommon(mndx))
|
||||
continue;
|
||||
if (Inhell && (ptr->geno & G_NOHELL))
|
||||
continue;
|
||||
ct = (int) (ptr->geno & G_FREQ) + align_shift(ptr);
|
||||
if (ct < 0 || ct > 127)
|
||||
panic("rndmonst: bad count [#%d: %d]", mndx, ct);
|
||||
g.rndmonst_state.choice_count += ct;
|
||||
g.rndmonst_state.mchoices[mndx] = (char) ct;
|
||||
weight = (int) (ptr->geno & G_FREQ) + align_shift(ptr);
|
||||
if (weight < 0 || weight > 127) {
|
||||
impossible("bad weight in rndmonst for mndx %d", mndx);
|
||||
weight = 0;
|
||||
}
|
||||
/* was unconditional, but if weight==0, rn2() < 0 will always fail;
|
||||
also need to avoid rn2(0) if totalweight is still 0 so far */
|
||||
if (weight > 0) {
|
||||
totalweight += weight; /* totalweight now guaranteed to be > 0 */
|
||||
if (rn2(totalweight) < weight)
|
||||
selected_mndx = mndx;
|
||||
}
|
||||
/*
|
||||
* Possible modification: if choice_count is "too low",
|
||||
* expand minmlev..maxmlev range and try again.
|
||||
*/
|
||||
} /* choice_count+mchoices[] recalc */
|
||||
|
||||
if (g.rndmonst_state.choice_count <= 0) {
|
||||
/* maybe no common mons left, or all are too weak or too strong */
|
||||
debugpline1("rndmonst: choice_count=%d", g.rndmonst_state.choice_count);
|
||||
return (struct permonst *) 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now, select a monster at random.
|
||||
* Possible modification: if totalweight is "too low" or nothing
|
||||
* viable was picked, expand minmlev..maxmlev range and try again.
|
||||
*/
|
||||
ct = rnd(g.rndmonst_state.choice_count);
|
||||
for (mndx = LOW_PM; mndx < SPECIAL_PM; mndx++)
|
||||
if ((ct -= (int) g.rndmonst_state.mchoices[mndx]) <= 0)
|
||||
break;
|
||||
|
||||
if (mndx == SPECIAL_PM || uncommon(mndx)) { /* shouldn't happen */
|
||||
impossible("rndmonst: bad `mndx' [#%d]", mndx);
|
||||
if (selected_mndx == NON_PM || uncommon(selected_mndx)) {
|
||||
/* maybe no common monsters left, or all are too weak or too strong */
|
||||
if (selected_mndx != NON_PM)
|
||||
debugpline1("rndmonst returning Null [uncommon 'mndx'=#%d]",
|
||||
selected_mndx);
|
||||
return (struct permonst *) 0;
|
||||
}
|
||||
return &mons[mndx];
|
||||
}
|
||||
|
||||
/* called when you change level (experience or dungeon depth) or when
|
||||
monster species can no longer be created (genocide or extinction) */
|
||||
void
|
||||
reset_rndmonst(mndx)
|
||||
int mndx; /* particular species that can no longer be created */
|
||||
{
|
||||
/* cached selection info is out of date */
|
||||
if (mndx == NON_PM) {
|
||||
g.rndmonst_state.choice_count = -1; /* full recalc needed */
|
||||
} else if (mndx < SPECIAL_PM) {
|
||||
g.rndmonst_state.choice_count -= g.rndmonst_state.mchoices[mndx];
|
||||
g.rndmonst_state.mchoices[mndx] = 0;
|
||||
} /* note: safe to ignore extinction of unique monsters */
|
||||
return &mons[selected_mndx];
|
||||
}
|
||||
|
||||
/* decide whether it's ok to generate a candidate monster by mkclass() */
|
||||
@@ -1687,7 +1661,7 @@ aligntyp atyp;
|
||||
(or lower) difficulty as preceding candidate (non-zero
|
||||
'num' implies last > first so mons[last-1] is safe);
|
||||
sometimes accept it even if high difficulty */
|
||||
if (num && toostrong(last, maxmlev)
|
||||
if (num && montoostrong(last, maxmlev)
|
||||
&& mons[last].difficulty > mons[last - 1].difficulty
|
||||
&& rn2(2))
|
||||
break;
|
||||
|
||||
@@ -299,7 +299,6 @@ newman()
|
||||
change_sex();
|
||||
|
||||
adjabil(oldlvl, (int) u.ulevel);
|
||||
reset_rndmonst(NON_PM); /* new monster generation criteria */
|
||||
|
||||
/* random experience points for the new experience level */
|
||||
u.uexp = rndexp(FALSE);
|
||||
|
||||
@@ -2107,7 +2107,6 @@ do_class_genocide()
|
||||
* have G_GENOD or !G_GENO.
|
||||
*/
|
||||
g.mvitals[i].mvflags |= (G_GENOD | G_NOCORPSE);
|
||||
reset_rndmonst(i);
|
||||
kill_genocided_monsters();
|
||||
update_inventory(); /* eggs & tins */
|
||||
pline("Wiped out all %s.", nam);
|
||||
@@ -2324,7 +2323,6 @@ int how;
|
||||
} else if (ptr == g.youmonst.data) {
|
||||
rehumanize();
|
||||
}
|
||||
reset_rndmonst(mndx);
|
||||
kill_genocided_monsters();
|
||||
update_inventory(); /* in case identified eggs were affected */
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user