How to prefer one enemy over another if preferred is around?


#1

Just as the topic suggests, I am trying to figure out how to pick one target over another if my preferred target is available. I am currently trying to play in JavaScript.

So far I am gathering the enemies I can see with -

var enemies = this.findEnemies();

I am trying to find out how to pick out the particular enemies out of the enemies found in the variable “enemies”.
Then if the preferred enemy is seen within enemies then attack that one.

As I am typing this out I’m getting ideas of how I might do this, but I am not sure how to code it.

sudo code:

loop {

var enemies = all enemies in sight;
var enemy = find closest of enemies

if (enemy.type ‘caster’ is within enemies) {
attack enemy.type ‘caster’;
}
else if (enemy.type ‘ranger’ is within enemies) {
attack enemy.type ‘ranger’;
}
else {
attack enemy;
}

}


#2

If you have good enough glasses you could use findByType, which allows you to find a unit by type. It also accepts a second argument to filter the enemies from, like this.findByType('caster', enemiesInRange)

Then you can have something like this:

// *pseudo* code ish
loop:
    enemies = findEnemies()
    enemiesInRange = somehowFindInRange(10, enemies) # look in sample code
    caster = findNearest(findByType("caster", enemiesInRnage))
    ranger = findNearest(findByType("ranger", enemiesInRange))
    enemy = findNearest(enemies) # just in case if there are no enemies in range
    if caster: attack(caster)
    elif ranger: attack(ranger)
    elif enemy: attack(enemy)
    # alternatively
    # target = caster or ranger or enemy or None
    # if target: attack(target)

If you don’t have findByType (I only assume that because you didn’t use findByType in your code) but you know how functions work, you could kind of define your own.

`customFindByType()`
// though you'll need to always pass in an array of units into
this.customFindByType = function (type, units) {
    units = units || []
    if (!units.length) return null
    var newUnits = []
    // you didn't try to use for loops in your "code" either, so I don't know if you know how these work
    for (var i = 0, len = units.length; i < len; i++) {
        var unit = units[i]
        if (unit.type === type) newUnits.push(unit) // just adds a item to the "right" side of an array
    }
    return newUnits
}
// use it like `this.customFindByType("caster", this.findEnemies())  
Sample Code
// might not work

// i don't like using semicolons
// and it so happens there's a huge debate over this...
// you can either add semicolons to the code,
// or add this line so jshint can ignore my lack of semicolons:
// jshint asi:true

// this is just another method in which it takes in two parameters, range and
// units where range is the maximum distance and units is an array of units
this.findInRange = function (range, units) {

    // if `units` is falsy (ie tests false), then we will have an empty array
    // it doesn't check if `units` is an array, though it would look something
    // like this: Array.isArray(units) && units || []
    units = units || []

    // if there is nothing in the `units` array, it returns an empty array and
    // exits the function
    // i think may have also been a problem; it originally it returned
    // `undefined`
    if (!units.length) return []

    // creates a temporary variable to store units into
    var newUnits = []

    // for-loop
    // initially let `i` be 0 and `len` be units.length
    // loop until `i` is less than `len`
    // after one "loop" has completed, increment `i` by 1
    for (var i = 0, len = units.length; i < len; i++) {
        var unit = units[i]
        if (this.distanceTo(unit) <= range) {
            newUnits.push(unit)
        }
    }

    // returns the temporary array of units that are in range returning a value
    // allows you to do this
    return newUnits
}

// chooses best target from an array of enemies
this.chooseTarget = function (enemies) {
    enemies = enemies || []
    var casters = this.findByType("caster", enemies)
    var rangers = this.findByType("ranger", enemies)
    var caster = this.findNearest(casters)
    var ranger = this.findNearest(rangers)
    var enemy = this.findNearest(enemies)
    // short circuit logic, basically if left side is truthy it returns the
    // left side; else the right
    var target = caster || ranger || enemy || null
    return target
}

loop {
    var enemies = this.findEnemies()
    var enemiesInRange = this.findInRange(10, enemies)
    var enemy = this.findNearest(enemies)
    var target = this.chooseTarget(enemiesInRange) || enemy || null

    if (target) {
        this.attack(target)
    }
}

You also might want to use the triple backticks (```) to wrap your code. It’s cleaner.


#3

Thank you for giving me some pseudo code of your own to help me out.

I actually now have the glasses that let me use the “findByType” so I can go with that bit of code. As for the functions, I wasn’t aware of those. I am still really new to scripting in general. Javascript is my first language I’m trying to learn.

If I am understanding it properly, it looks like you are first creating your own command “this.customFindByType” and giving it what kind of purpose it will have with the “function (parameters a, b) { /code to run here/ }”. Would I be correct in thinking that the [] are referring to a string if one is available?

Unfortunately I feel like I’m just playing a guessing game when I’m looking at the code that is given as examples when you hover over a command you have available through your gear. This is mostly why I have come to the “Discourse” area of CodeCombat for help.

In regards of the “for loops” I have not used those because I don’t really know how they work.

Thank you for your help, I am trying to make sense of your example code and make it work with javascript since it has various errors when I put it in as you have it. Was this by chance written in coffee script or something like it?

Also, what is the "newUnits really doing and how is it being used in this case when one of the variables is called “units”? I’m having a hard time feeling like I understand what those parts are doing in the code. That is unless if the newUnits are the units found within the range you tell it which then repopulate the previous string of units only with the string found in the newUnits???

Again, thank you for your help and any future help you may provide.


#4

You’ll learn for-loops and how to create your own functions in the Mountain Campaign.

Pseudo code was mostly written in python, without the self keyword Python uses.

[] is the shorthand way to create an empty array. I use it twice in the customFindByType() function:

// if units is falsy, assign it to an empty array
units = units || []
// assign newUnits to an empty array, will add items into the array later
var newUnits = []

If you wanted to create an array with items in it, you can do var nums = [0, 1, 2, 3], etc.

You’re mostly right. Both newUnits and units are arrays. You are right that newUnits are the units found in range specified by the range parameter. They aren’t strings, though. The reason why there is a temporary array is so that the function can “return” an array with the units in range, not to repopulate the units array. (Perhaps it could have been named better, ie. unitsInRange)

I think the main error you were having with my code is that there were no semicolons. Whoops. (I’ve updated the sample code above, maybe it should work.) I don’t know if the comments will help or not though; I’m not too good at explaining things.


#5

Tested on “Backwoods Brawl”

I’m not sure why it isn’t working for me. It might actually be CodeCombat itself at the earlier levels stopping me from creating my own functions. When I add the “jshint asi:true” it tells me “Unexpected Identifier”. Then, I tried adding in the semicolons where I thought they should go and it then lets my character attack. Unfortunately, when I change the target types to look for to be “thrower” instead of “ranger” it doesn’t seem to actually work even though it is supposed to be looking for the enemy type from my understanding.

I’m not sure how to paste my code here like you have done yours, if you can tell me how then I can show you the changes I made so that it doesn’t complain as much and at least attacks something. Otherwise, I will be trying to unlock new gear slowly to have more pre-made codes that I can use provided through the gear.

Your help has made it easier to understand how functions are made and how to use them in some ways, as well as understanding what the for loops actually do. For this, I am grateful.


#6

Interesting. I’ve tested the code, and it seems to work fine for me. I dunno.

Wrap you code with triple backticks (```) as shown here, for the pretty code formatting.

And for the “Unexpected Identifier”, the jshint asi:true should’ve been a comment. Should have made it more clear. (Though I guess you fixed it on your own.)


#7

I went back to change the jshint asi:true into a comment and that fixed that issue. It is vary nice to not have those triangles showing up telling me to put those in. lol

Anyways, this is how I have the code right now that for some reason isn’t wanting to work for me.

What code I have trying to run.
// jshint asi:true

this.findInRange = function (range, units) {
    units = units || []
    if (!units.length) return []
    var newUnits = []
    for (var i = 0, len = units.length; i < len; i++) {
        var unit = units[i]
        if (this.distanceTo(unit) <= range) {
            newUnits.push(unit)
        }
    }
    return newUnits
}

this.chooseTarget = function (enemies) {
    enemies = enemies || []
    // left in the caster commented out for future higher priority mobs
    //var casters = this.findByType("caster", enemies);
    // changed the ranger to fit this battle as I am looking for throwers
    var throwers = this.findByType("thrower", enemies)
    //var caster = this.findNearest(casters)
    var thrower = this.findNearest(throwers)
    var enemy = this.findNearest(enemies)
    var target = /*caster || */thrower || enemy || null
    return target
}

loop {
    var enemies = this.findEnemies()
    var enemiesInRange = this.findInRange(10, enemies)
    var enemy = this.findNearest(enemies)
    var target = this.chooseTarget(enemiesInRange) || enemy || null

    if (target) {
        this.attack(target)
    }
}

This second attempt with using some of your suggested code, but in a different place. It helped me to survive a bit longer (still need better gear) and it actually did what I wanted it to do by attacking the throwers if there are any otherwise attacking everything else. ^-^

Another attempt without functions.
loop {
    var throwers = this.findByType("thrower")
    var thrower = this.findNearest(throwers)
    var enemies = this.findEnemies()
    var enemy = this.findNearest(enemies)
    var target = thrower || enemy || null

    if (target) {
            if (this.isReady("cleave")) {
                this.cleave(enemy);
            }
            else {
                this.attack(target)
            }
        }
    }

Thank you for linking me to the FAQ, I really should have seen that but I guess I was to focused on asking for help. <_<;


#8

Just realized throwers have a range of 25m, so if the hero is killing some other enemy not within 10 meters, that may have been the issue.

Also, unless you have boots with the move property, there’s no easy way for your hero to change their target after they have “chosen” one.

But your solution works fine too, I suppose. :smile: .