With version 1.73 Littlewargame provides its own API for custom AI scripting. That means, you can write your own AI in javascript. At the moment, the API is still very small (only very few functions). But i rather release it now and then wait for feedback on which function players would want and then i can implement those. In this post im giving you an introduction on how to use the api.
To write an AI you create a new file and just start coding your actions. Everything you write will then be put in one function and that function will be called every 1 sec in the game. The function will be attached to an empty object, so you can use “this” inside of your function. Of course you can write and call your own function inside of your code if you need some. Then, start the game and click on the “AI” Button in the top left and load your file. After the file has been loaded, open a new singleplayer game and you will see an additional button for ever CPU player, that lets you choose, which AI the CPU player should be controled with. Click the button to switch between the different AI’s and then start the game to see your AI in action. You can also load more than 1 AI, for example to have a fight between two AI’s. Keep in mind, all the code you write will be called once every sec. The function in which your code will be put, will be called with 1 argument, “scope”. Scope is an object of type “Scope” and represents the map (or more clearly: those parts of the map that you can see at the moment). Scope provides a couple of function that let you get information about the current game state. It also provides a function to give order to your units. The following functions are available:
scope.getMyPlayerNumber()
returns the player number of the player you are controlling (if you are the red player for example, this function returns 1)
scope.getMyTeamNumber()
returns the team number of the player you are controlling. Players in the same team are allied.
scope.getUnits(filter)
returns an array of units. You can pass a filter argument to this function. If you call it without a filter, this function will return an array of all the units, you can currently see (no buildings). You can use the filter argument to get only specific units. The filter argument is expected to be an object with one ore more fields set. Possible fields are:
type: only get units of this type (for example “Worker”)
notOfType: only get units not of this type
player: only get units owned by this player (for example: 1)
team: only get units owned by a player in this team (for example: 1)
order: only get units currently executing this order: (for example “Move”)
enemyOf: only get units, that are owned by an enemy player of player (for example: 1)
Some examples:
var idleWorkers = scope.getUnits({type: “Worker”, player: myPlayerNumber, order: “Stop”}); // returns all own idle workers (the idle order is called “Stop”)
var fightingUnits = scope.getUnits({notOfType: “Worker”, player: myPlayerNumber}); // returns all own fighting units (=not workers)
scope.getBuildings(filter)
Pretty much the same as getUnits(), but for buildings. The only difference is, there is one more possible filter field:
onlyFinshed: returns only finished buildings (not under construction)
scope.order(order, units, o, shift)
Gives an order to any amount of units.
order (string): the name of the order; possible values are: “Train Worker”, “Train Soldier”, “Train Rifleman”, “Build Castle”, “Build Barracks”, “Build Watchtower”, “Build House”, “Stop” (= idle), “Hold Position”, “Attack”, “Cancel”, “Move”, “Moveto” (move to a building; this is what happens when you righclick a friendly building), “Mine”, “Repair”, “AMove”, “Train Mage”, “Build Mages Guild”, “Flamestrike”, “Build Catapult”, “Build Workshop”, “Build Forge”, “Attack Upgrade”, “Armor Upgrade”, “Heal”, “Research Flamestrike”, “Research Heal” (those are just the names you also see when hovering an order’s button ingame. So when new abilities will be added in future, just check the button to get their name.
units: an array of units that will receive the order. Of course you can only give orders to your own units. This has always to be an array, so keep in mind, when giving an order to only one unit, pass [unit] and not just unit.
o: an object with additional information. Some orders require a target. In this case you can pass target information in this object. Orders, that require a field (for example “Move”) need the fields “x” and “y” to bet set. Example: scope.order(“Move”, [anArrayOfUnits], {x: 5, y: 22}); // Now those units will be ordered to move to location 5/22
Orders, that require a unit as target need to have the field “unit” set. Example: Example: scope.order(“Attack”, [anArrayOfUnits], {unit: unit324}); // Now those units will be ordered to attack the unit324
shift: pass “true” if you want to queue the order instead of executing it immediately (what happens when you have “shift” pressed while giving an order)
scope.getCenterOfUnits(units)
Returns an object with fields “x” and “y” set as the center position of an array of units passed. Often when you see a bunch of enemy unit for example, you want to attack them. A good way to do this usually, is to amove into them. And a good target-point is the center of them.
scope.getGold()
Returns your current amount of gold.
patch 1.85 added the following functions:
scope.positionIsPathable(x, y)
Returns if a position is pathable (can be walked or build on)
scope.getMapWidth()
Returns the width of the map
scope.getMapHeight()
Returns the height of the map
scope.getArrayOfPlayerNumbers()
Returns an array containing all players numbers (for exmple [1, 2])
scope.getStartLocationForPlayerNumber(nr)
Returns an object of type {x: x, y: y} containing the x and y value of the position of a given players start location
patch 1.86 added the following functions:
scope.getCurrentSupply()
returns the current supply count for the player
scope.getMaxSupply()
returns the current max supply count for the player
scope.getCurrentGameTimeInSec()
get the current game time in sec
scope.getUpgradeLevel(upgName)
get the current level of an upgrade
All the buildings and units that you get in return from those getter functions have some methods on them to call, too. They are:
unit.getCurrentHP()
returns the current HP of this unit.
unit.getX()
returns the current X coordinate of this unit (the top left corner of the map is 0:0, the buttom right corner is for example 96:96 (on a 96:96 size map)
unit.getY()
returns the current Y coordinate of this unit
unit.getTypeName()
returns the type name of this unit (for example “Worker” or “Soldier”). The type name is the same name you see, when selecting the unit in a game.
unit.getOwnerNumber()
get the number of the player that owns this unit
unit.getTeamNumber()
get the number of the team that owns this unit
unit.getRemainingBuildTime()
get the remaining build time (only works for buildings; in case this building is currently producing a unit, return the remaining time until this unit will be done)
unit.getUnitTypeNameInProductionQueAt(nr)
returns the name of the unit type that is in production at queue position nr X. (only works on buildings, returns null if no unit in production)
unit.isUnderConstruction()
returns true, if this building is currently under construction (only works on buildings)
unit.isNeutral()
returns true, if this unit is neutral ( teamNr == 0 )
unit.getCurrentOrderName()
returns the name of the order, the unit is currently executing (“Stop” if idle)
patch 1.86 added the following functions:
unit.getFieldValue(fieldName)
Returns the field value for a unit (for example soldier.getFieldValue(“dmg”) returns 9). Values are: hp, dmg, supply, movementSpeed, weaponCooldown, weaponDelay, armor, range, size, vision, buildTimeInSec, cost
Here is a very small example AI. It does order idle workers to mine gold, makes workers constantly, makes a house and a barracks and if possible and attacks all enemy units in sight:
// get my own player number; we need this to check if a unit is mine or not
var myPlayerNumber = scope.getMyPlayerNumber();
// get my team number; we can compare this to a units team number to check if its allied or not
var myTeamNumber = scope.getMyTeamNumber();
// get my gold value
var gold = scope.getGold();
// get several units
var idleWorkers = scope.getUnits({type: "Worker", player: myPlayerNumber, order: "Stop"});
var workers = scope.getUnits({type: "Worker", player: myPlayerNumber});
var fightingUnits = scope.getUnits({notOfType: "Worker", player: myPlayerNumber});
var castles = scope.getBuildings({type: "Castle", player: myPlayerNumber});
var houses = scope.getBuildings({type: "House", player: myPlayerNumber});
var finishedHouses = scope.getBuildings({type: "House", player: myPlayerNumber, onlyFinshed: true});
var mines = scope.getBuildings({type: "Goldmine"});
var enemyUnits = scope.getUnits({enemyOf: myPlayerNumber});
// look for the next gold mine from our castle
if(castles.length >= 1)
{
// get nearest goldmine
var nearestMine = null;
var neasrestDist = 99999;
for(var i = 0; i < mines.length; i++)
{
var mine = mines[i];
var dist = Math.pow(mine.getX() - castles[0].getX(), 2) + Math.pow(mine.getY() - castles[0].getY(), 2);
if(dist < neasrestDist)
{
nearestMine = mine;
neasrestDist = dist;
}
}
}
// order all idle workers to mine from the nearest gold mine
if(nearestMine && idleWorkers.length > 0)
scope.order("Mine", idleWorkers, {unit: nearestMine});
// if the castle is idle, order to make a worker
if(castles.length >= 1 && !castles[0].getUnitTypeNameInProductionQueAt(1))
scope.order("Train Worker", [castles[0]]);
// if any enemy units in sight and we have fighting units, order them to attack them
if(enemyUnits.length > 0 && fightingUnits.length > 0)
scope.order("AMove", fightingUnits, scope.getCenterOfUnits(enemyUnits));
// if we dont have a single house, make one
if(houses.length == 0)
scope.order("Build House", workers, {x: castles[0].getX() - 7, y: castles[0].getY()});
// if we have at least one finished house (= we can make barracks), make a barracks (at some fixed position left of out castle, which will lead to some problems; you shouldnt do it like that)
if(finishedHouses.length > 0)
scope.order("Build Barracks", workers, {x: castles[0].getX() - 7, y: castles[0].getY() - 5});