Interactive Discord bot for tabletop RPG I'm new to Python, but have experience in a variety of other languages. I've learnt that a code review early on would make sure I don't start with bad habits. Which are more difficult to get out the longer you keep them.

This is a Discord bot for one of my hobby projects.

import os
import random
import discord
from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()
token = os.getenv('DISCORD_TOKEN')

bot = commands.Bot(command_prefix='!')

@bot.command(name='ability', aliases=['a', 'ab'], help='Roll an ability check. Parameters: ability [no. of heroic dice].')
async def ability(ctx, ability:int, heroic:int=0):
    if ability &lt; -5 or ability &gt; 5:
        await ctx.send('Invalid ability value "{}". Must be between -5 and +5'.format(ability))
        return
    if heroic &lt; 0 or heroic &gt; 5:
        await ctx.send('Invalid heroic dice amount "{}". Must be between 0 and 5 (3 + 2 ritual dice)'.format(heroic))
        return

    dice = 2 + abs(ability) + heroic
    keep_dice = 2 + heroic
    if (ability &lt; 0):
        keep = 'lowest'
    else:
        keep = 'highest'

    roll = []
    for x in range(0, dice):
        roll.append(roll_die())

    result = sorted(roll)
    if keep == 'highest':
        result.reverse()
    outcome = result[:keep_dice]

    # check for critical success (a pair) or failure (all 1s)
    last = -1
    critical = False
    fail = True
    for d in outcome:
        if d != 1:
            fail = False
        if d == last and d != 1:
            critical = True
        last = d

    if critical:
        # critical success - add the next highest/lowest die as well
        if keep_dice == dice:
            # roll an additional die
            outcome.append(roll_die())
            outcome.sort()
        else:
            outcome = result[:keep_dice+1]
    elif fail:
        outcome = []
        for x in range(0, keep_dice):
            outcome.append(0)

    # now sum up all our dice for the total
    sum = 0
    for d in outcome:
        sum += d


    embed = discord.Embed(title='**Dragon Eye** Ability Test for {}'.format(ctx.author.name),
                          description='Rolling {}d10, keep {} {}'.format(dice, keep, keep_dice),
                          color=0x22a7f0)
    embed.add_field(name='Roll', value=format(roll), inline=False)
    embed.add_field(name='Outcome', value=format(outcome), inline=True)
    embed.add_field(name='Total', value=format(sum), inline=True)
    if critical:
        embed.add_field(name='Additional Info', value='**Critical Success**', inline=True)
    elif fail:
        embed.add_field(name='Additional Info', value='**Critical Fail**', inline=True)

    await ctx.send(embed=embed)


def roll_die(sides=10):
    die = random.randint(1, sides)
    return die


bot.run(token)
List Comprehension

Instead of


roll = []
for x in range(0, dice):
    roll.append(roll_die())



do this

roll = [roll_die() for _ in range(0, dice)]


The result is the same, it requires less code, and more pythonic. This also utilizes the _. The underscore is meant to show that the variable in the loop isn't used. This applies to other places in your code as well.

Ternary Operator

Instead of


if (ability &lt; 0):
    keep = 'lowest'
else:
    keep = 'highest'



do this

keep = 'lowest' if ability &lt; 0 else 'highest'


Using a ternary operator can keep you from writing more code, and looks a lot nicer. In this case, it converts four lines into one, which is a big plus.

Direct Returns

You don't have to assign a value inside a function before returning it. In this case, you don't need a dice variable inside roll_die(). You can simply return the call to random.randint. Take a look:

def roll_die(sides=10):
    return random.randint(1, sides)


Using sum

Instead of


sum = 0
for d in outcome:
    sum += d



use pythons built-in sum function

dice_sum = sum(outcome)


The sum functions returns the sum of all the numbers in an iterable. So, you can pass the list used in the loop to that function. It does the same thing you do, but it's already written.