Answer in one line:
''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))
or even shorter starting with Python 3.6 using random.choices():
''.join(random.choices(string.ascii_uppercase + string.digits, k=N))
A cryptographically more secure version: see this post
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
In details, with a clean function for further reuse:
>>> import string
>>> import random
>>> def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
... return ''.join(random.choice(chars) for _ in range(size))
...
>>> id_generator()
'G5G74W'
>>> id_generator(3, "6793YUIO")
'Y3U'
How does it work ?
We import string, a module that contains sequences of common ASCII characters, and random, a module that deals with random generation.
string.ascii_uppercase + string.digits just concatenates the list of characters representing uppercase ASCII chars and digits:
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.digits
'0123456789'
>>> string.ascii_uppercase + string.digits
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
Then we use a list comprehension to create a list of 'n' elements:
>>> range(4) # range create a list of 'n' numbers
[0, 1, 2, 3]
>>> ['elem' for _ in range(4)] # we use range to create 4 times 'elem'
['elem', 'elem', 'elem', 'elem']
In the example above, we use [ to create the list, but we don't in the id_generator function so Python doesn't create the list in memory, but generates the elements on the fly, one by one (more about this here).
Instead of asking to create 'n' times the string elem, we will ask Python to create 'n' times a random character, picked from a sequence of characters:
>>> random.choice("abcde")
'a'
>>> random.choice("abcde")
'd'
>>> random.choice("abcde")
'b'
Therefore random.choice(chars) for _ in range(size) really is creating a sequence of size characters. Characters that are randomly picked from chars:
>>> [random.choice('abcde') for _ in range(3)]
['a', 'b', 'b']
>>> [random.choice('abcde') for _ in range(3)]
['e', 'b', 'e']
>>> [random.choice('abcde') for _ in range(3)]
['d', 'a', 'c']
Then we just join them with an empty string so the sequence becomes a string:
>>> ''.join(['a', 'b', 'b'])
'abb'
>>> [random.choice('abcde') for _ in range(3)]
['d', 'c', 'b']
>>> ''.join(random.choice('abcde') for _ in range(3))
'dac'
Answer from Ignacio Vazquez-Abrams on Stack OverflowAnswer in one line:
''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))
or even shorter starting with Python 3.6 using random.choices():
''.join(random.choices(string.ascii_uppercase + string.digits, k=N))
A cryptographically more secure version: see this post
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
In details, with a clean function for further reuse:
>>> import string
>>> import random
>>> def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
... return ''.join(random.choice(chars) for _ in range(size))
...
>>> id_generator()
'G5G74W'
>>> id_generator(3, "6793YUIO")
'Y3U'
How does it work ?
We import string, a module that contains sequences of common ASCII characters, and random, a module that deals with random generation.
string.ascii_uppercase + string.digits just concatenates the list of characters representing uppercase ASCII chars and digits:
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.digits
'0123456789'
>>> string.ascii_uppercase + string.digits
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
Then we use a list comprehension to create a list of 'n' elements:
>>> range(4) # range create a list of 'n' numbers
[0, 1, 2, 3]
>>> ['elem' for _ in range(4)] # we use range to create 4 times 'elem'
['elem', 'elem', 'elem', 'elem']
In the example above, we use [ to create the list, but we don't in the id_generator function so Python doesn't create the list in memory, but generates the elements on the fly, one by one (more about this here).
Instead of asking to create 'n' times the string elem, we will ask Python to create 'n' times a random character, picked from a sequence of characters:
>>> random.choice("abcde")
'a'
>>> random.choice("abcde")
'd'
>>> random.choice("abcde")
'b'
Therefore random.choice(chars) for _ in range(size) really is creating a sequence of size characters. Characters that are randomly picked from chars:
>>> [random.choice('abcde') for _ in range(3)]
['a', 'b', 'b']
>>> [random.choice('abcde') for _ in range(3)]
['e', 'b', 'e']
>>> [random.choice('abcde') for _ in range(3)]
['d', 'a', 'c']
Then we just join them with an empty string so the sequence becomes a string:
>>> ''.join(['a', 'b', 'b'])
'abb'
>>> [random.choice('abcde') for _ in range(3)]
['d', 'c', 'b']
>>> ''.join(random.choice('abcde') for _ in range(3))
'dac'
This Stack Overflow quesion is the current top Google result for "random string Python". The current top answer is:
''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))
This is an excellent method, but the PRNG in random is not cryptographically secure. I assume many people researching this question will want to generate random strings for encryption or passwords. You can do this securely by making a small change in the above code:
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
Using random.SystemRandom() instead of just random uses /dev/urandom on *nix machines and CryptGenRandom() in Windows. These are cryptographically secure PRNGs. Using random.choice instead of random.SystemRandom().choice in an application that requires a secure PRNG could be potentially devastating, and given the popularity of this question, I bet that mistake has been made many times already.
If you're using python3.6 or above, you can use the new secrets module as mentioned in MSeifert's answer:
''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(N))
The module docs also discuss convenient ways to generate secure tokens and best practices.
Videos
I made this as a quarantine project probably some time around May/June of last year. It was made in a span of probably a week or two. What initially made me think about making a character generator was that back when we were allowed to play D&D our group was somewhat notorious for accidentally stumbling on random NPCs and I figured something like this could help a DM out if they use a laptop while they run their game.
This project was also my foray into Python, and was just for fun.
I wanted to post it here to get some feedback on what I could do better because I'm sure parts of it are very sloppy.
https://github.com/ThatCoolNerd/dnd_5e_character_generator
Some things that I think could be improved:
How the stereotypical alignment and class is generated
How the stats are optimized on a per-class basis
How names are selected
Thanks for reading!
def roll_stats():
a = random.randint(1, 6)
b = random.randint(1, 6)
c = random.randint(1, 6)
d = random.randint(1, 6)
list = [a, b, c, d]
Call it dice or something more descriptive then list. You can also use list = [random.randint(1,6) for x in xrange(4)] rather then creating the variables separately.
list.sort()
add = sum(list[1:4])
Instead of sorting it and slicing it use: list.remove(min(list))
return add
Don't assign variables just to return them on the next line, use return sum(list[1:4])
def pow_mod():
a = "|+1 to hit on mele attack rolls| "
b = "|+1 damage on mele attack rolls|"
if pow >= 15 and pow < 17:
Don't take input to a function from global variables, pass it as a parameter
return a
if pow >= 17:
return a + b
else:
return " ~no modifiers~"
You return strings, but conceptually you are returning a list of modifiers. I suggest returning the list. You can then format the list anyway you like in the caller.
You've got a number of functions very similiar to this, it suggests moving some of the details into data structures. I'd do it like this:
POW_MODIFIERS = [
# specify minimum and maximum stat the modifier applies to
(15, 9999, "+1 to hit on mele attack rolls"),
(17, 9999, "+1 damage on mele attack rolls")
]
def calc_modifiers(stat, modifiers):
return [modifier for minimum, maximum, modifier in modifiers if minimum <= state <= maximum]
def format_modifiers(modifiers):
if modifiers:
return ' '.join('|%s|' % modifier for modifier in modifiers)
else:
return ' ~no modifiers~ '
By creating lists like POW_MODIFIERS for all your stats, you can reuse the functions for all the different stats. You should be able to apply similiar techniques to your other decisions. I'd also look at storing the data in a seperate file, perhaps using JSON.
I would suggest moving all your top level code into a main() routine and collecting it all in one place rather than having top level code and routines interspersed. I like to structure my code like this:
import sys
def main():
... top level code ...
routine1()
...
routine2()
...
all_done()
def routine1():
... do work here ...
def routine2():
... do work here ...
def all_done():
... finish up ...
if __name__ == '__main__':
main(sys.argv)
By doing it this way, if my code is in a file named, say, 'code.py', I can play with in the python interpreter by doing, eg.
python
>>> import code
>>> code.all_done() # test routine all_done() by itself
>>> ^D
With the top level code interspersed with the function definitions, when you try to import code, it all gets run. You can't run the pieces independently.
Simple:
>>> import string
>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> import random
>>> random.choice(string.ascii_letters)
'j'
string.ascii_letters returns a string containing the lower case and upper case letters according to the current locale.
random.choice returns a single, random element from a sequence.
>>> import random
>>> import string
>>> random.choice(string.ascii_letters)
'g'
» pip install StringGenerator
hi! I made a character generator for D&D. it includes name, appearance, stats, weapons, weapon data, armor, religion, alignment, and a few other goodies.
while i'm happy with the result, I am not so happy with the back end. a lof of the formatting feels wrong, and i'' guessing any sensible programmer would scoff at some of the decisions i made.
with all that said, heres the program! any criticisms/yelling about how bad i am would be appreciated
thank you!
Very cool, I'm working on something similar!
Some suggestions:
-
I'm not sure if you mean to use
randrangeorrandint. Based on how you use it I think you meanrandintas the max stat roll in D&D is 18 butrandrange(8, 18)means that the max value you'll get is 17. -
You might want to add some comments or more descriptive function names. While I understand what
lsdoes, I'm not sure what it stands for. -
I would include the Open Game License to make sure your project is on the up and up. Also review the license and basically, if it is from D&D but it isn't on SRD20 then take it down.
-
In the README I would include examples of how to use your program, as well as list the dependencies which in your case is
requirements.txt. For example, you can input the class on the first line to skip the second input, but that isn't ever document. -
You only use
consolefor its clear command. Instead of forcing your users to install another library to use your tool I would write a smallclearfunction. Something like:import os def clear(): if os.name == 'nt': os.system('cls') # Windows else: os.system('clear') # POSIX -
My final suggestions would just be formatting. Function names should be
cased_like_thisrather thanlikeThis. I would also lowercaseData.py.
EDITS:
-
Forgot to mention that you might want to get the stat values in a way more accurately depicting the distribution you would get when rolling dice. So rather than a single 8-18 random value you might want something like:
sum(randint(1, 6) for _ in range(3))That way you don't get 18's as often as you get 10's, which is the case when actually rolling dice.
-
Okay so I've been playing with it more, actually I made a fork to mess with, and you should probably include a way to save the output into a file.
-
I would also include error checking. If someone puts in bad input the program crashes.
-
getRacealso gets the class. I would separate these into two functions. -
This project could heavily benefit from using some classes. In particular, a class that creates the character.
I really like this project. Feel free to DM me if you have any questions.
Looks pretty good to start! There are a few formatting changes that you might want to integrate (using [] and {} instead of list() and dict(), 4 spaces instead of tabs, etc), but the structure looks fairly straight forward.
I'll have a closer look at this later today, but one suggestion would be to standardize the generate function and move all the constants into the data structure. As it stands, you've got a lot of data mixed in with the functional bits of code, which makes it more difficult to modify in future.
E.g.
RACE_CONSTANTS = {
"Human": {
"height_min": 60,
"height_max": 80
},
"Dwarf": {
"height_min": 48,
"height_max": 56
}
}
def generate(race):
stats = RACE_CONSTANTS[race]
height = random.randint(stats['height_min'], stats['height_max'])