DnD5e Character Creator Terminal Program

I made a functional DnD character sheet creator in Python. Right now it has a limited selection of classes and races, and is lacking in some features I’d like to add in later on, but it is usable and generates stats, proficiencies, and feats for a character based on user input.

I know my code can be made more efficient and compact, although I don’t really know where to start. I have a bad habit of rewriting things that I should probably turn into functions to save space. Let me know if you have any tips on where to start cleaning up my code, or how to avoid writing like this in the future.

The class Dice seems unnecessary. The Stat class in interesting, but asi seems unclear, and mod currently could return a string. Rather, you’d be better off chucking an error from stat when the value changes if mod can’t resolve.

If you wanted to use classes, DndCharacter seems something obvious to implement. Rather than having things like st = Stat("Strength") hanging about in global space, have those live in a class.

You have functions for each of your races. They all begin with something like:

def choose_dwarf():
    character_race = "Dwarf"

This is meant to set the global value for character_race, but that mayn’t always work. That character_race reasonably belong to DndCharacter. Choose race should be passed an instance of DndCharacter.

Feats, languages, and proficiencies are all floating about. They could be corralled into something more organized.

The stat pick looked neat. I played with this a little. You can probably apply this approach to other aspects of the code:

import random

def die_roll(sides):
    return random.randint(1,sides)


def roll_stat():
    return sum(sorted(die_roll(6) for _ in range (4))[1:])


class Stat:
    # just made a list of these: only 20
    MOD_LOOK = dict(((1, -5), (2, -4), (3, -4), (4, -3), (5, -3), (6, -2), (7, -2), (8, -1), (9, -1), (10, 0), (11, 0), (12, 1), (13, 1), (14, 2), (15, 2), (16, 3), (17, 3), (18, 4), (19, 4), (20, 5)))

    def __init__(self, name, value = 1):
        self.name = name
        self.value = value
        self.__validate()

    def __repr__(self):
        return "{}: {} ({})".format(self.name, self.value, self.mod())
    
    def __iadd__(self, n):
        self.value += n
        return self

    def __validate(self):
        if self.value not in Stat.MOD_LOOK:
            raise Exception("{} invalid stat value".format(self.value))
        
    def mod(self):
        return Stat.MOD_LOOK.get(self.value)


class Stats:
    def __init__(self):
        self.st = Stat("Strength")
        self.dx = Stat("Dexterity")
        self.cn = Stat("Constitution")
        self.nt = Stat("Intelligence")
        self.wm = Stat("Wisdom")
        self.ch = Stat("Charisma")

    def items(self):
        return [self.st, self.dx, self.cn, self.nt, self.wm, self.ch]
    

class DndCharacter:
    def __init__(self, name):
        self.name = name
        self.stats = Stats()
        self.level = 1
        self.alignment = " "
        self.race = ""
        self.dnd_class = ""

    def show(self):
        print(self.name + ", a level " + str(self.level) + " " + self.alignment + " " + self.race + " " + self.dnd_class + "\n")
        print("Stats:")
        for x in self.stats.items():
            print(x)


def assign_stats(dnd_char):
    attributes = sorted((roll_stat() for _ in range(len(dnd_char.stats.items()))), reverse=True)
    for attr in dnd_char.stats.items():
        pick = None
        while pick == None:
            try:
                pick = int(input("Choose which number ({}) you will attribute to your {} stat: ".format(attributes, attr.name)))
            except:
                pass
            if pick == None or pick not in attributes:
                print("Error, try again.")
                pick = None
        attr += pick - 1 # don't like this - 1, perhaps other elements could be rethought
        attributes.remove(pick)


x = DndCharacter("Alice")

assign_stats(x)

x.show()