Download Search Discord Facebook Instagram LinkedIn Meetup Pinterest Tumblr Twitch Twitter Vimeo YouTube

Simulation Engine Part 2: Resources and Conversions

In the previous post, I wrote about building simulations using production rules. In this next post, let’s look in detail into how exactly they’re used to build this particular game.

The game’s simulation is composed of the following layers:

  • Data Model
    • Resources
    • Resource bins in buildings
    • Resource bins in map tiles
  • Simulation Logic
    • Resource Conversion
    • Condition / Action Rules

(By the way, this data model has been hugely inspired by the excellent work of Willmott and others on SimCity 5 – you can check out their design here.)
 

Resources

The basic atomic unit of data is a Resource. This is an <id, amount> tuple, such as “50 dollars” or “10 units of gold” or “50 units of stone”. Everything that can be produced or consumed is a resource.

There are also two types of “special” resources: people, and map effects.

When you build a house, and people move in, that’s represented as the house gaining a “1 resident” resource – and later, when that resident gets a job, the workplace gains a “1 worker” resource as well (and the reverse happens when the resident moves out).

Map effects are things like crime, boredom, or fire risk. For example, every house creates a tiny amount of fire risk, say, “0.01 fire-risk” per day, and this resource collects up in the world, and later causes fires, as we’ll talk about in a moment.

Which is a great segue to…

Sleepy residential community about to be ravaged by fire (map below shows fire risk in red)

Resource Bins

Resources are not loose objects, rather, they’re stored in bins. A resource bin contains a whole bunch of resources and their amounts. There are several types of bins in the game:

1) Player’s inventory during a game session is stored in a bin. For example, the facts that I have $1000 in cash and 10 units of gold ready for trade, are just two resource entries in my bin.

2) Each building has its own bin, which is its own inventory. For example, when a wheat farm grows wheat, it inserts a bunch of wheat units in its bin. But those units are not yet usable by the player. There’s a separate delivery step that has to happen, to deliver this wheat from the building, to the player’s inventory.

3) Finally, each map tile is a resource bin, that’s separate from any building that might sit on top of it. For one example, gold underground is represented as a gold resource inside that tile’s bin. For another example, fire hazard is a resource, conjured up and inserted into the world by wooden buildings.

Resource Conversion

Most everything in the simulation is a resource, and a lot of the game is based on resource conversions. Most conversions are pretty straightforward, based on the logic of the game, for example:

  • Ranch produces meat and leather
      "doWork": 
        "outputs": [ "unit 6 meat", "unit 6 leather" ]
  • Ranch delivers it to the market
      "deliverWork2":
        "frequency": "every 5 days",
        "checks": [ "unit workers > 0" ],
        "inputs": [ "unit 20 leather" ],
        "outputs": [ "player 20 leather" ]
  • Cobbler buys leather, consumes it, and produces shoes
      "bringMaterials":
        "checks": [ "unit workers > 0", "unit leather < 2" ],
        "inputs": [ "player 8 leather" ],
        "outputs": [ "unit 8 leather" ]
    
      "doWork":
        "inputs": [ "unit 2 leather" ],
        "outputs": [ "unit 3 shoes" ]
  • Upper-level houses shop for shoes (and many other goods), consume them and produce cash for the player

But conversion rules don’t have to be limited to just buildings bins – they also frequently interact with map tiles underneath and around:

  • Gold mine consumes gold from the map tiles underneath, and produces gold in its own inventory, until all ground gold has been exhausted
      "doWork":
        "inputs": [ "map 5 gold" ],
        "outputs": [ "unit 5 gold" ]
  • Every wooden house produces a little bit of fire risk in the map tile underneath
      "produceFireHazard":
        "frequency": "every 7 days",
        "checks": [ "map fire-hazard < 1 max" ],
        "outputs": [ "map 0.04 fire-hazard" ]
  • Fire brigade consumes all fire risk from the map, within given radius
      "consumeMapResource":
        "frequency": "every 7 days",
        "checks": [ "unit workers > 0" ]
        "success": [ "_ a-change-resource-in-area 
                      radius 5 res fire-hazard amount -1" ]

 

(Quick aside: the fact that gold comes from underground, while food and other things are stored in building, is not actually hard-coded anywhere in the engine. Right now it’s just a matter of convention. Which means that you could rewrite the rules such that, for example, the cobbler makes shoes and inserts them underground inside the tile. You probably wouldn’t want to, because nobody would be able to get at those shoes if they wanted them, but you could… 🙂 )

Check / Action Rules

Finally, what binds all these elements together are the actual rules. As you’ve probably gathered from the examples above, a rule is a data structure made up of the following:

  • “frequency”: how often we check
  • “checks”: all of these have to be satisfied
  • “inputs”: if checks are satisfied, these will be consumed
  • “outputs”: if inputs were consumed successfully, these will be produced
  • “success”: actions to run if this rule was applied successfully
  • “failedInputs”: actions to run if inputs were insufficient
  • “failedChecks”: actions to run if checks failed

Then the rule application itself is pretty simple: at specified intervals, we iterate through all rules, and for each we verify that the checks pass, then consume inputs, produce outputs, and run success actions (e.g. spawn NPC); otherwise we run one of the failed action sets, depending on what failed (e.g. display a notification for the player).

It’s a system built from fairly simple pieces, but the combination of those simple pieces gives it a lot of configurability.