Skip to content

Instantly share code, notes, and snippets.

@MHeasell
Created April 15, 2015 09:53
Show Gist options
  • Save MHeasell/bb9f7253da45b5140a83 to your computer and use it in GitHub Desktop.
Save MHeasell/bb9f7253da45b5140a83 to your computer and use it in GitHub Desktop.
My CodeCombat Zero Sum tournament entry, compiled version. Check out https://gist.github.com/MHeasell/e7ddb00a279d141d03fd for the source code and http://michaelheasell.com/blog/2015/04/08/zero-sum-my-winning-strategy/ for my strategy write-up.
function vecAdd(a, b) {
return {
x: a.x + b.x,
y: a.y + b.y
};
}
function vecSub(a, b) {
return {
x: a.x - b.x,
y: a.y - b.y
};
}
function vecMul(v, s) {
return {
x: v.x * s,
y: v.y * s
};
}
function vecLen(v) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
function vecNorm(v) {
var len = Math.sqrt(v.x * v.x + v.y * v.y);
if (len === 0) {
return {
x: 1,
y: 1
};
}
return {
x: v.x / len,
y: v.y / len
};
}
function vecDir(a, b) {
return vecNorm({
x: b.x - a.x,
y: b.y - a.y
});
}
function vecDist(a, b) {
var x = b.x - a.x;
var y = b.y - a.y;
return Math.sqrt(x * x + y * y);
}
function makeBins() {
var bins = new Array(30);
for (var i = 0; i < 30; ++i) {
bins[i] = [];
}
return bins;
}
function putInBins(items) {
var bins = makeBins();
for (var i = 0, len = items.length; i < len; ++i) {
var item = items[i];
var itemPos = item.pos;
var binX = Math.floor(itemPos.x / 13.333333333333);
var binY = Math.floor(itemPos.y / 14);
if (// ignore corpses on the outskirts
binY >= 5 || binX >= 6) {
continue;
}
bins[binY * 6 + binX].push(item);
}
return bins;
}
function findBestBinPos(bins) {
var winIdx;
var winScore = -Infinity;
for (var i = 0; i < 30; ++i) {
var score = bins[i].length;
if (score > winScore) {
winScore = score;
winIdx = i;
}
}
return {
pos: {
x: winIdx % 6,
y: Math.floor(winIdx / 6)
},
score: winScore
};
}
function findBestBinPosAvgCoords(bins) {
var winIdx;
var winScore = -Infinity;
for (var i = 0; i < 30; ++i) {
var score = bins[i].length;
if (score > winScore) {
winScore = score;
winIdx = i;
}
}
var winX = 0;
var winY = 0;
var winBin = bins[i];
for (var i$2 = 0, len = winBin.length; i$2 < len; ++i$2) {
var item = winBin[i$2];
var itemPos = item.pos;
winX += itemPos.x;
winY += itemPos.y;
}
winX /= winBin.length;
winY /= winBin.length;
return {
pos: {
x: winX,
y: winY
},
score: winScore
};
}
function findLargestGroupPosAvg(items) {
var bins = putInBins(items);
var data = findBestBinPos(bins);
return data;
}
function findLargestGroupPos(items) {
var bins = putInBins(items);
var data = findBestBinPos(bins);
var pos = data.pos;
return {
score: data.score,
pos: {
x: pos.x * 13.333333333333 + 6.6666666666665,
y: pos.y * 14 + 7
}
};
}
function filterType(type, arr) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; ++i) {
var item = arr[i];
if (item.type === type) {
newArr.push(item);
}
}
return newArr;
}
function filterNotType(type, arr) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; ++i) {
var item = arr[i];
if (item.type !== type) {
newArr.push(item);
}
}
return newArr;
}
this.filterAllies = function (arr) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; ++i) {
var item = arr[i];
if (item.team === this.team) {
newArr.push(item);
}
}
return newArr;
};
function firstOfType(type, arr) {
for (var i = 0, len = arr.length; i < len; ++i) {
var ent = arr[i];
if (ent.type === type) {
return ent;
}
}
return null;
}
this.findInRadius = function (radius, arr) {
var items = [];
for (var i = 0, len = arr.length; i < len; ++i) {
var item = arr[i];
if (this.distanceTo(item) < radius) {
items.push(item);
}
}
return items;
};
function findInTargetRadius(target, radius, arr) {
var items = [];
for (var i = 0, len = arr.length; i < len; ++i) {
var item = arr[i];
if (target.distanceTo(item) < radius) {
items.push(item);
}
}
return items;
}
function findLowestHealth(units) {
var winner = null;
var winScore = Infinity;
for (var i = 0, len = units.length; i < len; ++i) {
var u = units[i];
var score = u.health;
if (score < winScore) {
winner = u;
winScore = score;
}
}
return winner;
}
var // globals ---------------------------------------------------------------------
globals = {
enemies: [],
filteredEnemies: [],
enemySoldiers: [],
enemyArtillery: [],
enemyArchers: [],
enemyGriffins: [],
enemySorc: null,
items: [],
corpses: [],
friends: [],
friendlySoldiers: [],
friendlyArchers: [],
friendlyGriffins: [],
friendlyArtillery: [],
yeti: null,
cachedRaiseDeadTime: -Infinity,
cachedRaiseDeadData: null,
cachedManaBlastTime: -Infinity,
cachedManaBlastData: null,
lastGoldstormTime: -Infinity,
lastSorcFearTime: -Infinity,
lastJumpTime: -Infinity
};
this.getManaBlastData = function () {
var time = this.now();
if (time - globals.cachedManaBlastTime >= 0.5) {
globals.cachedManaBlastData = findLargestGroupPosAvg(globals.filteredEnemies);
globals.cachedManaBlastTime = time;
}
return globals.cachedManaBlastData;
};
this.getRaiseDeadData = function () {
var time = this.now();
if (time - globals.cachedRaiseDeadTime >= 2) {
globals.cachedRaiseDeadData = findLargestGroupPos(globals.corpses);
globals.cachedRaiseDeadTime = time;
}
return globals.cachedRaiseDeadData;
};
this.updateGlobals = function () {
globals.enemies = this.findEnemies();
globals.filteredEnemies = filterNotType('yeti', filterNotType('cage', globals.enemies));
globals.enemySorc = firstOfType('sorcerer', globals.enemies);
globals.enemySoldiers = filterType('soldier', globals.enemies);
globals.enemyArchers = filterType('archer', globals.enemies);
globals.enemyGriffins = filterType('griffin-rider', globals.enemies);
globals.enemyArtillery = filterType('artillery', globals.enemies);
globals.yeti = firstOfType('yeti', globals.enemies);
globals.items = this.findItems();
globals.corpses = this.findCorpses();
globals.friends = this.findFriends();
globals.friendlySoldiers = filterType('soldier', globals.friends);
globals.friendlyArchers = filterType('archer', globals.friends);
globals.friendlyGriffins = filterType('griffin-rider', globals.friends);
globals.friendlyArtillery = filterType('artillery', globals.friends);
};
// Paths to the best coin via the "gravity" approach.
this.findGoalCoinPositionGravity = function (coins) {
var forceX = 0;
var forceY = 0;
var pos = this.pos;
var posX = pos.x;
var posY = pos.y;
var enemySorc = globals.enemySorc;
var yeti = globals.yeti;
var enemyIsFeared = this.now() - globals.lastSorcFearTime < 5;
for (var i = 0, len = coins.length; i < len; ++i) {
var coin = coins[i];
var dist = this.distanceTo(coin);
if (dist === 0) {
continue;
}
var enemyDist = enemySorc.distanceTo(coin);
var attractionStrength = coin.value;
var enemyMultiplier = (dist + enemyDist) / enemyDist;
if (enemyMultiplier > 2.22222222) {
if (enemyIsFeared) {
attractionStrength *= 2.22222222;
} else {
attractionStrength /= 2;
}
} else {
attractionStrength *= enemyMultiplier;
}
var // Strength is attenuated based on distance squared,
// however here we divide by distance again,
// making it distance cubed,
// because the direction vector is not normalized
// and is therefore proportional to distance.
finalStrength = attractionStrength / (dist * dist * dist);
var coinPos = coin.pos;
forceX += (coinPos.x - posX) * finalStrength;
forceY += (coinPos.y - posY) * finalStrength;
}
if (// yeti influence
yeti !== null) {
var // Strength is attenuated based on distance squared,
// however here we divide by distance again,
// making it distance cubed,
// because the direction vector is not normalized
// and is therefore proportional to distance.
yetiDist = this.distanceTo(yeti);
if (yetiDist !== 0) {
var yetiStrength = -20 / (yetiDist * yetiDist * yetiDist);
var yetiPos = yeti.pos;
forceX += (yetiPos.x - posX) * yetiStrength;
forceY += (yetiPos.y - posY) * yetiStrength;
}
}
var // wall influence
leftWallDist = pos.x;
var rightWallDist = 85 - pos.x;
forceX += 1 / (leftWallDist * leftWallDist) - 1 / (rightWallDist * rightWallDist);
var topWallDist = 72 - pos.y;
var bottomWallDist = pos.y;
forceY += 1 / (bottomWallDist * bottomWallDist) - 1 / (topWallDist * topWallDist);
var finalScale = 10 / Math.sqrt(forceX * forceX + forceY * forceY);
var newPos = {
x: posX + forceX * finalScale,
y: posY + forceY * finalScale
};
return newPos;
};
this.findGoalCoinPositionGreedy = function (coins) {
var winner = null;
var winScore = -Infinity;
for (var i = 0, len = coins.length; i < len; ++i) {
var coin = coins[i];
var dist = this.distanceTo(coin);
var strength = coin.value;
if (strength === 3) {
strength = 6;
}
var score = strength / dist;
var enemyDist = globals.enemySorc.distanceTo(coin);
if (dist > enemyDist) {
score /= 2;
}
if (globals.yeti) {
var yetiDist = globals.yeti.distanceTo(coin);
if (dist > yetiDist) {
score /= 4;
}
}
if (score > winScore) {
winner = coin;
winScore = score;
}
}
if (winner === null) {
return {
x: 40,
y: 38
};
}
return winner.pos;
};
this.findGoalCoinPositionGold = function (coins) {
var winner = null;
var winScore = -Infinity;
for (var i = 0, len = coins.length; i < len; ++i) {
var coin = coins[i];
var strength = coin.value;
if (strength === 3) {
strength === 9;
}
var score = strength / this.distanceTo(coin);
if (score > winScore) {
winner = coin;
winScore = score;
}
}
return winner.pos;
};
// actions
this.collectCoinAction = function () {
return { action: 'collect-coins' };
};
this.goldstormAction = function () {
if (!this.canCast('goldstorm')) {
return null;
}
if (this.distanceTo(globals.enemySorc) < 40) {
return null;
}
return { action: 'goldstorm' };
};
this.raiseDeadAction = function () {
if (this.now() < 5) {
return null;
}
if (!this.canCast('raise-dead') && !this.isReady('reset-cooldown')) {
return null;
}
var nearbyCorpses = this.findInRadius(20, globals.corpses);
if (// do not res artillery, it does more harm than good
firstOfType('artillery', nearbyCorpses) !== null) {
return null;
}
if (// don't waste the spell for too few corpses
nearbyCorpses.length < 2) {
return null;
}
if (!this.canCast('raise-dead')) {
return {
action: 'reset-cooldown',
target: 'raise-dead'
};
} else {
return { action: 'raise-dead' };
}
};
this.attackArchersAction = function () {
var targetArcher = findLowestHealth(this.findInRadius(25, globals.enemyArchers));
if (targetArcher === null) {
return null;
}
return {
action: 'attack',
target: targetArcher
};
};
this.attackSorcInEndGameAction = function () {
if (this.now() < 110) {
return null;
}
var sorc = globals.enemySorc;
if (sorc.health >= this.health) {
return null;
}
return {
action: 'attack',
target: globals.enemySorc
};
};
this.attackArtilleryAction = function () {
if (globals.enemyArtillery.length < 1) {
return null;
}
var target = this.findNearest(globals.enemyArtillery);
if (target === null) {
return null;
}
if (this.distanceTo(target) >= 40) {
return null;
}
return {
action: 'attack',
target: target
};
};
this.aggressiveManaBlastAction = function () {
if (this.now() < 5) {
return null;
}
if (!this.isReady('mana-blast') && !this.isReady('reset-cooldown')) {
return null;
}
var manaBlastData = this.getManaBlastData();
var enemyCount = manaBlastData.score;
if (// don't waste the spell on too few enemies
enemyCount <= 3) {
return null;
}
var manaBlastPos = manaBlastData.pos;
var distance = vecDist(this.pos, manaBlastPos);
if (distance > 10) {
return null;
}
if (!this.isReady('mana-blast')) {
return {
action: 'reset-cooldown',
target: 'mana-blast'
};
} else if (distance > 3) {
return {
action: 'move',
target: manaBlastPos
};
} else {
return { action: 'mana-blast' };
}
};
this.defensiveManaBlastAction = function () {
if (!this.isReady('mana-blast') && !this.isReady('reset-cooldown')) {
return null;
}
var nearEnemies = this.findInRadius(10, globals.filteredEnemies);
if (nearEnemies.length < 5) {
return null;
}
if (!this.isReady('mana-blast')) {
return {
action: 'reset-cooldown',
target: 'mana-blast'
};
} else {
return { action: 'mana-blast' };
}
};
this.drainLifeAction = function () {
if (!this.canCast('drain-life')) {
return null;
}
if (this.health / this.maxHealth > 0.3) {
return null;
}
var target = findLowestHealth(this.findInRadius(15, globals.enemies));
if (target === null) {
return null;
}
return {
action: 'drain-life',
target: target
};
};
this.fearEnemySorcererAction = function () {
if (this.canCast('fear')) {
if (!this.canCast('fear', globals.enemySorc)) {
return null;
}
} else if (!this.isReady('reset-cooldown')) {
return null;
}
if (this.distanceTo(globals.enemySorc) > 25 + 10) {
return null;
}
if (!this.canCast('fear')) {
return {
action: 'reset-cooldown',
target: 'fear'
};
} else {
return {
action: 'fear',
target: globals.enemySorc
};
}
};
this.fearYetiAction = function () {
if (globals.yeti === null) {
return null;
}
if (this.canCast('fear')) {
if (!this.canCast('fear', globals.yeti)) {
return null;
}
} else if (!this.isReady('reset-cooldown')) {
return null;
}
if (this.distanceTo(globals.yeti) > 20) {
return null;
}
if (globals.yeti.target !== this) {
return null;
}
if (!this.canCast('fear')) {
return {
action: 'reset-cooldown',
target: 'fear'
};
} else {
return {
action: 'fear',
target: globals.yeti
};
}
};
this.avoidYetiAction = function () {
if (!globals.yeti) {
return null;
}
if (this.distanceTo(globals.yeti) > 20) {
return null;
}
// the coin collection algorithm devalues coins near the yeti
// so it should steer us away.
return { action: 'collect-coins' };
};
// action selection and execution logic
this.goToTarget = function (pos) {
pos = this.pathAroundBox(pos);
if (this.isReady('jump') && vecDist(this.pos, pos) >= 8) {
this.jumpTo(pos);
globals.lastJumpTime = this.now();
} else {
this.move(pos);
}
};
this.pathAroundBox = function (pos) {
var box = firstOfType('cage', globals.enemies);
if (box === null) {
return pos;
}
if (this.distanceTo(box) > 7) {
return pos;
}
if (this.pos.y < box.pos.y && pos.y > box.pos.y || this.pos.y > box.pos.y && pos.y < box.pos.y) {
if (// our path crosses the box's y position
this.pos.x <= box.pos.x + 4 && this.pos.x >= box.pos.x - 4) {
if (// we are overlapping the box on the x axis
pos.x > box.pos.x) {
return {
// target is on the right, move right
x: pos.x + 10,
y: pos.y
};
} else {
return {
// target is on the left, move left
x: pos.x - 10,
y: pos.y
};
}
}
}
if (this.pos.x < box.pos.x && pos.x > box.pos.x || this.pos.x > box.pos.x && pos.y < box.pos.x) {
if (// our path crosses the box's x position
this.pos.y <= box.pos.y + 4 && this.pos.y >= box.pos.y - 4) {
if (// we are overlapping the box on the y axis
pos.y > box.pos.y) {
return {
// target is above, move up
x: pos.x,
y: pos.y + 10
};
} else {
return {
// target is below, move down
x: pos.x,
y: pos.y - 10
};
}
}
}
return pos;
};
this.executeAction = function (action$2) {
switch (action$2.action) {
case 'move':
this.goToTarget(action$2.target);
return;
case 'collect-coins':
var coinPos;
var timeSinceGS = this.now() - globals.lastGoldstormTime;
if (timeSinceGS > 0.5 && timeSinceGS < 10) {
coinPos = this.findGoalCoinPositionGold(globals.items);
} else {
coinPos = this.findGoalCoinPositionGravity(globals.items);
}
this.goToTarget(coinPos);
return;
case 'attack':
this.attack(action$2.target);
return;
case 'raise-dead':
this.cast('raise-dead');
return;
case 'mana-blast':
this.manaBlast();
return;
case 'drain-life':
this.cast('drain-life', action$2.target);
return;
case 'fear':
this.cast('fear', action$2.target);
if (action$2.target === globals.enemySorc) {
globals.lastSorcFearTime = this.now();
}
return;
case 'goldstorm':
this.cast('goldstorm');
globals.lastGoldstormTime = this.now();
return;
case 'reset-cooldown':
this.resetCooldown(action$2.target);
return;
default:
this.debug('How do I ' + action$2.type + '?');
return;
}
};
this.selectAction = function () {
var action$2;
action$2 = //this.fearYetiAction,
//this.drainLifeAction,
//this.fearEnemySorcererAction,
this.avoidYetiAction();
if (action$2 !== null) {
return action$2;
}
action$2 = this.raiseDeadAction();
if (action$2 !== null) {
return action$2;
}
action$2 = this.aggressiveManaBlastAction();
if (action$2 !== null) {
return action$2;
}
action$2 = this.defensiveManaBlastAction();
if (action$2 !== null) {
return action$2;
}
action$2 = //this.attackArchersAction,
this.attackArtilleryAction();
if (action$2 !== null) {
return action$2;
}
action$2 = this.attackSorcInEndGameAction();
if (action$2 !== null) {
return action$2;
}
action$2 = this.goldstormAction();
if (action$2 !== null) {
return action$2;
}
action$2 = this.collectCoinAction();
if (action$2 !== null) {
return action$2;
}
};
// ability use and movement ----------------------------------------------------
this.doAbilityLogic = function () {
this.executeAction(this.selectAction());
};
// army summoning --------------------------------------------------------------
this.percentageSummon = function () {
var griffinCount = globals.friendlyGriffins.length;
var soldierCount = globals.friendlySoldiers.length;
if (griffinCount < 3) {
return 'griffin-rider';
}
if (soldierCount < 5) {
return 'soldier';
}
if (soldierCount / griffinCount < 0.666666) {
return 'soldier';
}
return 'griffin-rider';
};
this.decideSummon = function () {
var //if (this.now() > 30) {
// return "griffin-rider";
//}
nearestEnemy = this.findNearest(globals.filteredEnemies);
if (// spawn troops if the enemy is near.
// if it's the early game, don't spawn unless they have military
// (i.e. more than just the sorc)
this.distanceTo(nearestEnemy) < 30 && (this.now() > 45 || globals.filteredEnemies.length > 1)) {
return this.percentageSummon();
}
if (this.shouldDoEndGameAttack()) {
return this.percentageSummon();
}
if (this.shouldGoOffensive()) {
return this.percentageSummon();
}
if (this.gold >= 150) {
return 'griffin-rider';
}
return null;
};
this.shouldDoEndGameAttack = function () {
return this.now() > 105 && globals.friends.length > globals.filteredEnemies.length;
};
this.shouldGoOffensive = function () {
return globals.friends.length > 9 && globals.friends.length / globals.filteredEnemies.length > 2;
};
this.doSummonLogic = function () {
var summonType = this.decideSummon();
var // if we summon while jumping we seem to always crash due to
// "cannot read property z of null"
timeSinceJump = this.now() - globals.lastJumpTime;
if (timeSinceJump > 0.5) {
while (summonType !== null && this.gold > this.costOf(summonType)) {
this.summon(summonType);
this.updateGlobals();
summonType = this.decideSummon();
}
return true;
}
return false;
};
this.doAttackNearestAI = function (enemies, units) {
for (var i = 0, len = units.length; i < len; ++i) {
var unit = units[i];
var nearestEnemy = unit.findNearest(enemies);
this.command(unit, 'attack', nearestEnemy);
}
};
this.selectRoamTarget = function (unit) {
var target = unit.findNearest(globals.enemyArtillery);
if (target !== null) {
return target;
}
target = unit.findNearest(globals.enemyArchers);
if (target !== null) {
return target;
}
target = unit.findNearest(globals.enemyGriffins);
if (target !== null) {
return target;
}
//target = unit.findNearest(globals.enemySoldiers);
//if (target !== null) {
// return target;
//}
return globals.enemySorc;
};
this.pickRandomPosition = function () {
return {
x: Math.random() * 83,
y: Math.random() * 70
};
};
this.maybeKiteSoldiers = function (leader, units) {
var leaderPos = leader.pos;
var nearestSoldier = leader.findNearest(globals.enemySoldiers);
if (nearestSoldier !== null) {
var soldierDistance = leader.distanceTo(nearestSoldier);
if (soldierDistance < 10) {
var soldierPos = nearestSoldier.pos;
var offsetScale = soldierDistance === 0 ? 10 : 10 / soldierDistance;
var newPos = {
x: leaderPos.x + (leaderPos.x - soldierPos.x) * offsetScale,
y: leaderPos.y + (leaderPos.y - soldierPos.y) * offsetScale
};
var len = units.length;
for (var i = 0; i < len; ++i) {
this.command(units[i], 'move', newPos);
}
return true;
}
}
return false;
};
this.doRoamAttackAI = function (units) {
if (units.length === 0) {
return;
}
var leader = units[0];
if (this.maybeKiteSoldiers(leader, units)) {
return;
}
var target = this.selectRoamTarget(leader);
var len = units.length;
for (var i = 0; i < len; ++i) {
this.command(units[i], 'attack', target);
}
};
this.doAttackTargetAndKiteAI = function (target, units) {
if (units.length === 0) {
return;
}
var leader = units[0];
if (this.maybeKiteSoldiers(leader, units)) {
return;
}
var len = units.length;
for (var i = 0; i < len; ++i) {
this.command(units[i], 'attack', target);
}
};
this.doAttackTargetAI = function (target, units) {
if (units.length === 0) {
return;
}
var len = units.length;
for (var i = 0; i < len; ++i) {
this.command(units[i], 'attack', target);
}
};
this.doAttackSpecificAI = function (units) {
if (units.length === 0) {
return;
}
var target = units[0].findNearest(globals.filteredEnemies);
var len = units.length;
for (var i = 0; i < len; ++i) {
this.command(units[i], 'attack', target);
}
};
this.maybeKiteIndividual = function (unit, enemies) {
var nearestSoldier = unit.findNearest(enemies);
if (nearestSoldier !== null) {
var soldierDistance = unit.distanceTo(nearestSoldier);
if (soldierDistance < 6) {
var soldierPos = nearestSoldier.pos;
var offsetScale = soldierDistance === 0 ? 10 : 10 / soldierDistance;
var unitPos = unit.pos;
var newPos = {
x: unitPos.x + (unitPos.x - soldierPos.x) * offsetScale,
y: unitPos.y + (unitPos.y - soldierPos.y) * offsetScale
};
this.command(unit, 'move', newPos);
return true;
}
}
return false;
};
this.doDefensiveGriffinAI = function (units) {
var enemySoldiers = globals.enemySoldiers;
var heroPos = this.pos;
var offsetHeroPos = {
x: heroPos.x + 2,
y: heroPos.y + 2
};
var yeti = globals.yeti;
var yetiArr = [yeti];
var artillery = globals.enemyArtillery.length > 0 ? globals.enemyArtillery[0] : null;
if (artillery !== null && enemySoldiers.length === 0) {
var len = units.length;
for (var i = 0; i < len; ++i) {
this.command(units[i], 'attack', artillery);
}
return;
}
for (var i$2 = 0, len$2 = units.length; i$2 < len$2; ++i$2) {
var unit = units[i$2];
if (this.maybeKiteIndividual(unit, enemySoldiers)) {
continue;
}
if (yeti !== null && this.maybeKiteIndividual(unit, yetiArr)) {
continue;
}
var lowestHealthEnemy = findLowestHealth(findInTargetRadius(unit, 20, globals.filteredEnemies));
if (lowestHealthEnemy !== null) {
this.command(unit, 'attack', lowestHealthEnemy);
continue;
}
this.command(unit, 'move', offsetHeroPos);
}
};
this.doOffensiveGriffinAI = function (units) {
var enemySoldiers = globals.enemySoldiers;
var enemyRanged = globals.enemyGriffins.concat(globals.enemyArchers, globals.enemyArtillery);
var sorc = globals.enemySorc;
var yeti = globals.yeti;
var yetiArr = [yeti];
for (var i = 0, len = units.length; i < len; ++i) {
var unit = units[i];
if (this.maybeKiteIndividual(unit, enemySoldiers)) {
continue;
}
if (yeti !== null && this.maybeKiteIndividual(unit, yetiArr)) {
continue;
}
var // prioritize ranged units, they are a threat to us
lowestHealthRanged = findLowestHealth(findInTargetRadius(unit, 20, enemyRanged));
if (lowestHealthRanged !== null) {
this.command(unit, 'attack', lowestHealthRanged);
continue;
}
if (// kill the sorc if in range
unit.distanceTo(sorc) < 20) {
this.command(unit, 'attack', sorc);
continue;
}
var // otherwise clean up soldiers
lowestHealthSoldier = findLowestHealth(findInTargetRadius(unit, 20, enemySoldiers));
if (lowestHealthSoldier !== null) {
this.command(unit, 'attack', lowestHealthSoldier);
continue;
}
var nearestEnemy = this.findNearest(globals.filteredEnemies);
this.command(unit, 'attack', nearestEnemy);
}
};
this.doDefensiveTroopLogic = function () {
this.doAttackNearestAI(globals.filteredEnemies, globals.friendlySoldiers);
this.doDefensiveGriffinAI(globals.friendlyGriffins);
};
this.doOffensiveTroopLogic = function () {
this.doAttackNearestAI(globals.enemies, globals.friendlySoldiers);
this.doOffensiveGriffinAI(globals.friendlyGriffins);
};
this.doEndGameTroopLogic = function () {
this.doAttackTargetAI(globals.enemySorc, globals.friends);
};
this.doTroopLogic = function () {
if (//var grifCount = globals.friendlyGriffins.length;
//var enemyGrifCount = globals.enemyGriffins.length;
//if (grifCount > 4 && grifCount / enemyGrifCount >= 1.8) {
// this.doOffensiveTroopLogic();
//}
//else {
// this.doDefensiveTroopLogic();
//}
this.shouldDoEndGameAttack()) {
this.doEndGameTroopLogic();
} else if (this.shouldGoOffensive()) {
this.doOffensiveTroopLogic();
} else {
this.doDefensiveTroopLogic();
}
};
if (// main loop -------------------------------------------------------------------
// There's some broken player floating around who spawns tharin,
// the knight, instead of their sorc.
// Just kill this guy.
this.findEnemies()[0].type === 'knight') {
loop {
this.attack(this.findEnemies()[0]);
}
}
for (;;) {
this.updateGlobals();
if (globals.enemySorc === null) {
return;
}
// command troops
this.doTroopLogic();
// "emergency" actions that take priority over summoning
var action;
action = this.fearYetiAction();
if (action !== null) {
this.executeAction(action);
continue;
}
action = this.fearEnemySorcererAction();
if (action !== null) {
this.executeAction(action);
continue;
}
// summon troops
this.doSummonLogic();
// use abilities and collect coins
this.doAbilityLogic();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment