Jump to content

Request for Comments: Hannibal Group Scripts

Recommended Posts

I think, I'm getting really close to finalize the domain specific lanaguage (DSL) used in Hannibal's groups and want to ask for some feedback, because this what this bot is all about. The idea is only groups talk to the engine. Some unit should gather a field, there is a group for that. Want to build houses - launch a builder group. Need resources, give the supplier group some entity ids of trees, mines or whales.

Groups are the building blocks of the behavior of Hannibal, everything else is low level or services. If something is going wrong a group is probably missing or not properly programmed. Actually you can think of Hannibal as a bot framework with groups as the user interface. Let's say you want an anarchy bot, but don't want to deal with map analysis or the 0AD bot API, all you need to do is fork Hannibal and define new groups with new behavior.

How does it work? Well, technically the DSL works by chaining JavaScript methods. Here is a trivial example:

array  .sort()  .filter(item => item > 5)  .forEach(/* do stuff */);

That works because both sort() and filter() return an array. Fortunately JavaScript's automatic semicolon inserter doesn't kick in. Hannibal's DSL looks similar, but it adds another level. Let's start with the most important group, the base of any village, the grain pickers. I'm going to challenge your imagination, but give it a try: Assume YOU are that little female unit contracted to do nothing but gathering food.

Still here? Ok, what is your job description? Basically: Stay alive and gather food. What are your options to stay alive? Let's say, if you got hit seriously flee and search for shelter. If the attacker is gone try to heal. And to gather? Well, that's simple: Gather food and carry it to a dropsite. OK, may be I should order a field if there is none. And even a dropsite, if all others are to far away.

You see a gather-food-unit only needs a very simple mind. That's the concept of Hannibal: Give units a simple job description so they never go idle and group similar jobs together. From there complex bot behavior will follow/emerge.

I now hear objections: You can't win a game with female food gatherer only! That's right. And I must say I haven't seen attacking units so far with this concept. So here is what I'm going to implement: The job description of a military is actually close to the above, stay alive and do damage. If you are successful move forward otherwise move back to a more secure place like a fully garrisoned fortress of a tower triangle. Hannibal's job is to provide this information, the attacking unit's job is to use it wisely. Can you now imagine a horde of cavalry keeping the right distance avoiding suicide attacks, never going idle and heal if exhausted?

Back to the DSL, a group's scripts are triggered by events only. There is a one time event when the group launches, another when Hannibal assigns an asset (read entity, e.g. unit, field, house, dropsite), when the group got attacked, an asset got destroyed or a timed event. Here is the list:


Obviously launch is kinda initialization. The group declares what assets are needed and presumably orders the first one. This is the grain pickers launch script:

function launch (w) {  w.units    = ["exclusive", "food.grain GATHEREDBY WITH costs.metal = 0, costs.stone = 0"];  w.field    = ["exclusive", "food.grain PROVIDEDBY"];  w.dropsite = ["shared", "food ACCEPTEDBY"];  w.units.size    = 5;  w.field.size    = 1;  w.dropsite.size = 1;  w.nounify("units", "field", "dropsite");  w.dropsite.on.request();   }

The assets are defined using the internal query language, they all need a size and then the definition gets 'nounified'. All left is requesting a dropsite which sooner or later triggers the assign script.

The DSL has kind of a grammar consisting of sentences, nouns, subjects, objects, verbs, attributes and modifiers.


Above is a sentence. JS experts see property chaining works too. 'w' is the world the language is defined in. Each group acts in its own world. 'w' is always the first parameter in script call. Whenever something is nounified it is available either as subject or object. Here is what in this sentence happens:

w             // each sentence starts with the world .dropsite    // dropsite is picked as current object .on          // the current object is picked as subject  .request()   // the current subject fires an action, amount defaults here to 1;             // sentence done

So, in plain English it means: Dear Hannibal, I desperately need a dropsite for food.

As most maps have a civic centre as start up building this command is actually a no-brainer. Probably already the next tick calls assign. If Hannibal can't provide a dropsite the map is probably un-playabale, because of lack of resources or whatever. Now things get a bit more complicated, the group may get assigned a dropsite, a field or an unit, so a filter is needed.

function assign (w, item) {  w.objectify("item", item);  // got dropsite, requests unit, exits  w.dropsite.on    .member(w.item)    .units.do.request()    .exit  ;  ...

Here dropsite is first selected as object, then as subject and then a member check follows. If that check fails all the rest of the sentence is ignored. In case of success units is selected as subject (do) and a unit is requested. 'exit' means all the rest of the whole script is ignored, think of 'return' in a JS function.

It should be clear what comes next - a sentence for a new unit:

// keep requesting units until size is metw.units.on  .member(w.item)  .lt(w.units.count, w.units.size)  .request();

'lt' - less than - compares 'size' as defined in launch with the actual amount of units. You miss the field?

//  the first unit requests field, exitsw.units.on  .member(w.item)  .match(w.units.count, 1)  .match(w.field.count, 0)  .field.do.request()   .exit;

Still the units do not gather, the script makes sure through the order of requesting there is a dropsite and units and a field.

// got the field foundation, all units repair, exitsw.field.on  .member(w.item)  .match(w.item.foundation)  .units.do.repair(w.field)  .exit;

Here is another modifier 'match' just checking whether a attribute is true. There are two more sentences, the whole thing is currently here: https://github.com/agentx-cgn/Hannibal/blob/master/source/simulation/ai/hannibal/grp-harvester.js#L61

For sure this is just the beginning, beside grain-picker, miners, lumber jacks, builders more is needed to support a village or even launch an attack. For example there will be a scout group having an object called scanner which scans the terrain and Hannibal transforms this information into a potential field needed to tell attackers which region is safe or not, thus giving the direction of forward or back.

If at this point you think - wait stop - I have some sentences in mind for another group, you're welcome. Please, let me know or even better paste them below. This is the purpose of Hannibal, it let you try out in a simple way how an AI can work. All you need to do is put yourself into the position of an 0AD unit and determine your options.

That's it. I nearly spend a year getting this idea running and actually see now units building villages, powered by groups scripts. I hope, I could understandably layout the potential. Now, my target is to release a sandbox (no attacks) version within weeks, showcasing this concept. Follow this topic for breathtaking videos :)

PS. Developers may have all alarm clocks ringing because of performance, but keep in mind the scripts are fired on events only, there are not that many groups needed and so far none took longer than a millisecond.

PPS: Ok, another one. A unit of your group got killed, what's the sentence?

function destroy (w, item) {  w.objectify("item", item);  // lost unit, request another  w.units    .member(w.item)    .request()  ;}
Link to comment
Share on other sites

Stupid Question : Are you trying to make it smart or realistic ?

High level Unit in bataillon killed(ie : Hero, E variant, champion unit) implies units split in every senses trying to find someone to kill ? Or change formation/stay in line, and try to adapt to the enemy they are dealing with ?

  • Like 1
Link to comment
Share on other sites

> smart or realistic?

Depends on the group script author. He/she may use: w.units.do.format("Testudo"). Same for stances: w.units.do.stance("standground")

PS: I've kept this intro simple, but I'll try to make some tests and showcase some battlefield tactics soon. In short groups can communicate with each other, keep a specific/flexible distance and have verbs like move, stance, format which can also be queued. But these are the essentials every human player has too. Not much of Hannibal, the API provides that.

Edited by agentx
Link to comment
Share on other sites

> is compatible with the main bot?

What do you expect from compatible bots? Do they they behave different from incompatible bots? If you want to see Petra fighting Hannibal on a map, that'll work. In that sense all 0AD bots are compatible with each other.

Link to comment
Share on other sites

I think av93's question shows an interesting point, which seemed swinging along "under the hood" of many AI-bot related discussions: Shall we concentrate all efforts on one - *THE* - 0AD bot for extraordinary quality and glory, or are we going to look at/for a zoo of different bots and concepts, so the best concepts will eventually evolve?

Personally i tend for the second option, but there seem quite some people in favor of the first one.

In your initial post, you state "because this what this bot is all about.". I get the impression Hannibal is more of a new "AI-bot SDK", than a bot. In your second post you say "Depends on the group script author. He/she may use:...". So will the bot behavior be hand-coded, or are you going to infer the group scripts from the triple store?

  • Like 1
Link to comment
Share on other sites

@Teiresias: I think your first observation is understandable if you see devs as limited resource. From a certain point 2 devs working on 2 bots is a waste of time and energy. However on the long run I perfectly agree with your tendency, mainly because 2 or more bots fighting each other bring up issues very fast and reproducible.

The triple store is basically a replacement for the entity collections and their filters. "food ACCEPTEDBY" may yield a farmhouse, a dock or a centre. The group can work with any outcome, so authors won't deal with different mods, civs, costs, templates or entities. So far each group's script fits in one short file and I like that. And yes, you can contribute to Hannibal by scripting groups.

> SDK.

Yes and no. The concept is: Every unit is part of an autonomous group for a specific task. If you agree with that, it is an SDK, actually a BDK :). If not it is just another bot.

Link to comment
Share on other sites

I think there are two fundamental improvements in your concept:

  1. Regarding performance, the event-driven approach is probably far superior to any polling system based on entity collections, as supplied with the current API. If driven far enough, maybe you could do away with the BaseAI.handleMessage() alltogether. IIRC that function has been identified as a performance dropper in some forum discussions.
  2. Your DSL nicely abstracts off the tedious details of reading templates, isolating entities etc., which tend to clutter up the JavaScript code of the current bots (including mine).

Generally, your DSL and event system implement an expert system somewhat similar to what was used in AoK, but at an entity-level granularity. This is probably more suited to defining intelligent behavior than pure JS.

Looks promising!

PS. I meant SDK=Software Development Kit. I was not sure whether you will provide a complete set of scripts or whether Hannibal is just the pure "script runtime".

Link to comment
Share on other sites


1) Basically all I use from the API is in m.BaseAI.prototype.Init() + Events + TechTemplates. I know this involves in some cases deep access to data which definition may change. But I make sure to not have more work then changing the API. Performance wise there are only spikes forced by map analysis. I've set myself a limit to stay below 100ms per tick in any case and so far there is lots of room. OTOH the test with 8 Hannibals throwing 2000 units into battle has still to run. But there is also the option to think less or less often if needed. I think time is just another resource like wood and stone and there are budget limits and a bot has to deal with.

2) Yes, I needed to separate group logic from API logic visibly and semantically, it really helps me thinking how a group should solve problem X.

Hannibal will consist of two parts: A set of groups + the rest/runtime. Everybody is free to take the rest and author his own set of groups and call it a bot, but not Hannibal :) There will even be kinda packager that renders all code into one xyz.js which goes with metadata and folder structure into a zip + readme.txt. So yes, Hannibal is a Bot Development Kit and a mod and you can make a bot mod mod.

Today I worked a bit on moving units around and started a "dance" group whose units run in circles. This is the relevant code:

function launch (w, config) {  // stringify group position  var path, pos = w.group.position[0] + " " + w.group.position[1];  // pick some citizens  w.units       = ["exclusive", "food.grain GATHEREDBY"];  w.units.size  = 9;  // move path 70m north and create circle with radius 10  path          = w.units.size + "; translate " + pos + "; translatep 0 70; circle 10";  w.path        = ["path", path];  w.path.size   = w.units.size;  w.nounify("units", "path");  w.path.on.request(); }function interval (w, tick, secs){  //  if complete and all idle, rotate path and redistribute units  w.units.on    .doing("idle")    .match(w.units.count, w.units.size)    .path.do.modify("rotate 40")    .units.do.spread(w.path)  ;}

Doing() temporarily filters units by UnitAiState. A path is just an array of [x, z] locations. It can be used to send units to each point (spread) or make them walk from one to the next (move). 'translatep' translates the path by polar coords, where north is 0° and points from CC to center of the map. I hope human opponents will accept bots celebrating victory, too :)

Link to comment
Share on other sites

  • 1 year later...
On 1/8/2015 at 8:03 PM, av93 said:

The thing that I tried to say is that your scripts can be added to Petra.

Hannibal bot of agentx must be defended here. It is always possible to add it to Petra. Just not in the current shape.

Edit: As of how this works, I already posted it way earlier. It is a design quest. agent did do all within the realm he had at hand and the algorithms are standing. So integrating it into  the main code base is easier.

Edited by Radagast.
text was truncated on request sent
  • Like 1
Link to comment
Share on other sites

[Video] HannibalAI Group Interaction (Hannibal by agentx, Group Interaction plugin by worlddevelopment.foranepicworld)

Difficult to see in the video, but dynamic group interaction can be seen in occasions, e.g. at the beginning when units are still idle or when they are freshly recruited or when a new project, e.g. a construction, is started.

What it essentially means is that there will be no idle units.

It was coded such that groups can detect when they need manpower and not only request it in the AI but also from other groups.

e.g. under attack the Hybrid AI (note agentx reserves the othello Hannibal label for him) is - unlike other AIs in 0AD - capable to summon all spared workers in a defensive formation.

Also group leadership is coded, such that more fancy strategies like direct officer targeting and destruction/disarray of army formations are possible.

Imagine the beardy general that you try to catch because he is so experienced a strategist that you fear at some point he - or his lord - might turn against you, the yet peaceful ally.


That the Hannibal AI is great work (of course the other bots are, too, they all build on each other) is not doubted, it's just that some parts need to be redesigned.

The HybridAI is not HannibalAI, it is an entirely different approach, basically it is a hybrid between Leviathan and interaction between parties in the corresponding spheres of influence.

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Create New...