rework cypher rolls to be via the lex/yacc parser
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
+3
-48
@@ -6,14 +6,12 @@ import re
|
||||
from django.conf import settings
|
||||
from irc.client import NickMask
|
||||
|
||||
from dice.lib import cypher_roll, reaction_roll
|
||||
from dice.lib import reaction_roll
|
||||
from dice.roller import DiceRoller
|
||||
from ircbot.lib import Plugin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CYPHER_ROLL_REGEX = r'((?P<type>A|T)(?P<difficulty>\d+))?(?P<mods>(?:\s*(-|\+)\d+)*)\s*(?P<comment>.*)?'
|
||||
CYPHER_COMMAND_REGEX = r'^!cypher\s+(' + CYPHER_ROLL_REGEX + ')'
|
||||
REACTION_COMMAND_REGEX = r'^!reaction$'
|
||||
|
||||
|
||||
@@ -22,14 +20,12 @@ class Dice(Plugin):
|
||||
|
||||
def __init__(self, bot, connection, event):
|
||||
"""Set up the plugin."""
|
||||
self.roller = DiceRoller()
|
||||
self.roller = DiceRoller(ircify=True)
|
||||
|
||||
super(Dice, self).__init__(bot, connection, event)
|
||||
|
||||
def start(self):
|
||||
"""Set up the handlers."""
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], CYPHER_COMMAND_REGEX,
|
||||
self.handle_cypher_roll, -20)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!roll\s+(.*)$',
|
||||
self.handle_roll, -20)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!random\s+(.*)$',
|
||||
@@ -48,47 +44,6 @@ class Dice(Plugin):
|
||||
|
||||
super(Dice, self).stop()
|
||||
|
||||
def handle_cypher_roll(self, connection, event, match):
|
||||
"""Handle the !cypher roll."""
|
||||
nick = NickMask(event.source).nick
|
||||
task = match.group(1)
|
||||
|
||||
task_group = re.search(CYPHER_ROLL_REGEX, task, re.IGNORECASE)
|
||||
difficulty = int(task_group.group('difficulty')) if task_group.group('difficulty') else None
|
||||
mods = task_group.group('mods')
|
||||
is_attack = True if task_group.group('type') and task_group.group('type').upper() == 'A' else False
|
||||
comment = task_group.group('comment')
|
||||
result, beats, success, effect = cypher_roll(difficulty=difficulty, mods=mods, is_attack=is_attack)
|
||||
|
||||
if success is not None:
|
||||
if success:
|
||||
if effect:
|
||||
result_str = f"9succeeded, with {effect}!"
|
||||
else:
|
||||
result_str = "9succeeded!"
|
||||
else:
|
||||
if effect:
|
||||
result_str = f"4failed, with {effect}!"
|
||||
else:
|
||||
result_str = "4failed."
|
||||
else:
|
||||
if effect:
|
||||
result_str = f"beats a difficulty {beats} task, with {effect}!"
|
||||
else:
|
||||
result_str = f"beats a difficulty {beats} task."
|
||||
|
||||
if success is not None:
|
||||
# show the adjusted difficulty
|
||||
detail_str = f"14(d20={result} vs. diff. {difficulty}{mods})"
|
||||
else:
|
||||
detail_str = f"14(d20={result}{f' with {mods} levels' if mods else ''})"
|
||||
|
||||
if comment:
|
||||
return self.bot.reply(event, f"{nick}: {comment} {result_str} {detail_str}")
|
||||
else:
|
||||
type_str = 'attack' if is_attack else 'check'
|
||||
return self.bot.reply(event, f"{nick}: your {type_str} {result_str} {detail_str}")
|
||||
|
||||
def handle_random(self, connection, event, match):
|
||||
"""Handle the !random command which picks an item from a list."""
|
||||
nick = NickMask(event.source).nick
|
||||
@@ -139,7 +94,7 @@ class Dice(Plugin):
|
||||
reply = "{0:s}: {1:s}".format(nick, reply_str)
|
||||
else:
|
||||
reply = "{0:s}".format(reply_str)
|
||||
return self.bot.reply(event, re.sub(r'(\d+)(.*?\s+)(\(.*?\))', r'\1\214\3', reply))
|
||||
return self.bot.reply(event, reply)
|
||||
|
||||
|
||||
plugin = Dice
|
||||
|
||||
+15
-8
@@ -1,17 +1,15 @@
|
||||
"""Dice rolling operations (outside of the lex/yacc roller)."""
|
||||
import random
|
||||
|
||||
import numexpr
|
||||
|
||||
rand = random.SystemRandom()
|
||||
|
||||
|
||||
def cypher_roll(difficulty=None, mods=0, is_attack=False):
|
||||
def cypher_roll(difficulty=None, mod=0, is_attack=False):
|
||||
"""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')
|
||||
mod: eases(-) and hindrances(+) to apply to the check, as a totaled int
|
||||
is_attack: if the roll is an attack (in which case the damage-only effects are included)
|
||||
Returns:
|
||||
tuple of:
|
||||
@@ -19,10 +17,19 @@ def cypher_roll(difficulty=None, mods=0, is_attack=False):
|
||||
- the highest difficulty beaten
|
||||
- if the difficulty is known, if the target was beat
|
||||
- miscellaneous effects
|
||||
- miscellaneous negative effects
|
||||
- the level of the result on the d20
|
||||
- the net difficulty level, if known
|
||||
"""
|
||||
roll = rand.randint(1, 20)
|
||||
result_lvl = roll // 3
|
||||
if difficulty:
|
||||
net_difficulty = difficulty + mod
|
||||
else:
|
||||
net_difficulty = None
|
||||
|
||||
if roll == 1:
|
||||
return (roll, None, False if difficulty else None, 'a GM intrusion')
|
||||
return (roll, None, False if difficulty else None, None, 'a GM intrusion', result_lvl, net_difficulty)
|
||||
|
||||
effect = None
|
||||
if roll == 17 and is_attack:
|
||||
@@ -34,12 +41,12 @@ def cypher_roll(difficulty=None, mods=0, is_attack=False):
|
||||
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,
|
||||
# if we know the difficulty, the mod 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 = result_lvl - mod
|
||||
beats = 0 if beats < 0 else beats
|
||||
return (roll, beats, difficulty <= beats if difficulty else None, effect)
|
||||
return (roll, beats, difficulty <= beats if difficulty else None, effect, None, result_lvl, net_difficulty)
|
||||
|
||||
|
||||
def reaction_roll():
|
||||
|
||||
+163
-5
@@ -1,23 +1,37 @@
|
||||
"""Dice rollers used by the views, bots, etc."""
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
|
||||
import ply.lex as lex
|
||||
import ply.yacc as yacc
|
||||
|
||||
import dice.lib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiceRoller(object):
|
||||
|
||||
tokens = ['NUMBER', 'TEXT', 'ROLLSEP']
|
||||
tokens = ['NUMBER', 'TEXT', 'ROLLSEP', 'DIFFICULTYSTR', 'TASKSTR', 'ATTACKSTR',
|
||||
'MIGHT_STR', 'SPEED_STR', 'INTELLECT_STR']
|
||||
literals = ['#', '/', '+', '-', 'd']
|
||||
|
||||
t_TEXT = r'\s+[^;]+'
|
||||
t_ROLLSEP = r';\s*'
|
||||
t_DIFFICULTYSTR = r'difficulty\s*'
|
||||
t_TASKSTR = r'\s*task'
|
||||
t_ATTACKSTR = r'\s*attack'
|
||||
t_MIGHT_STR = r'\s*might\s*'
|
||||
t_SPEED_STR = r'\s*speed\s*'
|
||||
t_INTELLECT_STR = r'\s*intellect\s*'
|
||||
|
||||
def __init__(self, ircify=False):
|
||||
"""Initialize the object for dice rolling purposes."""
|
||||
self.ircify = ircify
|
||||
|
||||
def build(self):
|
||||
lex.lex(module=self)
|
||||
lex.lex(module=self, reflags=re.IGNORECASE)
|
||||
yacc.yacc(module=self)
|
||||
|
||||
def t_NUMBER(self, t):
|
||||
@@ -114,11 +128,20 @@ class DiceRoller(object):
|
||||
total += mode * res
|
||||
if repeat == 1:
|
||||
if comment is not None:
|
||||
output = "%d %s (%s)" % (total, comment.strip(), curr_str)
|
||||
if self.ircify:
|
||||
output = "%d %s 14(%s)" % (total, comment.strip(), curr_str)
|
||||
else:
|
||||
output = "%d %s (%s)" % (total, comment.strip(), curr_str)
|
||||
else:
|
||||
output = "%d (%s)" % (total, curr_str)
|
||||
if self.ircify:
|
||||
output = "%d 14(%s)" % (total, curr_str)
|
||||
else:
|
||||
output = "%d (%s)" % (total, curr_str)
|
||||
else:
|
||||
output += "%d (%s)" % (total, curr_str)
|
||||
if self.ircify:
|
||||
output += "%d 14(%s)" % (total, curr_str)
|
||||
else:
|
||||
output += "%d (%s)" % (total, curr_str)
|
||||
if i == repeat - 1:
|
||||
if comment is not None:
|
||||
output += " (%s)" % (comment.strip())
|
||||
@@ -163,6 +186,141 @@ class DiceRoller(object):
|
||||
p[0] = self.process_roll(None, mods, p[2])
|
||||
output = p[0]
|
||||
|
||||
def p_cypher_stat(self, p):
|
||||
"""cypher_stat : MIGHT_STR
|
||||
| SPEED_STR
|
||||
| INTELLECT_STR"""
|
||||
p[0] = p[1]
|
||||
|
||||
def p_cypher_mod_pos(self, p):
|
||||
"""cypher_mod : '+' NUMBER"""
|
||||
p[0] = p[2]
|
||||
|
||||
def p_cypher_mod_neg(self, p):
|
||||
"""cypher_mod : '-' NUMBER"""
|
||||
p[0] = -1 * p[2]
|
||||
|
||||
def p_cypher_mod_none(self, p):
|
||||
"""cypher_mod :"""
|
||||
p[0] = 0
|
||||
|
||||
def p_cypher_mod_multiple(self, p):
|
||||
"""cypher_mod : cypher_mod '+' NUMBER
|
||||
| cypher_mod '-' NUMBER"""
|
||||
if p[2] == '+':
|
||||
p[0] = p[1] + p[3]
|
||||
else:
|
||||
p[0] = p[1] - p[3]
|
||||
|
||||
def p_cypher_task_roll(self, p):
|
||||
"""roll : DIFFICULTYSTR NUMBER cypher_mod cypher_stat TASKSTR comment
|
||||
| DIFFICULTYSTR NUMBER cypher_mod cypher_stat ATTACKSTR comment"""
|
||||
global output
|
||||
|
||||
difficulty = p[2]
|
||||
mod = p[3]
|
||||
stat = p[4].strip()
|
||||
is_attack = True if re.match(self.t_ATTACKSTR, p[5], re.IGNORECASE) else False
|
||||
if p[6]:
|
||||
comment = f"{p[5].strip()} {p[6].strip()}"
|
||||
else:
|
||||
comment = f"{p[5].strip()}"
|
||||
result, beats, success, effect, neg_effect, result_lvl, diff_level = dice.lib.cypher_roll(difficulty=difficulty,
|
||||
mod=mod,
|
||||
is_attack=is_attack)
|
||||
|
||||
# sanity check; we know the difficulty so we should always know the success/failure
|
||||
assert success is not None
|
||||
|
||||
# TODO: less nested ifs
|
||||
if success:
|
||||
if self.ircify:
|
||||
style_prefix = '9'
|
||||
style_suffix = ''
|
||||
else:
|
||||
style_prefix = ''
|
||||
style_suffix = ''
|
||||
|
||||
if effect:
|
||||
result_str = f"{style_prefix}succeeded, with {effect}!{style_suffix}"
|
||||
else:
|
||||
result_str = f"{style_prefix}succeeded!{style_suffix}"
|
||||
else:
|
||||
if self.ircify:
|
||||
style_prefix = '4'
|
||||
style_suffix = ''
|
||||
else:
|
||||
style_prefix = ''
|
||||
style_suffix = ''
|
||||
|
||||
if neg_effect:
|
||||
result_str = f"{style_prefix}failed, with {neg_effect}!{style_suffix}"
|
||||
else:
|
||||
result_str = f"{style_prefix}failed.{style_suffix}"
|
||||
|
||||
# show the adjusted difficulty
|
||||
if mod > 0:
|
||||
modded_diff = f"{difficulty}+{mod}"
|
||||
elif mod < 0:
|
||||
modded_diff = f"{difficulty}{mod}"
|
||||
else:
|
||||
modded_diff = difficulty
|
||||
|
||||
if self.ircify:
|
||||
detail_str = f"14(res. {result_lvl} (d20={result}) vs. {stat} diff. {diff_level} ({modded_diff}))"
|
||||
else:
|
||||
detail_str = f"(res. {result_lvl} (d20={result}) vs. {stat} diff. {diff_level} ({modded_diff}))"
|
||||
|
||||
p[0] = f"{comment} {result_str} {detail_str}"
|
||||
output = p[0]
|
||||
|
||||
def p_cypher_graduated_task_roll(self, p):
|
||||
"""roll : cypher_mod cypher_stat TASKSTR comment
|
||||
| cypher_mod cypher_stat ATTACKSTR comment"""
|
||||
global output
|
||||
|
||||
mod = p[1]
|
||||
stat = p[2].strip()
|
||||
is_attack = True if re.match(self.t_ATTACKSTR, p[3], re.IGNORECASE) else False
|
||||
if p[4]:
|
||||
comment = f"{p[3].strip()} {p[4].strip()}"
|
||||
else:
|
||||
comment = f"{p[3].strip()}"
|
||||
result, beats, success, effect, neg_effect, result_lvl, diff_level = dice.lib.cypher_roll(difficulty=None,
|
||||
mod=mod,
|
||||
is_attack=is_attack)
|
||||
|
||||
# sanity check; we don't know the difficulty, so don't know success or not
|
||||
assert success is None
|
||||
|
||||
if beats is not None:
|
||||
if effect:
|
||||
result_str = f"beats a difficulty {beats} task, with {effect}!"
|
||||
else:
|
||||
result_str = f"beats a difficulty {beats} task."
|
||||
else:
|
||||
# this can only happen on an intrusion
|
||||
if self.ircify:
|
||||
result_str = f"4beats nothing, with {neg_effect}!"
|
||||
else:
|
||||
result_str = f"beats nothing, with {neg_effect}!"
|
||||
|
||||
# show the adjusted difficulty
|
||||
if mod > 0:
|
||||
modded_diff = f"-{mod}"
|
||||
elif mod < 0:
|
||||
modded_diff = f"+{-1 * mod}"
|
||||
else:
|
||||
modded_diff = ""
|
||||
|
||||
if self.ircify:
|
||||
detail_str = f"14(res. {result_lvl}{modded_diff} (d20={result}) vs. {stat})"
|
||||
else:
|
||||
detail_str = f"(res. {result_lvl}{modded_diff} (d20={result}) vs. {stat})"
|
||||
|
||||
p[0] = f"{comment} {result_str} {detail_str}"
|
||||
output = p[0]
|
||||
|
||||
def p_comment(self, p):
|
||||
# Parse a comment.
|
||||
|
||||
|
||||
@@ -23,64 +23,6 @@ class MarkovTestCase(TestCase):
|
||||
|
||||
self.plugin = dice.ircplugin.Dice(self.mock_bot, self.mock_connection, mock.MagicMock())
|
||||
|
||||
def test_cypher_roll_strings(self):
|
||||
"""Simulate incoming Cypher System requests."""
|
||||
mock_event = mock.MagicMock()
|
||||
mock_event.source = 'test!test@test'
|
||||
mock_event.target = '#test'
|
||||
mock_event.recursing = False
|
||||
|
||||
# general task roll (no damage output on a 17)
|
||||
mock_event.arguments = ['!cypher T3']
|
||||
match = re.search(dice.ircplugin.CYPHER_COMMAND_REGEX, mock_event.arguments[0])
|
||||
with mock.patch('random.SystemRandom.randint', return_value=17):
|
||||
self.plugin.handle_cypher_roll(self.mock_connection, mock_event, match)
|
||||
self.mock_bot.reply.assert_called_with(
|
||||
mock_event,
|
||||
'test: your check 9succeeded! 14(d20=17 vs. diff. 3)'
|
||||
)
|
||||
|
||||
# general attack roll (incl. damage output on a 17)
|
||||
mock_event.arguments = ['!cypher A3']
|
||||
match = re.search(dice.ircplugin.CYPHER_COMMAND_REGEX, mock_event.arguments[0])
|
||||
with mock.patch('random.SystemRandom.randint', return_value=17):
|
||||
self.plugin.handle_cypher_roll(self.mock_connection, mock_event, match)
|
||||
self.mock_bot.reply.assert_called_with(
|
||||
mock_event,
|
||||
'test: your attack 9succeeded, with +1 damage! 14(d20=17 vs. diff. 3)'
|
||||
)
|
||||
|
||||
# general task roll, case insensitive
|
||||
mock_event.arguments = ['!cypher t3']
|
||||
match = re.search(dice.ircplugin.CYPHER_COMMAND_REGEX, mock_event.arguments[0])
|
||||
with mock.patch('random.SystemRandom.randint', return_value=17):
|
||||
self.plugin.handle_cypher_roll(self.mock_connection, mock_event, match)
|
||||
self.mock_bot.reply.assert_called_with(
|
||||
mock_event,
|
||||
'test: your check 9succeeded! 14(d20=17 vs. diff. 3)'
|
||||
)
|
||||
|
||||
# unknown target roll
|
||||
mock_event.arguments = ['!cypher +1']
|
||||
match = re.search(dice.ircplugin.CYPHER_COMMAND_REGEX, mock_event.arguments[0])
|
||||
with mock.patch('random.SystemRandom.randint', return_value=17):
|
||||
self.plugin.handle_cypher_roll(self.mock_connection, mock_event, match)
|
||||
self.mock_bot.reply.assert_called_with(
|
||||
mock_event,
|
||||
'test: your check beats a difficulty 4 task. 14(d20=17 with +1 levels)'
|
||||
)
|
||||
|
||||
# no mod or known difficulty
|
||||
mock_event.arguments = ['!cypher unmodded attempt']
|
||||
match = re.search(dice.ircplugin.CYPHER_COMMAND_REGEX, mock_event.arguments[0])
|
||||
self.plugin.handle_cypher_roll(self.mock_connection, mock_event, match)
|
||||
with mock.patch('random.SystemRandom.randint', return_value=9):
|
||||
self.plugin.handle_cypher_roll(self.mock_connection, mock_event, match)
|
||||
self.mock_bot.reply.assert_called_with(
|
||||
mock_event,
|
||||
'test: unmodded attempt beats a difficulty 3 task. 14(d20=9)'
|
||||
)
|
||||
|
||||
def test_reaction_roll_strings(self):
|
||||
"""Simulate incoming reaction requests."""
|
||||
mock_event = mock.MagicMock()
|
||||
|
||||
+27
-25
@@ -14,77 +14,79 @@ class DiceLibTestCase(TestCase):
|
||||
# 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))
|
||||
self.assertEqual(result, (5, 1, True, None, None, 1, 1))
|
||||
|
||||
# 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))
|
||||
self.assertEqual(result, (2, 0, False, None, None, 0, 1))
|
||||
|
||||
# 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'))
|
||||
self.assertEqual(result, (1, None, False, None, 'a GM intrusion', 0, 1))
|
||||
|
||||
# rolled a 17 on an attack
|
||||
with mock.patch('random.SystemRandom.randint', return_value=17):
|
||||
result = dice.lib.cypher_roll(difficulty=1, is_attack=True)
|
||||
self.assertEqual(result, (17, 5, True, '+1 damage'))
|
||||
self.assertEqual(result, (17, 5, True, '+1 damage', None, 5, 1))
|
||||
|
||||
# rolled a 18 on an attack
|
||||
with mock.patch('random.SystemRandom.randint', return_value=18):
|
||||
result = dice.lib.cypher_roll(difficulty=1, is_attack=True)
|
||||
self.assertEqual(result, (18, 6, True, '+2 damage'))
|
||||
self.assertEqual(result, (18, 6, True, '+2 damage', None, 6, 1))
|
||||
|
||||
# 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, None))
|
||||
self.assertEqual(result, (17, 5, True, None, None, 5, 1))
|
||||
|
||||
# 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, None))
|
||||
self.assertEqual(result, (18, 6, True, None, None, 6, 1))
|
||||
|
||||
# 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'))
|
||||
self.assertEqual(result, (19, 6, True, 'a minor effect', None, 6, 1))
|
||||
|
||||
# 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'))
|
||||
self.assertEqual(result, (20, 6, True, 'a MAJOR EFFECT', None, 6, 1))
|
||||
|
||||
# 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))
|
||||
result = dice.lib.cypher_roll(difficulty=1, mod=-5)
|
||||
self.assertEqual(result, (2, 5, True, None, None, 0, -4))
|
||||
|
||||
# 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))
|
||||
result = dice.lib.cypher_roll(difficulty=3, mod=-3)
|
||||
self.assertEqual(result, (2, 3, True, None, None, 0, 0))
|
||||
|
||||
# ...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))
|
||||
result = dice.lib.cypher_roll(mod=-5)
|
||||
self.assertEqual(result, (2, 5, None, None, None, 0, None))
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=2):
|
||||
result = dice.lib.cypher_roll()
|
||||
self.assertEqual(result, (2, 0, None, None, None, 0, None))
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
result = dice.lib.cypher_roll()
|
||||
self.assertEqual(result, (1, None, None, None, 'a GM intrusion', 0, 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))
|
||||
result = dice.lib.cypher_roll(mod=-2)
|
||||
self.assertEqual(result, (10, 5, None, None, None, 3, 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))
|
||||
result = dice.lib.cypher_roll(mod=2)
|
||||
self.assertEqual(result, (10, 1, None, None, None, 3, None))
|
||||
|
||||
def test_reaction_roll(self):
|
||||
"""Roll possible reactions."""
|
||||
|
||||
@@ -28,3 +28,30 @@ class DiceRollerTestCase(TestCase):
|
||||
result = self.roller.do_roll('6#3/4d6')
|
||||
self.assertEqual(result, '3 (3[1,1,1,1]), 6 (6[2,2,2,2]), 9 (9[3,3,3,3]), '
|
||||
'12 (12[4,4,4,4]), 15 (15[5,5,5,5]), 18 (18[6,6,6,6])')
|
||||
|
||||
def test_cypher_rolls(self):
|
||||
"""Roll a variety of cypher rolls."""
|
||||
with mock.patch('random.SystemRandom.randint', return_value=5):
|
||||
result = self.roller.do_roll('difficulty 5 might task to muscle good')
|
||||
self.assertEqual(result, 'task to muscle good failed. (res. 1 (d20=5) vs. might diff. 5 (5))')
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=4):
|
||||
result = self.roller.do_roll('difficulty 2-2 might attack to hit good')
|
||||
self.assertEqual(result, 'attack to hit good succeeded! (res. 1 (d20=4) vs. might diff. 0 (2-2))')
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=20):
|
||||
result = self.roller.do_roll('difficulty 2+2 might attack to hit good')
|
||||
self.assertEqual(result, 'attack to hit good succeeded, with a MAJOR EFFECT! '
|
||||
'(res. 6 (d20=20) vs. might diff. 4 (2+2))')
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=5):
|
||||
result = self.roller.do_roll('-1 might task to muscle good')
|
||||
self.assertEqual(result, 'task to muscle good beats a difficulty 2 task. (res. 1+1 (d20=5) vs. might)')
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=5):
|
||||
result = self.roller.do_roll('-1 might attack')
|
||||
self.assertEqual(result, 'attack beats a difficulty 2 task. (res. 1+1 (d20=5) vs. might)')
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
result = self.roller.do_roll('-1 might attack')
|
||||
self.assertEqual(result, 'attack beats nothing, with a GM intrusion! (res. 0+1 (d20=1) vs. might)')
|
||||
|
||||
Reference in New Issue
Block a user