AI: Overworld Adventures Test – Complete, moving on

After some bug fixing and restructuring the Blackboard class (yet again), I did manage to fix the AI. After a lot of sweating swearing and sighing, I decided to have 2 Blackboard classes, which are annoyingly similar:

One Blackboard class is for general variable storage explicitly handled by the client, typically application states. So that they can for example pass variables from one state to another.

The other Blackboard class is for automatic variable storage where the variables, on request for access, are calculated automatically via functions and cache their values to avoid recalculations. So there can be automatic function chaining if a variable depends on another and so on. This can still be overridden by the user, and in that case I need to track dependencies to invalidate the cached values of all dependents. Example:


In the above, if I explicitly set “entity:CurrentEnemy”, then float:EnemyPowerLevel and float:BattleSuccessEstimate both become dirty, therefore next time I request them they need to run their respective functions. This functionality will be used in the Instanced Decision Groups that I mentioned in another post

Now the AI manages to actually stay alive for a while. So compared to 8 months of survival (1 move is approx one day), the new AI can stay alive for more than 7 years!

So I guess I’ll consider that good enough for now and move on to other things. As an interlude, I wanted to do some graphics… So I was testing some visualizations and recreated a style that I want to have at some point in the game, perhaps in the form of a cutscene or who knows… So, here it goes!

The style is reminiscent of SNES Mode 7 when applied for overworld visualization, and I remember it (and loved it) from the ending of Chrono Trigger

At the moment of course the tile visualization is severely boring and flat (even though I like the palette), soon-to-come features will be smooth tile transitions, textures (whenever I find good enough textures for all the biomes), trees, mountains, and some water animation. And perhaps try a few visualization tricks 🙂

AI: Overworld Adventures Test – Blackboard Dependency Chains

As I said last time, I modified the test to include a basic hunger clock: Heroes start with 100 rations, rations can be bought at the rate of 10/coin and they are consumed at the rate of 1/tile (diagonals cost 1.41). I added the appropriate bits in the behavior tree, added some utility-specific stuff and voila, the heroes actually survive for a while using utility AI + behavior trees!

… but the heroes keep starving! That is a sign of an AI bug, such as a badly programmed curve/input. But I’m not completely to blame (obviously): Using the normalized inputs for the utility AI, it is quite awkward to combine them. For example:

  • MyRations input: Min/Max I care about: [0, 30]
  • MyDistanceToClosestCity input: Min/Max I care about: [0, 15]

Now if I want to figure the ratio: rations/city_distance, all I have at this point are these normalized values. The ratio of the normalized values is useless, unless I de-normalize it using the parameters, but that’s just redundant. So what now?

One answer (that I’m pursuing) is to do the following:

  • Keep in the blackboard both normalized (for utility AI) and unnormalized values (for general use)
  • Have generic calculators for blackboard variables
  • Have dependency graphs for blackboard variables
    • e.g. MyRationsNormalized depends on MyRations
    • e.g. MyRationsOverCityDistance depends on MyRations and MyDistanceToClosestCity

Now when utility AI requests MyRationsOverCityDistance, then we’ll try to grab MyRations and MyDistanceToClosestCity from the blackboard. If they are not there, then they will be calculated — if during those calculations they need any more variables, the chain goes on. The dependency graph comes into play because the blackboard variables have a dirty flag: we don’t want to calculate a variable every time we want to access its value. Now when we update a variable, using the dependency graph, we mark all the dependents dirty, and that’s it!

Decisions and Instancing

Utility AI can be used to select for example:

  • Go shopping?
  • Go dungeoneering?
  • Go heal?
  • Go for groceries?

but it can also be used for:

  • Go to dungeon_0?
  • Go to dungeon_1?
  • Go to dungeon_N?

So, the above is fairly dynamic in terms of the dungeons to compare. So it seems reasonable to abstract:

for( auto& d : dungeons)
blackboard.set("current_dungeon", d);
scores.push_back( choice.eval() );
auto iChoice = select_best_score( scores );

In the above, the considerations will always use the “current_dungeon” property to evaluate the inputs that will pass through the response curves. This is the point where this functionality plays well with the dependency graph! Every time the current_dungeon property is changed, the whole blackboard variable subgraph that depends on that variable needs to be marked as dirty.

AI: Overworld Adventures Test – Response Curve Tool

That’s my progress –> snail-160313_640.png


Originally I was hoping that by this week I’d have an example using utility AI. Well, I miscalculated how much time things take. On the plus side, I developed a tool that is going to help with setting up the curves properly and visualize how considerations are combined together. The tool has a GUI (using AntTweakBar) that allows adding/removing considerations, adjusting curves, setting up mock inputs, calculating scores and saving/loading to a file (as a list of lines in the form “consideration_name normalized_input curve_type curve_coeffs”). Here’s how it looks like:

response_curve_toold 2017-05-04 22-17-04-59.png

The left graph shows all the response curves from all considerations and the right graph shows how the final score varies depending on the input value of one of the considerations. The points correspond to input values (on graph, as well as stacked)

So, here are current curves that I’m going to use for the AI.

VisitTemple Choice

If we have low health, give a big boost to score (purple curve). If we have zero coins we won’t go to the city (green curve == zero),  but otherwise the score rises quickly. Distance has a small effect (the closer the better), as it’s not as important as health for example.

response_curve_toold 2017-05-04 22-46-11-92.png

VisitBlacksmith Choice

Blacksmith will provide boost to power and health. It’s not as important as getting rations or healing, so we’ll need more coins to get higher score. Distance again is not that important. If I’m in the city and I have enough rations and on full health, I should be choosing this.

response_curve_toold 2017-05-04 22-49-59-20.png

VisitGrocer Choice

This depends on the current number of rations (yellow curve), distance to city (green curve) and current coins (blue curve). Low number of coins or high number of rations lower the score quickly.

response_curve_toold 2017-05-04 22-54-49-27.png

RaidDungeon Choice

If we have 50% chance or below of clearing the dungeon, don’t go (green curve). It also depends on the distance: closer is better

response_curve_toold 2017-05-04 23-14-02-80.png

SelectDungeon Choice (Selecting the best dungeon out of a list of available ones)

Use the RaidDungeon choice, applying it to each dungeon in the list and select the best.

So… that’s so far. I’ve made the curves and I need to make sure the (now, moderately complex) behavior tree does all the magic by itself…