0

I'm trying to implement a feature in my program that the user would be able to add or subtract a randomly generated number using a specific sided die. The foundation of my code is posted here:

import discord
import random

DND_1d20 = range(1, 21)

# Roll d20
    if message.content == ";roll 1d20":
        response = random.choice(DND_1d20)
        response_str = "You rolled {0}".format(response)
        if response_str == "You rolled 20":
            await message.channel.send("**Critical Hit!**\n You rolled 20")
        if response_str == "You rolled 1":
            await message.channel.send("**Critical Fail!**\n You rolled 1")

I would like the user to be able to specify a dice roll ";1d20" BUT also have the ability to add ";1d20+(x)" or subtract ";1d20-(x)" any number (x) from the generated dice roll. The logic would look something like this

-user ";1d20+2" Lets say the random number generated would be 6. Since the user wants to add 2 to the random number we generated, the outcome would be 8.

-bot "You rolled 8"

# Roll d20
    if message.content == ";roll 1d20":
        response = random.choice(DND_1d20)
        response_str = "You rolled {0}".format(response)
        if response_str == "You rolled 20":
            await message.channel.send("**Critical Hit!**\n You rolled 20")
        if response_str == "You rolled 1":
            await message.channel.send("**Critical Fail!**\n You rolled 1")
        else:
            if message.content == "-":

How would I go about doing this? I am really confused on where to start. I dont think the code above is right, because the message would have to exactly be a "-". Also, how would I incorporate the value (x), since it could be a vast array of numbers, or the +/- signs from the user input?

Any help is appreciated!

1 Answers1

1

Here's a more advanced solution using a library called lark to define a grammar for these dice expressions (cribbed from this question), parse those expressions into syntax trees, then evaluate those trees. Make a file named dice_grammar.py with this string:

grammar="""
start: _expr

_expr: add
    | subtract
    | roll
    | NUMBER
add: _expr "+" _expr
subtract: _expr "-" _expr
roll: [NUMBER] ("d"|"D") (NUMBER|PERCENT)

NUMBER: ("0".."9")+
PERCENT: "%"

%ignore " "
"""

If you're not familiar with grammars like this, don't panic. All this is showing is that we can roll dice, add, and subtract. We can then have a dice_transformer.py to consume the trees the parser will produce:

from lark import Transformer, v_args
from random import randint

class DiceTransformer(Transformer):
    PERCENT = lambda self, percent: 100
    NUMBER = int
    def __init__(self):
            super().__init__(visit_tokens=True)
    @v_args(inline=True)
    def start(self, expr):
            return expr
    @v_args(inline=True)
    def add(self, left, right):
            return left + right
    @v_args(inline=True)
    def subtract(self, left, right):
            return left - right
    @v_args(inline=True)
    def roll(self, qty, size):
            qty = qty or 1
            return sum(randint(1, size) for _ in range(qty))

and a dice_bot.py that uses these to evaluate dice expressions from the user:

from discord.ext import commands
from lark import Lark
from lark.exceptions import LarkError
from dice_grammar import grammar
from dice_transformer import DiceTransformer

bot = commands.Bot(";")

parser = Lark(grammar, maybe_placeholders=True)
transformer = DiceTransformer()

@bot.command()
async def roll(ctx, *, expression):
    try:
        tree = parser.parse(expression)
    except LarkError:
        await ctx.send("Bad Expression")
        return
    print(tree.pretty()) # Log the roll
    result = transformer.transform(tree)
    await ctx.send(f"You rolled: {result}")

bot.run("token")

This allows us to ask for the computation of more complicated rolls like

;roll 2d6 +7 + d% - 3d4

On the advice of Erez in the comments, I changed my answer to use lark.Transformer. You should be able to see my original code in the edit history of this answer.

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • I followed every step but when I type ";roll 2d6 +7 + d% - 3d4" the bot is unresponsive. It does however respond to a command, for example the ;roll 1d20 command I have made. There are no errors in console or any grammatical errors in my code. Do I have to run the two separate files concurrently through PyCharm for it to work? – Ghostortoast Apr 07 '20 at 22:49
  • -scratch this comment- After putting the last block of code into its own separate script, the code appeared to work. Might have to fiddle around with it to get it working in my own script, thank you very much! – Ghostortoast Apr 07 '20 at 22:55
  • If you have an `on_message` event, make sure that it includes a `bot.process_commands(message)` line – Patrick Haugh Apr 07 '20 at 23:39
  • so I tried to add a simple "hello" command, and i added the `bot.process_commands(message)` line, but I get an error in console Ignoring exception in command None: discord.ext.commands.errors.CommandNotFound: Command "hello" is not found `@client.event async def on_message(message): await bot.process_commands(message) if message.content == ";hello": await message.channel.send("Hello!")` Thoughts? I saw you answer this on another post some time ago, but the solution didn't quite work for me. – Ghostortoast Apr 08 '20 at 15:26
  • 1
    You should use different prefixes for your `bot.command` commands and the commands you define inside `on_message`. Every time your bot sees the commands prefix (which we defined to be `;`) it assumes that someone is trying to invoke a command. You can also put `process_commands` at the end of `on_message`, so that even if it fails your other code executes first – Patrick Haugh Apr 08 '20 at 15:53
  • 1
    Your code would be a lot cleaner if you used Lark's transformers to evaluate the tree. `from lark import Transformer`. See here for the docs: https://lark-parser.readthedocs.io/en/latest/visitors/ – Erez Apr 08 '20 at 19:19