Level: Find the Spy

Here’s another one of @gosnat’s new levels. After debugging this level a bit, it’s ready for action. Tharin’s squad has been infiltrated by secret ogre spies, and in this elementary level, you practice using if-statements and loops to find the enemies. Please post any feedback or questions here.

In this level you can still use getEnemies()… maybe not an issue because those who don’t know ifs and loops are not likely to know about getEnemies().

Huh. I was under the impression that only methods that were listed in the programmableProperties section were eligible to be used by the player. Is there some other way to restrict method usage? I suppose now that we have custom API ability (wasn’t available when I created this level) I could make this a property other than team so that the getFriends() and getEnemies() methods won’t be impacted.

@nick, could you perhaps weigh in on this?

Edit: I’ve altered the level to use .loyal instead of .team. @no_login_found, would you mind trying the level from scratch and seeing if that addressed your concerns?

Hmm, I think that team works better because then you can visually see the enemy soldiers having different colors. I’ve pushed a commit enabling API protection here. If you want to add any other levels to the works-with-API-protection list, you can either let me know or submit a pull request to add the level. You can test any level with it like so:

Just so I’m clear then, if API protection is enabled, only the methods listed in programmableProperties can be used by the player?

@nick I think there’s at least one bug: If you add the HasAPI component and then add a custom attribute to the API, that doesn’t seem to be an allowable reference like the default attributes are. I had added a .loyal attribute to hasAPI, but couldn’t actually reference it with API protection on. Reverting back to .team does work.

I can’t claim to understand the aether code at the moment, so I can’t be sure I’m correct on this, but this seems to be the behavior I’m seeing.

It’s a bit disorganized in that apiProperties and programmableProperties get merged, so a property will be accessible if it is in either array.

If you want it accessible to the player’s code, you need to add it to the apiProperties list inside of HasAPI, not just as an extra property in the root-level HasAPI config. So you would have both loyal: true in the object and "loyal" in the array. Is this what you did, and it’s still not working, or did you just have it in one place?

@nick

Ah, you’re right, I didn’t have it listed in the top section. I’ve already switched everything back to .team, but I’ll keep that in mind for any future levels that need API protection.

I’m hoping that someone can walk me through this. So I’m supposed to select items from an array that have the attribute team = “ogres”, then have my guy attack the spies.

It sounds like I would use a conditional statement:
if (soldiers && soldiers.team == “ogres”){
// enter command.
}

Is that on the right track or would I use some sort of loop?

Yes, that’s headed in the right direction, but you’ll need a loop in order to iterate through all of the units in the array.

I’ve got a working solution in JavaScript and I normally wouldn’t post the answer explicitly but there might be an issue in the lesson learned on this level, so code will have to be included:

var soldiers = this.getCombatants();
// Just killing the first soldier doesn't seem like a winning strategy...
// Click the "Guide" button if you need a hint on telling who is on what team.

for(var i = 0; i < soldiers.length; i++)
{
   var spy = soldiers[i];
   if(spy && spy.team == "ogres")
   {
       this.setTarget(spy);
       if(this.distanceTo(spy) > this.attackRange)
       {
           this.setAction("move");
       }
       else if(this.distanceTo(spy) < this.attackRange)
       {
           this.setAction("attack");
       }
   }
}

I had a difficult time understanding the order of how the loops are nested, and I got some good information from my dad who worked on this with me. Why would the loop use an else if condition to attack if the move condition is being satisfied? Is that programming convention, or applicable only in this case?

Yeah, this code segment should run once for each spy. But in the solution the condition seems to be tested repeatedly with each move, allowing the distance to be evaluated multiple times for each spy.

if(this.distanceTo(spy) > this.attackRange) 
{
     this.setAction("move");
}
else if(this.distanceTo(spy) < this.attackRange)
{
     this.setAction("attack");
}

Perhaps @nick might want to also weigh in, but my understanding of how the “chooseAction” method works is that chooseAction() will be evaluated in full once per frame of animation. If more than one “action” type statement (like “move” or “attack”) is made as part of that evaluation, the last action triggered is the one that “wins” and will be made for that frame.

This is true for any Code Combat level that uses chooseAction.

You can just use an else instead of an else if there. You can also move the if-else outside of the loop, since you just need to determine whether to move or attack it once per chooseAction() call, after you have found the spy target. And if you really want to, you can break out of the loop early, once you’ve found one spy to target.