Priority targeting


#1

Disclaimer, this isn’t aimed at beginners, but to intermediate / advanced CodeCombaters.
This post is inspired by the Notepad idea. Here is one of my CodeCombat Notepad functions :slight_smile:

In repeatable levels, I’ve been improving from the bottom up my initial code. As new enemy types are to be considered, I’ve been having a lot of

if ( enemy.type == "munckin" ) {

} else if ( enemy.type == "thrower" ){

} else if ( ... ) {

} else if ( ... ) { 
etc...

As complexity goes up, it’s rather messy and clunky. So I decided to try and create a function to sort the prioritizing problem. Here is my shot at it.

var arrEnemyTypes = ["brawler","shaman","fangrider","ogre","thrower","scout","munchkin"];
// Types from highest priority to lowest.
    
function findBestTarget(This){
    var myTarget = null ;
    for( var iType = 0 ; iType < arrEnemyTypes.length ; iType++ ){
        // Checking each group of enemy, from top priority to bottom priority.
        var currGroup = This.findByType( arrEnemyTypes[iType] );
        if( myTarget ){ // If I found a priority target _
            break;    // _ no need to go further in lower priorities.
        }
        if( currGroup[0] ) { // If this group isn't empty
            
           // ****** Selection Algorithm *********
           // For loop within the currGroup
           // that sets myTarget if a specific criterium is met
           // **************************************

        } // if currGroup[0]
    } // for iType
    return myTarget;
}

In the example below, I’m playing a PotionMaster with Lightning Twig with a stream of unit coming my way. I want to throw bolts through the entire stream, and selecting a close target could tilt 45°, rendering my deadly weapon useless.
Thus my Selection Algorithm is selecting the farthest highpriority enemy away from me. I also check if it is not out of range, and if it isn’t too close. You can swap in any selection algorithm you want (like selecting closest, or healthiest, or fastest or whatever criterium you want).

    if( currGroup[0] ) { // If this group isn't empty
        var maxiDist = 0; // loop initialization
        for( var iUnit = 0 ; iUnit < currGroup.length ; iUnit++){
            // Loop to select the criterium you want.
            var currUnit = currGroup[iUnit];
            var currDist = This.distanceTo( currUnit ) ;
            if( currDist > This.attackRange  ||  currDist < 10 ){
                // if out of range, or too close _
                continue; // _ don't select this unit.
            }
            if(currDist>maxiDist){
                // Selecting farthest unit
                maxiDist = currDist;
                myTarget = currUnit;
            }
        } // for iUnit
    } // if currGroup

I’d be glad if someone told me “you can improve this here”. I’d love if this function could be useful to somebody.


#2

I don’t know if this helps… but it helped with my warrior types…

I take all enemies and group them manually… then pick the target from what I know of all enemies. For instance, a munchkin within attack range is more important than a thrower outside of attack range. This is because the munchkin can damage me while I’m moving to attack the thrower. Of course, if nothing is in attack range, I move toward missile types until I can hit them. This structure has helped me:

var i, type,                 // Counter
    foes, foe, nFoes,   // For the enemy loop
    grpFoes= {};        // The grouping object

loop {
// Loop through enemies and add them to group.
    foes = this.findEnemies();
    nFoe = foes.length;
    grpFoes = {};
    for (i = 0; i < nFoes; i++) {
        foe = foes[i];
        type = foe.type;
    // If no group for this type, create it.
        if (!grpFoes[type])
            grpFoes[type] = [];
    // Add foe to the group.
        grpFoes[type].push(foe);
    }

// Prioritze according to group....

        /* ...Targetting algorithm here... */
}

Using throttling keeps instructions low, and largely meets my needs. This allows me to get info on the enemy I’d like from each group (i.e farthest thrower vs closest munchkin) and weigh each independently for threat analysis. Since you are using a wiz, this may not necessarily apply to you, but thought I would throw it out there.


Question about sort by type
#3

I like the bottom-up approche, instead of my top-down approche with the help of the push method. I do think it’s valuable in terms of processing efficiency. In fact I knew the

for( var iType = 0 ; iType < arrEnemyTypes.length ; iType++ ){
    // Checking each group of enemy, from top priority to bottom priority.
    var currGroup = This.findByType( arrEnemyTypes[iType] );

wasn’t optimal, as it would be called most of the time with no enemies from that type were present. Also I didn’t have an easy possibility to prevent unecessary computation with the trick “don’t compute stuff if they are already computed”.

Example you gave in another thread
loop {
// New placeholder variable
    var numCoins;  
    var allCoins = this.findByType("coin");
// Wrap processing in a condition that runs when only changed
    if (numCoins !== allCoins.length) {
    // Has changed: Record the new value
        numCoins = allCoins.length;
        nextCoin = findNextCoin(this);
    }
// Continue moving because nothing has changed.
    this.move(nextCoin.pos);
}

Thanks for the structure. I might use it from time to time.


#4

I’m coming back on your structure to point out a problem of syntax I can’t circumvent on my own.

type = foe.type;
if (!grpFoes[type])

I’ve been trying to use more and more these kind of structure (gather all datas in an object and access all subItem via their property name). For enemy ordering, but also friends and spells.

I found an annoying flaw in its use however : if a subObject has a name with an hyphen ( - ) (ex :

type = "griffin-rider" // or, drain-life, raise-dead, forest-archer...

the use of

grpFoes[type] // ( equivalently (?)  grpFoes.type )

isn’t working.

I know it’s intended, and it feels normal to me JS works that way. However, is there a way to solve this without removing every hyphen in the game strings ? (which obviously I can’t do and don’t want to ask for it to be modified).

**TLDR** Working code highlighting my problem
var summonList={
    soldier:       5,
    archer:        2,
    griffinrider : 3, // Change this line with griffin-rider : 3,
    artillery:     0
};

for (var i in summonList){
    this.say(i+summonList[i]);
}
Solution I fell back to : back to Arrays, byebye Objects ?
var typeList=["soldier","archer","griffin-rider","artillery"];
var summonList= [5,2,3,0] ; // 5 soldiers, 2 archers, 3 griffin riders

for (var i in summonList){
    this.say(typeList[i]+summonList[i]);
}

#5

It should… In fact, that is specifically what it is for…

objName[property] will allow for numbers are property names, complex multiword strings and a whole bunch of other nonsense.

There is one problem… griffinrider: 3 should be "griffin-rider": 3. Anytime you need to use object notation to build an object, if the property name is either not a string or more complex string than a simple word, you must wrap the property name in quotes.

Example:

obj = {
    name: value,               // Accessed or set by: obj.name OR obj[name] 
    "hyphen-ated": value,      // Access by only: obj["hyphen-ated"]
}
obj[2] = value;                // Even integers may be used.

If the property is variable, as in my structure, you must always use array notation:

propName = "hypen-ated";
obj[propName]

The above means that it should be:

var summonList={
    soldier:       5,
    archer:        2,
    "griffin-rider" : 3, 
    artillery:     0
};

This often leads many devs to always use quotes for property names, no matter what. In truth, it often creates more flaws for complex structures than it is worth, so knowing the above is valuable for many situations. Now, when you have access to native APIs, a lot of this goes away with Object.getOwnPropertyNames and its variants.


#6

Alright, I’ll give it a another try. I thought I tried the quoted notation for properties and something should have gone wrong so I scratched this idea. Maybe I’ve been confused with the game advicing me to go with the dot notation of the property I’ve been using. I’m testing on my side to search what lead me to think it wouldn’t work.

That’s what I did, then the ‘griffin-rider’ came up :frowning: .

I think I found the weird thing that made me blame the hyphen. Message received : I only use quoted text for my properties. Works indeed as intended. However, I can’t understand yet why this behaviour :

Dummy Code used in ZeroSums (for debug instead of say)
var summonList={
    "soldier":       5,
    "archer":        2,
    "griffin-rider": 3, 
    "artillery":     0
};

var requestedGold=0 ;
for(var i in summonList) {
    this.debug(i);
    this.debug(summonList[i]);
    this.debug(summonList.i);   // When i="soldier" summonList.i is undefined.
    this.debug(summonList.soldier); // But this line does work. 
    requestedGold += summonList[i]*this.costOf(i);
}

Using the variable i is compatible with everything except the dot notation. And I thought it was because of the hyphen. Actually, it’s because the variable i must be a quoted string that :

summonList.soldier                 is working
i = soldier ; summonList.i         is working
i = "soldier" ; summonList.i       isn't working.

So I blamed the hyphen for preventing me to access properties, but actually it seems it’s the dot notation that isn’t compatible with the “all properties should be quoted” ?

I’m so used to the dot notation in JS now (that I learnt only this month), and I was glad I could get rid of square brackets when using properties of objects. Do I need to specificatlly use square brackets in this instance ? You answered in you last (edited ?) post :

obj = {
    name: value,               // Accessed or set by: obj.name OR obj[name] 
    "hyphen-ated": value,      // Access by only: obj["hyphen-ated"]
}
obj[2] = value;                // Even integers may be used.

Thanks, once again, for pulling me up.


#7

summonList.i is equivalent to summonList["i"]–dot notation doesn’t pursue variable references.


#8

I was trying to improve my code for priority targeting, but I’m not sure if my code is right. Could someone please review it and confirm if the two versions should have the same result?

Old code:

    # define three separate lists for priorities
    headhunters = self.findByType("headhunter", enemies)
    shamans = self.findByType("shaman", enemies)
    throwers = self.findByType("thrower", enemies)
    
    for friend in friends:
        # check the first list
        if len(headhunters) > 0:
            enemy = friend.findNearest(headhunters)
        # then the second list
        elif len(shamans) > 0:
            enemy = friend.findNearest(shamans)
        # then the third list
        elif len(throwers) > 0:
            enemy = friend.findNearest(throwers)
        # if no priority target found...
        else:
            enemy = self.findNearest(enemies)

New code:

    # define a super-list containing three sublists
    priorities = [self.findByType(type, enemies) for type in ["headhunter", "shaman", "thrower"] ]
    
    for friend in friends:
        enemy = None
        # iterate through the sublists
        for group in priorities:
            enemy = friend.findNearest(group)
        # if priority enemy found
            if enemy:
                break
        # if no priority target found...
        if not enemy:
            enemy = self.findNearest(enemies)

Thanks!