# [Tutor] AD&D Character generator problems

Tue, 4 Apr 2000 11:15:10 -0700

> To test out my abilities so far I am writing an AD&D character generator. It
> is going fine so far except that even if I tell the program not to it still
> re-rolls the character's stats. Hopefully someone here can spot the problem.

<big snip>

> def MakeStats():
>     for genstat in statlist:
> 	stats[genstat]= random.randint(3, 18)

I'm revealing my roleplayer past here, but this won't do what you want it
to do.  randint generates a uniform distribution - in AD&D terms you would
be rolling 1d16+2 - when you want 3d6 which is a more bell-shaped
distribution.  I'd suggest that you write a little utility function,
something like:

def dice(number, sides, bonus = 0):
# roll (number)d(sides)+(bonus)
total = bonus
for i in range(number):
total = total + random.randint(1, sides)

and then your MakeStats() would look something like this:

def MakeStats():
for stat in statlist:
stats[stat] = dice(3,6)
print stat, ":", stats[stat]

In fact, you would have a more versatile program if you passes the stats
variable in and out, rather than using a global.

> def OkStats():
>     goodstats= raw_input("Re-roll? ")
>     if goodstats == 'yes' or 'y':

The problem you noticed is here: Python reads this as ((goodstats == 'yes')
or 'y') which evaluates to either 1 or 'y' (and because 'y' is not the
empty string, it is always true).

You should re-write the line to be something like

if goodstats == 'yes' or goodstats == 'y':

or

if goodstats in ['yes', 'y']:

> 	MakeStats()
>     elif goodstats == 'no' or 'n':

And you'll need to make a similar change here for the same reason.

> 	return ''
>     else:
> 	print "(y)es or (n)o please."
> 	OkStats()

And this is rather unneeded recursion - a while loop would suit you better
for this whole thing.

There are a number of other style problems with the program, but since it's
a learning exercise, not a production program, that shouldn't matter too
much, but here are some pointers:

- A character should probably be represented by an object, rather than a
collection of data.  Try writing it as a class, with methods like
"MakeStats".

- save output to a file until the end, once you know that the user really
has something they want to save.

- separate the user interface out from the generating routines as much as
possible, possibly even putting it in a module of its own. You never know
when you might want to add a nice little Tk GUI front-end to your code.

- as I recall, AD&D has large amounts of data that your program will need
to have access to (tables and so forth).  Try to separate these out from
the rest of the code as much as possible.

- take care with your variable names.  For example, the name "goodstats"
above is misleading because goodstats == 'yes' means the stats are _bad_.

With this sort of thing in mind, I'd re-write it something like this:

# Another AD&D generator (code untested)

import random, string

# Data

statnames = ['str', 'dex', 'con', 'int', 'wis', 'cha']
yes = ['yes', 'y']
no = ['no', 'n']

# Utility functions

def dice(number, sides, bonus = 0):
# roll (number)d(sides)+(bonus)
total = bonus
for i in range(number):
total = total + random.randint(1, sides)

def ask(question, responses = None, invalid = "Please enter a valid
response."):
# ask a question which must match a list of valid responses
while 1:
if responses:
if string.lower(answer) in responses:
else:
print invalid
else:

# Main class

class Character:
def __init__(self, name):
self.name = name
self.stats = {}
self.makestats():

def makestats(self):
for stat in statnames:
self.stats[stat] = dice(3,6)

def printstats(self):
for stat in statnames:
print stat, ":", self.stats[stat]

# etc

# Interface

def CreateChar():
name = raw_input("Name your character: ")
char = Character(name)

# generate stats
while 1:
char.printstats()
answer = ask("Re-roll? (y/n) ", yes + no, "(y)es or (n)o please.")
if answer in no:
break
char.makestats()

#and so on

if __name__ == "__main__":
CreateChar()

Regards,
Corran