-
Notifications
You must be signed in to change notification settings - Fork 31
Deep Dive: Generator Data Patterns
There are three basic types of data storage pattern for generators- List and Weighted data. Before we get that far, we should talk about how a generator knows what data it owns.
At the very lowest level, the name of the generator is used to name the data fields in the backend. Hence the NPC class will contain the following
- npc_height
- npc_phobia_chance
- npc_phobia
- npc_wisdom
As long as the data field matches 'npc_*', it's considered NPC data. So, what kind of data is stored in the backend?
We're using Redis as the backend, and it has several datatypes which are at our disposal to contain the data. These formats both inform and limit how we can store information.
The simplest form of data storage for a generator is a list, which uses the redis list datatype:
# LPUSH key text
LPUSH npc_height short
LPUSH npc_height average height
LPUSH npc_height tall
To select a height in our code, it works something like this:
# get the total number of values
total=redis.llen('npc_height')
# randomly select a number between 1 and the total
randomid=random.randint(1,total)
# return the value at that index (remember to offset by one because arrays start at 0)
redis.lindex('npc_height', randomint-1)
While Redis does offer the ability to randomly select a value, that's of no use to us; while this implementation appears to be random, it's actually using a seeded random value, which means when you set seed=37, the 140th call of randint(1,100) will always return 25. Using redis for random would remove the ability to reproduce worlds.
Sometimes you don't want all things being equal; For example, when generating a magical item, you don't want an equal chance of artifact and trivial magical item- Artifacts are the rarest of the rare. Hence we use the redis sorted set:
# ZADD key score textstring
ZADD magicitem_value 25 {"name":"trivial magical item", "score": 25 }
ZADD magicitem_value 65 {"name":"minor magical item", "score": 65 }
ZADD magicitem_value 90 {"name":"medium magical item", "score": 90 }
ZADD magicitem_value 97 {"name":"major magical item", "score": 97 }
ZADD magicitem_value 100 {"name":"artifact", "score": 100 }
There's a lot going on in these lines, so I'll try and break down each section
Each line has a score associated with it. a number between 1-100 is selected as our "roll". The matching key is then reverse-sorted by score, and the first value between our roll and 0 is selected. Hence if we roll a 94, we reverse sort between 94 and 0, and select the highest score, which is 90- a "medium magical item".
Hence what those score really represent is the percentage chance it has of being selected, with the number below it being the lower end of the range, and it's own score being the higher end of the range.
You'll notice the text area looks like some sorta special code- it's called JSON. Redis simply treats it as a string, but the python code takes another step and creates an object out of it. This gives us the ability to associated many different types of data with a single information point- using a json string gives us that ability.
I should also mention the seemingly redundant "score" value that's included. That's a lazy shortcut I took to simplify some code, which is outside the scope of this conversation. The important part is to make sure it matches the actual score.