add a simple oracle to the dice roller

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
2026-07-25 09:03:19 -05:00
parent 2cdb6feb44
commit 49f3d35f2e
4 changed files with 172 additions and 26 deletions
+27 -2
View File
@@ -6,11 +6,12 @@ from django.conf import settings
from irc.client import NickMask
from dice import dice_roller
from dice.lib import reaction_roll
from dice.lib import oracle_roll, reaction_roll
from ircbot.lib import Plugin
logger = logging.getLogger(__name__)
ORACLE_COMMAND_REGEX = r'^!oracle\s+(.*\?)$'
REACTION_COMMAND_REGEX = r'^!reaction$'
@@ -28,6 +29,8 @@ class Dice(Plugin):
self.handle_roll, -20)
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!random\s+(.*)$',
self.handle_random, -20)
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], ORACLE_COMMAND_REGEX,
self.handle_oracle_roll, -20)
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], REACTION_COMMAND_REGEX,
self.handle_reaction_roll, -20)
@@ -37,7 +40,7 @@ class Dice(Plugin):
"""Tear down handlers."""
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_roll)
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_random)
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_cypher_roll)
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_oracle_roll)
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_reaction_roll)
super(Dice, self).stop()
@@ -59,6 +62,28 @@ class Dice(Plugin):
reply = "{0:s}".format(choice)
return self.bot.reply(event, reply)
def handle_oracle_roll(self, connection, event, match):
"""Ask the oracle a question, get a yes/no/etc. answer."""
inquiry = match.group(1)
roll, result = oracle_roll()
result_str = result
if result in ('YES', 'YES, AND'):
result_str = f"9{result_str}"
elif result in ('YES, BUT', ):
result_str = f"3{result_str}"
elif result in ('NO, BUT', ):
result_str = f"14{result_str}"
elif result in ('NO', 'NO, AND'):
result_str = f"4{result_str}"
if result in ('YES, AND', 'NO, AND'):
result_str = f"{result_str}"
result_str = f"{result_str} 14({roll})"
return self.bot.reply(event, f"{inquiry} {result_str}")
def handle_reaction_roll(self, connection, event, match):
"""Handle the !reaction roll."""
nick = NickMask(event.source).nick
+27
View File
@@ -229,6 +229,33 @@ def generic_result(trials, mods, comment, ircify):
return output
def oracle_roll() -> Tuple[int, str]:
"""Make a roll with some specific interpretations for simple yes/no/etc. renderings.
1: no, and (something terrible happens; 5% chance)
2-4: no (shut down; 15% chance)
5-10: no, but (not really, but maybe a compromise twist; 30% chance)
11-16: yes, but (yes, but there's a complication; 30% chance)
17-19: yes (yes, up to creativity to decide if it's simple or with a complication; 15% chance)
20: yes, and (yes, and something cool happens; 5% chance)
"""
roll = rand.randint(1, 20)
if roll == 1:
result_str = 'NO, AND'
elif 2 <= roll <= 4:
result_str = 'NO'
elif 5 <= roll <= 10:
result_str = 'NO, BUT'
elif 11 <= roll <= 16:
result_str = 'YES, BUT'
elif 17 <= roll <= 19:
result_str = 'YES'
elif roll == 20:
result_str = 'YES, AND'
return roll, result_str
def reaction_roll():
"""Make a reaction roll, which gives some oracle-like randomness to uncertain situations (for solo play or similar).
+68 -1
View File
@@ -8,7 +8,7 @@ import dice.ircplugin
from ircbot.models import IrcServer
class MarkovTestCase(TestCase):
class DiceTestCase(TestCase):
"""Test the markov plugin."""
fixtures = ['tests/fixtures/irc_server_fixture.json']
@@ -23,6 +23,73 @@ class MarkovTestCase(TestCase):
self.plugin = dice.ircplugin.Dice(self.mock_bot, self.mock_connection, mock.MagicMock())
def test_oracle_roll_strings(self):
"""Simulate incoming oracle requests."""
mock_event = mock.MagicMock()
mock_event.source = 'test!test@test'
mock_event.target = '#test'
mock_event.recursing = False
# very good yes is bold green
mock_event.arguments = ['!oracle is this a test?']
match = re.search(dice.ircplugin.ORACLE_COMMAND_REGEX, mock_event.arguments[0])
with mock.patch('random.SystemRandom.randint', return_value=20):
self.plugin.handle_oracle_roll(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_with(
mock_event,
'is this a test? 9YES, AND 14(20)'
)
# good yes is green
mock_event.arguments = ['!oracle is this a test?']
match = re.search(dice.ircplugin.ORACLE_COMMAND_REGEX, mock_event.arguments[0])
with mock.patch('random.SystemRandom.randint', return_value=17):
self.plugin.handle_oracle_roll(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_with(
mock_event,
'is this a test? 9YES 14(17)'
)
# decent yes is dark green
mock_event.arguments = ['!oracle is this a test?']
match = re.search(dice.ircplugin.ORACLE_COMMAND_REGEX, mock_event.arguments[0])
with mock.patch('random.SystemRandom.randint', return_value=11):
self.plugin.handle_oracle_roll(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_with(
mock_event,
'is this a test? 3YES, BUT 14(11)'
)
# decent no is dark gray
mock_event.arguments = ['!oracle is this a test?']
match = re.search(dice.ircplugin.ORACLE_COMMAND_REGEX, mock_event.arguments[0])
with mock.patch('random.SystemRandom.randint', return_value=10):
self.plugin.handle_oracle_roll(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_with(
mock_event,
'is this a test? 14NO, BUT 14(10)'
)
# no is red
mock_event.arguments = ['!oracle is this a test?']
match = re.search(dice.ircplugin.ORACLE_COMMAND_REGEX, mock_event.arguments[0])
with mock.patch('random.SystemRandom.randint', return_value=3):
self.plugin.handle_oracle_roll(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_with(
mock_event,
'is this a test? 4NO 14(3)'
)
# terrible no is bold red
mock_event.arguments = ['!oracle is this a test?']
match = re.search(dice.ircplugin.ORACLE_COMMAND_REGEX, mock_event.arguments[0])
with mock.patch('random.SystemRandom.randint', return_value=1):
self.plugin.handle_oracle_roll(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_with(
mock_event,
'is this a test? 4NO, AND 14(1)'
)
def test_reaction_roll_strings(self):
"""Simulate incoming reaction requests."""
mock_event = mock.MagicMock()
+50 -23
View File
@@ -109,29 +109,6 @@ class CypherRollTestCase(TestCase):
self.assertEqual(result, (1, 2, True, None, "a GM intrusion", 0, 0))
class DiceLibTestCase(TestCase):
"""Test that a variety of dice rolls work as expected."""
def test_reaction_roll(self):
"""Roll possible reactions."""
with mock.patch('random.SystemRandom.randint', return_value=1):
self.assertEqual(dice.lib.reaction_roll(), (1, 'VERY negative', '--'))
with mock.patch('random.SystemRandom.randint', return_value=2):
self.assertEqual(dice.lib.reaction_roll(), (2, 'negative', '-'))
with mock.patch('random.SystemRandom.randint', return_value=6):
self.assertEqual(dice.lib.reaction_roll(), (6, 'negative', '-'))
with mock.patch('random.SystemRandom.randint', return_value=7):
self.assertEqual(dice.lib.reaction_roll(), (7, 'positive, with complications', '~'))
with mock.patch('random.SystemRandom.randint', return_value=14):
self.assertEqual(dice.lib.reaction_roll(), (14, 'positive, with complications', '~'))
with mock.patch('random.SystemRandom.randint', return_value=15):
self.assertEqual(dice.lib.reaction_roll(), (15, 'positive', '+'))
with mock.patch('random.SystemRandom.randint', return_value=19):
self.assertEqual(dice.lib.reaction_roll(), (19, 'positive', '+'))
with mock.patch('random.SystemRandom.randint', return_value=20):
self.assertEqual(dice.lib.reaction_roll(), (20, 'VERY positive', '++'))
class CypherRollStringTestCase(TestCase):
"""Test that Cypher System roll results are in the expected string representation."""
@@ -177,3 +154,53 @@ class CypherRollStringTestCase(TestCase):
comment="Test")
self.assertEqual(result,
"Test Might roll succeeded, with a GM intrusion! (result 0 (d20=1) vs. difficulty 0 (2-2))")
class OracleRollTestCase(TestCase):
"""Test that a variety of oracle results are as expected."""
def test_oracle_results(self):
"""Roll possible oracle reactions."""
with mock.patch('random.SystemRandom.randint', return_value=1):
self.assertEqual(dice.lib.oracle_roll(), (1, 'NO, AND'))
with mock.patch('random.SystemRandom.randint', return_value=2):
self.assertEqual(dice.lib.oracle_roll(), (2, 'NO'))
with mock.patch('random.SystemRandom.randint', return_value=4):
self.assertEqual(dice.lib.oracle_roll(), (4, 'NO'))
with mock.patch('random.SystemRandom.randint', return_value=5):
self.assertEqual(dice.lib.oracle_roll(), (5, 'NO, BUT'))
with mock.patch('random.SystemRandom.randint', return_value=10):
self.assertEqual(dice.lib.oracle_roll(), (10, 'NO, BUT'))
with mock.patch('random.SystemRandom.randint', return_value=11):
self.assertEqual(dice.lib.oracle_roll(), (11, 'YES, BUT'))
with mock.patch('random.SystemRandom.randint', return_value=16):
self.assertEqual(dice.lib.oracle_roll(), (16, 'YES, BUT'))
with mock.patch('random.SystemRandom.randint', return_value=17):
self.assertEqual(dice.lib.oracle_roll(), (17, 'YES'))
with mock.patch('random.SystemRandom.randint', return_value=19):
self.assertEqual(dice.lib.oracle_roll(), (19, 'YES'))
with mock.patch('random.SystemRandom.randint', return_value=20):
self.assertEqual(dice.lib.oracle_roll(), (20, 'YES, AND'))
class ReactionRollTestCase(TestCase):
"""Test that a variety of dice rolls work as expected."""
def test_reaction_roll(self):
"""Roll possible reactions."""
with mock.patch('random.SystemRandom.randint', return_value=1):
self.assertEqual(dice.lib.reaction_roll(), (1, 'VERY negative', '--'))
with mock.patch('random.SystemRandom.randint', return_value=2):
self.assertEqual(dice.lib.reaction_roll(), (2, 'negative', '-'))
with mock.patch('random.SystemRandom.randint', return_value=6):
self.assertEqual(dice.lib.reaction_roll(), (6, 'negative', '-'))
with mock.patch('random.SystemRandom.randint', return_value=7):
self.assertEqual(dice.lib.reaction_roll(), (7, 'positive, with complications', '~'))
with mock.patch('random.SystemRandom.randint', return_value=14):
self.assertEqual(dice.lib.reaction_roll(), (14, 'positive, with complications', '~'))
with mock.patch('random.SystemRandom.randint', return_value=15):
self.assertEqual(dice.lib.reaction_roll(), (15, 'positive', '+'))
with mock.patch('random.SystemRandom.randint', return_value=19):
self.assertEqual(dice.lib.reaction_roll(), (19, 'positive', '+'))
with mock.patch('random.SystemRandom.randint', return_value=20):
self.assertEqual(dice.lib.reaction_roll(), (20, 'VERY positive', '++'))