1

I'm designing a Character Sheet from Dungeons & Dragons in Python, using Tkinter to take care of the graphical interface. However, I wanted to add an element (in this case a "proficiency" in a skill) to a list, if the checkbox corresponding to the same skill is active. The checkbox buttons are working for some commands like exit ("root.destroy") but this in particulary doesn't seem to do anything. I created a class Character, this Character has an empty Proficencies list and to add a proficiency to it, I created a function that did it whenever the matching checkbox value was set to True ("1")

import tkinter as tk

def modifier(score):
    score = int(score)

    return (score - 10) // 2

class Character:   
    proficiencies = []

    abilities = {"Strength": 12, "Dexterity": 16, "Constitution": 14, "Intelligence": 10, "Wisdom": 16, "Charisma": 9}        

    skills = {"Athletics": 0, "Acrobatics": 0, "Sleight of Hand": 0, "Stealth": 0, "Arcana": 0,  "History": 0,
              "Investigation": 0, "Nature": 0, "Religion": 0,"Animal Handling": 0, "Insight": 0, "Medicine": 0, 
              "Perception": 0, "Survival": 0, "Deception": 0, "Intimidation": 0, "Performance": 0, "Persuasion": 0}

counter = 0 
variables = []
skills = []

root = tk.Tk()

def addSkill():
    if exec("var" + str(counter) + ".get() == 1"):
        Character.proficiencies.append(skills[counter].replace('_', ' '))
    elif exec("var" + str(counter) + ".get() == 0"):
        pass

for skill in sorted(Character.skills.keys()): 
    skills.append(skill.replace(" ", "_"))
    exec("var" + str(counter) + " = tk.IntVar()")
    exec(skill.replace(" ", "") + "= tk.Checkbutton(root, text=skill, variable = var" + str(counter) + ", command = addSkill)")
    exec(skill.replace(" ", "") + ".pack()")    
    variables.append("var" + str(counter))

    counter += 1

counter = 0

root.mainloop()

index = 0

for skill in Character.skills:
        if index == 0:
            ability = "Strength"

        elif index >= 1 and index <= 3:
            ability = "Dexterity"

        elif index >= 4 and index <= 8:
            ability = "Intelligence"

        elif index >= 9 and index <= 13:
            ability = "Wisdom"

        elif index >= 14:
            ability = "Charisma"            

        if skill in Character.proficiencies:
            Character.skills[skill] = 10 + (modifier(Character.abilities[ability]) + 2) * 2
        else:
            Character.skills[skill] = 10 + modifier(Character.abilities[ability]) * 2  

        index += 1     
martineau
  • 119,623
  • 25
  • 170
  • 301
Educorreia
  • 375
  • 5
  • 21
  • @martineau I deleted what didn't matter already – Educorreia Feb 05 '19 at 13:18
  • 2
    You really shouldn't be using `exec` this way. Your code will be _much_ easier to understand, debug, and maintain if you use a dictionary to hold your references rather than try to create dynamically named variables. See [How do I create a variable number of variables?](https://stackoverflow.com/questions/1373164/how-do-i-create-a-variable-number-of-variables) – Bryan Oakley Feb 05 '19 at 19:15
  • @BryanOakley Thank you for the suggestion – Educorreia Feb 05 '19 at 19:54
  • Completely agree with @Bryan. Regardless, here's a couple of observations about your code—all but the first of which should be pertinent even after making your code dictionary-based. The first is that the `eval()` always returns `None`, so a conditional like `exec("var" + str(counter) + ".get() == 1")` will _never_ be `True`. The other is that, even if the latter wasn't the case, the `addSkill()` callback always references the value of `var18.get()` because that's the final value of the global variable `counter` it references whenever it executes, regardless of which `Checkbutton` was clicked. – martineau Feb 05 '19 at 20:28

2 Answers2

0

To whoever is having troubles with the same mistake, I think I've found the error. Instead of:

def addSkill():
    if exec("var" + str(counter) + ".get() == 1"):
        Character.proficiencies.append(skills[counter].replace('_', ' '))
    elif exec("var" + str(counter) + ".get() == 0"):
        pass 

I wrote:

def addSkill():
    if var0.get() == 1:
        Character.proficiencies.append(skills[0].replace('_', ' '))
    if var1.get() == 1:
        Character.proficiencies.append(skills[1].replace('_', ' '))
    ...
    if var17.get() == 1:
        Character.proficiencies.append(skills[17].replace('_', ' '))

And now it works :)

Educorreia
  • 375
  • 5
  • 21
0

Here's an example of following Bryan Oakley's suggestion to avoid using exec() and not dynamically create named variables, I think you'll agree it's quite a bit easier to read and understand than the code you where using.

import tkinter as tk


SKILL_NAMES = ('Athletics', 'Acrobatics', 'Sleight of Hand', 'Stealth', 'Arcana',
               'History', 'Investigation', 'Nature', 'Religion', 'Animal Handling',
               'Insight', 'Medicine', 'Perception', 'Survival', 'Deception',
               'Intimidation', 'Performance', 'Persuasion')

class Character:
    proficiencies = []
    abilities = {"Strength": 12, "Dexterity": 16, "Constitution": 14,
                 "Intelligence": 10, "Wisdom": 16, "Charisma": 9}
    skills = dict.fromkeys(SKILL_NAMES, 0)


def modifier(score):
    return (int(score) - 10) // 2


root = tk.Tk()

# Create tk.IntVars for holding value of each skill.
skill_vars = {skill: tk.IntVar() for skill in Character.skills}

# tkinter Checkbutton callback.
def addSkill(skill, var):
    """ Add skill to proficiencies if Checkbutton is now checked. """
    if var.get() == 1:
        Character.proficiencies.append(skill)

# Create a Checkbutton corresponding to each skill.
for skill in Character.skills:
    btn = tk.Checkbutton(root, text=skill, variable=skill_vars[skill],
                command=lambda name=skill, var=skill_vars[skill]: addSkill(name, var))
    btn.pack()


root.mainloop()


for index, skill in enumerate(Character.skills):
    if index == 0:
        ability = "Strength"
    elif 1 <= index <= 3:
        ability = "Dexterity"
    elif 4 <= index <= 8:
        ability = "Intelligence"
    elif 9 <= index <= 13:
        ability = "Wisdom"
    elif index >= 14:
        ability = "Charisma"

    if skill in Character.proficiencies:
        Character.skills[skill] = 10 + (modifier(Character.abilities[ability]) + 2) * 2
    else:
        Character.skills[skill] = 10 + modifier(Character.abilities[ability]) * 2
martineau
  • 119,623
  • 25
  • 170
  • 301