Light Style© by Fisana

Jump to content


Photo

Trying to building a new AI from scratch


  • Please log in to reply
17 replies to this topic

#1 FeXoR

FeXoR

    Centurio

  • Community Members
  • PipPipPipPipPip
  • 900 posts

Posted 19 April 2012 - 12:42 PM

What do I need to write a new AI from scratch?

I have:
data.json
{
	"name": "FeXoR Bot",
	"description": "A simple AI by FeXoR.",
	"constructor": "fbotAI"
}


fbot.js
function fbotAI()
{
}


When starting a game in Atlas it claims:
"ERROR: AI script Serialize call failed"
"ERROR: AI script Deserialize call failed"

It's the same when I add:
_init.js
Engine.IncludeModule("common-api-v2");

So what do I need in addition to avoid errors?

In addition I would like to know how the connection to the engine works.

It would be nice if someone would explain it to me first without any API usage (so just what the engine does) and then how the common-api-v2 achieves communication with the engine.

I know this is very basic and so might not be that easy to explain but I'm not very good at reading and understanding code especially when something appears like "Engine.xyz" and "Engine" is not defined in the API, sorry for that :sorry:


Thx in advance
  • 0

#2 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 19 April 2012 - 01:08 PM

So what do I need in addition to avoid errors?


You need everything that you have and you also need to make sure that fbotAI inherits from baseAI. Here is the relevant qBot section.

function QBotAI(settings) {
	BaseAI.call(this, settings);

	...
}

QBotAI.prototype = new BaseAI();

Your other question is quite a big one, I will try and answer it later, I don't have time right now.
  • 0

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!


#3 FeXoR

FeXoR

    Centurio

  • Community Members
  • PipPipPipPipPip
  • 900 posts

Posted 19 April 2012 - 01:43 PM

You need everything that you have and you also need to make sure that fbotAI inherits from baseAI. Here is the relevant qBot section.

function QBotAI(settings) {
	BaseAI.call(this, settings);

	...
}

QBotAI.prototype = new BaseAI();

Your other question is quite a big one, I will try and answer it later, I don't have time right now.


Thx already! Though I don't know what "settings" is for right now but it works without errors. It seams that communication without importing the API is not that easy (because I already need it to avoid the error).

I will read more code to get used to the common-api-v2 and look forward to some explanation of yours. Do I get it right that in the AI API rotation is again like in the engine but height isn't really needed? ^^

However, I look forward for some explanation and thx for your patience with me.


  • 0

#4 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 20 April 2012 - 02:16 PM

The AI is a sub-section of the network synchronised gameplay simulation. The game provides a proxy representation of the state of the simulation. Direct access to the rest of the simulation is not allowed so that theAI can be safely made asynchronous. The proxy representation is a set of javascript objects, with one for every entity in the game, with appropriate properties. Diff's are sent each AI turn to keep the list up to date. This is done with the combination of AIInterface and AIProxy (simulation/components).

The AI has the function HandleMessage(state) called each turn. state is an object with quite a few properties, see the function in base.js for more details. The thing which gives you all of the entity information is state.entities which is an object of the form {5: {id: 5, owner:1, ...} , 7:{...},...}. So state.entities[id] is an object containing all of he changes to an entity in the last turn. When an object is created then state.entities[id] will contains all of the properties for that object. The common-api applies the diffs each turn to keep all of the entities up to date for the AI.

The AI performs actions by calling an interface called Engine.PostCommand() which sends a message to simulation/helpers/commands.js which runs the command. There are a few other Engine commands, which should be self explanatory (run a search on the AI folder to find them all). They are defined somewhere in the C++ (I can't remember exactly).
  • 1

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!


#5 FeXoR

FeXoR

    Centurio

  • Community Members
  • PipPipPipPipPip
  • 900 posts

Posted 20 April 2012 - 03:13 PM

The AI is a sub-section of the network synchronised gameplay simulation. The game provides a proxy representation of the state of the simulation. Direct access to the rest of the simulation is not allowed so that theAI can be safely made asynchronous. The proxy representation is a set of javascript objects, with one for every entity in the game, with appropriate properties. Diff's are sent each AI turn to keep the list up to date. This is done with the combination of AIInterface and AIProxy (simulation/components).

The AI has the function HandleMessage(state) called each turn. state is an object with quite a few properties, see the function in base.js for more details. The thing which gives you all of the entity information is state.entities which is an object of the form {5: {id: 5, owner:1, ...} , 7:{...},...}. So state.entities[id] is an object containing all of he changes to an entity in the last turn. When an object is created then state.entities[id] will contains all of the properties for that object. The common-api applies the diffs each turn to keep all of the entities up to date for the AI.

The AI performs actions by calling an interface called Engine.PostCommand() which sends a message to simulation/helpers/commands.js which runs the command. There are a few other Engine commands, which should be self explanatory (run a search on the AI folder to find them all). They are defined somewhere in the C++ (I can't remember exactly).


THX! I think I will start writing the bot next week.

As far as I get it and questions:

- First each turn the data changed in the engine the turn before are applied to the BasiAI's data

- BaseAI.OnUpdate() is the call for the actual custom AI script that should be overwritten in the constructor.

- In opposite to the data update at the beginning of the turn (engine -> BaseAI) setting orders is send individually? Or did I miss a function sending a diff of orders from the BaseAI to the engine? And if it's like think doesn't it defeat the ability of the AI to be threaded (because of many interactions with the engine)?
- What does 'settings' in the constructor contain?

 


Edited by FeXoR, 26 April 2012 - 12:14 PM.

  • 0

#6 FeXoR

FeXoR

    Centurio

  • Community Members
  • PipPipPipPipPip
  • 900 posts

Posted 26 April 2012 - 12:16 PM

Here's a very rough concept of what my AI should do and perhaps makes more clear why I want it. Beware the typos... :S :

Main aims of fbot:
- Everything that needs resources in one queue of entity/condition pairs
- Gather existent resources close to the deposits rather than collecting by type (A growing 'gatherRange' can do that reset when a new deposit is finished)
- What entities are build should depend on the available resources rather then be fixed
- Expand by territory rather than senselessly attacking
- Add conditions to give up (e.g. no civil center and (in sum 500 resources missing to build one OR no deposit for the missing resource), has to be tweaked...)
- Force units to return resources if enemies near and if close enough to be attacked by CC garrison if this adds damage to CC. Otherwise walk to the opposite side of the CC as the enemies are as long as some amount of hit-points. Otherwise garrison to safety.
- Re-decide what a unit should gather when it returned resources
- Develop an easy_ai_api (build as far as possible upon common-api-v2) that only requires fixed code (shown in a demo bot) and a custom list of entity/condition pairs (and attack conditions)

Abbreviations:
CC: Civil Center
CF: Citizen Female
CS: Citizen Soldier

More detailed:
----- Cover CC/food/wood/gatherers/popcap -----
- Civil center (CC)
(Without CC the game will be lost so this is the first priority even if you have drop-sides for all resources and enough resources and a builder)
(Edge case enough resources/popcap and barracks)
(Edge case drop sides/barracks/enough resources/popcap for CS but not enough resources for CC (hard...))
- Enough popcap to build units (If CC/enough resources for a gatherer but no gatherers/enough popcap to build gatherer -> force attack with all non-citizen soldiers)
- Food gatherer (citizens females (CF) if non-running food source near, else if resources for farm/farm available build farm, else if running food source and enough resources for CS build CS, else if wood near and farm available prefer gather wood)
- Wood gatherer (citizen soldiers (CS) if enough wood, else CF until enough wood)
- Field (Still prefer non-running Gaia resources in gather range)
- 10 CF and 10 CS
----- Food/wood/gatherers/popcap covered, before that don't do anything else! -----
- Build house(if not already present)
- Build mill
- Build farmstead
- Advance to T2
- Build 2 markets
- Build trade units (TU) for all non-food resources (5 each starting with wood if no wood in gatherRange, no wood if most of the entities in gatherRange grant wood and start with metal)
- 20 CS
- Add stone to be gathered (CS)
- 30 CS
- Build a tower in front (towards the enemy) of all markets if stone in gatherRange, else if enough metal otherwise 5 more stone trade units
- Build temple if stone in gatherRange
- Build barracks
- Build temple (if not already)
- (Maybe defensive structures right now but don't think so)
----- T3 -----
- Advance to T3
- Build a building crew (10-20)
- Build 5 fortresses at the border to the enemy
- Build non-preasant production buildings (for heroes, elite, siege)
- Build a defense force garrisoning in buildings good versus siege weapons that can be garrisoned anywhere(10-20, heroes/elite(unmounted?), CS (unmounted?))
- Build one of all non-present building types
- Expand territory with CCs near fortresses
- Build a hunting force of 5-10 ranged mounted citizen soldiers
- Further expand territory with CCs near fortresses
- Build 3 fortresses at the border to the enemy
- Build an attack force depending on civilization (Preferring ranged mounted/siege towers, heroes, catapults and rams)
- Further expand territory with CCs near fortresses
- Reinforce attack force until current pop-cap
...
- If popcap is reached -> Attack with all soldiers but foot citizen soldiers

In between sometimes check if markets could be placed better and raze unneeded fortresses (IMO the number shouldn't be limited but fortresses should be balanced though they are about to be balanced already) 

Edited by FeXoR, 26 April 2012 - 12:37 PM.

  • 0

#7 FeXoR

FeXoR

    Centurio

  • Community Members
  • PipPipPipPipPip
  • 900 posts

Posted 26 April 2012 - 02:07 PM

Sorry for the triple post but I saw Quantumstate already read this and forgot to add the main thing.

It would be really nice of you to tell me what code is needed to produce a female peasant, build a farm and send that female peasant to gather from that farm (Ignoring the other starting units)

In addition I need to know how to change the resource gathered if a gatherer has returned the resources he carried and how to garrison and drop units to/from a specified building (I think that could be explained in words).

But until now I'm quite helpless because everything seams to depend on everything else in the common-api for me ATM(I know it might not get better by my concept :wacko:)

Thx in advance.


  • 0

#8 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 02 May 2012 - 04:10 PM

Sorry for the triple post but I saw Quantumstate already read this and forgot to add the main thing.

It would be really nice of you to tell me what code is needed to produce a female peasant, build a farm and send that female peasant to gather from that farm (Ignoring the other starting units)

In addition I need to know how to change the resource gathered if a gatherer has returned the resources he carried and how to garrison and drop units to/from a specified building (I think that could be explained in words).

But until now I'm quite helpless because everything seams to depend on everything else in the common-api for me ATM(I know it might not get better by my concept :wacko:)

Thx in advance.


To send commands to the game you use

Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued});

The commands are shared with the GUI so your AI can give all of the same commands a player can. See simulation/helpers/commands.js which is where he commands are handled. Alternatively you can read the entity.js file in common-api which is where most commands are sent for AI's.

In order to train a unit you must first find a structure to train the unit from. The other AI's use functions in gamestate.js which filter the list of entities that the game gives to find the correct entities to train from. The process is similar for finding a worker to construct the building (working out where to put it is quite complicated), then you need to send a repair command to make the unit actually build it (slightly strange, but that is how it works).

If you look at the existing AI code it should help you figure out how things are done. The demo/test bot is the simplest demonstration.
  • 0

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!


#9 FeXoR

FeXoR

    Centurio

  • Community Members
  • PipPipPipPipPip
  • 900 posts

Posted 02 May 2012 - 04:56 PM

To send commands to the game you use

Engine.PostCommand({"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued});

The commands are shared with the GUI so your AI can give all of the same commands a player can. See simulation/helpers/commands.js which is where he commands are handled. Alternatively you can read the entity.js file in common-api which is where most commands are sent for AI's.

In order to train a unit you must first find a structure to train the unit from. The other AI's use functions in gamestate.js which filter the list of entities that the game gives to find the correct entities to train from. The process is similar for finding a worker to construct the building (working out where to put it is quite complicated), then you need to send a repair command to make the unit actually build it (slightly strange, but that is how it works).

If you look at the existing AI code it should help you figure out how things are done. The demo/test bot is the simplest demonstration.


Thanks for the reply.

I'll start with demo bot to get the concept as a whole. Tried with qbot but gave up half the way on.

I seam to be somehow handicapped in matters of reading foreign code :S

Was a bit stupid of me to ask for the code I imagine :D

I'll try harder...


  • 0

#10 madmax

madmax

    Sesquiplicarius

  • Community Members
  • PipPip
  • 165 posts

Posted 10 May 2012 - 05:04 AM

I am interested in this too. I have begun looking at qbot and testbot.

So why do you need the queues :

// this.queues cannot be modified past initialisation or queue-manager will break
	this.queues = {
		house : new Queue(),
		citizenSoldier : new Queue(),
		villager : new Queue(),
		economicBuilding : new Queue(),
		field : new Queue(),
		advancedSoldier : new Queue(),
		siege : new Queue(),
		militaryBuilding : new Queue(),
		defenceBuilding : new Queue(),
		civilCentre: new Queue()
	};

	this.productionQueues = [];


Also in this snippet you are trying to get a list of civ centers which if destroyed will end the game ? That is why they are key resources ?

var myKeyEntities = gameState.getOwnEntities().filter(function(ent) {
			return ent.hasClass("CivCentre");
		});

Also have you used Persistent entity collections as in this post ?
http://www.wildfireg...ndpost&p=230498

Edited by madmax, 10 May 2012 - 05:24 AM.

  • 0

#11 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 10 May 2012 - 08:03 PM

So why do you need the queues :

Have a read of https://github.com/q...ucture-Overview hopefully it should give you an idea of what the queues are for. If not then feel free to ask more questions.

Also in this snippet you are trying to get a list of civ centers which if destroyed will end the game ? That is why they are key resources ?

This is only used for working out where fortresses should be placed. To work out the placement it is necessary to know what they are trying to defend so this is a crude estimate of the most important stuff to defend.

Also have you used Persistent entity collections as in this post ?


Yes, they are now used extensively throughout qBot.
  • 0

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!


#12 madmax

madmax

    Sesquiplicarius

  • Community Members
  • PipPip
  • 165 posts

Posted 10 May 2012 - 08:38 PM

Full fledged docs..joy!!...will give it a read.

By the way do you have an idea as to why qbot doesnt create fields close to its civ center when it runs out of food ?

Edited by madmax, 10 May 2012 - 08:38 PM.

  • 0

#13 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 10 May 2012 - 09:00 PM

Full fledged docs..joy!!...will give it a read.

By the way do you have an idea as to why qbot doesnt create fields close to its civ center when it runs out of food ?


As far as I know this is because qBot has run out of wood or can't find space to place the fields. Of course other bugs are possible.
  • 0

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!


#14 madmax

madmax

    Sesquiplicarius

  • Community Members
  • PipPip
  • 165 posts

Posted 11 May 2012 - 04:29 AM

Have you used the Escrow idea from testbot by any chance for sharing resources ? Or is it implemented differently ?

TestBotAI.prototype.ShareResources = function(remainingResources, unaffordablePlans)
{
	// Share our remaining resources among the plangroups that need
	// to accumulate more resources, in proportion to their priorities

	for each (var type in remainingResources.types)
	{
		// Skip resource types where we don't have any spare
		if (remainingResources[type] <= 0)
			continue;

		// Find the plans that require some of this resource type,
		// and the sum of their priorities
		var ps = [];
		var sumPriority = 0;
		for each (var p in unaffordablePlans)
		{
			if (p.plan.getCost()[type] > p.group.getEscrow()[type])
			{
				ps.push(p);
				sumPriority += p.priority;
			}
		}

		// Avoid divisions-by-zero
		if (!sumPriority)
			continue;

		// Share resources by priority, clamped to the amount the plan actually needs
		for each (var p in ps)
		{
			var amount = Math.floor(remainingResources[type] * p.priority / sumPriority);
			var max = p.plan.getCost()[type] - p.group.getEscrow()[type];
			p.group.getEscrow()[type] += Math.min(max, amount);
		}
	}
};

just curious.

Edited by madmax, 11 May 2012 - 04:30 AM.

  • 0

#15 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 11 May 2012 - 09:09 PM

Have you used the Escrow idea from testbot by any chance for sharing resources ? Or is it implemented differently ?


No this has been completely superseded by the queue manager which handles resource distribution.
  • 0

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!


#16 madmax

madmax

    Sesquiplicarius

  • Community Members
  • PipPip
  • 165 posts

Posted 12 May 2012 - 02:59 AM

I was looking at queue-manager.js :

// Each queue has an account which records the amount of resource it can spend.  If no queue has an affordable item
// then the amount of resource is increased to all accounts in direct proportion to the priority until an item on one
// of the queues becomes affordable.
//
// A consequence of the system is that a rarely used queue will end up with a very large account.  I am unsure if this
// is good or bad or neither.

So what about checking if a queue has something to build. Do not increase the amount of resources to it in proportion(or whatever other mechanism) if it has nothing to build.

Edited by madmax, 12 May 2012 - 03:00 AM.

  • 0

#17 madmax

madmax

    Sesquiplicarius

  • Community Members
  • PipPip
  • 165 posts

Posted 12 May 2012 - 08:31 AM

Ok I have read through the qbot docs and now I am looking at queue-manager.js

I am trying to understand this function :

QueueManager.prototype.futureNeeds = function(gameState) {
	// Work out which plans will be executed next using priority and return the total cost of these plans
	var recurse = function(queues, qm, number, depth){
		var needs = new Resources();
		var totalPriority = 0;
		for (var i = 0; i < queues.length; i++){
			totalPriority += qm.priorities[queues[i]];
		}
		for (var i = 0; i < queues.length; i++){
			var num = Math.round(((qm.priorities[queues[i]]/totalPriority) * number));
			if (num < qm.queues[queues[i]].countQueuedUnits()){
				var cnt = 0;
				for ( var j = 0; cnt < num; j++) {
					cnt += qm.queues[queues[i]].queue[j].number;
					needs.add(qm.queues[queues[i]].queue[j].getCost());
					number -= qm.queues[queues[i]].queue[j].number;
				}
			}else{
				for ( var j = 0; j < qm.queues[queues[i]].length(); j++) {
					needs.add(qm.queues[queues[i]].queue[j].getCost());
					number -= qm.queues[queues[i]].queue[j].number;
				}
				queues.splice(i, 1);
				i--;
			}
		}
		// Check that more items were selected this call and that there are plans left to be allocated
		// Also there is a fail-safe max depth 
		if (queues.length > 0 && number > 0 && depth < 20){
			needs.add(recurse(queues, qm, number, depth + 1));
		}
		return needs;
	};
	
	//number of plans to look at
	var current = this.getAvailableResources(gameState);
	
	var futureNum = 20;
	var queues = [];
	for (q in this.queues){
		queues.push(q);
	}
	var needs = recurse(queues, this, futureNum, 0);
	// Return predicted values minus the current stockpiles along with a base rater for all resources
	return {
		"food" : Math.max(needs.food - current.food, 0) + 150,
		"wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0) + 150, //TODO: read the house cost in case it changes in the future
		"stone" : Math.max(needs.stone - current.stone, 0) + 50,
		"metal" : Math.max(needs.metal - current.metal, 0) + 100
	};
};


I am able to understand upto :

var num = Math.round(((qm.priorities[queues[i]]/totalPriority) * number));

So you are calculating a fraction of number(which I guess is the amount of resources irrelevant of type) , in proportion to the priority, and storing it in the floating point value num.

So what is this supposed to refer to :
qm.queues[queues[i]].queue[j].getCost()

A queue inside a queue ? I mean if all you want is to pick a particular queue, then
qm.queues[i]
would seem more intuitive. Then to get the cost :
qm.queues[i].getCost()

But yeah I don't really know the interfaces of the queues or the queue manager yet.

I am not able to get my head around the nested for-j loops :)
---------------------

ok so looking further down I see you always pass number as 20 via futureNum

so for each queue in the for-loop, you are calculating fractions of 20 and then checking the value against qm.queues[queues[i]].countQueuedUnits()

                        if (num < qm.queues[queues[i]].countQueuedUnits()){
				var cnt = 0;
				for ( var j = 0; cnt < num; j++) {
					cnt += qm.queues[queues[i]].queue[j].number;
					needs.add(qm.queues[queues[i]].queue[j].getCost());
					number -= qm.queues[queues[i]].queue[j].number;
				}
			}else{
				for ( var j = 0; j < qm.queues[queues[i]].length(); j++) {
					needs.add(qm.queues[queues[i]].queue[j].getCost());
					number -= qm.queues[queues[i]].queue[j].number;
				}
				queues.splice(i, 1);
				i--;
			}

So you are essentially checking that the queues have the number of queued up units in proportion to their priority ?

Also why is there a need to recurse ?

Edited by madmax, 12 May 2012 - 08:54 AM.

  • 0

#18 quantumstate

quantumstate

    Primus Pilus

  • WFG Retired
  • 1,150 posts

Posted 12 May 2012 - 10:08 AM

This function isn't the prettiest code which I have written.

The main idea is to predict the next 20 items which will be produced from the queues and return the resources needed by these items.

The parameters for recurse are:
queues: A list of queues to pick items from, e.g. ["house", "villager", "economicBuilding"]. Initially this contains all of the queues that QueueManager controls. The naming is confusing so I shall now call the queues in this parameter cQueues.
qm: The QueueManager object (I should probably remove this parameter and use the self=this; style)
number: The number of items which need to be picked from the queues. Initially this is 20
depth: This stops infinite loops (Probably an indicator of the not so great code quality)

So what we want to do is use the priorities to work out what proportion of those 20 items should come from each queue. This is done by weighting each queue according to priority so totalPriority is calculated and each queue's number of items is just given by 20 * priority/totalPriority.

The problem is that the queues only have a limited number of items. So at this point we probably don't actually have 20 predicted items. So this is why the function is recursive. All queues which had all of their items picked are dropped fom the cQueues list and then the function calls itself to get the remaining 20-(#picked) items.

Writing this made me realize that there is a bug since the number of items taken from a queue is not stored so the first items in a queue will be picked repeatedly.

The general approach is not great though since it should be taking the cost of items into account when using the priorities since this is what the QueueManager does. I don't think i intended this to be permanent code, but ti works reasonably so hasn't been replaced yet.

I thought I should also explain this nasty looking line:
qm.queues[queues[i]].queue[j].getCost()So qm is the queue manager, and qm.queues contains the Queue objects (queue.js) for each of "house", "villager",.. queues[i] is the cQueue we are currently looking at so qm.queues[queues[i]]is the Queue object for the current cQueue. A Queue object has a list of items in this queue which is stored in the queue member so .queue[j] is the j'th item from this queue. Each item has a getCost() function which returns th cost of the item.
  • 0

Jonathan Waller [ aka quantumstate ]

Wildfire Games Programmer
Contact me: jonathanmarkwaller at gmail dot com


Support Wildfire Games!