Search code examples
python-3.xbotsdiscord.pydice

Adding or Subtracting a number from a randomly generated number


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!


Solution

  • 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.