Dice rolls are feature complete.

This commit is contained in:
Josh W 2024-07-02 16:28:26 -04:00
parent 54f0ec1d29
commit 589ea29b6d
2 changed files with 135 additions and 27 deletions

View file

@ -1,16 +1,20 @@
"""
Chance - Commands for simulating 'random' chance, such as dice rolls, picking
cards from a deck, etc.
"""
import asyncio
from random import choice, randint
import re
import niobot
import niobot # type: ignore
from niobot import (
CommandParserError,
Context,
Module,
NioBot,
)
from .stubs import ERROR_STARTS, get_random_stub, format_mention
from .stubs import ERROR_STARTS, get_random_stub
_DICE_LOCATIONS = [
@ -19,20 +23,44 @@ _DICE_LOCATIONS = [
"spins around and a number of dice appear in his hands.",
"opens the closest drawer and takes out a container of random dice.",
"powers on a tablet and executes a dice-rolling application.",
"steals several dice from various board game boxes on the shelf.",
]
_DICE_EXCLAMATIONS = [
"Daddy needs a new pair of shoes!",
"Blessed Mother of Acceleration, don't fail me now!",
"Come on, Box Car Willy!",
"I cast Magic Missle into the darkness!",
"Lightning Bolt! Lightning Bolt! SLEEEP!",
"RNGesus--please bless this roll we are about to receive.",
"I'm going to backstab with a ballista!",
"Who wants to blow on my dice!? anyone!? ...guys?",
]
class ChanceModule(Module):
"""Commands that simulate chance (Dice rolls, card selection, etc)."""
def __init__(self, bot: NioBot):
self.bot = bot
@niobot.command()
async def roll(self, ctx: Context, dice: str):
"""Roll some dice with an optional modifier."""
"""
Roll some dice with an optional modifier.
Syntax:
!roll <n>d<s>[<+,-,*,/><m>]
n = number of dice (1-100)
s = number of sides on dice (2-100)
m = optional modifier (1-1000000)
Examples:
!roll 1d20
!roll 6d6+3
"""
err_prelude = get_random_stub(ERROR_STARTS, ctx.message.sender)
valid = re.match(r"(\d+)d(\d+)([\+-/\*]\d+)?", dice)
# Validate the entire dice roll syntax
valid = re.match(r"^(\d+)d(\d+)([\+-/\*]\d+)?", dice)
if valid is None:
await ctx.client.send_message(
ctx.room,
@ -42,8 +70,9 @@ class ChanceModule(Module):
content_type="html",
)
raise CommandParserError("Invalid dice roll notation.")
dice_parts = (int(valid.group(1)), int(valid.group(2)), valid.group(3))
# Validate number of dice and dice surface count.
dice_parts = (int(valid.group(1)), int(valid.group(2)), valid.group(3))
if not 1 <= dice_parts[0] <= 100:
await ctx.client.send_message(
ctx.room,
@ -64,26 +93,100 @@ class ChanceModule(Module):
)
raise CommandParserError("Dice must have between 2 and 100 faces.")
# Validate the modifier, if one was detected.
extra = dice.replace(f"{dice_parts[0]}d{dice_parts[1]}", "")
if dice_parts[2] is None and extra != "":
await ctx.client.send_message(
ctx.room,
(
f"{err_prelude}that's an invalid modifier. I only take "
f"addition (+), subtraction (-), multiplication (*), and "
f"division (/). The number following must be between 1 and "
f"1000000."
),
reply_to=ctx.message,
message_type="m.text",
content_type="html",
)
raise CommandParserError("Invalid dice roll modifier.")
if dice_parts[2] is not None:
modifier = int(dice_parts[2][1:])
if not 1 <= modifier <= 1000000:
await ctx.client.send_message(
ctx.room,
(
f"{err_prelude}the modifier amount can only be between "
f"1 and 1000000."
),
reply_to=ctx.message,
message_type="m.text",
content_type="html",
)
raise CommandParserError("Modifier must be between 1 and 1000000.")
# Tom gets the dice.
await ctx.client.send_message(
ctx.room,
f"<em>{choice(_DICE_LOCATIONS)}</em>",
message_type="m.emote",
content_type="html",
)
await asyncio.sleep(1)
# Makes a silly comment before rolling.
await ctx.client.send_message(
ctx.room,
f"{choice(_DICE_EXCLAMATIONS)}",
message_type="m.text",
content_type="html",
)
await asyncio.sleep(1)
rolls: list[int] = []
for _ in range(dice_parts[0]):
rolls.append(randint(1, dice_parts[1]))
total = sum(rolls)
# Then reveals the results.
if dice_parts[2] is not None:
match dice_parts[2][0]:
case "+":
action = "adds"
new_total = total + modifier
case "-":
action = "subtracts"
new_total = total - modifier
case "*":
action = "multiplies by"
new_total = total * modifier
case "/":
action = "divides by"
new_total = total // modifier
case _:
raise CommandParserError("Strange dice modifier parse error.")
await ctx.client.send_message(
ctx.room,
choice(_DICE_LOCATIONS),
(
f"<em>rolls <strong>{dice_parts[0]}d{dice_parts[1]}</strong> "
f"with a count of <strong>{total}</strong>. He then "
f"<strong>{action} {modifier}</strong> for a final count "
f"of <strong>{new_total}</strong>.</em>"
),
reply_to=ctx.message,
message_type="m.emote",
content_type="html",
)
# Give some delay to allow for reading the emote.
await asyncio.sleep(1)
html_sender = format_mention(ctx.message.sender)
else:
# html_sender = format_mention(ctx.message.sender)
await ctx.client.send_message(
ctx.room,
f"{html_sender}: {dice} = {total} ({rolls})",
(
f"<em>rolls <strong>{dice_parts[0]}d{dice_parts[1]}</strong> "
f"and the final count is <strong>{total}</strong>.</em>"
),
reply_to=ctx.message,
message_type="m.text",
message_type="m.emote",
content_type="html",
)

View file

@ -1,3 +1,8 @@
"""
Stubs - A collection of personalized message starters/finishers and functions to
give the bot some personality.
"""
from random import choice