def test_wild_die(): """The Wild Die is an exploding d6 which botches on 1""" _check( opend6.wild_die, d6.explode().apply(lambda x: 0 if x == 1 else x), totals=d6.explode(), )
def test_more_dice(reg): """ Test with higher numbers of dice. Three dice is wild + 2 @ regular, etc. """ def remove_highest(values): return sum(values) - max(values) expected = DRV.weighted_average(( (pool(d6, count=reg).apply(remove_highest), Fraction(1, 6)), (reg @ d6 + d6.explode().given(lambda x: x != 1), Fraction(5, 6)), )) _check( opend6.dice(reg + 1), expected, single=False, totals=reg @ d6 + d6.explode(), highest=pool(d6, count=reg + 1).apply(max), )
def test_dice(): """The dice() function returns the summarised data from the rolls.""" with pytest.raises(ValueError): opend6.dice(0) # One die is a wild die. assert opend6.dice(1).is_same(opend6.wild_die) # Two dice is wild + regular, and we need to test that addition is correct. expected = DRV.weighted_average(( (DRV({0: 1}), Fraction(1, 6)), (d6 + d6.explode().given(lambda x: x != 1), Fraction(5, 6)), )) _check( opend6.dice(2), expected, single=False, totals=d6 + d6.explode(), highest=pool(d6, d6).apply(max), )
def test_explode(): """ The "Blowing the top" rule doesn't require any special result values, it just adds an exploding d6 sometimes. """ # Probabilities for explosion on 2 dice, with bonus/penalty normal_prob = Fraction(1, 36) bonus_prob = Fraction(1 + 3 * 5, 216) pen_prob = Fraction(1, 216) def low(result): return result < 12 def high(result): return result >= 12 assert ote.total(2, explode=True).given(low).is_same((2 @ d6).given(low)) assert ote.total(2, explode=True).given(high).is_same(d6.explode() + 12, ) assert ote.total(2, explode=5).given(low).is_same((2 @ d6).given(low)) assert ote.total(2, explode=5).given(high).is_same( d6.explode(rerolls=5) + 12, ) assert p(ote.total(2, bonus=0, explode=True) > 12) == normal_prob assert p(ote.total(2, bonus=1, explode=True) > 12) == bonus_prob assert p(ote.total(2, bonus=-1, explode=True) > 12) == pen_prob def short(result): return len(result.values) == 2 def long(result): return len(result.values) == 3 assert ote.pool(2, explode=True).given(short).is_same( Pool(d6, d6).given(lambda x: x != PlainResult(6, 6))) assert ote.pool(2, explode=True).given(long).is_same( (d6.explode().apply(lambda x: PlainResult(6, 6, x))), ) assert ote.pool(2, explode=5).given(short).is_same( Pool(d6, d6).given(lambda x: x != PlainResult(6, 6))) assert ote.pool(2, explode=5).given(long).is_same( (d6.explode(rerolls=5).apply(lambda x: PlainResult(6, 6, x))), ) assert p(ote.pool(2, bonus=0, explode=True).apply(sum) > 12) == normal_prob assert p(ote.pool(2, bonus=1, explode=True).apply(sum) > 12) == bonus_prob assert p(ote.pool(2, bonus=-1, explode=True).apply(sum) > 12) == pen_prob
def test_dice_char(): """The char parameter adds dice bought with Character Points""" # I don't think the rules explicity say what happens on a botch, if an # exploded character die is the highest score rolled. I'm going to assume # that the botch cancels the 6. def cancel(value): return max(value - 6, 0) expected = DRV.weighted_average(( (d6.explode().apply(cancel), Fraction(1, 6)), (d6.explode() + d6.explode().given(lambda x: x != 1), Fraction(5, 6)), )) _check( opend6.dice(1, char=1), expected, single=False, totals=2 @ d6.explode(), highest=pool(d6, d6).apply(max), )
def test_botch_cancels(): """Optionally you can switch off the botch-cancels rule.""" result = opend6.total(2, botch_cancels=False) def botch(x): return x.botch def nobotch(x): return not x.botch assert result.apply(lambda x: x.total).is_same(d6 + d6.explode()) assert p(result.apply(botch)) == Fraction(1, 6) assert result.given(botch).apply(lambda x: x.total).is_same(d6 + 1) assert result.given(nobotch).apply(lambda x: x.total).is_same( d6 + d6.explode().given(lambda x: x != 1)) # We're happy with the result of total(), so we can use it to check a few # results of test() for target in range(0, 20, 5): success = opend6.test(2, target, botch_cancels=False) assert success.apply(lambda x: x.success).is_same( result.apply(lambda x: x.total >= target))
def test_character_die(): """A die bought with a Character Point explodes but doesn't botch""" _check(opend6.character_die, d6.explode(), botch=0)
def _get_extra(explode: Union[bool, int]) -> DRV: if explode is True: return d6.explode() return d6.explode(rerolls=explode)
:param botch: True if the roll was a botch, False if not. """ success: bool botch: bool #: One regular (non-wild) die regular_die = ( d6 .apply(lambda x: Result(x, x, False)) .replace_tree(Atom('regular_die')) ) #: One wild die wild_die = ( d6.explode() .apply(lambda x: Result(x, x, x == 1)) .replace_tree(Atom('wild_die')) ) #: One bonus wild die from a character point (which cannot botch) character_die = ( d6.explode() .apply(lambda x: Result(x, x, False)) .replace_tree(Atom('character_die')) ) def dice(d: int, *, pips: int = 0, char: int = 0) -> DRV: """ Summarised results of a roll. :param d: Base number of dice (one of which will be wild).