Skip to content

Instantly share code, notes, and snippets.

@tchalvak
Forked from nwinter/NoJuice4u.js
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tchalvak/9a4a105625286257b211 to your computer and use it in GitHub Desktop.
Save tchalvak/9a4a105625286257b211 to your computer and use it in GitHub Desktop.
var myTeam = this.team;
var eSoldiers = [];
var itemUpdateDelay = 0.5;
var itemTimer = 0;
var soldiersCount = 0;
var goldStormCD = 0;
var firstBlast = true;
var initiate = false;
var atkType = 2;
var manaBlastCD = this.now();
var cooldownResetTime = this.now();
var sorcerers = this.findByType("sorcerer");
var startE = this.findEnemies();
var enemySorcerer = startE[0];
var goldCoins = 20;
var spanningRadius = 100;
for(var i=0; i<sorcerers.length; ++i)
{
if(sorcerers[i].team != myTeam) enemySorcerer = sorcerers[i];
}
loop {
var enemies = this.findEnemies();
var yeti = this.findByType("yeti");
var cage = this.findByType("cage");
var friends = this.findFriends();
var corpses = this.findCorpses();
var numInBlast = 0;
var numNearBlast = 0;
var noNearbyEnemies = true;
var eArchers = [];
var eGriffins = [];
var eArtillery = [];
var enemyFaction = [];
var enemyFactionNoSorc = [];
enemyFaction.push(enemySorcerer);
eSoldiers = [];
var enemiesInRange = [];
cogX = 0;
cogY = 0;
for(i=0; i<enemies.length; ++i)
{
if(this.distanceTo(enemies[i]) < 20)
{
numInBlast++;
cogX += enemies[i].pos.x;
cogY += enemies[i].pos.y;
}
nearestFriend = enemies[i].findNearest(friends);
if(friends.length > 0 && enemies[i].distanceTo(nearestFriend) < enemies[i].attackRange)
{
if(this.now() > 20 || enemies[i].type != "sorcerer") enemiesInRange.push(enemies[i]);
}
if(enemies[i].type == "soldier") eSoldiers.push(enemies[i]);
else if(enemies[i].type == "griffin-rider") eGriffins.push(enemies[i]);
else if(enemies[i].type == "archer") eArchers.push(enemies[i]);
else if(enemies[i].type == "artillery") eArtillery.push(enemies[i]);
if(enemies[i].type != "cage" && enemies[i].type != "yeti")
{
enemyFactionNoSorc.push(enemies[i]);
}
}
enemyFaction.push(enemyFactionNoSorc);
var eRange = eArchers.concat(eGriffins);
nearestEnemy = this.findNearest(enemyFaction);
if(initiate === false && numInBlast > 0)
{
cogX /= numInBlast;
cogY /= numInBlast;
}
else if(enemyFactionNoSorc.length > 0 && initiate === true)
{
for(i=0; i<enemyFactionNoSorc.length; ++i)
{
numInBlast++;
cogX += enemyFactionNoSorc[i].pos.x;
cogY += enemyFactionNoSorc[i].pos.y;
}
cogX /= numInBlast;
cogY /= numInBlast;
}
if(this.distanceTo(nearestEnemy) < 25) noNearbyEnemies = false;
// She also commands your allies in battle.
soldiersCount = 0;
friends = this.findFriends();
if(this.now() < 15 && firstBlast === true) initiate = true;
for (friendIndex = 0; friendIndex < friends.length; ++friendIndex)
{
friend = friends[friendIndex];
if(friend.team != myTeam) continue;
fNearestEnemy = friend.findNearest(enemyFaction);
fNearestRange = friend.findNearest(eRange);
if(friend.type == "soldier") soldiersCount++;
distance = 999;
health = 200;
for (eIndex = 0; eIndex < enemiesInRange.length; ++eIndex)
{
if(enemiesInRange[eIndex].type == "soldier") eHValue = enemiesInRange[eIndex].health * 2;
else eHValue = enemiesInRange[eIndex].health;
if(eHValue < health && this.distanceTo(enemiesInRange[eIndex]) < (this.attackRange+5) && (enemiesInRange[eIndex].type != 'cage' || enemiesInRange[eIndex].type != 'yeti'))
{
health = enemiesInRange[eIndex].health;
nearestEnemy = enemiesInRange[eIndex];
fNearestRange = enemiesInRange[eIndex];
fNearestEnemy = enemiesInRange[eIndex];
}
}
if((enemiesInRange.length <= 1 || enemies.length <= 2) && friend.type != "soldier" && this.now() < 10)
{
initiate = false;
this.command(friend, "move", this.pos);
continue;
}
else if(yeti.length > 0 && yeti[0].target !== null && yeti[0].target.team == myTeam)
{
if(yeti[0].target == friend)
{
this.command(friend, "move", enemySorcerer.pos);
continue;
}
else
{
this.command(friend, "attack", enemySorcerer);
continue;
}
}
else if(friend.type == "soldier")
{
this.command(friend, "attack", fNearestEnemy);
continue;
}
if(eArtillery.length > 0) this.command(friend, "attack", friend.findNearest(eArtillery));
else if(fNearestEnemy && friend.distanceTo(fNearestEnemy) < (friend.attackRange-15))
{
if((fNearestRange && fNearestRange.type == "soldier") || !fNearestRange)
{
var escapeVector = Vector.add(Vector.multiply(Vector.normalize(Vector.subtract(friend.pos, fNearestEnemy.pos)), 8), this.pos);
this.command(friend, "move", escapeVector);
}
}
else if(eRange.length > 0) this.command(friend, "attack", fNearestRange);
else if(fNearestEnemy.type == "sorcerer" && this.now() > 20) this.command(friend, "attackPos", fNearestEnemy.pos);
else if(fNearestEnemy && friend.distanceTo(fNearestEnemy) < (friend.attackRange-15)) this.command(friend, "attack", fNearestEnemy);
else this.command(friend, "attack", fNearestEnemy);
}
if(!this.canCast("fear") && this.now() > cooldownResetTime)
{
this.resetCooldown('fear');
cooldownResetTime = this.now() + 20;
}
if(yeti.length === 0 && this.canCast("fear") && this.distanceTo(enemySorcerer) < 30 && !enemySorcerer.hasEffect("fear"))
{
this.cast("fear", enemySorcerer);
continue;
}
if(corpses.length > 0 && this.canCast('raise-dead') && this.distanceTo(this.findNearest(corpses)) < 15)
{
this.cast('raise-dead');
continue;
}
if (this.gold > this.costOf("soldier") && ((eSoldiers.length/soldiersCount) >= 2)) {
soldiersCount++;
this.summon("soldier");
continue;
} else if(this.gold > this.costOf("archer") && atkType >= 3) {
atkType = 0;
this.summon("archer");
continue;
} else if(this.gold > this.costOf("griffin-rider")) {
atkType++;
this.summon("griffin-rider");
continue;
}
if((numInBlast > 5 || (numInBlast*2) > (enemies.length+1) || initiate === true) && this.now() > manaBlastCD && enemies.length > 3)
{
if(this.distanceTo({x: cogX, y: cogY}) > 5)
{
this.move({x: cogX, y: cogY});
continue;
}
else
{
manaBlastCD = this.now() + 10;
firstBlast = false;
initiate = false;
this.manaBlast();
}
}
if(yeti.length > 0)
{
if(yeti[0].distanceTo(this) < yeti[0].distanceTo(enemySorcerer))
{
if(this.canCast("fear", yeti[0]) && yeti[0].target == this )
{
this.cast("fear", yeti[0]);
continue;
}
}
else
{
if(corpses.length > 0 && this.canCast('raise-dead') && this.distanceTo(this.findNearest(corpses)) < 15)
{
this.cast('raise-dead');
continue;
}
if(yeti[0].target == enemySorcerer && this.distanceTo(enemySorcerer) < this.attackRange)
{
this.attack(enemySorcerer);
continue;
}
}
}
if(true)
{
goldCoins = 0;
var items = this.findItems();
var desiredDirectionX = 0;
var desiredDirectionY = 0;
for(var c=0; c<items.length; ++c)
{
if(this.distanceTo(items[c]) > spanningRadius) continue;
var multiplier = 1/(this.distanceTo(items[c])/items[c].value);
var vDiff = Vector.subtract(items[c].pos, this.pos);
var direction = Vector.multiply(Vector.normalize(new Vector(vDiff.x, vDiff.y)), 1/(Math.pow((this.distanceTo(items[c])), 2.24)));
desiredDirectionX += direction.x * Math.pow(items[c].bountyGold, 2.14) * multiplier;
desiredDirectionY += direction.y * Math.pow(items[c].bountyGold, 2.14) * multiplier;
if(items[c].value >= 3) goldCoins++;
}
if(goldCoins > 20) spanningRadius--;
else spanningRadius++;
normalizedVector = Vector.normalize(new Vector(desiredDirectionX, desiredDirectionY));
if(numInBlast < 3 && this.now() > goldStormCD)
{
this.cast("goldstorm");
goldStormCD = this.now() + 15;
}
if(cage.length > 0 && this.distanceTo(cage[0]) < 5)
{
cageDirectionVector = Vector.normalize(Vector.subtract(this.pos, cage[0].pos));
normalizedVector = Vector.normalize(Vector.add(cageDirectionVector, normalizedVector));
}
this.move(Vector.add(Vector.multiply(normalizedVector, 10), new Vector(this.pos.x, this.pos.y)));
continue;
}
if(this.distanceTo(enemySorcerer) < 26 && !enemySorcerer.hasEffect("fear") && !this.canCast("fear") && this.now() > cooldownResetTime)
{
this.resetCooldown('fear');
cooldownResetTime = this.now() + 20;
}
if(this.canCast("drain-life", nearestEnemy) && this.distanceTo(nearestEnemy) < 15)
{
this.cast("drain-life", nearestEnemy);
continue;
}
if(enemiesInRange.length > 0)
{
eHealth = 500;
eDistance = 999;
for (eIndex = 0; eIndex < enemiesInRange.length; ++eIndex)
{
if(enemiesInRange[eIndex].type == "soldier") eHValue = enemiesInRange[eIndex].health * 2;
else eHValue = enemiesInRange[eIndex].health;
if(eHValue < eHealth && this.distanceTo(enemiesInRange[eIndex]) < this.attackRange)
{
health = enemiesInRange[eIndex].health;
nearestEnemy = enemiesInRange[eIndex];
}
}
this.attack(nearestEnemy);
continue;
}
else
{
this.attack(nearestEnemy);
}
}

Gold Collection Strategy

I initially copied my Gold Rush code over, because it was reasonably strong, and I believed that all the logic was there for a strong collection strategy.  However, as soon as I brought that code over, I blew out the hard execution limit imposed on this game mode, primarily because the “Fear” and “Goldstorm” spells can result in many coins on the battlefield.

In the end, I had to cut out half of the code to stay under the limit, because I felt that the cost of computation could be better spent on other areas. As a result, I was left with this:

  • Take an inverse-exponent of the distance and value to get a vector that points towards the coin, and a magnitude that reflects how desirable it is.

  • Execute this calculation on all the coins and add up all the vectors.  This is the direction that I will move.  Due to the inverse-exponent, it causes me to “gravitate” to a coin which results in a pick-up.  I never target an individual coin.

  • To help reduce the execution cost, due to the inverse-exponent, I can ignore coins beyond a certain distance, because the magnitude of their vectors is too insignificant.  I use a radius variable to determine the distance, and grow/shrink that radius depending on how many gold coins/gems I find in the radius.

Unless I am casting a spell, or spawning a unit, I am collecting coins.  I do not bother to use my sorcerer to attack.  One of my earlier strategies attempted to use my sorcerer to attack enemies in range to help whittle down the enemy forces, but the problem is that if the enemy is collecting coins, they will spawn units at the same rate you kill.  Then, there’s the fact that I will no longer be collecting coins, allowing the level’s coin-density increase greatly.  This means that the opponent’s collection rate will steadily rise, while I slowly lose forces.  Eventually, I just get overwhelmed, so I decided to not bother with my sorcerer’s attack, despite the high damage and massive range.

The parts of my “Gold Rush” strategy that I couldn’t bring over was:

  • Deprioritizing coins that the enemy is moving towards and is closer to than I am.
  • Prioritizing coins that the enemy is moving towards but that I am closer to.
  • Prioritizing coins that the enemy is moving away from.

Spells

Fear

To me, Fear is the most important spell because the target loses complete control.  Since the infantry units need to receive commands from the sorcerer, fearing the sorcerer is as good as disabling the enemy’s forces.  I always fear the enemy sorcerer, unless the yeti is out and targeting my sorcerer.

Goldstorm

Goldstorm generates gold starting at your location, so it’s quite an increase to your gold collection rate.  You cast, and then get going with your logic again.  However, due to the one-second cast time, I make sure that there aren’t too many enemies near me when casting, because that’s too much damage to take. I can almost always outrun them, then cast.

Reset Cooldown

My first priority with reset cooldown is to see if I can cast fear and if the enemy sorcerer is under the effect of fear.  This sometimes lets me chain two fears on the enemy sorcerer if I’m lucky.  I don’t bother resetting the cooldown otherwise, because it’s not worth chaining any other spell.  Forcing the opponent AI to lose control is extremely valuable.

Mana Blast

Mana Blast was a tricky one.  Its power is proportional to the distance from center, and there seems to be an inner radius that causes a knockback.  While it’s good to run up to a large group and blast one off, it’s even better to get in the middle of them to do it.  However, I don’t use the exact center, I use the averaged vector, to maximize the damage, and maximize the opportunity to scatter the opponent’s forces.

I also have a custom logic for the “First-time mana blast” designed to scatter the opponent when our forces first engage.  As a result most battles go like this:

  1. Forces engage into a fight.
  2. Sorcerers go in and fear each other.
  3. I mana blast the enemy forces to scatter them, which usually gives me a significant advantage.

The main goal is to spread out the enemy, so that my unit strategy can pick off the enemy individually.

Raise Dead

Pretty much cast as soon as there are a few units within your cast radius.

Drain Life

Cast as a last priority.

Unit Strategy

Initial Strategy

Initially, I always built 4 soldiers to start with, then move on to a 3 griffin-rider, 1 archer pattern.  I used this build pattern to refine the AI of my forces.  I started my refinements with prioritizing griffins and archers, as I felt they were the most deadly at the time.  (I didn’t know artillery existed at that time).  It worked reasonably well, until I encountered an opponent that used artillery.

Artillery was ridiculously powerful, but had a fatal flaw.  Its attacks took too long to land, and its health wasn’t very high, and it was expensive to summon.  As long as I made the artillery the “first-to-kill” target, the cannonball won’t cause much damage by the time I killed it.  I risk my forces taking additional damage to travel to the artillery, but it also means that the opponent is lacking units that can deal serious, reliable damage to my forces.

In the higher ranking levels however, the enemy’s soldiers wer actually doing considerable damage to my griffins and archers.  Due to this, I made some adjustments to move away from the soldiers.  If an opponent range is behind a soldier, I would go in to attack, and when they die, my units would scatter out to avoid taking any more damage.

Archers are usually the ones in the back, so my griffin-riders don’t spend too much time under soldier attack, and Griffin-Riders move so fast that they are almost always at the front-line.

Final Strategy

The final strategy I settled on was this:

  • If the enemy has 1.75 times more soldiers than me, I summon a soldier when I have the gold.  I need to ensure that I have some soldiers if my opponent also has some, due to their tankiness, and cheap cost.
  • Otherwise, I always spawn 3 griffin-riders, then 1 archer in an alternating pattern.  The reason I don’t spawn many archers, is because their health is extremely low.  I find their merit comes from their range and speed (lack thereof), where they can avoid certain disasters if they aren’t actively being hunted.  Otherwise, the griffin-riders can take much more damage.  I often see the enemy sorcerer mana blasting my griffin-riders, and my archers happen to be outside the range, due to their speed.

Now, my battle strategy is broken up into a few phases.  First is the “Pre-initiation” phase.

  • I command all units except soldiers to move to my position.  I don’t use defend, because I don’t want them to run out to attack without my explicit order.  In addition, if I get feared, they will stay put.  In “defend” mode, they might try to attack the enemy, and then overshoot the enemy when I am feared.
  • I command all my soldiers to attack the nearest enemy.  Their goal is to be the front line, and hold that line so that when it’s time to initiate, my griffin riders and archers will launch their attack from behind my soldiers.
  • The “Pre-initiation” phase ends when the game time exceeds 10, or an enemy comes within range of any of my units.  I chose 10 seconds, because it’s enough time for my soldiers to get close enough that I can initiate.

Next is the initiation phase:

  • Soldier AI remains the same.  They’re my meat shields.
  • I add up all enemies that are in range to attack any of my units excluding the sorcerer into an array, and apply a score modifier based on the unit type, health remaining and distance to determine what to focus fire.  This allows my archers and griffin-riders to focus certain targets to reduce the enemy numbers quickly.  Generally the priority resolves to: Artillery, Archers, Griffin-Riders, Soldiers if everything is at full health.
  • The above strategy factors in distance in such a way that if my forces get scattered somehow, they will cluster up into multiple small groups to focus fire their area, and eventually coalesce back into a large group.
  • If a soldier or sorcerer gets too close to my range, I move that unit towards an escape vector to kite and avoid taking unnecessary damage.  I don’t run away from ranged opponents, because it’s better to fight them head-on.  However the enemy sorcerer’s “Mana Blast” is extremely deadly so I make sure my range try to attack at half to max range, to reduce the damage taken from a mana blast, and it also helps prevent my ranged units from clustering, which would be easily devastated by a well-placed mana blast.
  • As mentioned earlier, I have a “First time mana blast” logic that explicitly goes in and fires off a blast.  It’s slightly different from my standard strategy.  After that, I scan for opportunities to execute a mana blast based on the clustering of enemies, and whether I am close enough that the detour is worth it.

Then there’s the yeti phase:

  • At the yeti phase, I issue all my forces to attack the enemy sorcerer.  The reason for this is because the yeti runs insanely fast and kills fast.  If I clutter my forces on the enemy sorcerer, it’s less likely that the yeti will kill a unit, and then move to a unit that is closer to me, and eventually reach me.
  • If the yeti does decide to attack me, I fear it, and continue on my coin collection strategy.  I rely on spawning new units and moving them to the enemy to direct the yeti to the opponent.
  • If the yeti is attacking a unit of mine, I explicitly issue it a move command to the enemy sorcerer’s position.  The reason I use a position, and not the enemy sorcerer is because it’s more predictable what will happen if I get feared while this is happening.  Targeting the sorcerer directly can cause the unit to “overshoot”, and put your forces out of position.
  • At this point, I no longer have absolute priority on fearing the enemy sorcerer.  I only fear them if they are closer to me than the yeti is.
  • However, my yeti phase logic is very unpolished, and I believe most of my losses probably happened in this phase.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment