AI Base Class and API

Custom AI implementations will have a prototype based on this object. The prototype is made available to implementations via a local variable named **scope**.

```
function Player(name, controller, number, team, aiRandomizer, custom_ai_index, no_ai_update, dances, noAI)
{
	this.name = name;
	this.controller = controller; // Human, remote, cpu, none
	this.gold = game.getStartGold();
	this.number = number;
	this.supply = 0; // current supply
	this.lastAttackMessage = 0; // for ai
	this.team = game.teams[team] ? game.teams[team] : game.teams[0];
	this.team.hasPlayers = true;
	this.originalTeam = this.team; // gotta be stored, cuz when the game ends, the team of a player changes to 0 (so he gets full vision), but then when the replay is saved, the team needs to be his original team in the replay file
	this.aiUpdate = !no_ai_update ? true : false;
	this.isRevealed = false;
	this.noAI = noAI;
	this.isAlive = true;

	// global game variables (supply, tech requirements, ...)
	this.maxSupply = 0;
	this.realMaxSupply = 0;
	this.supplyCap = game.getMaxSupply();
	this.buildings = {}; // format: {id_string: count}
	this.buildingsUC = {}; // (under construction) format: {id_string: count}
	this.pseudoBuildings = {}; // format: {id_string: level}
	this.upgrades = {}; // format: {id_string: level}
	this.upgradeMods = {}; // all modified fields from upgrades (format: unitType: {fieldName: mod, fieldName2: mod})

	// statistics
	this.apm = 0;
	this.minedGold = 0;
	this.unspentGold = 0;

	// randomize AI; if randomizer number was passd use that to choose the strategy
	if(this.controller == CONTROLLER.COMPUTER);
	{
		this.randomAI = aiRandomizer ? Math.floor(aiRandomizer + number) % 4 : Math.floor(Math.random() * 3);
		this.aiObject = {};

		if(custom_ai_index >= 0)
			this.aiObject.update = customAIFunctions[custom_ai_index];

		// else
		//	this.aiObject.update = ouchyAI;

		this.scope = new Scope(this);
	}

	this.dances = dances ? dances : "";

	// ai variables
	if(this.randomAI == 0)
	{
		this.unitCountToAttack = 9;
		this.makeTowerAtStart = true;
		this.expand = false;
		this.raxAt1Base = 2;
		this.raxAt2Base = 2;
		this.AIName = "2rax allin";
	}

	else if(this.randomAI == 1)
	{
		this.unitCountToAttack = 14;
		this.raxAt1Base = 0;
		this.raxAt2Base = 4;
		this.makeTowerAtStart = false;
		this.expand = true;
		this.AIName = "greedy expansion";
	}

	else if(this.randomAI == 2)
	{
		this.unitCountToAttack = 8;
		this.makeTowerAtStart = true;
		this.raxAt1Base = 1;
		this.raxAt2Base = 4;
		this.expand = true;
		this.AIName = "safe expansion";
	}

	else
	{
		this.makeWolves = true;
		this.raxAt1Base = 1;
		this.raxAt2Base = 3;
		this.unitCountToAttack = 5;
		this.makeTowerAtStart = false;
		this.expand = true;
		this.makeRaxAtStart = true;
		this.AIName = "beast";
	}
};

Player.prototype.die = function()
{
	this.isAlive = false;
	message += "plDie$" + this.number + "~";
	game.killAllUnitsOfPlayer(this);
};

// called for example when a unit / building is created, dies, updated some variables that are used for example for tech requirements
Player.prototype.updateGlobalVariables = function()
{
	this.buildings = {};
	this.buildingsUC = {};
	this.pseudoBuildings = {};

	// check all buildings by this player
	for(var i = 0; i < game.buildings.length; i++)
	{
		var b = game.buildings[i];

		if(b.owner == this && !b.isUnderConstruction)
			this.buildings[b.type.id_string] = this.buildings[b.type.id_string] ? this.buildings[b.type.id_string] + 1 : 1;

		else if(b.owner == this && b.isUnderConstruction)
			this.buildingsUC[b.type.id_string] = this.buildingsUC[b.type.id_string] ? this.buildingsUC[b.type.id_string] + 1 : 1;
	}

	// check all finished buildings by this player
	for(var i = 0; i < game.units.length; i++)
		if(game.units[i].owner == this)
		{
			var u = game.units[i];
			for(var k = 0; k < u.pseudoBuildings.length; k++)
				this.pseudoBuildings[u.pseudoBuildings[k].type.id_string] = this.pseudoBuildings[u.pseudoBuildings[k].type.id_string] ? this.pseudoBuildings[u.pseudoBuildings[k].type.id_string] + 1 : 1;
		}

	// check revealed
	if(this.number >= 1)
	{
		var isRevealed = true;
		_.each(this.buildingsUC, function(val, key){
			if(lists.types[key] && lists.types[key].preventsReveal)
				isRevealed = false;
		});
		_.each(this.buildings, function(val, key){
			if(lists.types[key] && lists.types[key].preventsReveal)
				isRevealed = false;
		});
		this.isRevealed = isRevealed;
	}

	message += "techUpd$" + this.number + "$" + JSON.stringify(this.buildings) + "$" + JSON.stringify(this.buildingsUC) + "$" + JSON.stringify(this.pseudoBuildings) + "$" + (isRevealed ? "1" : "0") + "~";
};

Player.prototype.getCostOfNextInstanceForBuilding = function(type)
{
	if(!type.costIncrease)
		return type.cost;

	var cost = type.cost;
	if(type.costIncreaseGroup)
		for(var i = 0; i < type.costIncreaseGroup.length; i++)
		{
			cost += (this.buildings[type.costIncreaseGroup[i].id_string] ? this.buildings[type.costIncreaseGroup[i].id_string] : 0) * type.costIncrease;
			cost += (this.buildingsUC[type.costIncreaseGroup[i].id_string] ? this.buildingsUC[type.costIncreaseGroup[i].id_string] : 0) * type.costIncrease;
			cost += (this.pseudoBuildings[type.costIncreaseGroup[i].id_string] ? this.pseudoBuildings[type.costIncreaseGroup[i].id_string] : 0) * type.costIncrease;
		}

	return cost;
};

// returns a modifier for a given unit field, based on which upgrades this player has researched
Player.prototype.getValueModifier = function(value, type, alsoUnderConstruction)
{
	var mod = (this.upgradeMods[type.id_string] && this.upgradeMods[type.id_string][value]) ? this.upgradeMods[type.id_string][value] : 0;

	if(alsoUnderConstruction)
		for(var i = 0; i < game.upgrades.length; i++)
		{
			var u = game.upgrades[i];
			var count = this.upgradeCountInResearch(u);

			for(var k = 0; k < u.effectsTypes.length; k++)
			{
				if(u.effectsFields[k] == value && u.effectsTypes[k] == type)
					mod += u.effectsModifications[k] * count;

				if(u.effectsModsMultiplier && k in u.effectsModsMultiplier)
					for(var j = 0; j < count; j++)
						mod += type[value] * (u.effectsModsMultiplier[k] - 1);
			}
		}

	return mod;
};

Player.prototype.modifyUpgrade = function(upg, level)
{
	this.upgrades[upg.id_string] = this.upgrades[upg.id_string] ? (this.upgrades[upg.id_string] + level) : level;

	for(var k = 0; k < upg.effectsTypes.length; k++)
		if(upg.effectsTypes[k] && upg.effectsFields[k])
		{
			var type = upg.effectsTypes[k];
			var fieldName = upg.effectsFields[k];
			var dataField = upg.effectsTypes[k].getDataFields()[fieldName];

			{
				if(upg.effectsModifications && upg.effectsModifications[k])
				{
					if(!this.upgradeMods[type.id_string])
						this.upgradeMods[type.id_string] = {};

					this.upgradeMods[type.id_string][fieldName] = this.upgradeMods[type.id_string][fieldName] ? (this.upgradeMods[type.id_string][fieldName] + upg.effectsModifications[k]) : upg.effectsModifications[k];
				}

				if(upg.effectsModsMultiplier && k in upg.effectsModsMultiplier && (dataField.type == "float" || dataField.type == "integer"))
				{
					if(!this.upgradeMods[type.id_string])
						this.upgradeMods[type.id_string] = {};

					this.upgradeMods[type.id_string][fieldName] = this.upgradeMods[type.id_string][fieldName] ? (this.upgradeMods[type.id_string][fieldName] + (upg.effectsModsMultiplier[k] - 1) * type[fieldName]) : (upg.effectsModsMultiplier[k] - 1) * type[fieldName];
				}

				// var newVal = type.getValue(fieldName, this);
				// var checkVal = checkField(dataField, newVal, true);

				// if(checkVal != newVal)
				//	this.upgradeMods[type.id_string][fieldName] -= newVal - checkVal;
			}
		}

	message += "setUpg$" + this.number + "$" + JSON.stringify(this.upgrades) + "$" + JSON.stringify(this.upgradeMods) + "~";
};

Player.prototype.getUpgradeLevel = function(upg)
{
	return this.upgrades[upg.id_string] ? this.upgrades[upg.id_string] : 0;
};

Player.prototype.modifyGold = function(i)
{
	this.gold += Math.max(i, -this.gold);
	message += "modPlVal$" + this.number + "$gold$" + this.gold + "~";
};

Player.prototype.modifySupply = function(i)
{
	this.realMaxSupply += i;
	this.maxSupply = Math.min(this.realMaxSupply, this.supplyCap);
	message += "modPlVal$" + this.number + "$maxSupply$" + this.maxSupply + "~";
};

Player.prototype.setValAndSync = function(field, i)
{
	this[field] = i;
	message += "modPlVal$" + this.number + "$" + field + "$" + this[field] + "~";
};

Player.prototype.isEnemyOfPlayer = function(player)
{
	if ( game.globalVars.isPassiveNeutral) {
		return this.team.number != 0 && player.team.number != 0 && this.team != player.team;
	} else {
		return this.team != player.team;
	}
};

// for AI
Player.prototype.youAreAttackedAtPosition = function(pos)
{
	/*
	if(this.aiObject)
		return;

	// react, if last attack msg was more than 5 sec ago
	if(this.lastAttackMessage + 99 < ticksCounter)
	{
		this.lastAttackMessage = ticksCounter;

		// get all fighting units
		var fightingUnits = this.getUnitsOfTypeOfPlayer(lists.types.soldier, this).concat(this.getUnitsOfTypeOfPlayer(lists.types.archer, this));

		// order them to amove there
		for(var i = 0; i < fightingUnits.length; i++)
			if(fightingUnits[i].order.type == COMMAND.IDLE)
				fightingUnits[i].issueOrder(lists.types.amove, pos);

		// if player has no fighting units, send workers to attack
		if(fightingUnits.length == 0)
		{
			// get all workers sorted by their distance to the attacking position
			var workersSortedByDistToAttPos = _.sortBy(this.getUnitsOfTypeOfPlayer(lists.types.worker, this), function(worker){ return worker.pos.distanceTo2(pos); });

			// order ~half of them to attack
			var units = [];
			for(var i = 0; i < workersSortedByDistToAttPos.length * 0.6; i++)
				units.push(workersSortedByDistToAttPos[i]);

			game.issueOrderToUnits2(units, lists.types.amove, pos.add3((ticksCounter / 7) % 1, (ticksCounter / 31) % 1));
		}

		this.setValAndSync("apm", this.apm + 1);
	}
	*/
};

Player.prototype.upgradeCountInResearch = function(upgrade)
{
	var c = 0;

	for(var i = 0; i < game.buildings.length; i++)
	{
		var b = game.buildings[i];
		if(b.owner == this)
			for(var k = 0; k < BUILDING_QUEUE_LEN; k++)
				if(b.queue && b.queue[k] == upgrade)
					c++;
	}

	return c;
};

// is called every ~sec or so if this is a cpu player, does all the AI stuff
Player.prototype.AIUpdate = function()
{
	if(this.noAI)
		return;

	if(this.aiObject && this.aiObject.update)
	{
		this.aiObject.update(this.scope);
		return;
	}

	// ouchyAI(this.scope);
	// return;

	var ccs = this.getUnitsOfTypeOfPlayer(lists.types.castle, this);
	var rax = this.getUnitsOfTypeOfPlayer(lists.types.barracks, this).concat(this.getUnitsOfTypeOfPlayer(lists.types.wolvesden, this));
	var towers = this.getUnitsOfTypeOfPlayer(lists.types.watchtower, this);
	var houses = this.getUnitsOfTypeOfPlayer(lists.types.house, this);

	var workers = this.getUnitsOfTypeOfPlayer(lists.types.worker, this);
	var soldiers = this.getUnitsOfTypeOfPlayer(lists.types.soldier, this);
	var wolves = this.getUnitsOfTypeOfPlayer(lists.types.wolf, this);
	var riflemen = this.getUnitsOfTypeOfPlayer(lists.types.archer, this);
	var mages = this.getUnitsOfTypeOfPlayer(lists.types.mage, this);
	var fightingUnits = soldiers.concat(riflemen, mages, wolves);

	// find mines but remove empty mines from list
	var mines = this.getUnitsOfTypeOfPlayer(lists.types.goldmine);
	for(var i = 0; i < mines.length; i++)
		if(!mines[i].gold)
		{
			mines.splice(i, 1);
			i--;
		}

	var enemyCCs = [];
	for(var i = 1; i <= MAX_PLAYERS; i++)
		if(game.players[i] && game.players[i].isEnemyOfPlayer(this))
			enemyCCs = enemyCCs.concat(this.getUnitsOfTypeOfPlayer(lists.types.castle, game.players[i]));

	var enemyBaseCenter = enemyCCs.length > 0 ? enemyCCs[0].pos : new Field(ticksCounter % 60 + 20, ticksCounter % 60 + 2, true);

	// make workers
	for(var i = 0; i < ccs.length; i++)
		if(!ccs[i].queue[0] && workers.length < 9 * ccs.length)
		{
			ccs[i].orderMake(lists.types.trainworker);
			this.setValAndSync("apm", this.apm + 1);
		}

	// make units
	for(var i = 0; i < rax.length; i++)
		if(!rax[i].queue[0])
		{
			rax[i].orderMake(ticksCounter % 13 > 6 ? lists.types.trainsoldier : lists.types.trainarcher);
			this.setValAndSync("apm", this.apm + 1);
		}

	// send idle workers to work
	if(mines.length > 0)
		for(var i = 0; i < workers.length; i++)
			if(workers[i].order.type == COMMAND.IDLE)
			{
				workers[i].issueOrder(lists.types.mine, this.getClosestUnit(workers[i].pos, mines));
				this.setValAndSync("apm", this.apm + 1);
			}

	// make houses if needed
	if(((this.supply >= this.maxSupply - 4 && this.maxSupply < this.supplyCap) || houses.length == 0) && ccs.length >= 1 && workers.length > 0)
	{
		var place = ccs[0].pos.add3((ticksCounter / 21) % 16 - 8, (ticksCounter / 17) % 16 - 8);

		// if random place it not close to a mine (might block mining path)
		if(mines.length > 0 && this.getClosestUnit(place, mines).pos.distanceTo2(place) > 7)
		{
			var worker = this.getClosestUnit(place, workers, true);

			if(worker)
			{
				worker.issueOrder(lists.types.buildhouse, place);
				this.setValAndSync("apm", this.apm + 1);
			}
		}
	}

	// expand
	if(this.gold > 350 && workers.length > 0 && ccs.length <= 1 && this.expand)
	{
		var mineOfChoice = null;
		var allCCs = ccs.concat(enemyCCs);
		var freeMines = [];

		// find all free mines
		if(allCCs.length > 0)
			for(var i = 0; i < mines.length; i++)
				if(mines[i].distanceTo(this.getClosestUnit(mines[i].pos, allCCs)) > 12)
					freeMines.push(mines[i]);

		// find closest free mine
		var closestPathLen = 99999;
		for(var i = 0; i < freeMines.length; i++)
		{
			var pathLen = workers[0].pos.distanceTo2(freeMines[i].pos);
			if(pathLen < closestPathLen)
			{
				closestPathLen = pathLen;
				mineOfChoice = freeMines[i];
			}
		}

		// if we found a free mine, expand there
		if(mineOfChoice)
		{
			// search the next worker
			var workerOfChoice = this.getClosestUnit(mineOfChoice.pos, workers, true);

			if(workerOfChoice)
			{
				var originPos = workerOfChoice.pos;
				var placeToMakeMine = mineOfChoice.pos;

				// search for place next to the gold mine
				var i = 0;
				while(!lists.types.castle.couldBePlacedAt(placeToMakeMine) && i < 5)
				{
					placeToMakeMine = mineOfChoice.pos.addNormalizedVector(originPos, 6);

					var k = 0;
					while(!lists.types.castle.couldBePlacedAt(placeToMakeMine) && placeToMakeMine.distanceTo(mineOfChoice.pos) < 11 && k < 10)
					{
						placeToMakeMine = placeToMakeMine.addNormalizedVector(originPos, 1);
						k++;
					}

					// if we couldnt find a pos, try approaching the mine from a different (random) angle
					originPos = mineOfChoice.pos.add3((ticksCounter / 33) % 38 - 19, (ticksCounter / 4) % 38 - 19);
					i++;
				}

				// if we found a valid place, issue order
				if(lists.types.castle.couldBePlacedAt(placeToMakeMine))
				{
					workerOfChoice.issueOrder(lists.types.buildcastle, placeToMakeMine);
					this.setValAndSync("apm", this.apm + 1);
				}
			}

		}
	}

	// make rax
	if((this.gold >= (!this.makeWolves ? lists.types.barracks.cost : lists.types.wolvesden.cost)) && this.buildings.house && workers.length > 0 && ccs.length > 0 && (rax.length < (ccs.length == 1 ? this.raxAt1Base : this.raxAt2Base)))
	{
		var place = ccs[0].pos.add3((ticksCounter / 21) % 19 - 9, (ticksCounter / 17) % 19 - 9);

		// if random place it not close to a mine (might block mining path)
		if(mines.length > 0)
			if(this.getClosestUnit(place, mines).pos.distanceTo2(place) > 7)
			{
				var worker = this.getClosestUnit(place, workers, true);

				if(worker)
				{
					worker.issueOrder(lists.types.buildbarracks, place);
					this.setValAndSync("apm", this.apm + 1);
				}
			}
	}

	// make tower
	if((this.gold >= 500 && ccs.length > 0 && workers.length > 6 && towers.length < 2) || (this.makeTowerAtStart && this.gold >= lists.types.watchtower.cost && ccs.length > 0 && workers.length > 1 && towers.length < 1))
	{
		var place = ccs[0].pos.addNormalizedVector(enemyBaseCenter, 10);
		place = place.add3((ticksCounter / 21) % 12 - 6, (ticksCounter / 17) % 12 - 6);

		var worker = this.getClosestUnit(place, workers, true);

		if(worker)
		{
			worker.issueOrder(lists.types.buildwatchtower, place);
			this.setValAndSync("apm", this.apm + 1);
		}
	}

	// attack
	var units = [];
	if(fightingUnits.length > this.unitCountToAttack)
		for(var i = 0; i < fightingUnits.length; i++)
			if(fightingUnits[i].order.type == COMMAND.IDLE)
				units.push(fightingUnits[i]);

	game.issueOrderToUnits2(units, lists.types.amove, enemyBaseCenter);

	if(units.length > 0)
	{
		game.issueOrderToUnits2(units, lists.types.amove, enemyBaseCenter);
		this.setValAndSync("apm", this.apm + 1);
	}
};

// from all units in units, return the one that is closest to pos (should maybe not be a method of "Player", but instead of "Game")
Player.prototype.getClosestUnit = function(pos, units, onlyIdleOrMining)
{
	var bestUnit = null;
	var bestDist = 999999;

	for(var i = 0; i < units.length; i++)
		{
			bestUnit = units[i];
			bestDist = units[i].pos.distanceTo2(pos);
		}

	return bestUnit;
};

// get all units of type of player (should maybe not be a method of "Player", but instead of "Game")
Player.prototype.getUnitsOfTypeOfPlayer = function(type, player)
{
	var units = [];
	var unitsAndBuildings = game.units.concat(game.buildings);

	for(var i = 0; i < unitsAndBuildings.length; i++)
		if((!player || unitsAndBuildings[i].owner == player) && unitsAndBuildings[i].type == type)
			units.push(unitsAndBuildings[i]);

	return units;
};

```