Tinkering with society generation

I have been tinkering with building societies for my game. Basic idea behind the whole thing is that I have two tier system: one level generates blueprints and another level turns those blueprints to actual objects in game world. For scrolls (see first and second post) it already works.

Since the basic system was sound, I decided to adopt that for building societies (village, people, factions and such). Nice advantage here is that since game could already generated blueprints and then turn those blueprints into objects, I could add another multimethod to turn those same blueprints into something more suitable for simulating society. So a scroll that has very detailed representation in adventure mode, might look quite a bit different in society simulation.

I haven’t yet decided how to actually represent various objects and things in society mode, because I have been mainly concerned on building code for blueprints. I added one extra step in the process: modifying existing blueprint. This allows me to build blueprints in layered fashion, hopefully leading to code reuse and interesting results.

For example, to create a wise human scribe, I would use something like following:

(->> (create-blueprint 'human)
     (modify-blueprint 'wise)
     (modify-blueprint 'scribe)

Result will be a human, instantiated from a blueprint that has been customized to have aspects of both wise and scribe.

Insides of those methods are pretty bare currently, but I expect them to be fleshed out eventually:

(defmethod create-blueprint 'human [object-type &optional [seed nil]]
  "create blueprint for human"
  (with-seed [rng seed] :create 'human
    {:name (generate-random-name seed)
     :body 6
     :finesse 6
     :mind 6
     :inventory []}))

(defmethod modify-blueprint 'wise [modifier-type blueprint]
  "modify blueprint to create wise character"
  (with-seed [rng] :modify blueprint 'wise
    {:mind (.randint rng 1 3)}))

(defmethod modify-blueprint 'scribe [modifier-type blueprint]
  "modify blueprint to create scribe"
  (with-seed [rng] :modify blueprint 'scribe
    {:body (.randint rng -1 0)
     :mind (.randint rng 1 2)
     :inventory [(create-blueprint 'scroll (new-seed rng))
                 (create-blueprint 'scroll (new-seed rng))]}))

Blueprint is just a dictionary. Special key :type is used to select which instantiate-blueprints method gets called in the end. Another special key sub-types is used to track which modifications has been done to the blueprint. We don’t want to end up with double scribe, who is thrice wise.

Most complex part of the solution is with-seed form. Exact details are a bit hazy still, as I fine tune the implementation and syntax as the work with blueprints progresses. The goal is to have easy and clear way of producing random, yet determinate results. This is achieved by instantiating new random number generator, bound to first symbol (rng in examples) with given seed. If seed is omitted, a random one will be chosen instead. The used seed is stored in the blueprint for the future use.

Second parameter :create or :modify instructs the system if a new blueprint is created or existing one modified. This affects (among other things) how seed is generated if it has not been supplied from outside. In case of modification, we need to also tell which variable we’re modifying (I’m not quite sure if I want to modify an existing blueprint or create a completely new copy).

Next parameter tells what type or subtype we’re dealing with. Type is later used to choose method implementation which implements generation of the object. Subtype merely tracks that no same subtype modification is used twice.

Finally we’re specifying blueprint as a dictionary. Here we can use the random number generator that was created earlier and we’re quaranteed to get the same results with the same seed (technique I learned from Elite). And of course we can call other systems to generate more parts (for example a name for our character). And as long as we keep our seed threading through the system, we end up with random, yet repeatable result.

Here’s something to ponder about: if those subparts are created based on the seed, should I use the original seed or should I generate a new seed based on the original seed? The answer actually depends on the situation. If I’m generating only one of those subthings, I can reuse the seed (like with the random name). But if I’m generating multiple items of same type (like scrolls for the scribe), I should generate new seed for each of them. Otherwise I would end up with identical items in the end.

But, what actually happens when modification is applied to an existing blueprint? That actually depends on what kind of values are stored in the blueprint (this part I haven’t yet implemented completely, so it might change). In case of numbers, they’re just added together: 1 + 2 = 3. In case of lists (think inventory for example), new items are appened to old list: [‘apple] + [‘scroll ‘pen] = [‘apple ‘scroll ‘pen]. With strings, I don’t know yet. This sounds like an easy system to code and expressive enough that I can build layered blueprints.

While working on this system, I have had constant, growing nagging in the back of my head: should I use this system for generating items and monsters in adventure mode too? Maybe even levels? The system certainly would be able to handle it and as a added bonus, I could start generating monsters in both adventure mode and society mode based on same blueprints. But do I really want to overhaul the whole content generation system for the adventure mode? There’s lots of corner cases that would need attention (for example, some levels can exist only once in the game. How would this work if same general location can be generated for adventure and society mode?). So most likely I’ll try and resist that urge for a while still.

6 thoughts on “Tinkering with society generation

  1. Intriguing article! It’s always interesting to see that other devs are also tinkering with very similar things (like blueprints and generating them).

  2. However the idea to use multiple RNGs per item and also extent that later. You should perhaps check whether the RNG implementation you’re using has that performance, especially if you’re going to use it in a larger scale. If you need to create for one RNG instance for each seed, I can imagine this going very slow or memory-inefficient… Can’t you just use a single RNG and just let it generate numbers as you require the moment you’re generating an item?
    Or you could use a ID generator that creates unique IDs and use those IDs as seeds for your item generator. Then you won’t end up with duplicate items.
    Just a few thoughts.

    • RNG will be discarded as soon as blueprint has been created, so there shouldn’t be problems with memory. Based on what little testing I have done, it should also be reasonably fast method too. Especially since I’m not going to be creating very large scale structures with this system.

      The reason I want to use RNG tied to ID of item (be it really an item or village or person in that village), is that it makes process repeatable. At the broadest scale, I can have one hard coded seed that is used every time the game is played. This seed is used to generate background story for the game, including people, their home villages, adventures and so on, mixed with some hand crafted content for heroes of legends and such. And backstory is then same for every player, every time they play. I’m hoping I can then draw from this backstory and generate actual gameplay content, like special items that appeared in old legends and player might find during their adventures.

      Actual levels where player explores are generated every time differently, hopefully giving them a new challenges as they play. The deterministic portion of generation is just for creating some background info for game and maybe some special items (think of Golden Fleece for example).

      I have been wondering about duplicate IDs a bit and haven’t really made up my mind about them. Sometimes they might be useful, like when some specific person appears in two different legends. But on some other cases they don’t make up much sense, like when those two legends are hundreds of years apart and the duplicate person shouldn’t be alive during both legends. The whole system is still in its infancy and there are probably lots of things I haven’t thought of yet and have to tackle later on.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s