[3.1+] NullBot: an adaptive skirmish AI

Did you create a mod, map, music, or a tool? Present them here and earn feedback!
Note: addon requests do not belong here.
Note, everything uploaded to this forum, MUST have a license!
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

Try this...

Code: Select all

// note: i've not tested this code!

// Get a list of enemy player IDs
const ENEMY_LIST_TTL = 1500; // number of ms the cache lasts for
function getListOfEnemies() {
  var player = maxPlayers;
  if (getListOfEnemies.TTL < gameTime) {
    // always treat scavs as enemy...
    getListOfEnemies.cache = [scavengerPlayer];
    // add other non-allied players to list...
    while (-1<--player) if (!allianceExistsBetween(me,player) getListOfEnemies.cache.push(player);
    // make sure we don't do this work again until the cache expires...
    getListOfEnemies.TTL = gameTime + ENEMY_LIST_TTL;
  }
  return getListOfEnemies.cache;
}
// make sure we create cache on first run...
// (also means we don't need to check for null value in first "if" statement above)
getListOfEnemies.TTL = ENEMY_LIST_TTL * -1;

// Get danger level for a given map location
// Defining constants in a function is bit weird, might as well keep them global
const DANGER_RANGE = BASE_SIZE/2; // how far to search from x,y for enemies

// function for 3.1 branch (enumRange not available)
function dangerLevel31(x,y) {
  var enemies = getListOfEnemies();
  var list = [];
  // build list
  enemies.forEach(function(enemy) {
    // use much more specific enums to reduce size of list
    list.concat(enumDroid(enemy,DROID_CYBORG));
    list.concat(enumDroid(enemy,DROID_WEAPON));
    list.concat(enumStruct(enemy,DEFENCE));
  });
  // filter to those within range
  list = list.filter(function(obj) {
    return (distBetweenTwoPoints(obj.x,obj.y,x,y) < DANGER_RANGE);
  });
  // danger level defined by number of dangerous things
  return list.length;
}

// function for 3.2 branch (uses enumRange which is quicker)
function dangerLevel32(x,y) {
  var list = enumRange(x,y,DANGER_RANGE,ENEMIES);
  // filter out non-dangerous things
  list = list.filter(function(obj) {
    switch (obj.type) {
      case DROID: {
        return (obj.droidType == DROID_WEAPON || obj.droidType == DROID_CYBORG);
      }
      case STRUCTURE: {
        return (obj.stattype == DEFENCE);
      }
    }
    return false; // we're not interested in anything else
  });
  // danger level defined by number of dangerous things
  return list.length;
}

// now expose relevant function to code
var dangerLevel = (!!enumRange) ? dangerLevel32 : dangerLevel31;
// use dangerLevel() in remainder of your code
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

Note:

It might be worth renaming dangerLevel(x,y) to getThreatsNear(x,y) and have the functions return the list (rather than list length).

That way you have a handy function for getting a list of dangerous objects at a given map point, and can easily do .length to quantify the danger level of that location.
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
NoQ
Special
Special
Posts: 6226
Joined: 24 Dec 2009, 11:35
Location: /var/zone

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by NoQ »

aubergine: i thought about caching, but it's not worth it. The situation often changes considerably between calls. Probably i did something wrong though ... but anyway your approach doesn't change the complexity: we still have number of enemy units times number of derricks.
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

Just realised the alliance type setting can be used to improve cache of enemy player id's:

Code: Select all

const ENEMY_LIST_TTL = 1500; // number of ms the cache lasts for if using dynamic alliances

// get list of enemy player id's at current game tick
function getCurrentEnemies() {
  var list = [scavengerPlayer];
  var player = maxPlayers;
  while (-1<--player) if (!allianceExistsBetween(me,player)) list.push(player);
  return list;
}

// your code should use this function, as it caches intelligently
function getListOfEnemies() {
  if (alliancesType == ALLIANCES && getListOfEnemies.TTL < gameTime) { // refresh cache
    getListOfEnemies.cache = getCurrentEnemies();
    getListOfEnemies.TTL = gameTime + ENEMY_LIST_TTL;
  }
  return getListOfEnemies.cache;
}
// set default cache and TTL
getListOfEnemies.cache = getCurrentEnemies();
getListOfEnemies.TTL = gameTime + ENEMY_LIST_TTL;
Last edited by aubergine on 11 Apr 2012, 13:42, edited 1 time in total.
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

Note that the dangerLevel() functions *are not cached*.

The function to get a list of enemy player IDs is what's being cached. I assume you will need to know a list of enemy player IDs for several functions so having a function that despams that processing will a) make code more transparent and b) reduce processing for a large number of functions. With my revised cache of enemy player IDs (see post above) then you'll completely bypass generating lists of enemie player IDs when there are no alliances or fixed alliances. Even with dynamic alliances, I don't think it's worth working out enemy players on a call by call basis. Caching the list of enemy players will decruft that.

In terms of the 3.1 function to get danger level, simply filtering to specific droid/struct types will instantly reduce the list. And using the revised code (in my earlier post) will take advantage of a number of JS compiler optimisations making it faster still.
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

BTW, I did consider caching the dangerLevel functions but realised a) the cache would need to be based on x,y location and b) as you mentioned things change considerably in short space of time. I concluded that caching of dangerLevel() functions would be crufty and ineffective and instead focussed on just making them more refined and faster.
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
NoQ
Special
Special
Posts: 6226
Joined: 24 Dec 2009, 11:35
Location: /var/zone

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by NoQ »

Ok, i just blindly pasted your code and it worked pretty well :shock: :D
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

There are some typos in it - let me quickly fix...
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

Updated code - fixed some bugs & used more meaningful function names, etc:

Code: Select all

const ENEMY_ID_LIST_TTL = 5000; // cache TTL for getEnemyPlayerIDs() function
const DANGER_RANGE = Math.ceil(BASE_SIZE/2); // range of getThreatsNear() function

// backport scavengerPlayer global if not present (it's not there in 3.1 branch!)
// https://warzone.atlassian.net/wiki/display/backportjs/Globals
if (!scavengerPlayer) {
  // define as const so it doesn't go in to savegames
  const scavengerPlayer = (scavengers) ? Math.max(7,maxPlayers) : -1;
}

// getEnemyPlayerIDs() will return an array of enemy player IDs

var getEnemyPlayerIDs = (function() {
  var getIDs = function() {
    var list = (scavengers) ? [scavengerPlayer] : [];
    var player = maxPlayers;
    while (-1<--player) if (!allianceExistsBetween(me,player)) list.push(player);
    return list;
  }
  var TTL = gameTime + ENEMY_ID_LIST_TTL;
  var cache = getIDs();
  return function() {
    if (alliancesType == ALLIANCES && TTL < gameTime) { // refresh cache
      cache = getIDs();
      TTL = gameTime + ENEMY_ID_LIST_TTL;
    }
    return cache;
  }
})();

// getThreatsNear(x,y) will return an array of dangerous enemy objects near x,y

var getThreatsNear = (function() {
  var oldApproach = function(x,y) {
    var enemies = getEnemyPlayerIDs();
    var list = [];
    // build list of dangerous enemy objects
    enemies.forEach(function(enemy) {
      // use specific enums to reduce size of list
      list = list.concat(enumDroid(enemy,DROID_CYBORG));
      list = list.concat(enumDroid(enemy,DROID_WEAPON));
      list = list.concat(enumStruct(enemy,DEFENCE));
    });
    // filter to those within range
    list = list.filter(function(obj) {
      return (distBetweenTwoPoints(obj.x,obj.y,x,y) < DANGER_RANGE);
    });
    return list;
  }
  var newApproach = function(x,y) {
    // get list of all enemy objects within range
    var list = enumRange(x,y,DANGER_RANGE,ENEMIES);
    // filter to dangerous objects
    list = list.filter(function(obj) {
      switch (obj.type) {
        case DROID: return (obj.droidType == DROID_WEAPON || obj.droidType == DROID_CYBORG);
        case STRUCTURE: return (obj.stattype == DEFENCE);
      }
      return false;
    });
    return list;
  }
  // return relevant function based on presence of enumRange function
  return (!!enumRange) ? newApproach : oldApproach;
})();

// if you want dangerLevel, use getThreatsNear(x,y).length
Last edited by aubergine on 11 Apr 2012, 14:32, edited 1 time in total.
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

Just noticed that you check scavengerPlayer!=-1 a lot in your code, so tweak to code above:

Code: Select all

// backport scavengerPlayer global if not present (it's not there in 3.1 branch!)
// https://warzone.atlassian.net/wiki/display/backportjs/Globals
if (!scavengerPlayer) {
  // define as const so it doesn't go in to savegames
  const scavengerPlayer = (scavengers) ? Math.max(7,maxPlayers) : -1;
}
EDIT: I've updated code in previous post to use this.
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
NoQ
Special
Special
Posts: 6226
Joined: 24 Dec 2009, 11:35
Location: /var/zone

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by NoQ »

Emm.

Code: Select all

!!enumRange
says

Code: Select all

error   |04:48:36: [js_include:289] Uncaught exception at line 519, include file multiplay/skirmish/nullbot-1-16-tail.inc.js: ReferenceError: Can't find variable: enumRange
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

ick, ok, change to:

Code: Select all

  return (typeof enumRange != "undefined") ? newApproach : oldApproach;
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
NoQ
Special
Special
Posts: 6226
Joined: 24 Dec 2009, 11:35
Location: /var/zone

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by NoQ »

Anyway, i seriously don't understand this code.
I already have scavengers backported, and check if they are ==-1 is still necessary (or alternatively check the scavengers variable).
User avatar
aubergine
Professional
Professional
Posts: 3462
Joined: 10 Oct 2010, 00:58

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by aubergine »

another typo: DEFENCE should be DEFENSE (S not C)

where do you backport scavengerPlayer - I couldn't find that anywhere in nullbot?

also, what aspects of the code need more explanation?
"Dedicated to discovering Warzone artefacts, and sharing them freely for the benefit of the community."
-- https://warzone.atlassian.net/wiki/display/GO
User avatar
NoQ
Special
Special
Posts: 6226
Joined: 24 Dec 2009, 11:35
Location: /var/zone

Re: NullBot: an adaptive skirmish AI for Warzone 2100 v3.1+

Post by NoQ »

Just fixed everything, but still having weird stuff (some players aren't working, while others do).
where do you backport scavengerPlayer - I couldn't find that anywhere in nullbot?
How do you think i'm using it at all? (:
It's in eventStartLevel.