Lesson 8 - Tug of War
Introduction
Welcome to the final test of your training. Conquer this quest and your training as a Novice will be complete.Up until now, you have only commanded one Dragon. In this quest, you will learn how to command many Dragons to battle against many enemy Dragons.
for
loop
In order to command many dragons, you will need to learn a new statment: the for
loop. for
loops are handy when you want to run the same code many times. E.g., if you want to run the same code for many Dragons :) For example...
In your old code, since you had only one Dragon, you could always pick the first Dragon:
var myUnits = Helper.getMyUnits(state);
// Pick the first Dragon
// (first dragon is at index 0 of "myUnits"
// second dragon is at index 1, and so on...)
var dragon = myUnits[0];
// Do something with "dragon"
// ...
But now that you have many Dragons, you can't just pick the first Dragon using
myUnits[0]
anymore. You have to use the for
loop to loop over all your Dragon units. Like this:
var myUnits = Helper.getMyUnits(state);
// "myUnits" is an array that can contain many Dragons.
// We want to visit each Dragon, using the "for" loop:
for (var i=0; i < myUnits.length; i++)
{
// Pick the next Dragon in the "myUnits" array.
// Notice that instead of using myUnits[0] like before,
// we now say myUnits[i]
// In this "for" loop, "i" starts at 0.
// E.g. if you have 3 Dragons, the "for" loop will
// execute the code below 3 times, once for each Dragon.
// Each time, "i" will automatically change like this:
// i = 0
// i = 1
// i = 2
var dragon = myUnits[i];
// Do something with this "dragon"
// ...
}
Notice that the
for
loop syntax is made up of 3 parts:
for (var i=0; i < myUnits.length; i++)
Let's break that down...
for (
var i=0; // 1) Start "i" at 0.
i < myUnits.length; // 2) Keep going for as long as "i" is less than
// than the number of Dragon units
// (that'll be 3 Dragons in this Quest).
i++) // 3) Add 1 to "i" after each round of the loop
// (each round of the for loop is also known
// as an "iteration").
Tug of War
Finally, let's upgrade your A.I. to usefor
loops so that it can control any number of Dragons -- instead of just one.Look for all the places in the code below with the comment "for-loop added".
function captureOrb(state)
{
var myUnits = Helper.getMyUnits(state);
// for-loop added to visit all Dragon units
for (var i=0; i < myUnits.length; i++)
{
var dragon = myUnits[i];
if (!dragon.cargo)
{
// Dragon is not carrying anything. Check for enemies...
var enemyFound = Helper.getClosestEnemy(state, dragon);
if (!enemyFound)
{
// No enemies in sight. Safe to search for Orbs :)
var orbFound = Helper.getClosestOrb(state, dragon);
if (orbFound)
{
// found Orb!
var orb = orbFound.orb;
// move to Orb
Game.moveTo(dragon.id, orb.pos.x, orb.pos.y);
// pick it up
Game.carryOrb(dragon.id);
}
}
}
else
{
// Dragon is already carrying an Orb. Find closeset Base...
var baseDist = Helper.getClosestBase(state, dragon);
var base = baseDist.base;
// return to base
Game.moveTo(dragon.id, base.pos.x, base.pos.y);
}
}
}
function attack(state)
{
// get array of all my units
var myUnits = Helper.getMyUnits(state);
// for-loop added to visit all Dragon units
for (var i=0; i < myUnits.length; i++)
{
// get the next Dragon in the array
var dragon = myUnits[i];
// get this Dragon's ID
var dragonId = dragon.id;
// get this Dragon's closest enemy
var enemyFound = Helper.getClosestEnemy(state, dragon);
if (!enemyFound)
{
// no enemies around to attack
return;
}
// get enemy Dragon's ID
var enemyId = enemyFound.unit.id;
// attack!
Game.attack(dragonId, enemyId);
}
}
function advanceOrRetreat(state)
{
// get array of all my units
var myUnits = Helper.getMyUnits(state);
// for-loop added to visit all Dragon units
for (var i=0; i < myUnits.length; i++)
{
// get the next Dragon in the array
var dragon = myUnits[i];
var dragonId = dragon.id;
var enemyFound = Helper.getClosestEnemy(state, dragon);
if (!enemyFound)
{
// no enemies to attack or retreat from
return;
}
var enemy = enemyFound.unit;
// Choose offense or defense.
// Decide to move Dragon toward enemy or away from enemy
if (isShieldEnergyLow(dragon))
{
// shield is low, retreat!
var escapePos = Helper.computeRetreatPoint(dragon.pos, enemy.pos);
Game.moveTo(dragon.id, escapePos.x, escapePos.y);
}
else
{
// move Dragon to enemy Dragon's position
// (but only if enemy is too far away)
if (enemyFound.dist > 350)
{
// move toward enemy -- it's more than 350 units away
Game.moveTo(dragonId, enemy.pos.x, enemy.pos.y);
}
}
}
}
function isShieldEnergyLow(dragon)
{
if (dragon.maxShields == 0)
{
// this Dragon has no shields at all
return true;
}
// compute percent shield energy remaining
var shieldPct = dragon.shields / dragon.maxShields * 100;
// if shield energy is less than 20%,
// say that shields are low
if (shieldPct < 20)
return true;
// shields are greater than 20%,
// say that shields are not low
return false;
}
function run(state)
{
advanceOrRetreat(state);
attack(state);
captureOrb(state);
}
Upload your new A.I. and unleash the full power of your Dragon army! Load up the game, then sit back and admire your beautiful code in action.
Tips for writing Good Code
And now an important lesson. By now, you have seen that code tends to grow over time, because goals change and it's common to need to make your code do more.Part of being a good coder is learning how to manage your code as it grows. Any time software engineers need to add code, they work hard to tame it so that doesn't get too messy, complex or out of control. This means:
- Add comments to explain each step of your code. You can never add too many comments.
- Use
functions
to break up large blocks of code. - The code in each
function
should be small and specific. Whenever possible, a function should only do one thing. - If your code makes you feel confused, reorganize it until it is logical and clear. In real-world projects, you often have to work with other coders. So if you already have trouble understanding your own code, your teammates have no chance.
- When you keep your code neat and clear, you're also doing your future self a favor. Because chances are, you'll visit your code again. Any corners you cut or any junk you sweep under the rug will only come back to bite you later. So be nice to yourself and leave everything in a nice state. You'll thank yourself and enjoy it when you come back to it later.
- Delete unused code.
Congratulations, with this quest conquered, you graduate from Novice to Apprentice!
Cheatcode
Spoiler alert! Click to reveal full solution.
<!DOCTYPE html>
<html>
<body>
<div id="display">hello</div>
<script>
function captureOrb(state)
{
var myUnits = Helper.getMyUnits(state);
// for-loop added to visit all Dragon units
for (var i=0; i < myUnits.length; i++)
{
var dragon = myUnits[i];
if (!dragon.cargo)
{
// Dragon is not carrying anything. Check for enemies...
var enemyFound = Helper.getClosestEnemy(state, dragon);
if (!enemyFound)
{
// No enemies in sight. Safe to search for Orbs :)
var orbFound = Helper.getClosestOrb(state, dragon);
if (orbFound)
{
// found Orb!
var orb = orbFound.orb;
// move to Orb
Game.moveTo(dragon.id, orb.pos.x, orb.pos.y);
// pick it up
Game.carryOrb(dragon.id);
}
}
}
else
{
// Dragon is already carrying an Orb. Find closeset Base...
var baseDist = Helper.getClosestBase(state, dragon);
var base = baseDist.base;
// return to base
Game.moveTo(dragon.id, base.pos.x, base.pos.y);
}
}
}
function attack(state)
{
// get array of all my units
var myUnits = Helper.getMyUnits(state);
// for-loop added to visit all Dragon units
for (var i=0; i < myUnits.length; i++)
{
// get the next Dragon in the array
var dragon = myUnits[i];
// get this Dragon's ID
var dragonId = dragon.id;
// get this Dragon's closest enemy
var enemyFound = Helper.getClosestEnemy(state, dragon);
if (!enemyFound)
{
// no enemies around to attack
return;
}
// get enemy Dragon's ID
var enemyId = enemyFound.unit.id;
// attack!
Game.attack(dragonId, enemyId);
}
}
function advanceOrRetreat(state)
{
// get array of all my units
var myUnits = Helper.getMyUnits(state);
// for-loop added to visit all Dragon units
for (var i=0; i < myUnits.length; i++)
{
// get the next Dragon in the array
var dragon = myUnits[i];
var dragonId = dragon.id;
var enemyFound = Helper.getClosestEnemy(state, dragon);
if (!enemyFound)
{
// no enemies to attack or retreat from
return;
}
var enemy = enemyFound.unit;
// Choose offense or defense.
// Decide to move Dragon toward enemy or away from enemy
if (isShieldEnergyLow(dragon))
{
// shield is low, retreat!
var escapePos = Helper.computeRetreatPoint(dragon.pos, enemy.pos);
Game.moveTo(dragon.id, escapePos.x, escapePos.y);
}
else
{
// move Dragon to enemy Dragon's position
// (but only if enemy is too far away)
if (enemyFound.dist > 350)
{
// move toward enemy -- it's more than 350 units away
Game.moveTo(dragonId, enemy.pos.x, enemy.pos.y);
}
}
}
}
function isShieldEnergyLow(dragon)
{
if (dragon.maxShields == 0)
{
// this Dragon has no shields at all
return true;
}
// compute percent shield energy remaining
var shieldPct = dragon.shields / dragon.maxShields * 100;
// if shield energy is less than 20%,
// say that shields are low
if (shieldPct < 20)
return true;
// shields are greater than 20%,
// say that shields are not low
return false;
}
function run(state)
{
advanceOrRetreat(state);
attack(state);
captureOrb(state);
}
Game.register(run);
</script>
</body>
</html>