Exemple #1
0
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(),
    )
Exemple #2
0
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),
    )
Exemple #3
0
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
Exemple #5
0
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),
    )
Exemple #6
0
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))
Exemple #7
0
def test_character_die():
    """A die bought with a Character Point explodes but doesn't botch"""
    _check(opend6.character_die, d6.explode(), botch=0)
Exemple #8
0
def _get_extra(explode: Union[bool, int]) -> DRV:
    if explode is True:
        return d6.explode()
    return d6.explode(rerolls=explode)
Exemple #9
0
    :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).