Jump to content

agentx

Community Members
  • Posts

    276
  • Joined

  • Last visited

  • Days Won

    7

Posts posted by agentx

  1. Thanks.

    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 :)

  2. @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.

  3. > 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.

  4. 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:

    launchassignattackdestroyinterval

    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.

    w.dropsite.on.request();

    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()  ;}
  5. > there is no viable defense strategie...

    It depends, if there are more than 10,000 stone available early I tend to build (double) walls and about 12-15 siege just to defend the village. I call it cocooning. However, if all players choose a defensive strategy there is not much of a game.

  6. I've completely underestimated the time it takes to create a framework for a bot. E.g, the economy, which fires commands like train, build, research towards the engine has been rewritten already three times. I've seen recently I wrote the first line of code last February, so, to sum up, I think I spend one month to understand API and Aegis, three to research the things I like to improve and now iterate for 6 months the framework to make the group scripts run on top. I don't know whether a documentation would have cut a lot from the first month, once it clicked that entity ids + tech template names is basically all what's needed to talk to the engine, I'd made progress very fast.

    I've also thought of publishing here a minimal bot, which trains 1 unit, builds 1 house, researches 1 tech and runs on a given map with everything hard coded. This sounds like a 20 lines of code project and it actually is. However, to make that bot run on *any* map the next hurdle then is making the location of the building soft coded and you are knee deep in map analysis. And that is not trivial at all. I've started a wiki page about: https://github.com/agentx-cgn/Hannibal/wiki/Map-Analysis

    In short, I think it makes sense to lower the hurdles at the beginning, but after the first month there are still some very huge roadblocks and after that you fight with your own code and are alone anyway. I agree with feneur that an invitation to AI devs needs more than a documentation. Given the recent media coverage in deep learning and other AI topics, it might be an idea to put AI/bots on the road map and reach out. I've seen trompetin17 reactivated the JS debugger, that's a very good start.

  7. > specially designed maps with entities positioned

    I've stopped that, because I lost overview facing a packed map folder and switched to generating maps on the fly. Have a look at mainland.js or what I did to it https://github.com/agentx-cgn/Hannibal/blob/master/maps/random/brainland.js It only takes around 0.1 secs to create a map like this.

    > may limit the number of newcomers in this area more and more.

    I think most players have lots of ideas to improve AIs. There is probably a whole range of ideas, starting with "too many idle units", or "no point in attacking building X" up to "you did that already three times and never succeeded". It should be possible to try out new ideas without coding bots from scratch.

  8. > the hurdles...

    And there are more to manage after start :) Currently I think the most time consuming thing is late game debugging. A remote debugger to inspect and change variables would speed up development by magnitudes. Actually writing code, wrapping variables with print(), starting the game and starring at the console feels like web development 15 years ago. I think developers are attracted by IDE's, too.

    • Like 1
×
×
  • Create New...