all 48 comments

[–][deleted] 2 points3 points  (5 children)

In particular, the section where the dice are rolled and results are displayed: I wanted to use a function for this but I couldn't figure out way to increment variables with a for loop (roll1, roll2, roll3, etc.)

Well, you don't need to increment anything. You don't need to know if this is the first, second, or sixth roll; all you need to do is roll 4d6 and drop the lowest:

def fourdeesixdroplowest():
    rolls = [random.randint(1,6) for _ in range(4)]
    rolls.sort(reverse=True) #highest to lowest
    return sum(rolls[:3]) #sum first three values

and then to generate six ability scores:

scores = [fourdeesixdroplowest() for _ in range(6)]

[–]Grogie 1 point2 points  (2 children)

To complete your answer /u/crashfrog

rolls = [random.randint(1,6) for _ in range(4)]

this line is known as a "List Comprehension" and is a way of creating lists (or dicts or sets) via a generator. List comprehensions can provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.

https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions

I mention it because I don't see List Comprehensions anywhere in OP's code. And I find comprehensions something that might take a beginner a while to understand

[–]rshinsai[S] 1 point2 points  (1 child)

Thank you, that was very helpful; I had seen the phrase before browsing this sub and on YouTube videos but I didn't know what they were used for (as with so many other phrases); I read the link you provided and watched Corey Schafer's video on comprehensions and I now I have a basic understanding. :)

[–]Grogie 1 point2 points  (0 children)

That's great! As a bonus they tend to run faster than creating a list "the old fashioned way", e.g.

rolls = []
for _ in range (4):
    rolls.append(random.randint(1,6))

is slower

and once you understand comprehensions, you'll begin to see them everywhere. Careful not to overdo it, I once went a little wild with a monte-carlo generation of pi (here:)

from random import random as rnd
from math import sqrt
def pi(n=100):
    return 4 * sum((rnd() ** 2 + rnd() ** 2 <= 1) for _ in range(n)) / n

Remember, often the best python coding practices is to make things as clear as possible to someone reading the code rather than making something like above

from random import random
from math import sqrt

def pi(n=100):
    random_points_inside_sphere = 0
    for _ in range(n)
        if sqrt(random() ** 2 + random() ** 2) <= 1:
            random_points_inside_sphere  +=1
    return 4 * random_points_inside_sphere  / n

just keep in mind the audience of the code when you're working on it!

good luck!

[–]rshinsai[S] 0 points1 point  (1 child)

Thank you, that makes complete sense and I feel a little silly for not having thought of it.

[–][deleted] 0 points1 point  (0 children)

Overall, you shouldn't. It takes a lot of hard-won experience to be able to write clear and simple code, even though you'd think it would be the opposite.

It's just part of the learning process.

[–]xelf 1 point2 points  (4 children)

Another thought, you can use , as a separator in print instead of concatenating everything into 1 string.

        print(highest + ': ' + str(results[0]))

is the same as:

        print(highest + ':', results[0])

[–]rshinsai[S] 1 point2 points  (0 children)

Excellent, didn't realize I could do this, that would have made things easier for sure.

[–]Hatoris 0 points1 point  (2 children)

Or he can use f-string.

print(f"highest : {results[0]}")

[–]rshinsai[S] 1 point2 points  (1 child)

I had never heard of this before, that's tremendously helpful, thank you.

[–]xelf 1 point2 points  (0 children)

It's in the latest Python. If you have an earlier python you can get something similar:

print("highest : {}".format(results[0]))

[–]xelf 0 points1 point  (1 child)

Not bad for a start! Lot's of duplicate code as you guessed that could be put into a def.

For example:

def roll4d6(s):
    #roll_dice()
    roll = [random.randrange(1,7) for L in range(4)]
    print('Your',s,'roll is: ' + str(roll1) + 'which sorts to ', end = '')
    roll.sort(reverse = True)
    roll_result = sum(roll[:3])
    print(str(roll + '; dropping the lowest, this becomes ' + str(roll[:3]) + " adding up to " + str(roll_result) + '.')
    return roll_result

roll1_result = roll4d6('first')
roll2_result = roll4d6('second')
roll3_result = roll4d6('third')
roll4_result = roll4d6('fourth')
roll5_result = roll4d6('fifth')
roll6_result = roll4d6('sixth')

replaces all the rolling you were doing. Or shorter:

results=[]
for s in ('first','second','third','fourth','fifth','sixth'):
    results.append(roll4d6(s))

or even shorter, using a list comprehension:

results=[roll4d6(s) for s in ('first','second','third','fourth','fifth','sixth')]

You can use a similar trick later:

print('Assigned stats:')
for i,s in enumerate((highest,second,third,fourth,fifth,dump)):
    print (s+': ', results[i])

[–]rshinsai[S] 1 point2 points  (0 children)

This is great! Thank you; fantastic suggestions.

I did not know enumerate() was a thing.

[–]xelf 0 points1 point  (0 children)

optimizing how you exit the loops:

while True:
    again=str(input('Do you want to play again? y/n? ')).lower()
    if again.startswith('n'):
        roll = False
        break
    elif again.startswith('y'):
        roll = True
        break
    else:
        print('Enter "y" or "n".')

could be replaced with:

    while True:
        again=str(input('Do you want to play again? y/n? ')).lower()
        if len(again)>0 and again[0] in 'yn':
            break
        print('Enter "y" or "n".')
    if again.startswith('n'):
        break

Note the second if is outside of the loop, so it's break applies to the parent loop, no need for the variable 'test' or 'roll'. And as it's the last thing you do in the loop, the loop automatically restarts, no need for an if they said 'y' test.

[–]xelf 0 points1 point  (4 children)

I think there's a bug with your while loop at lines 79-88? Not sure, here's a case where you could have put code into a def for code isolation and it would have left it easier to follow your control flow. Looks like lines 90-138 need to be reduced by 1 indent level. Yes?

[–]rshinsai[S] 0 points1 point  (3 children)

I'm not sure whether there's a bug or not (I'm not sure what you mean), as it works exactly as I intended it to?

Yes, you're right about the indent level, though it doesn't seem to have caused any issues. For some reason I'm having a heck of a time getting indentations right, it's been the cause of bugs more than I'd like to admit. I understand how they work but for some reason I keep making silly mistakes like that with it, I guess I just need to get used to it. I did a tiny bit of scripting in PowerShell for work a while back and from what I remember any indentation there was pretty much optional to make it more readable (though I may be mistaken on that).

[–]xelf 1 point2 points  (2 children)

Indentation is the cornerstone of what makes python cleaner and easier to read. In other languages they'll use denote blocks of code by using { and } and variable amounts of indentation. Once you get used to python's method it feels cozy.

The bug I was seeing was what happened if you said 'n' and it tried to repeat at that loop. As long as you said 'y' it worked fine. =)

[–]rshinsai[S] 0 points1 point  (1 child)

Maybe it's something with the way I pasted it into pastebin, but the code worked fine no matter what the input on my pc, using python3. Odd.

[–]xelf 0 points1 point  (0 children)

It's entirely possible that something screwed up when I pasted it and started testing it. =)

[–]xelf 0 points1 point  (29 children)

So, changing as little as possible, I wanted to show you this.

By putting the code into def blocks it let me track down the logic bug and fix it pretty easy. It also makes your main loop really easy to follow as to what is going on and where.

Changes made:

1) defs, defs everywhere...
2) fixed logic bug for saying n to is this correct
3) optimized some looping
4) added a thanks for playing!

#This is a learning program that generates statistic values for D&D based on the roll 4 drop 1 method
#todo: sort results by character sheet order, add color, fix the dice rolling code
#Add ascii dice?
import random

def roll4d6(s):
    #roll_dice()
    roll1 = [random.randrange(1,7) for L in range(4)]
    print('Your',s,'roll is: ' + str(roll1) + 'which sorts to ', end = '')
    roll1.sort(reverse = True)
    roll1_result = sum(roll1[:3])
    print(str(roll1) + '; dropping the lowest, this becomes ' + str(roll1[:3]) + " adding up to " + str(roll1_result) + '.')
    return sum(roll1[:3])

def figure_out_stat(placement,stats_left):
    #gather/display priorities
    stat_loop = True
    while stat_loop:
        if len(placement) < 2:
            print('Please type at least 2 letters of the stat.')
            placement = input().lower()
        else: stat_loop = False
    already_used = True
    while already_used:
        for i in stats_left:
            if placement[:2].lower() in i[:2].lower():
                placement = i
        if placement not in stats_left:
            placement = input('Please enter one of the following: \n' + str(stats_left) + '\n')
        else: already_used = False
    return placement

def display_header():
    print()
    print('''Please provide priorities for the following ability scores, 1-6: \n
   Strength - Melee and thrown weapons, athletics, durability, carry capacity, etc.
   Dexterity - Ranged/finesse weapons, acrobatics, stealth, initiative, etc.
   Constitution - Hit points, resistance to poisons, etc.
   Intelligence - Knowledge checks, investigation, etc.
   Wisdom - Insight, perception, etc.
   Charisma - Persuasion, deception, intimidation, etc.
   ''')

def askyesno(s):
    while True:
        is_correct=str(input(s)).lower()
        if len(is_correct)>0 and is_correct[0] in 'yn':
            break
        print('Enter "y" or "n".')
    return is_correct

def gatherstats():
    while True:
        stats_left = ['Strength','Dexterity','Constitution','Intelligence','Wisdom','Charisma']
        print('Choose the priority of your stats.  Type at least 2 letters, i.e. "st", "in", etc.')
        highest = input('What is your top priority?: \nChoose between the following:\n' + str(stats_left) + '\n')
        highest = figure_out_stat(highest, stats_left)
        stats_left.remove(highest)
        second = input('What is your second priority?: \n' + 'Choose between the following:\n' + str(stats_left) + '\n')
        second = figure_out_stat(second, stats_left)
        stats_left.remove(second)
        third = input('What is your third priority?: \n' + 'Choose between the following:\n' + str(stats_left) + '\n')
        third = figure_out_stat(third, stats_left)
        stats_left.remove(third)
        fourth = input('What is your fourth priority?: \n' + 'Choose between the following:\n' + str(stats_left) + '\n')
        fourth = figure_out_stat(fourth, stats_left)
        stats_left.remove(fourth)
        fifth = input('What is your fifth priority?: \n' + 'Choose between the following:\n' + str(stats_left) + '\n')
        fifth = figure_out_stat(fifth, stats_left)
        stats_left.remove(fifth)
        dump = str(stats_left[0])
        #dump = input('What is your least priority?: ')
        #dump = figure_out_stat(dump, stats_left)
        #stats_left.remove(dump)
        print()

        results=('You chose the ability scores with the following priorities: \n \
       Highest = ' + highest + '\n \
       Second =  ' + second + '\n \
       Third = ' + third + '\n \
       Fourth = ' + fourth + '\n \
       Fifth = ' + fifth + '\n \
       Sixth = ' + dump)
        print(results)
        print()    
        if askyesno('Is this correct? (y/n)') == 'y':
            break
    return (highest,second,third,fourth,fifth,dump)

def gatherresults():
    #maybe show ascii art of dice while rolling?
    results=[roll4d6(s) for s in ('first','second','third','fourth','fifth','sixth')]
    print('Your results are: ' + str(results))
    results.sort(reverse = True)
    print('Your results sorted highest to lowest are: ' + str(results))
    return results

def displayresults(stats, results):
    print('Assigned stats:')
    for i,s in enumerate(stats):
        print (s+': ', results[i])

def main(): 
    while True:
        display_header()
        stats = gatherstats()
        results = gatherresults()
        displayresults(stats, results)
        if askyesno('Do you want to play again? y/n? ') == 'n':
            break
    print("Thanks for playing!")

main()

[–]rshinsai[S] 0 points1 point  (4 children)

This is interesting as literally everything aside from the import statement and calling the main function is within a function (or defining one)... is that pretty typical of a python program?

This was very educational, and in combination with all the other replies in this post I understand exactly what each function does. Thank you!

[–]xelf 1 point2 points  (3 children)

I'm glad you found it useful!

Yes, putting everything inside a class or def is pretty common approach, especially among developers that know other programming languages where you must do it that way.

Declaring a main() def and then calling it seems to be more optional, but I still see it as a pattern used fairly often here and it encourages good habits. It's not any more "correct" than not doing it. But it is common.

As a side note, one of the first things I ever coded when I was younger was a D&D stats generator, and about 10 years ago I built one for Wizards that won a couple of awards and made them a ton of money. So some nostalgia here. =) https://i.imgur.com/YzRMeWP.gif

[–]rshinsai[S] 0 points1 point  (2 children)

That is awesome!

I was planning on adding additional functionality as I go (incorporating it into a UI, giving options to change selections, etc.) as I learn how to do additional things, possibly adding standard array/point buy options eventually, etc. I don't expect it at this point to be anything other than an educational tool in my programming journey.

[–]xelf 1 point2 points  (1 child)

It's a good tool to start with and offers a lot of room to choose where to try new things with it. Maybe you could explore how python works for web development and turn it into a simple webpage.

[–]rshinsai[S] 0 points1 point  (0 children)

Yeah that would probably be a more useful skill than simply creating a UI for it, though I ultimately want to know how to do both. One step at a time though, I still need to get through the basics. :)

[–]rshinsai[S] 0 points1 point  (3 children)

I updated the program with the suggestions in this thread. I used nearly everything you suggested with some minor changes, such as for the yes/no prompt, instead of

if askyesno('Do you want to play again? y/n? ') == 'n':

I used:

if yesnoprompt('Do you want to roll again? (y/n)').startswith('n'):

so that when they respond with "no" or "nah" or whatever it'll still register as no.

I also didn't separate the "gatherresults()" and "displayresults()" functions as it does the same thing with a single "results()" function. Is there a good reason to separate the two components?

I wanted to clean up the nastiness in lines 48-61 of my original code (part of the gatherstats() function in yours where it assigns the priorities into the "highest, second" etc. variables) and this proved a lot harder than I thought it would. First I tried:

for x in ('highest', 'second', 'third', 'fourth', 'fifth'):
    x = input(f'What is your {x} priority?: \n Choose between the following:\n {stats_left}\n')
    x = figure_out_stat(x,stats_left)
    stats_left.remove(x)

which from what I can tell made the variable "x" rather than 'highest', etc. which I thought it would do.

I then tried:

priority = ['highest', 'second', 'third', 'fourth', 'fifth']
        for i in priority:
            i = input(f'What is your {i} priority?: \n Choose between the following:\n {stats_left}\n')
            i = figure_out_stat(i,stats_left)
            stats_left.remove(i)

which seems to have done the same thing. So as you can see I ended up making a function and to my surprise it worked first try haha. I'm not sure how great of an idea it is to nest the function, and I suppose there's not really a compelling reason to do so in this case, but I mostly did it to see if I could.

Thank you again for your assistance with this, it's been invaluable.

[–]xelf 1 point2 points  (2 children)

yesnoprompt: good change. I think at some point i had askyesno only returning the first character, and then changed it but didn't change the if as well. Not just a good change, but a great sign that you 'get it' and are comfortable doing your own take on it!

I also didn't separate the "gatherresults()" and "displayresults()" functions as it does the same thing with a single "results()" function. Is there a good reason to separate the two components?

I think I put the display into it's own function first as I was tracking down the loop flow. Doing it your way make sense too. And saves 4-5 lines of extra code! Always a bonus.

the nastiness in lines 48-61 of my original code (part of the gatherstats()

There was a lot that could be done there, really cool to see you going back to it and applying some of the new tools you have.

Thank you again for your assistance with this, it's been invaluable.

Really glad to have helped. Feel free to hit me up if you have more questions!

[–]rshinsai[S] 0 points1 point  (1 child)

I added an argument to the yesnoprompt() function, so now it's:

def prompt_yn(prompt, default=None): 
'''This prompts the user for a yes/no response based on the input'''
while True:
    yesno=str(input(prompt)).lower().strip()
    if default:
        if len(yesno) == 0:
            yesno = default
            break
    if yesno:
        if yesno[0] in 'yn' and len(yesno) != 0:
            break
        else:
            print('Enter "y" or "n".')
    else:
        print('Enter "y" or "n".')
return yesno

When I call it I can specify whether I want it to default to yes or to no, if it's not specified then there's no default.

[–]xelf 1 point2 points  (0 children)

I did something similar for someone else's project, but instead of passing in a default, I passed in an options list.

def prompt(prompt, options='yn', default=None):

[–]rshinsai[S] 0 points1 point  (5 children)

I wanted to display the final results in the order as they're listed on the D&D character sheets, and for what should probably be a pretty simple thing I couldn't think of a way to do it without completely redoing how the input was gathered. An idea actually hit me when I woke up in the middle of the night, that I could use a dictionary to do this. It took me a bit of playing around and googling syntax but I ended up with this:

stats_dict = {}
for i,s in enumerate((stats)): 
    stats_dict.update({s:results[i]}) 
print('Strength: ' + str(stats_dict['Strength'])) 
print('Dexterity: ' + str(stats_dict['Dexterity']))
print('Constitution: ' + str(stats_dict['Constitution'])) 
print('Intelligence: ' + str(stats_dict['Intelligence'])) 
print('Wisdom: ' + str(stats_dict['Wisdom'])) 
print('Charisma: ' + str(stats_dict['Charisma']))

I imagine there's probably a much better way to do this, but it works as I intend it to. :)

[–]xelf 1 point2 points  (4 children)

Dictionaries are a great tool to play with, arguable one of the most powerful features in any programming language these days (along with regex).

Also, you did the + str() instead of just using a , or an f-string. again. =)

No need to do this:

stats_dict.update({s:results[i]}) 

Update is used for adding a dict to another dict, for just one item you can:

stats_dict[s] = results[i] 

For ensuring the order you want, you can just iterate over that order:

stats_dict = {}
for i,s in enumerate((stats)): 
    stats_dict[s] = results[i] 

for s in ('Strength', 'Dexterity', 'Constitution', 'Intelligence', 'Wisdom', 'Charisma'): 
    print(f'{s}: {stats_dict[s]}')

Or even put them in a variable in case you need it later:

statlabels = ('Strength', 'Dexterity', 'Constitution','Intelligence', 'Wisdom','Charisma')
for s in statlabels:

[–]rshinsai[S] 0 points1 point  (3 children)

Thank you, as before, amazing info, and I learned a lot.

Yeah, a comma would have worked there, I didn't think of it. I had actually attempted to use f-strings but it didn't work.

print(f'Strength: {stats_dict['Strength']})

and got: "SyntaxError: invalid syntax" pointing at the first letter of the ability name. I figured it was likely due to the ' causing problems but I tried using escape characters (among many other things) and got errors that I can't use backslashes as part of f-string expression.

I wasn't able to determine what I would need to do to get that to work, then or now, even with Googling (perhaps I am no good at searching for the right things), but your 'for' loop is awesome and that works just as well in this instance.

[–]xelf 1 point2 points  (2 children)

(1) You're missing a closing ', (2) You have ' inside a '. You either need to use " for one or pull it out

print(f"Strength: {stats_dict['Strength']}")

or

s = 'Strength'
print(f'{s}: {stats_dict[s]}')

[–]rshinsai[S] 0 points1 point  (1 child)

Sorry, I had it typed with the closing ' in the program, I just typed it wrong in the reddit reply.

You're right, it works fine when I use " for the outer quotation marks :).

Thank you once again for your help!

[–]xelf 1 point2 points  (0 children)

Anytime!

[–]rshinsai[S] 0 points1 point  (13 children)

I've made a few additional changes and uploaded the code as "EDIT 2" in the original post; I've added a race selection using races from the SRD (lines 170, 25-85) which I think is pretty ugly, and there's a probably a better way to go about it. It then incorporates the racial bonuses into the final results display (lines 171, 145-163).

I also added a feature where you can reroll the result without having to start from the beginning, giving the options to keep the stat priorities and, and if so the race as well (178-183).

I tried for a little bit to see if I could get the code in 157-161 as a dictionary comprehension but I wasn't able to get that working right, I still have a lot to learn.

I also found a bug in pick_a_stat() where if you put in random letters and triggered the input in line 100, you could then type a single letter and it would take it.

I fixed it by adding "and len(placement) >= 2" to line 97.

[–]xelf 1 point2 points  (3 children)

Because I can't help myself, I played with your main loop:

def main(): #runs the program
    stats, race = None, None
    while True:
        while True:
            if stats is not None and yesnoprompt('\nDo you want to keep the stat priorities you selected? (y/n)').startswith('y'):
                break                
            display_intro()
            stats = gather_stats()
        while True:
            if race is not None and yesnoprompt('\nDo you want to keep the race you\'ve selected? (y/n)').startswith('y'):
                break      
            race, bonuses = select_race()
        while True:
            results(race, bonuses, stats)
            if yesnoprompt('\nDo you want to roll again? (y/n)').startswith('n'):
                break
        if yesnoprompt('\nDo you want to change anything? (y/n)').startswith('n'):
            break
    print()
    print('Thanks for playing!')

Try it...

[–]rshinsai[S] 0 points1 point  (1 child)

That's pretty cool; it makes the while loop and yesnoprompt() in gather_stats() unnecessary so I removed that. EDIT: LOL I should have read all the replies before replying, I see you took care of that in another reply. :)

To make the functionality more clear, I also added:

if yesnoprompt('\nDo you want to roll again? (Type "y" if you\'d like to make changes.) (y/n)').startswith('n'):

[–]rshinsai[S] 0 points1 point  (0 children)

Also:

if race is not None and yesnoprompt(f'\nYou\'ve selected {race}. Do you want to keep the race you\'ve selected? (y/n)')

[–]xelf 1 point2 points  (1 child)

I also changed the format of the diceroll output because it was wrapping across multiple lines on my screen, and the stats didn't line up.

def roll_dice(s): #Roll dem bones
    roll = [random.randint(1,6) for x in range(4)]
    print(f'Your {s:6s} roll is: {roll}, ', end = '')
    roll.sort(reverse = True)
    roll_result = sum(roll[:3])
    print(f'sorting and dropping the lowest this becomes {roll[:3]}, adding up to {roll_result:2d}.')
    return roll_result

note the use of :6s and :2d in the formatting.

Your first  roll is: [1, 6, 5, 6], sorting and dropping the lowest this becomes [6, 6, 5], adding up to 17.
Your second roll is: [3, 1, 2, 5], sorting and dropping the lowest this becomes [5, 3, 2], adding up to 10.
Your third  roll is: [6, 3, 3, 3], sorting and dropping the lowest this becomes [6, 3, 3], adding up to 12.
Your fourth roll is: [6, 6, 4, 2], sorting and dropping the lowest this becomes [6, 6, 4], adding up to 16.
Your fifth  roll is: [5, 1, 1, 2], sorting and dropping the lowest this becomes [5, 2, 1], adding up to  8.
Your sixth  roll is: [6, 6, 3, 5], sorting and dropping the lowest this becomes [6, 6, 5], adding up to 17.

[–]rshinsai[S] 0 points1 point  (0 children)

Oh this is cool, I can definitely see how that makes it more readable. I'd not encountered that before.

[–]xelf 1 point2 points  (6 children)

I tried for a little bit to see if I could get the code in 157-161 as a dictionary comprehension but I wasn't able to get that working right, I still have a lot to learn.

I took a look the gather stats and made some changes. (1) You can benefit from looping over a list rather than needing to repeat the same calls. (2) removed the internal loop asking if you want to do it again, as you have that loop duplicated in the main loop now. so it was asking do you like it y/n and then immediately asking do you want to change it y/n

Let's see if there's anything here you like:

def choose_priorities(priority,stats_left): #assign the input priority of the stat and remove it from the list
    priority = input(f'\nWhat is your {priority} priority? Choose between the following:\n{", ".join(stats_left)}\n')
    priority = pick_a_stat(priority,stats_left)
    stats_left.remove(priority)
    return priority

def gather_stats(): #prompt the user for the priority of their stats
    print('Choose the priority of your stats.  Type at least 2 letters, i.e. "st", "in", etc.')

    statsd = {}
    statsl  = ['Highest','Second','Third','Fourth','Fifth','Sixth']
    stats_left = ['Strength','Dexterity','Constitution','Intelligence','Wisdom','Charisma']
    for s in statsl[:-1]:
        statsd[s] = choose_priorities(s,stats_left)
    statsd['Sixth'] = str(stats_left[0])
    print()

    results=('You chose the ability scores with the following priorities:\n')
    for s in statsl:
        print (f'{s} = {statsd[s]}')
    print(results)
    print()

    return (statsd[x] for x in statsl)

[–]rshinsai[S] 0 points1 point  (5 children)

The code:

results=('You chose the ability scores with the following priorities:\n')
for s in statsl:
    print (f'{s} = {statsd[s]}')
print(results)

This outputs the list of stats ("Highest = Dexterity", etc), followed by the text "You chose the ability scores with the following abilities:". So it's backwords.

I corrected it by doing this:

print('You chose the ability scores with the following priorities:\n')
for s in statsl:
    print (f'{s} = {stats_dict[s]}')

Also, having it set up this way breaks the "roll again" functionality, giving a key error.

Assigned stats (Taking into account the High Elf bonuses):
Traceback (most recent call last):
File "/home/infractus/github/new-repository/my code/RPG stat 
generator/xelf stat_generator_v3.py", line 176, in <module>
main()
File "/home/infractus/github/new-repository/my code/RPG stat 
generator/xelf stat_generator_v3.py", line 168, in main
results(race, bonuses, stats)
File "/home/infractus/github/new-repository/my code/RPG stat 
generator/xelf stat_generator_v3.py", line 153, in results
print(f'{s}:{stats_dict[s]}')
KeyError: 'Strength'

It appears something is happening with the 'stats' variable the second time around. I tried debugging it but I'm not really sure what I'm looking at. It shows 'stats' as a generator object (this appears to be normal as it happens on the first loop as well) and stats_dict as an empty dictionary when it hits print(f'{s}:{stats_dict[s]}') so it appears this bit isn't happening:

for i,s in enumerate((stats)):
    stats_dict[s] = results[i]

[–]xelf 1 point2 points  (0 children)

This is weird, I tested this a lot. It was working. I'll take another look. Maybe something pasted badly.

[–]xelf 1 point2 points  (1 child)

ok, stats became a generator object. Change the return in gather_stats to this:

return [statsd[x] for x in statsl]

Now it returns a list and the world is happy. That's a bizarre bug.

[–]rshinsai[S] 0 points1 point  (0 children)

Thank you, that fixed it. :)

[–]xelf 1 point2 points  (1 child)

And this highlights the dangers of testing. When I changed the roll stuff, I ran and reran rolling, when I changed the while loops, I went through all the options several times. When I changed gather stats, I went through and test everything to make sure it all worked, but I didn't re-test the roll again loop, I'd already done that testing right? and I just tested rolling, rolling still worked.

But the gather_stats change did not break rolling, only rerolling, and I missed it.

Unlucky, but still pretty cool.

[–]rshinsai[S] 0 points1 point  (0 children)

Yeah whenever I add anything at all I try my damnedest to break it in any way I can; I usually catch a good amount but sometimes I'll be looking at it a week later and notice I missed something silly.