Compare commits

..

2 Commits

Author SHA1 Message Date
mat ess 1c54f860fa Use hypothesis for testing 2023-08-13 16:46:47 -04:00
mat ess 5f877c7d68 housekeeping 2023-08-13 16:46:26 -04:00
8 changed files with 59 additions and 29 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ __pycache__
.pre-commit-config.yaml .pre-commit-config.yaml
.vscode .vscode
.coverage .coverage
.hypothesis

View File

@ -27,6 +27,5 @@ total | 1
## todo ## todo
- [x] roll with (dis)advantage - [x] roll with (dis)advantage
- [ ] interactive rolling mode
- [x] print criticals - [x] print criticals
- [ ] use property testing - [x] use property testing

View File

@ -23,8 +23,8 @@ env:
clean: clean:
hatch clean hatch clean
hatch env purge hatch env prune
rm -r .{mypy,pytest,ruff}_cache -rm -r .{mypy,pytest,ruff}_cache
rm -r dist -rm -r dist
rm .coverage -rm .coverage
fd __pycache__ --no-ignore --exec rm -r fd __pycache__ --no-ignore --exec rm -r

View File

@ -38,6 +38,7 @@ path = "roll/__about__.py"
[tool.hatch.envs.default] [tool.hatch.envs.default]
dependencies = [ dependencies = [
"coverage[toml]>=6.5", "coverage[toml]>=6.5",
"hypothesis",
"pytest", "pytest",
] ]
[tool.hatch.envs.default.scripts] [tool.hatch.envs.default.scripts]

View File

@ -16,7 +16,7 @@ class Roll:
dice_count: int = 1 dice_count: int = 1
sides: int = 20 sides: int = 20
modifier: int | None = None modifier: int = 0
def __post_init__(self) -> None: def __post_init__(self) -> None:
if self.dice_count < 1: if self.dice_count < 1:
@ -34,14 +34,13 @@ class Roll:
msg = f"expected {value!r} to match pattern {ROLL_PATTERN.pattern!r}" msg = f"expected {value!r} to match pattern {ROLL_PATTERN.pattern!r}"
raise ValueError(msg) raise ValueError(msg)
dice_count, sides, modifier = match.groups() dice_count, sides, modifier = match.groups()
return cls(int(dice_count), int(sides), int(modifier) if modifier else None) return cls(int(dice_count), int(sides), int(modifier) if modifier else 0)
def modifier_str(self) -> str: def modifier_str(self) -> str:
"""return the modifier as a string""" """return the modifier as a string"""
if self.modifier is None: if self.modifier == 0:
return "" return ""
sign = "+" if self.modifier > 0 else "" return f"{self.modifier:+}"
return f"{sign}{self.modifier}"
def to_str(self) -> str: def to_str(self) -> str:
"""return the short representation of a roll, e.g. 3d4 or 2d20+3""" """return the short representation of a roll, e.g. 3d4 or 2d20+3"""

View File

@ -7,12 +7,12 @@ D20_MAX = 20
class Throw: class Throw:
results: list[int] results: list[int]
sides: int sides: int
modifier: int | None = None modifier: int = 0
@property @property
def total(self) -> int: def total(self) -> int:
"""calculate the total of the throw, accounting for the modifier""" """calculate the total of the throw, accounting for the modifier"""
return sum(self.results) + (self.modifier or 0) return sum(self.results) + self.modifier
@property @property
def is_critical_hit(self) -> bool: def is_critical_hit(self) -> bool:

View File

@ -1,13 +1,33 @@
import pytest import pytest
from hypothesis import assume, given
from hypothesis import strategies as st
from roll.roll import Roll from roll.roll import Roll
def test_roll_validation(): @given(st.integers(max_value=0))
def test_bad_dice_count(n: int):
with pytest.raises(ValueError): with pytest.raises(ValueError):
Roll(0, 20) Roll(n, 20)
@given(st.integers(max_value=1))
def test_bad_sides(sides: int):
with pytest.raises(ValueError): with pytest.raises(ValueError):
Roll(1, 1) Roll(1, sides)
@given(
st.integers(min_value=1),
st.integers(min_value=2),
st.integers(),
)
def test_valid_dice(n: int, sides: int, modifier: int):
string = f"{n}d{sides}{modifier:+}" if modifier else f"{n}d{sides}"
roll = Roll(n, sides, modifier)
roll_from_str = Roll.from_str(string)
assert roll == roll_from_str
assert string == roll.to_str() and string == roll_from_str.to_str()
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -25,16 +45,21 @@ def test_from_bad_str(roll: str):
Roll.from_str(roll) Roll.from_str(roll)
def test_modify(): @given(
roll = Roll(2, 20) st.integers(min_value=1),
modified_roll = roll.modify(3) st.integers(min_value=2),
assert modified_roll == Roll(2, 20, 3) st.integers(),
assert roll == Roll(2, 20) st.integers(),
)
def test_modify(n: int, sides: int, modifier: int, new_modifier: int):
assume(modifier != new_modifier)
roll = Roll(n, sides, modifier)
modified_roll = roll.modify(new_modifier)
assert modified_roll == Roll(n, sides, new_modifier)
assert modified_roll is not roll and modified_roll != roll assert modified_roll is not roll and modified_roll != roll
@pytest.mark.parametrize("n", list(range(1, 5))) @given(st.integers(min_value=1, max_value=50_000), st.integers(min_value=2))
@pytest.mark.parametrize("sides", list(range(2, 100)))
def test_throw(n: int, sides: int): def test_throw(n: int, sides: int):
roll = Roll(n, sides) roll = Roll(n, sides)
throw = roll.throw() throw = roll.throw()

View File

@ -1,16 +1,21 @@
from hypothesis import given
from hypothesis import strategies as st
from roll.throw import Throw from roll.throw import Throw
def test_throw(): @given(st.lists(st.integers(min_value=1), min_size=1), st.integers(min_value=2))
throw = Throw([1, 2, 3], sides=4) def test_throw(results: list[int], sides: int):
assert throw.total == 6 throw = Throw(results, sides=sides)
assert throw.total == sum(results)
assert not throw.is_critical_hit assert not throw.is_critical_hit
assert not throw.is_critical_miss assert not throw.is_critical_miss
def test_throw_with_modifier(): @given(st.lists(st.integers(min_value=1), min_size=1), st.integers(min_value=2), st.integers())
throw = Throw([1, 2, 3], sides=4, modifier=5) def test_throw_with_modifier(results: list[int], sides: int, modifier: int):
assert throw.total == 11 throw = Throw(results, sides=sides, modifier=modifier)
assert throw.total == (sum(results) + modifier)
assert not throw.is_critical_hit assert not throw.is_critical_hit
assert not throw.is_critical_miss assert not throw.is_critical_miss