Jump to content

AI and ApplyEntitiesDelta


Recommended Posts

Not sure if I'm already literate enough to pose this question properly, but here it goes: On game start from scratch or from saved game, I'd like to know whether there is a Civil Centre or not. From that point on I'm only interested in changes, e.g. destroyed by enemy or building of another one has finished. How can I keep track of the number of Civil Centres?

To me looks like the engine is internally heavily based on messages/events and on each call 'SharedScript.prototype.ApplyEntitiesDelta' processes new events into a datastructure/collection ready to get filtered by an AI. So the proposed approach is to query this collection to find out the number of Civil Centers. If this is true so far there is a conflict: on one hand the number of CCs is critical to the AI and on the other hand querying the collection is very costly. This leads to the question how often can I afford to query the collection for the number of CCs? And the answer depends very much on the horse power of the local machine. Actually there many similar questions, number of females, warriors, champions, houses, towers, etc.

I hope, here someone can intercept and tell me why this thinking is wrong and all these queries are not needed at all. If not...

An alternate interface/API would give the AI all its items at startup and from then both only talk about changes. The AI would demand the construction of a building *and* provide a callback function the API uses to inform about changes like destroyed or construction finished. Since functions are first class citizens in JavaScript this would solve two things: Queries are no longer needed and coding an AI would be much simpler.

I made a test and put this into CustomInit

  sharedScript.ApplyEntitiesDelta = function(state){    debMsg("ApplyEntitiesDelta: %s events", state.events.length);  }

Which works as expected at the cost of disconnecting the enemy AI from the game.

May I ask to apply a little change to shared.js and allow AIs to implement ApplyEntitiesDelta as they like?

Edit: I understand the consequences on the use of gamestate.js

Edited by agentx
Link to comment
Share on other sites

ApplyEntitiesDelta also, crucially, applies the "state" changes, not only events. Stuffs like a position change, or really anything will need to be updated.

Furthermore, it's shared between all AIs, so you can't really let AIs use their own version of the stuff.

Querying the collection isn't actually that slow if you create a persistent entity collection. It'll sort through all entities once, and then only add/remove entities that are relevant.

Perhaps you could expand a bit on what you want to do?

Link to comment
Share on other sites

First I hacked a few lines into the shared code, so AI's could register a callback. Then I stepped deeper into your code and it looks like Aegisbot.savedEvents has all I need. So I started a simple game and watched event tv in dbgView. What I understood is 'Create' is called when training starts, but it lacks the owner. 'TrainingFinished' is more useful and 'Destroy' is nice too. Similar to 'ConstructionFinished'.

What I want to achieve is what I call 'Groups' actually EntityCollections subclassed. Let's say the manager or mayor of a civic centre asks for more food. He launches a Group of 5 females and one field, all do not exist. So the group asks the major for females, once trained they get automatically assigned to the group choosing a place for the field and let them work. That part should work by push not by poll.

It would be simple (one possibility) if postcommand returns the ids of the future entities. All needed then is a dictionary/map with the ids as key and the callback as value. When 'TrainingFinished' comes in I would just dispatch the message via callback and the id to the Group which then takes care. When 'Destroy' comes in the Group asks for a new female and the entry got deleted from the map . Similar with 'Destroy' field.

I understand that is currently not possible, what I'm planning now, not tested, is polling idle entities not registered in a Group, check which Group has asked for these kind of entities and set up the dispatcher accordingly.

That way the mayor only deals with resource allocation and training entities. Groups ensure no idle workers. And on top I believe that system would also work nicely with armies. Another idea is that in easy mode the mayor does not launch e.g. repair groups listening at 'Attack' events.

Does that makes sense?

Btw, two questions: Is there any possibilitiy to communicate with the AI once the game runs? Maybe with chat commands, clicking at hot spots, mouse gestures, konami code, whatever? Just to trigger functions.

I've found the developer overlay, very useful, can an AI also draw shapes on the map, just for debugging? Or how can I use dumpimage? Google is of no help.

  • Like 2
Link to comment
Share on other sites

When 'Destroy' comes in the Group asks for a new female and the entry got deleted from the map . Similar with 'Destroy' field.

Sorry, currently I'm a bit squeezed between two frontiers, so I could not shift into this thread earlier.

Interesting. This looks like another precondition for our more dynamic military system. Your efforts turn essentially for my plans. I now also realize that we might have three AIs soon as my approach decentralizes AI decisions completely - though it still requires to query each AIs data just like you pointed out.

This is a really critical point. I hope you can figure more details. Once I finished my exam I will rejoin the effort, finally posting my code in the repository if you like. But it could be useful to keep the development separate for now as it's still possible that I will hit a cornerstone .. that renders my approach unsuitable. (One such problem is the timescale, how often should a command/or how you call it: group redecide its military movements?)

Your focus on rates of change is essential. I like the idea. In these terms our AIs could turn out compatible.

Link to comment
Share on other sites

To me looks like the engine is internally heavily based on messages/events and on each call 'SharedScript.prototype.ApplyEntitiesDelta' processes new events into a datastructure/collection ready to get filtered by an AI. So the proposed approach is to query this collection to find out the number of Civil Centers. If this is true so far there is a conflict: on one hand the number of CCs is critical to the AI and on the other hand querying the collection is very costly.

That's the point and where I'm stuck. Why do we create a snapshot of the game state this? Can't have the reference to the simulation state object instead?

Is it because we use simulation 'rounds' and only synchronize each round?

var API3 = (function() {var m = {};//...}());
Am I correct that we scope our object? And then we execute this function immediately, hence creating an 'instance' of this 'class' (so to say in javascript terms).

Isn't the class a scope in itself? So isn't this equivalent:

var API3 = function() {    /*<b>this</b> being the anonymous function in this case*/    this/*or API3*/.MySubClass = function(subClassAttribute) {        MySubClass.subClassAttribute = subClassAttribute;        MySubClass.subClassMethod = function() {            //do something        };        //create an instance:        this.mySubClass = new MySubClass();    };}();//instantiate:var api3 = new API3();
I'm pretty sure you have a good reason for the extra wrapper, so I need your help to understand why.

Edit: Got it. .. it's because of the scope and the instance creation ... this way we ensure we have only one object of this 'class' Object - and this object is m as it is returned at this anonymous function's end. Somehow this scoping looks special, so let's examine it in more detail.

What we finally do in common-api/baseAI.js is:

  • to create an wrapped object of type Object within an anonymous function (scope),
  • to then create a SubClass called BaseAI (in this example file baseAI.js),
  • to then extend the created wrapped subclass BaseAI via prototype,
  • run the function so that the wrapped object m gets out of its scope via the internal implicit copy operator.
The question is, why wrap it in its own scope if we copy it in the globally scoped variable API3 at the end of the anonymous function anyway?

Is this to avoid a conflict if another API had BaseAI class too? It's my only guess.

Edited by Hephaestion
Link to comment
Share on other sites

First I hacked a few lines into the shared code, so AI's could register a callback. Then I stepped deeper into your code and it looks like Aegisbot.savedEvents has all I need. So I started a simple game and watched event tv in dbgView. What I understood is 'Create' is called when training starts, but it lacks the owner. 'TrainingFinished' is more useful and 'Destroy' is nice too. Similar to 'ConstructionFinished'.

There's an ownerShipChanged event you can check. When the ownership changes from invalid (-1) to something valid, you know a new entity has been created.

You have to prepare for ownership changes anyway, as we will include capturing in the future.

...

It would be simple (one possibility) if postcommand returns the ids of the future entities. All needed then is a dictionary/map with the ids as key and the callback as value. When 'TrainingFinished' comes in I would just dispatch the message via callback and the id to the Group which then takes care. When 'Destroy' comes in the Group asks for a new female and the entry got deleted from the map . Similar with 'Destroy' field.

The ID is only known by the engine when the unit is spawned. keeping track of all future id's isn't possible. The human player can create a new entity in one of his buildings, and it can finish faster than your creation. So the id's will be reversed.

Btw, two questions: Is there any possibilitiy to communicate with the AI once the game runs? Maybe with chat commands, clicking at hot spots, mouse gestures, konami code, whatever? Just to trigger functions.

The AI can show chat messages (it's done when an allied AI attacks). It has no mouse, and generally can only communicate through predefined commands.

That's the point and where I'm stuck. Why do we create a snapshot of the game state this? Can't have the reference to the simulation state object instead?

nope, we want the AI to run in separate threads. So it will have to copy the data in some way, to not edit the simulation data live.
  • Like 1
Link to comment
Share on other sites

That's the point and where I'm stuck. Why do we create a snapshot of the game state this? Can't have the reference to the simulation state object instead?

nope, we want the AI to run in separate threads. So it will have to copy the data in some way, to not edit the simulation data live.

Absolutely! Thread is the secret here. Thanks for pointing out!

So now I can continue making my AI common-api compatible. Let's hope I don't hit the next problem in understanding too soon.

Edited by Hephaestion
Link to comment
Share on other sites

> There's an ownerShipChanged event you can check. When the ownership changes from invalid (-1) to something valid, you know a new entity has been created.

Ok, thanks I'll check this.

> The AI can show chat messages (it's done when an allied AI attacks). It has no mouse, and generally can only communicate through predefined commands.

Yes, I know how to make a bot speaking, but I want it to listen...

Link to comment
Share on other sites

One more problem. Properties that are only read have not to be copied for threadsafity's sake, or am I getting it wrong?

The problem is here in the BaseAI.js (the AI superclass):

m.BaseAI.prototype.Init = function(state, playerID, sharedAI){        PlayerID = playerID;        // define some references        this.entities = sharedAI.entities;        this.templates = sharedAI.templates;        this.passabilityClasses = sharedAI.passabilityClasses;        this.passabilityMap = sharedAI.passabilityMap;        this.territoryMap = sharedAI.territoryMap;        this.accessibility = sharedAI.accessibility;        this.terrainAnalyzer = sharedAI.terrainAnalyzer;        this.techModifications = sharedAI._techModifications[this.player];        this.playerData = sharedAI.playersData[this.player];        this.gameState = sharedAI.gameState[this.player];        this.gameState.ai = this;        this.sharedScript = sharedAI;//<-- TODO Remove as sharedScript is the live version and for allowing threading should never be written.        this.timeElapsed = sharedAI.timeElapsed;        this.barterPrices = sharedAI.barterPrices;        this.CustomInit(this.gameState, this.sharedScript);}
The question is, do we ever write barterPrices? Or is reading also a thread safety issue?

Let's examine it:

If another thread could have changed data in the meantime, then the data we read could be invalid as it's no longer guaranteed to be consistent for each thread/AI. But wait. Didn't we say no thread is allowed to write, so that's a contradiction and thus no thread can change the data and reading will always deliver valid and consistent dat (if it's not allowed to change it, it cannot change).

Thus we store a reference to sharedAI in the attribute sharedScript to cover all reading purposes.

Now I'm a bit confused as there are some variables copied that are usually not written:

e.g.

  • Is timeElapsed ever written/changed from an AI? Or should it read timeElapsedUntilBeginningOfThisTurn? If it's used the latter way then it's okay to copy it.

    this.timeElapsed = sharedAI.timeElapsed;
  • gameState as we collect the changes from every AI at the end of a turn I think. So there is no writing involved at all, gameState is only being read. So why should we make a copy of it?
Or is this simply because we don't need it for thread safety but just for having all AIs the same values independent of which AI currently is assigned to CPU (as apparently not all AIs can be assigned at the same point of time). But this can only be relevant if the gameState is changing constantly (continuously). The alternative is that it's constant for the whole turn? Usually it should be the latter as every simulation is step based. So essentially it's about if the gameState simulation/evolution time step is smaller that the smallest possible AI time step?

Or is it wrong that we should we make copies of variables only if we are about to change it? There must be a reason, I guess why it's used so heavily and I just can't see it.

Edited by Hephaestion
Link to comment
Share on other sites

It's not for thread safety, as the aiInterface already clones this variables. This means that there can only be unsafe threads between different AIs, if you edit values needed by both.

Also, in js, everything is a reference. So if you edit this.entities, it's a reference to sharedAI.entities, and so you have lost thread safety. Because it's not doing any cloning here, this also isn't a slow method.

The code is merely for shorter reference, as this.entities looks better than this.sharedscript.entities, and it's also a tiny bit faster to evaluate (every reference evaluation costs a bit of time).

Link to comment
Share on other sites

I think of it this way: The moment OnUpdate() is called the gamestate is a snapshot of the game. So gamestate is readonly by concept, no copy needed. You initiate changes to the game via Engine.PostCommand() or whatever level of the API you use.

Link to comment
Share on other sites

Now that I thought about this more, I wonder if dereferencing will make any difference at all as it's in O(1), i.e. needs only constant time. Currently I wonder if it's not even relativated by the extra function that's required only to create those shortcuts.

It's a bit confusing as objects can suddenly be accessed in many different ways - bringing the logical order in disarray?

Will calling the function create as much overhead as time saved by the fewer O(1) dereferences?

I know readability is the reason, unfortunately in my opinion the program gets less readable if we reassign attributes at arbitrary levels in the prototype hierarchy.

No offense, I'm completely okay with it - it's just a question of what you're used to. As I often use search and replace functionality, I don't really create shortcuts that much. If we followed the current heavy shortcut and less typing path consequently shouldn't we then change our random numbers 0, 1, ... to constants too, e.g. instead of parameter[3] use parameter[RESOURCE_FOOD] or comparable?

Also no offense, I only want to extend my horizon as I try to get familiar with the system and as usual it's tempting to change all and everything that one is not used to. (Luckily so far I could resist it.. so that the AI is purely additive for now.)

Thanks for the hint agentx and all those explanations sander.

Link to comment
Share on other sites

Posted · Hidden by sanderd17, March 6, 2014 - spam
Hidden by sanderd17, March 6, 2014 - spam

That being said, fresh Public auction Home working with D3 Gold a real earnings engaged is unquestionably adding far more excitement straight into Diablo Several game and will draw in plenty of beginners looking to try this newest pattern.

Link to comment

Join the conversation

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

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

 Share

×
×
  • Create New...