-
-
Save MHeasell/9c2088816c7c756b92a9 to your computer and use it in GitHub Desktop.
My CodeCombat Ace of Coders tournament entry source code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var gulp = require('gulp'); | |
var replace = require('gulp-replace'); | |
var sweet = require('gulp-sweetjs'); | |
var rename = require('gulp-rename'); | |
var del = require('del'); | |
var concat = require('gulp-concat'); | |
var files = [ | |
'macros.js', | |
'util.js', | |
'strategy.js' | |
]; | |
var outfile = 'output.js'; | |
gulp.task('build', function() { | |
gulp.src(files) | |
.pipe(concat('all.js')) | |
.pipe(sweet()) | |
.pipe(replace(/for \(;;\) \{/, "loop {")) | |
.pipe(rename(outfile)) | |
.pipe(gulp.dest('.')); | |
}); | |
gulp.task('watch', ['build'], function() { | |
gulp.watch(files, ['build']); | |
}); | |
gulp.task('default', ['build'], function() { | |
}); | |
gulp.task('clean', function() { | |
del([outfile]); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// macros ---------------------------------------------------------------------- | |
// es6 style for loops | |
let for = macro { | |
rule { | |
(let [$idx, $ident] of enumerate($coll)) { $body ... } | |
} => { | |
for (var $idx = 0, len = $coll.length; $idx < len; ++$idx) { | |
var $ident = $coll[$idx]; | |
$body ... | |
} | |
} | |
rule { | |
(let $ident of $coll) { $body ... } | |
} => { | |
for (var i = 0, len = $coll.length; i < len; ++i) { | |
var $ident = $coll[i]; | |
$body ... | |
} | |
} | |
rule { | |
($ident of $coll) { $body ... } | |
} => { | |
for (var i = 0, len = $coll.length; i < len; ++i) { | |
$ident = $coll[i]; | |
$body ... | |
} | |
} | |
// default syntax | |
rule { | |
($a; $b; $c) { $body ... } | |
} => { | |
for ($a; $b; $c) { | |
$body ... | |
} | |
} | |
rule { | |
($a in $b) { $body ... } | |
} => { | |
for ($a in $b) { | |
$body ... | |
} | |
} | |
// empty loop | |
rule { | |
(;;) { $body ... } | |
} => { | |
for (;;) { | |
$body ... | |
} | |
} | |
} | |
// compile-time constants | |
macro constexpr { | |
case { _($e:expr) } => { | |
return localExpand(#{ | |
macro cexpr { | |
case { _ } => { | |
return [makeValue($e, #{ here })]; | |
} | |
} | |
cexpr | |
}); | |
} | |
} | |
macro const { | |
rule { | |
$ident:ident = $val:expr; | |
} => { | |
macro $ident { rule {} => { constexpr($val) } } | |
} | |
} | |
// fat arrow functions | |
macro => { | |
rule infix { | |
($x:ident (,) ...) | { $body ... } | |
} => { | |
function($x (,) ...) { | |
$body ... | |
} | |
} | |
rule infix { | |
($x:ident (,) ...) | $body:expr | |
} => { | |
($x (,) ...) => { | |
return $body; | |
} | |
} | |
rule infix { | |
$x:ident | $body:expr | |
} => { | |
($x) => $body | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
global | |
Vector | |
firstOfType | |
findNearestTo | |
findInRadiusOfUnit | |
findInRadiusOfPosition | |
findNearestTargetPosTo | |
firstInRadiusOfPosition | |
findLowestHealthNearest | |
removeType | |
*/ | |
// constants | |
/* eslint-disable no-unused-vars */ | |
const SOLDIER_RANGE = 3; | |
const ARCHER_RANGE = 25; | |
const ARTILLERY_RANGE = 65; | |
const ARROW_TOWER_RANGE = 30; | |
const GOLIATH_RANGE = 5; | |
const GOLIATH_TYPE = "goliath"; | |
const SOLDIER_TYPE = "soldier"; | |
const ARCHER_TYPE = "archer"; | |
const TOWER_TYPE = "arrow-tower"; | |
const ARTILLERY_TYPE = "artillery"; | |
const SHELL_TYPE = "shell"; | |
const BOULDER_TYPE = "boulder"; | |
const ARROW_TYPE = "arrow"; | |
const YAK_TYPE = "ice-yak"; | |
/* eslint-enable no-unused-vars */ | |
// utility functions ----------------------------------------------------------- | |
// Computes the total value of combat units | |
function valuateFighters(units) { | |
var sum = 0; | |
for (let u of units) { | |
sum += valuateFighter(u); | |
} | |
return sum; | |
} | |
function valuateFighter(u) { | |
switch (u.type) { | |
case SOLDIER_TYPE: | |
return 20; | |
case ARCHER_TYPE: | |
return 25; | |
case ARTILLERY_TYPE: | |
return 75; | |
case TOWER_TYPE: | |
return 100; | |
case GOLIATH_TYPE: | |
return 500; | |
default: | |
return 0; | |
} | |
} | |
// unit selection -------------------------------------------------------------- | |
var emergArtyBuiltTime = -Infinity; | |
this.chooseUnitToBuild = function() { | |
// it's not a good idea to build units | |
// while on the wrong side of the map | |
// and on the wrong side of the enemy goliath | |
var farPos = pointsMap.farCorner.pos; | |
var nearPos = pointsMap.nearCorner.pos; | |
var farDistance = this.pos.distance(farPos); | |
var nearDistance = this.pos.distance(nearPos); | |
// positive value means closer to enemy base than home base. | |
// negative value means closer to home base than enemy base. | |
// 0 is equidistant. | |
var distanceDelta = nearDistance - farDistance; | |
// if we're almost closer to the enemy side, | |
// and the enemy goliath is behind us, | |
// inhibit spawning. | |
if (distanceDelta > -10 && goliath.pos.distance(farPos) >= farDistance) { | |
return null; | |
} | |
var artillery = this.findByType(ARTILLERY_TYPE, friends); | |
// if we are under fire, | |
// and our artillery can't get into range, | |
// build an emergency artillery | |
var nearestEnemyArtillery = this.findNearest(enemyArtillery); | |
if (nearestEnemyArtillery) { | |
var friendlyArtillery = findNearestTo(nearestEnemyArtillery.pos, artillery); | |
var friendlyArtilleryDistance = Infinity; | |
if (friendlyArtillery) { | |
friendlyArtilleryDistance = nearestEnemyArtillery.pos.distance(friendlyArtillery.pos); | |
} | |
if (this.gold > 75 && this.gold > goliath.gold * 3 | |
&& this.distanceTo(nearestEnemyArtillery) <= ARTILLERY_RANGE | |
&& friendlyArtilleryDistance > ARTILLERY_RANGE + 20 | |
&& this.now() - emergArtyBuiltTime > 3) { | |
emergArtyBuiltTime = this.now(); | |
return ARTILLERY_TYPE; | |
} | |
} | |
// suppress spawning when in range of shells | |
var shell = this.findNearest(shells); | |
if (shell && this.pos.distance(shell.targetPos) < 20) { | |
return null; | |
} | |
var boulder = this.findNearest(boulders); | |
if (boulder && this.pos.distance(boulder.targetPos) < 15) { | |
return null; | |
} | |
// inhibit spawning if archers are in range | |
// and we haven't recently thrown a rock at them | |
var nearestEnemyArcher = this.findNearest(enemyArchers); | |
if (nearestEnemyArcher | |
&& this.distanceTo(nearestEnemyArcher) <= ARCHER_RANGE | |
&& this.now() - throwArcherTime > 1) { | |
return null; | |
} | |
var archers = this.findByType(ARCHER_TYPE, friends); | |
if (this.now() < 5 && artillery.length < 1) { | |
return ARTILLERY_TYPE; | |
} | |
// if we already have an army, | |
// and the enemy has artillery, but we don't, | |
// build an artillery | |
if (this.now() > 5 | |
&& enemyArtillery.length > 0 | |
&& artillery.length === 0 | |
&& archers.length >= 5) { | |
return ARTILLERY_TYPE; | |
} | |
// if the enemy is hanging back with siege, make sure we have an artillery | |
var siegeCount = enemyTowers.length + enemyArtillery.length; | |
var armyCount = | |
enemyTowers.length + | |
enemyArtillery.length + | |
enemyArchers.length + | |
enemySoldiers.length; | |
if (this.now() > 5 | |
&& pointsMap.center.team === this.team | |
&& siegeCount/armyCount > 0.33 | |
&& artillery.length === 0 | |
&& this.distanceTo(goliath) > 10) { | |
return ARTILLERY_TYPE; | |
} | |
return ARCHER_TYPE; | |
}; | |
this.chooseStructureToBuild = function() { | |
if (friends.length < 5) { | |
return null; | |
} | |
var point = this.findNearest(this.getControlPoints()); | |
// don't build towers while enemy artillery lives, | |
// it's a waste. | |
if (enemyArtillery.length > 0) { | |
return null; | |
} | |
// suppress spawning when in range of shells | |
var shell = this.findNearest(shells); | |
if (shell && this.pos.distance(shell.targetPos) < 20) { | |
return null; | |
} | |
// build a tower on the nearest point if we're close enough | |
// and enemy archers are threatening it | |
var archersNearPoint = findInRadiusOfPosition(point.pos, 30, enemyArchers); | |
if (archersNearPoint > 2 && this.pos.distance(point.pos) <= 20) { | |
var tower = findNearestTo(point.pos, this.findByType(TOWER_TYPE, friends)); | |
if (!tower || tower.pos.distance(point.pos) > 20) { | |
return { structure: TOWER_TYPE, pos: new Vector(this.pos.x - buildOffsetX, this.pos.y - buildOffsetY) }; | |
} | |
} | |
return null; | |
}; | |
this.buildArmy = function() { | |
var struct = this.chooseStructureToBuild(); | |
if (struct) { | |
if (this.gold >= this.costOf(struct.structure)) { | |
this.buildXY(struct.structure, struct.pos.x, struct.pos.y); | |
return 1; | |
} | |
} | |
else { | |
var type = this.chooseUnitToBuild(); | |
if (type && this.gold >= this.costOf(type)) { | |
this.buildXY(type, this.pos.x - buildOffsetX, this.pos.y - buildOffsetY); | |
return 1; | |
} | |
} | |
return 0; | |
}; | |
// unit objective allocation --------------------------------------------------- | |
this.computeEnemyStrength = function(point) { | |
var units = findInRadiusOfPosition(point.pos, 20, enemies); | |
var strength = valuateFighters(units); | |
return strength; | |
}; | |
this.allocateAndCommandBasic = function() { | |
var archers = this.findByType(ARCHER_TYPE, friends); | |
var pA = pointsMap.nearA; | |
var archerA = findNearestTo(pA.pos, archers); | |
var pB = pointsMap.nearB; | |
var archerB = findNearestTo(pB.pos, archers); | |
if (archerA) { | |
this.archerBasic(archerA, pA); | |
} | |
if (archerB) { | |
this.archerBasic(archerB, pB); | |
} | |
if (archerA && archerB && archerA.id === archerB.id) { | |
if (archerA.pos.distance(pA.pos) < archerA.pos.distance(pB.pos)) { | |
this.archerBasic(archerA, pA); | |
} | |
else { | |
this.archerBasic(archerA, pB); | |
} | |
} | |
var center = pointsMap.center; | |
var centerCaptured = center.team === this.team; | |
var a; | |
if (!centerCaptured || this.now() <= 8) { | |
for (a of archers) { | |
if (a.id === archerA.id) { | |
continue; | |
} | |
if (a.id === archerB.id) { | |
continue; | |
} | |
this.archerBasic(a, center); | |
} | |
} | |
else { | |
var archerCenter = findNearestTo(center.pos, archers); | |
for (a of archers) { | |
if (a.id === archerA.id) { | |
continue; | |
} | |
if (a.id === archerB.id) { | |
continue; | |
} | |
if (a.id === archerCenter.id) { | |
this.archerBasic(archerCenter, center); | |
continue; | |
} | |
this.archerBasic(a, null); | |
} | |
} | |
}; | |
// unit control ---------------------------------------------------------------- | |
this.commandTower = function(unit) { | |
var targets = findInRadiusOfUnit(unit, ARROW_TOWER_RANGE, enemies); | |
var target = findLowestHealthNearest(unit, targets); | |
if (target) { | |
this.command(unit, "attack", target); | |
} | |
}; | |
this.commandArtillery = function(unit) { | |
// attack artillery in range | |
var nearestArtillery = findNearestTo(unit.pos, enemyArtillery); | |
if (nearestArtillery) { | |
var artilleryDistance = unit.pos.distance(nearestArtillery.pos); | |
if (artilleryDistance <= ARTILLERY_RANGE) { | |
this.command(unit, "attack", nearestArtillery); | |
return; | |
} | |
else if (artilleryDistance <= ARTILLERY_RANGE + 10) { | |
this.command(unit, "attackPos", Vector.add(unit.pos, Vector.multiply(Vector.normalize(Vector.subtract(nearestArtillery.pos, unit.pos)), ARTILLERY_RANGE))); | |
return; | |
} | |
} | |
else if (this.now() < 4 && goliath.gold < 75) { | |
this.command(unit, "attackPos", new Vector(goliath.pos.x - (buildOffsetX*2), goliath.pos.y - (buildOffsetY*2))); | |
return; | |
} | |
// attack towers in range | |
var nearestTower = findNearestTo(unit.pos, enemyTowers); | |
if (nearestTower | |
&& unit.pos.distance(nearestTower.pos) <= ARTILLERY_RANGE) { | |
this.command(unit, "attack", nearestTower); | |
return; | |
} | |
// kite nearby enemies | |
var nearestEnemy = findNearestTo(unit.pos, enemies); | |
if (unit.pos.distance(nearestEnemy.pos) <= 35) { | |
var dir = Vector.normalize( | |
Vector.subtract(unit.pos, nearestEnemy.pos)); | |
this.command(unit, "move", Vector.add( | |
unit.pos, | |
Vector.multiply(dir, 10))); | |
return; | |
} | |
// dodge shells | |
var nearestShell = findNearestTo(unit.pos, shells); | |
if (nearestShell && nearestShell.targetPos.distance(unit.pos) <= 15) { | |
var shellDir = Vector.normalize( | |
Vector.subtract(unit.pos, nearestShell.targetPos)); | |
if (shellDir.magnitude() < 0.8) { | |
this.command(unit, "move", pointsMap.nearCorner.pos); | |
return; | |
} | |
this.command(unit, "move", Vector.add( | |
unit.pos, | |
Vector.multiply(shellDir, 10))); | |
return; | |
} | |
// attack artillery, regardless of range | |
if (nearestArtillery) { | |
var goVec = new Vector(0, 0); | |
var nearArtyDir = Vector.normalize(Vector.subtract(nearestArtillery.pos, unit.pos)); | |
goVec = Vector.add(goVec, Vector.multiply(nearArtyDir, 1)); | |
for (let en of enemies) { | |
if (en.id === nearestArtillery.id) { | |
continue; | |
} | |
var enDist = en.pos.distance(unit.pos); | |
if (enDist > 40) { | |
continue; | |
} | |
var enDir = Vector.normalize(Vector.subtract(unit.pos, en.pos)); | |
goVec = Vector.add(goVec, Vector.multiply(enDir, (36*36)/(enDist * enDist))); | |
} | |
var topDist = 95 - unit.pos.y; | |
var botDist = unit.pos.y; | |
var leftDist = unit.pos.x; | |
var rightDist = 115 - unit.pos.x; | |
goVec = Vector.add(goVec, new Vector(0, -100/(topDist * topDist))); | |
goVec = Vector.add(goVec, new Vector(0, 100/(botDist * botDist))); | |
goVec = Vector.add(goVec, new Vector(100/(leftDist * leftDist), 0)); | |
goVec = Vector.add(goVec, new Vector(-100/(rightDist * rightDist), 0)); | |
var movePos = Vector.add(unit.pos, Vector.multiply(Vector.normalize(goVec), 10)); | |
this.command(unit, "move", movePos); | |
return; | |
} | |
// attack enemy points | |
var farA = pointsMap.farA; | |
var farB = pointsMap.farB; | |
var siegePoint = null; | |
if (farA.team && farA.team !== this.team) { | |
if (farB.team && farB.team !== this.team) { | |
if (unit.pos.distance(farA.pos) < unit.pos.distance(farB.pos)) { | |
siegePoint = farA; | |
} | |
else { | |
siegePoint = farB; | |
} | |
} | |
else { | |
siegePoint = farA; | |
} | |
} | |
else if (farB.team && farB.team !== this.team) { | |
siegePoint = farB; | |
} | |
if (siegePoint) { | |
this.command(unit, "attackPos", siegePoint.pos); | |
return; | |
} | |
// attack towers, regardless of range | |
if (nearestTower) { | |
this.command(unit, "attack", nearestTower); | |
return; | |
} | |
// then goliath | |
this.command(unit, "attack", goliath); | |
}; | |
this.archerBasic = function(unit, defendPoint) { | |
var threats = []; | |
// figure out the threats | |
// avoid the splash of artillery shells | |
var shell = findNearestTargetPosTo(unit.pos, shells); | |
if (this.now() > 5 && shell && shell.targetPos.distance(unit.pos) <= 20) { | |
threats.push(shell.targetPos); | |
} | |
// avoid the splash of boulders | |
var boulder = findNearestTargetPosTo(unit.pos, boulders); | |
if (boulder && boulder.targetPos.distance(unit.pos) <= 15) { | |
threats.push(boulder.targetPos); | |
} | |
// avoid soldiers | |
var soldier = findNearestTo(unit.pos, enemySoldiers); | |
if (soldier && soldier.pos.distance(unit.pos) <= 10) { | |
threats.push(soldier.pos); | |
} | |
// avoid the goliath | |
if (unit.pos.distance(goliath.pos) <= 18) { | |
threats.push(goliath.pos); | |
} | |
// if we have threats, | |
// flee in a direction leading away from them | |
if (threats.length > 0) { | |
if (unit.pos.y > 90) { | |
threats.push(new Vector(unit.pos.x, 95)); | |
} | |
else if (unit.pos.y < 5) { | |
threats.push(new Vector(unit.pos.x, 0)); | |
} | |
else if (unit.pos.x < 5) { | |
threats.push(new Vector(0, unit.pos.y)); | |
} | |
else if (unit.pos.x > 110) { | |
threats.push(new Vector(115, unit.pos.y)); | |
} | |
var fleeDirection = new Vector(0, 0); | |
for (let threat of threats) { | |
var localFleeDirection = Vector.normalize( | |
Vector.subtract(unit.pos, threat)); | |
// if we got no direction, | |
// use the direction of home base | |
if (localFleeDirection.magnitude() < 0.8) { | |
localFleeDirection = Vector.normalize( | |
Vector.subtract(pointsMap.nearCorner.pos, unit.pos)); | |
} | |
fleeDirection = Vector.add(fleeDirection, localFleeDirection); | |
} | |
fleeDirection = Vector.normalize(fleeDirection); | |
if (fleeDirection.magnitude() < 0.8) { | |
fleeDirection = Vector.normalize( | |
Vector.subtract(pointsMap.nearCorner.pos, unit.pos)); | |
} | |
var fleePos = Vector.add(unit.pos, Vector.multiply(fleeDirection, 20)); | |
this.command(unit, "move", fleePos); | |
return; | |
} | |
// find the lowest health target and shoot it | |
var enemiesInRange; | |
if (this.now() < 15) { | |
enemiesInRange = findInRadiusOfUnit(unit, ARCHER_RANGE, removeType(GOLIATH_TYPE, enemies)); | |
} | |
else { | |
enemiesInRange = findInRadiusOfUnit(unit, ARCHER_RANGE, enemies); | |
} | |
var enemy = findLowestHealthNearest(unit, enemiesInRange); | |
if (enemy) { | |
this.command(unit, "attack", enemy); | |
return; | |
} | |
if (defendPoint) { | |
var pointDistance = unit.pos.distance(defendPoint.pos); | |
if (defendPoint.team !== this.team || pointDistance >= 9) { | |
this.command(unit, "move", defendPoint.pos); | |
return; | |
} | |
else { | |
this.command(unit, "move", unit.pos); | |
return; | |
} | |
} | |
// seek out and destroy the nearest archer, soldier or artillery | |
var nearestEnemy = findNearestTo(unit.pos, removeType(GOLIATH_TYPE, removeType(TOWER_TYPE, enemies))); | |
if (nearestEnemy) { | |
this.command(unit, "attack", nearestEnemy); | |
return; | |
} | |
// attack whatever is left | |
this.command(unit, "attack", findNearestTo(unit.pos, enemies)); | |
}; | |
this.commandArmy = function() { | |
this.allocateAndCommandBasic(); | |
var artillery = this.findByType(ARTILLERY_TYPE, friends); | |
for (let a of artillery) { | |
this.commandArtillery(a); | |
} | |
var towers = this.findByType(TOWER_TYPE, friends); | |
for (let t of towers) { | |
this.commandTower(t); | |
} | |
// decide the goliath defend point. | |
// Thanks, Nick63 Cheesy strat. | |
var nearADistance = goliath.pos.distance(pointsMap.nearA.pos); | |
var nearBDistance = goliath.pos.distance(pointsMap.nearB.pos); | |
var nearCornerDistance = goliath.pos.distance(pointsMap.nearCorner.pos); | |
if (nearADistance < 25) { | |
goliathDefendPos = pointsMap.nearA.pos; | |
} | |
else if (nearBDistance < 25) { | |
goliathDefendPos = pointsMap.nearB.pos; | |
} | |
else if (nearCornerDistance < 25) { | |
goliathDefendPos = pointsMap.nearCorner.pos; | |
} | |
else { | |
goliathDefendPos = pointsMap.center.pos; | |
} | |
}; | |
// hero control ---------------------------------------------------------------- | |
var throwArcherTime = -Infinity; | |
this.controlHero = function(defendPos) { | |
// look for towers near nearA and nearB points | |
// and get rid of them. | |
// Nice strategy, solarflare. | |
var nearATower = firstInRadiusOfPosition(pointsMap.nearA.pos, 15, enemyTowers); | |
if (nearATower) { | |
this.attack(nearATower); | |
return; | |
} | |
var nearBTower = firstInRadiusOfPosition(pointsMap.nearB.pos, 15, enemyTowers); | |
if (nearBTower) { | |
this.attack(nearBTower); | |
return; | |
} | |
// stomp if there are multiple units in range | |
if (this.isReady("stomp")) { | |
var enemiesInStompRange = this.findInRadius(15, enemies); | |
if (enemiesInStompRange.length >= 3) { | |
this.stomp(); | |
return; | |
} | |
} | |
var nearestTower = this.findNearest(enemyTowers); | |
// pick a target to throw at | |
if (this.isReady("throw")) { | |
// throw a boulder to hit artillery in range | |
var nearestArtillery = this.findNearest(enemyArtillery); | |
if (nearestArtillery && this.distanceTo(nearestArtillery) <= 25) { | |
this.throw(nearestArtillery); | |
return; | |
} | |
// throw a boulder to hit towers in range | |
if (nearestTower && this.distanceTo(nearestTower) <= 25) { | |
this.throw(nearestTower); | |
return; | |
} | |
// throw a boulder to hit archers in range | |
var nearestArcher = this.findNearest(enemyArchers); | |
if (nearestArcher && this.distanceTo(nearestArcher) <= 25) { | |
this.throw(nearestArcher); | |
throwArcherTime = this.now(); | |
return; | |
} | |
} | |
// try to throw the enemy goliath out of range of the point | |
if (this.isReady("hurl") && this.distanceTo(goliath) <= 6 | |
&& this.pos.distance(pointsMap.farCorner.pos) < goliath.pos.distance(pointsMap.farCorner.pos)) { | |
this.hurl(goliath, pointsMap.farCorner.pos); | |
return; | |
} | |
if (this.isReady("stomp") && this.distanceTo(goliath) <= 10) { | |
this.stomp(); | |
return; | |
} | |
// attack any nearby arrow towers | |
if (nearestTower && this.distanceTo(nearestTower) < 15) { | |
this.attack(nearestTower); | |
return; | |
} | |
// attack any nearby soldiers and artillery | |
var slowEnemies = removeType(ARCHER_TYPE, removeType(GOLIATH_TYPE, enemies)); | |
if (slowEnemies.length > 0) { | |
var nearestEnemy = findNearestTo(defendPos, slowEnemies); | |
if (defendPos.distance(nearestEnemy.pos) < 15) { | |
this.attack(nearestEnemy); | |
return; | |
} | |
} | |
// advance towards the objective | |
if (pointsMap.center.team !== this.team | |
&& this.pos.distance(defendPos) > 4) { | |
this.move(defendPos); | |
return; | |
} | |
// attack the enemy goliath if they are threatening the objective | |
if (defendPos.distance(goliath.pos) < 15) { | |
// sometimes we get stuck with a friendly tower between us | |
// and the enemy goliath. | |
// we need to detect this and go around the tower. | |
var nearestFriendlyTower = this.findNearest(friendlyTowers); | |
if (nearestFriendlyTower) { | |
var towerVec = Vector.subtract(nearestFriendlyTower.pos, this.pos); | |
var goliathVec = Vector.subtract(goliath.pos, this.pos); | |
var towerDist = towerVec.magnitude(); | |
var goliathDist = goliathVec.magnitude(); | |
if (towerDist <= 6 && towerDist < goliathDist) { | |
var towerAngle = towerVec.heading(); | |
var goliathAngle = goliathVec.heading(); | |
if (Math.abs(towerAngle - goliathAngle) < Math.PI/4) { | |
this.move(Vector.add(this.pos, Vector.rotate(goliathVec, -Math.PI/2))); | |
return; | |
} | |
} | |
} | |
this.attack(goliath); | |
return; | |
} | |
this.move(defendPos); | |
}; | |
// globals | |
var enemies; | |
var enemyArchers; | |
var enemySoldiers; | |
var enemyTowers; | |
var enemyArtillery; | |
var friends; | |
var friendlyTowers; | |
var goliath; | |
var missiles; | |
var shells; | |
var boulders; | |
var pointsMap; | |
var goliathDefendPos; | |
// main loop ------------------------------------------------------------------- | |
this.updateGlobals = function() { | |
enemies = removeType(YAK_TYPE, this.findEnemies()); | |
enemySoldiers = this.findByType(SOLDIER_TYPE, enemies); | |
enemyArchers = this.findByType(ARCHER_TYPE, enemies); | |
enemyTowers = this.findByType(TOWER_TYPE, enemies); | |
enemyArtillery = this.findByType(ARTILLERY_TYPE, enemies); | |
friends = this.findFriends(); | |
friendlyTowers = this.findByType(TOWER_TYPE, friends); | |
goliath = firstOfType(GOLIATH_TYPE, enemies); | |
missiles = this.findEnemyMissiles(); | |
shells = this.findByType(SHELL_TYPE, missiles); | |
boulders = this.findByType(BOULDER_TYPE, missiles); | |
pointsMap = this.getControlPointsMap(); | |
if (!goliath) { | |
// There's some broken player floating around who spawns tharin, | |
// the knight, instead of their goliath. | |
goliath = firstOfType("knight", enemies); | |
} | |
}; | |
// opening moves | |
var buildOffsetX, buildOffsetY; | |
if (this.team === "humans") { | |
buildOffsetX = 4.2; | |
buildOffsetY = 4.2; | |
} | |
else { | |
buildOffsetX = -4.2; | |
buildOffsetY = -4.2; | |
} | |
var initialCount = 0; | |
// center archers | |
this.buildXY(ARCHER_TYPE, this.pos.x + buildOffsetX, this.pos.y + buildOffsetY); | |
this.command(this.findFriends()[++initialCount], "move", this.getControlPointsMap().center.pos); | |
this.buildXY(ARCHER_TYPE, this.pos.x + buildOffsetX, this.pos.y + buildOffsetY); | |
this.command(this.findFriends()[++initialCount], "move", this.getControlPointsMap().center.pos); | |
this.buildXY(ARCHER_TYPE, this.pos.x + buildOffsetX, this.pos.y + buildOffsetY); | |
this.command(this.findFriends()[++initialCount], "move", this.getControlPointsMap().center.pos); | |
// side archers | |
this.buildXY(ARCHER_TYPE, this.pos.x + buildOffsetX, this.pos.y - buildOffsetY); | |
this.command(this.findFriends()[++initialCount], "move", this.getControlPointsMap().nearA.pos); | |
this.buildXY(ARCHER_TYPE, this.pos.x - buildOffsetX, this.pos.y + buildOffsetY); | |
this.command(this.findFriends()[++initialCount], "move", this.getControlPointsMap().nearB.pos); | |
// artillery | |
this.buildXY(ARTILLERY_TYPE, this.pos.x - buildOffsetX, this.pos.y + buildOffsetY); | |
mainloop: | |
for (;;) { | |
this.updateGlobals(); | |
if (!goliath) { | |
break mainloop; | |
} | |
this.commandArmy(); | |
this.updateGlobals(); | |
if (!goliath) { | |
break mainloop; | |
} | |
var built = true; | |
while (built) { | |
built = this.buildArmy(); | |
this.updateGlobals(); | |
if (!goliath) { | |
break mainloop; | |
} | |
this.commandArmy(); | |
this.updateGlobals(); | |
if (!goliath) { | |
break mainloop; | |
} | |
} | |
this.controlHero(goliathDefendPos); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint no-unused-vars:0 */ | |
/* global enumerate */ | |
function filterType(type, arr) { | |
var newArr = []; | |
for (let item of arr) { | |
if (item.type === type) { | |
newArr.push(item); | |
} | |
} | |
return newArr; | |
} | |
function removeType(type, arr) { | |
var newArr = []; | |
for (let item of arr) { | |
if (item.type !== type) { | |
newArr.push(item); | |
} | |
} | |
return newArr; | |
} | |
function firstOfType(type, arr) { | |
for (let ent of arr) { | |
if (ent.type === type) { | |
return ent; | |
} | |
} | |
return null; | |
} | |
function findNearestTo(pos, arr) { | |
var winner = null; | |
var winDistance = Infinity; | |
for (let item of arr) { | |
var distance = pos.distance(item.pos); | |
if (distance < winDistance) { | |
winner = item; | |
winDistance = distance; | |
} | |
} | |
return winner; | |
} | |
function findNearestToIndexed(pos, arr) { | |
var winIndex = -1; | |
var winDistance = Infinity; | |
for (let [i, item] of enumerate(arr)) { | |
var distance = pos.distance(item.pos); | |
if (distance < winDistance) { | |
winIndex = i; | |
winDistance = distance; | |
} | |
} | |
return winIndex; | |
} | |
function findNearestTargetPosTo(pos, arr) { | |
var winner = null; | |
var winDistance = Infinity; | |
for (let item of arr) { | |
var distance = pos.distance(item.targetPos); | |
if (distance < winDistance) { | |
winner = item; | |
winDistance = distance; | |
} | |
} | |
return winner; | |
} | |
function findInRadiusOfUnit(unit, radius, arr) { | |
var items = []; | |
for (let item of arr) { | |
if (unit.distanceTo(item) < radius) { | |
items.push(item); | |
} | |
} | |
return items; | |
} | |
function findInRadiusOfPosition(pos, radius, arr) { | |
var items = []; | |
for (let item of arr) { | |
if (pos.distance(item.pos) < radius) { | |
items.push(item); | |
} | |
} | |
return items; | |
} | |
function firstInRadiusOfPosition(pos, radius, arr) { | |
for (let item of arr) { | |
if (pos.distance(item.pos) < radius) { | |
return item; | |
} | |
} | |
return null; | |
} | |
function findLowestHealth(units) { | |
var winner = null; | |
var winScore = Infinity; | |
for (let u of units) { | |
var score = u.health; | |
if (score < winScore) { | |
winner = u; | |
winScore = score; | |
} | |
} | |
return winner; | |
} | |
function findLowestHealthNearest(unit, units) { | |
var winner = null; | |
var winHealth = Infinity; | |
var winDistance = Infinity; | |
for (let u of units) { | |
var health = u.health; | |
var distance = unit.pos.distance(u.pos); | |
if (health < winHealth) { | |
winner = u; | |
winHealth = health; | |
winDistance = distance; | |
} | |
else if (health === winHealth) { | |
if (distance < winDistance) { | |
winner = u; | |
winHealth = health; | |
winDistance = distance; | |
} | |
} | |
} | |
return winner; | |
} | |
this.filterFriends = function(arr) { | |
var newArr = []; | |
for (let item of arr) { | |
if (item.team === this.team) { | |
newArr.push(item); | |
} | |
} | |
return newArr; | |
}; | |
this.filterEnemies = function(arr) { | |
var newArr = []; | |
for (let item of arr) { | |
if (item.team === this.team) { | |
newArr.push(item); | |
} | |
} | |
return newArr; | |
}; | |
this.findInRadius = function(radius, arr) { | |
var items = []; | |
for (let item of arr) { | |
if (this.distanceTo(item) < radius) { | |
items.push(item); | |
} | |
} | |
return items; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
jesus christ this is long