Example #1
0
def test_bad_count(bad):
    """
    Less than empty is not allowed, and neither is count with many DRVs.
    """
    with pytest.raises(ValueError):
        pools.pool(d6, count=bad)
    with pytest.raises(TypeError):
        pools.pool(d6, d8, count=1 - bad)
Example #2
0
def test_empty_pool():
    """
    An empty pool has one possible value: the empty collection of values.
    """
    empty1 = pools.pool()
    empty2 = pools.pool(d6, count=0)
    assert empty1.is_same(empty2)
    assert empty1.to_dict() == {pools.PlainResult(): 1}
Example #3
0
def test_mixed_pool():
    """
    Not all dice in pool need to be the same, and you can build up a pool one
    item at a time if you want to.
    """
    pool = pools.pool(d6, d8)
    assert p(pool == pools.PlainResult(1, 1)) == Fraction(1, 48)
    assert p(pool == pools.PlainResult(1, 2)) == Fraction(2, 48)
    assert p(pool == pools.PlainResult(6, 7)) == Fraction(1, 48)
    assert pool.is_same(pools.pool(d6) + d8)
Example #4
0
def test_drv_equivalence():
    """
    You can work out some DRV operations "the slow way" using a pool.
    """
    drv1 = d6 + d6
    drv2 = 2 @ d6
    pool_drv = pools.pool(d6, d6).apply(lambda x: sum(x.values))
    pool_drv2 = pools.pool(d6, d6).apply(sum)
    assert drv1.is_same(pool_drv)
    assert drv2.is_same(pool_drv)
    assert pool_drv2.is_same(pool_drv)
Example #5
0
def test_bad_keep_numbers(bad):
    """
    You can't pass a negative number of dice to keep, but you can drop more
    dice than you have (resulting in no dice).
    """
    with pytest.raises(ValueError):
        pools.keep_highest(bad, d6, d6)
    with pytest.raises(ValueError):
        pools.keep_lowest(bad, d6, d6)
    assert pools.drop_highest(10, d6, count=-bad).is_same(pools.pool())
    assert pools.drop_lowest(10, d6, count=-bad).is_same(pools.pool())
Example #6
0
def test_pool():
    """
    Take a bucket of DRVs, and consider the results irrespective of order.
    """
    pool = pools.pool(d6, count=2)
    assert p(pool == pools.PlainResult(1, 1)) == Fraction(1, 36)
    assert p(pool == pools.PlainResult(1, 2)) == Fraction(2, 36)
Example #7
0
def test_normalize():
    """
    You can optimize how the pool works in two different ways: by passing a
    class or by passing a function.
    """
    class Keep2(pools.PlainResult):
        def normalize(self, values):
            return sorted(values, reverse=True)[0:2]

    expected = pools.keep_highest(2, d6, count=3)
    assert pools.pool(d6, count=3, result_type=Keep2).is_same(expected)

    assert pools.pool(
        d6, count=3,
        normalize=lambda values: sorted(values, reverse=True)[0:2],
    ).is_same(expected)
Example #8
0
def test_pool_addition():
    """
    You can add a constant, DRV or pool to a pool, and the effect is of
    including one or more extra dice in the pool.
    """
    pool = pools.pool(d6)
    assert p(pool + 1 == pools.PlainResult(1, 1)) == Fraction(1, 6)
    assert p(pool + d6 == pools.PlainResult(1, 1)) == Fraction(1, 36)
    assert p(pool + pool == pools.PlainResult(1, 1)) == Fraction(1, 36)
Example #9
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),
    )
Example #10
0
def test_custom_pools():
    """
    I'm not sure how useful this is, but for completeness you can add together
    pools with different result types, including those not derived from the
    default result type. The type of the result pool is taken from the pool on
    the left of the addition.
    """
    class Keep2(pools.PlainResult):
        def normalize(self, values):
            return sorted(values, reverse=True)[0:2]

    poolA = pools.pool(d6, count=3, result_type=Keep2)
    poolB = pools.pool(d6, count=2)
    assert all(type(x) is Keep2 for x, prob in (poolA + poolB)._items())
    assert all(len(x.values) == 2 for x, prob in (poolA + poolB)._items())
    assert (poolA + poolB).is_same(pools.pool(d6, count=5, result_type=Keep2))

    for result, _ in (poolB + poolA)._items():
        assert type(result) is pools.PlainResult
    assert all(len(x.values) == 4 for x, prob in (poolB + poolA)._items())
    assert not (poolB + poolA).is_same(poolA + poolB)

    # result_type doesn't even need to inherit from pools.PlainResult. We don't
    # have to consider the order of results insignificant if we don't want to.
    class Ordered(pools.Result):
        def __init__(self, *values):
            self._values = values
        @property
        def values(self):
            return self._values

    assert Ordered(1, 2) == Ordered(1, 2)
    assert not Ordered(1, 2) != Ordered(1, 2)
    assert Ordered(1, 2) != Ordered(2, 1)
    assert not Ordered(1, 2) == Ordered(2, 1)

    poolC = pools.pool(d6, count=3, result_type=Ordered)
    assert len(poolC.to_dict()) == 6 ** 3
    poolD = pools.pool(d6, count=3)
    assert len(poolD.to_dict()) == 56
    assert all(type(x) is Ordered for x, prob in (poolC + poolD)._items())
    assert len((poolC + poolD).to_dict()) == (6 ** 3) * 56
    for result, _ in (poolD + poolC)._items():
        assert type(result) is pools.PlainResult
Example #11
0
def test_keep_highest():
    """
    Roll N, keep the best K of some DRV.
    """
    pool = pools.keep_highest(2, d6, count=3)
    # There are three ways each to get 6, 6, x for x = 1..5, plus 6, 6, 6.
    assert p(pool == pools.PlainResult(6, 6)) == Fraction(16, 216)
    assert p(pool == pools.PlainResult(1, 1)) == Fraction(1, 216)
    # count=1000 acts as a performance test: if the implementation tries to
    # compute all possibilities and then restrict to 0 dice, it will fail.
    pool0 = pools.keep_highest(0, d6, count=1000)
    assert pool0.is_same(DRV({pools.PlainResult(): 1}))
    # Examples from docs
    poolA = pools.keep_highest(2, d6) + d6 + d6
    poolB = pools.pool(d6, count=3)
    poolC = pools.keep_highest(2, d6, count=3)
    poolD = pools.pool(d6, result_type=pools.KeepHighest(2)) + d6 + d6
    assert poolA.is_same(poolB)
    assert not poolA.is_same(poolC)
    assert poolD.is_same(poolC)
Example #12
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),
    )
Example #13
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),
    )
Example #14
0
def test_result_sum():
    """
    Result is summable, since that's a common final step.
    """
    pool = pools.pool(d6, d6, d8)
    assert pool.apply(sum).is_same(pool.apply(lambda x: sum(x.values)))