add a Cypher System dice rolling library method
not available to the bot, yet, but that's next Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
parent
994f9ef50b
commit
ac16ec99e5
41
dice/lib.py
Normal file
41
dice/lib.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""Dice rolling operations (outside of the lex/yacc roller)."""
|
||||||
|
import random
|
||||||
|
|
||||||
|
import numexpr
|
||||||
|
|
||||||
|
rand = random.SystemRandom()
|
||||||
|
|
||||||
|
|
||||||
|
def cypher_roll(difficulty=None, mods=0):
|
||||||
|
"""Make a Cypher System roll.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
difficulty: the original difficulty to beat; if provided, success or failure is indicated in the results
|
||||||
|
mods: eases(-) and hindrances(+) to apply to the check, as a string (e.g. '-3+1')
|
||||||
|
Returns:
|
||||||
|
tuple of:
|
||||||
|
- the result on the d20
|
||||||
|
- the highest difficulty beaten
|
||||||
|
- if the difficulty is known, if the target was beat
|
||||||
|
- miscellaneous effects
|
||||||
|
"""
|
||||||
|
roll = rand.randint(1, 20)
|
||||||
|
if roll == 1:
|
||||||
|
return (roll, None, False if difficulty else None, 'a GM intrusion')
|
||||||
|
|
||||||
|
effect = None
|
||||||
|
if roll == 17:
|
||||||
|
effect = '+1 damage'
|
||||||
|
elif roll == 18:
|
||||||
|
effect = '+2 damage'
|
||||||
|
elif roll == 19:
|
||||||
|
effect = 'a minor effect'
|
||||||
|
elif roll == 20:
|
||||||
|
effect = 'a MAJOR EFFECT'
|
||||||
|
|
||||||
|
# if we know the difficulty, the mods would adjust the difficulty, but for the case where we don't,
|
||||||
|
# and maybe just in general, it's easier to modify the difficulty that the roll beats, so we flip the logic
|
||||||
|
# if incoming eases are a negative number, they should add to the difficulty the roll beats
|
||||||
|
beats = (roll // 3) - (numexpr.evaluate(mods).item() if mods else 0)
|
||||||
|
beats = 0 if beats < 0 else beats
|
||||||
|
return (roll, beats, difficulty <= beats if difficulty else None, effect)
|
@ -11,8 +11,8 @@ authors = [
|
|||||||
{name = "Brian S. Stephan", email = "bss@incorporeal.org"},
|
{name = "Brian S. Stephan", email = "bss@incorporeal.org"},
|
||||||
]
|
]
|
||||||
requires-python = ">=3.10,<3.12"
|
requires-python = ">=3.10,<3.12"
|
||||||
dependencies = ["Django<5.1", "django-bootstrap3", "django-extensions", "djangorestframework", "irc", "parsedatetime", "ply",
|
dependencies = ["Django<5.1", "django-bootstrap3", "django-extensions", "djangorestframework", "irc", "numexpr",
|
||||||
"python-dateutil", "python-mpd2", "pytz", "zalgo-text"]
|
"parsedatetime", "ply", "python-dateutil", "python-mpd2", "pytz", "zalgo-text"]
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Framework :: Django",
|
"Framework :: Django",
|
||||||
|
@ -152,6 +152,10 @@ more-itertools==10.5.0
|
|||||||
# jaraco-functools
|
# jaraco-functools
|
||||||
# jaraco-stream
|
# jaraco-stream
|
||||||
# jaraco-text
|
# jaraco-text
|
||||||
|
numexpr==2.10.1
|
||||||
|
# via dr.botzo (pyproject.toml)
|
||||||
|
numpy==2.1.3
|
||||||
|
# via numexpr
|
||||||
packaging==24.1
|
packaging==24.1
|
||||||
# via
|
# via
|
||||||
# build
|
# build
|
||||||
|
@ -49,6 +49,10 @@ more-itertools==10.5.0
|
|||||||
# jaraco-functools
|
# jaraco-functools
|
||||||
# jaraco-stream
|
# jaraco-stream
|
||||||
# jaraco-text
|
# jaraco-text
|
||||||
|
numexpr==2.10.1
|
||||||
|
# via dr.botzo (pyproject.toml)
|
||||||
|
numpy==2.1.3
|
||||||
|
# via numexpr
|
||||||
parsedatetime==2.6
|
parsedatetime==2.6
|
||||||
# via dr.botzo (pyproject.toml)
|
# via dr.botzo (pyproject.toml)
|
||||||
ply==3.11
|
ply==3.11
|
||||||
|
77
tests/test_dice_lib.py
Normal file
77
tests/test_dice_lib.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""Tests for dice operations."""
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
import dice.lib
|
||||||
|
|
||||||
|
|
||||||
|
class DiceLibTestCase(TestCase):
|
||||||
|
"""Test that a variety of dice rolls work as expected."""
|
||||||
|
|
||||||
|
def test_cypher_rolls(self):
|
||||||
|
"""Roll a variety of Cypher System rolls."""
|
||||||
|
# simple task, simple check
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=5):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (5, 1, True, None))
|
||||||
|
|
||||||
|
# simple failure
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=2):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (2, 0, False, None))
|
||||||
|
|
||||||
|
# rolled a 1
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (1, None, False, 'a GM intrusion'))
|
||||||
|
|
||||||
|
# rolled a 17
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=17):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (17, 5, True, '+1 damage'))
|
||||||
|
|
||||||
|
# rolled a 18
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=18):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (18, 6, True, '+2 damage'))
|
||||||
|
|
||||||
|
# rolled a 19
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=19):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (19, 6, True, 'a minor effect'))
|
||||||
|
|
||||||
|
# rolled a 20
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=20):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1)
|
||||||
|
self.assertEqual(result, (20, 6, True, 'a MAJOR EFFECT'))
|
||||||
|
|
||||||
|
# mods affect the result of what the roll beats
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=2):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=1, mods='-5')
|
||||||
|
self.assertEqual(result, (2, 5, True, None))
|
||||||
|
|
||||||
|
# complex mods
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=2):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=3, mods='+1-4')
|
||||||
|
self.assertEqual(result, (2, 3, True, None))
|
||||||
|
|
||||||
|
# complex mods
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=2):
|
||||||
|
result = dice.lib.cypher_roll(difficulty=3, mods='-4+1')
|
||||||
|
self.assertEqual(result, (2, 3, True, None))
|
||||||
|
|
||||||
|
# ...even without a difficulty known
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=2):
|
||||||
|
result = dice.lib.cypher_roll(mods='-5')
|
||||||
|
self.assertEqual(result, (2, 5, None, None))
|
||||||
|
|
||||||
|
# general "don't know the difficulty" kind of check
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=10):
|
||||||
|
result = dice.lib.cypher_roll(mods='-2')
|
||||||
|
self.assertEqual(result, (10, 5, None, None))
|
||||||
|
|
||||||
|
# general "don't know the difficulty" kind of check in the other direction
|
||||||
|
with mock.patch('random.SystemRandom.randint', return_value=10):
|
||||||
|
result = dice.lib.cypher_roll(mods='2')
|
||||||
|
self.assertEqual(result, (10, 1, None, None))
|
Loading…
Reference in New Issue
Block a user