Question about "Pesky Yaks"


#1

Hello again!

I’ve been struggling with those pesky yaks for a while and when I finally discovered my problem was not understanding the difference between using = and == and the code actually started working, a new, more interesting problem appeared. Namely, some poor yak is inevitably caught in the crossfire when the ogres intentionally or unintentionally use them as meat shields to protect them from Narias bullets.

So my question is: how can I tell the ranger not to shoot when there’s an innocent animal in the line of fire?

I tried isPathClear but as expected that didn’t really do the job.

(The easy solution to finish the level would be switching to some melee-type but then I need better armor and I’m still curious about the problem)


#2

isPathClear checks if obstacles are in the way. Yaks are no obstacles, so that won’t work.


How familiar are you with vectors? They can help you a lot here. As these solutions need a bit more mathematical knowledge I will not formulate them here until requested.


Possible solution:
Imagine a plus placed on your hero. This cross is obviously dividing the field in four areas. The current enemy has to be in one of these quarters. Every Yak that is not in the same quarter can’t be blocking the enemy (not entirely true, see below). So we can remove them from the list.
Now we place a plus on the enemy. Every Yak that is not in the same quarter as your hero can’t block the way.
In the best case there is no Yak left and you can happily fire away.
This won’t always be the case (sadly).
Now comes the math part.
Leta be the distance from the hero to the yak, b the distance from the enemy to the yak and c be the distance between the hero and the enemy. Everyone knows that a^2+b^2=c^2. We are more interested in a+b>=c. If a>x*c and b>x*c then we are safe. If one of the two is false, the yak is either to close to the hero, the enemy or both. But which value to give tox? We can’t go below 0.5, because the Yak could stand exactly between us. We also can’t go above 1/sqrt(2) (we sorted that out earlier, some mathematical trick). I can’t give you an exact value here, just try some out. The smaller the value, the less you care about the Yaks.


var self = this;

/*
 *   Implement the this.removeByType-Method, we're going to need it! And it is the task of the level, so you will have to do it yourself.
 */

this.findQuarter = function(obj, reference){
    var quarter = 0;
    if (obj.pos.x > reference.pos.x)
        quarter+=1;
    if (obj.pos.y > reference.pos.y)
         quarter+=2;
    return quarter;
    // Can you tell me which quarter got which number? 
};


//Javascript
loop{
    var possibleYaks = [];
    var yaks = this.findByType("sand-yak");
    var enemy = this.findNearest(this.removeByType(this.findEnemies(), "sand-yak"));
    
    if(!enemy){
        continue; //There might be no enemy, nothing to do here.
    }
    
    var enemyForHeroQuarter = this.findQuarter (enemy, this);
    var heroForEnemyQuarter = this.findQuarter (this, enemy);
    var c = this.distanceTo (enemy);
    var x = 0.6; //tweak this!
    
    for (var i = 0; i < yaks.length; i++){
        if (enemyForHeroQuarter == this.findQuarter (yaks [i], this) && heroForEnemyQuarter == this.findQuarter ( yaks[i], enemy))
        possibleYaks.push(yaks[i]);
    }
    
    var canShoot = true;
    for (var j=0; j < possibleYaks.length; j++){
        if ( this.distanceTo (possibleYaks[j]) <= x*c || enemy.distanceTo (possibleYaks[j] ) <= x*c){
            canShoot=false;
            break;
        }
    }
    
    if (canShoot)
        this.attack (enemy);
    else
        this.say ("Too dangerous, I could hit a Yak!");
}

Completely typed on a smartphone, please ignore typos and syntax-errors
EDIT: Revised on computer, code should be correct now


#3

Such beautiful logic! Thank you for the in-depth reply. I will try it out when I come home.
I suppose I would also need to instruct the ranger to reposition a little because enemy throwers can shoot through the poor yaks. Unfair, but shouldn’t be much of a problem I guess.


#4

What you do when you find out that you can’t hit is entirely up to you. Moving is usually advised.


I now have the possibility to test the code, I will edit this post as soon as I can give a more precise answer.
EDIT: First posts code is now corrected and actually works (previous versions had some serious bugs, I blame the small screen). You will have to change something in the last part though, simply saying that you can’t do anything won’t keep you alive…


#5

I managed to beat the level with something similar to your code. I’m doing it in python so it was a good exercise in learning the difference between the languages, however I’m afraid I probably made some (or many) mistakes in the process. Especially since yaks still occasionally get hit, leading to subpar winrate.

If it’s not too much trouble to look at:

def findQuarter(obj, reference):
    quarter = 0
    if obj.pos.x > reference.pos.x:
        quarter+=1
    if obj.pos.y > reference.pos.y:
         quarter+=2
    return quarter

loop:
    possibleYaks = []
    yaks = self.findByType("sand-yak")
    enemy = self.findNearest(removeByType(self.findEnemies(), "sand-yak"))
    if not enemy:
        continue
    
    enemyForHeroQuarter = findQuarter(enemy, self)
    heroForEnemyQuarter = findQuarter(self, enemy)
    c = self.distanceTo(enemy)
    x = 0.8
    
    for yak in yaks:
        if enemyForHeroQuarter == findQuarter(yak, self) and heroForEnemyQuarter == findQuarter(yak, enemy):
            possibleYaks.append(yak)
    canShoot = True
    for yak in possibleYaks:
        if self.distanceTo(yak) <= x*c and enemy.distanceTo(yak) <= x*c:
            canShoot = False
            break
    if canShoot:
        self.attack(enemy)

The removeByType is cut, but it’s there and working. My guess would be the “and” part when checking the distances because I didn’t feel confident about it but I don’t know how else to write…

Thank you again for all the great help.


#6

That and should be an or.

At the moment you say IF the yak is to close to me AND the enemy. That is of course not what you want.

Also you get a problem if a yak is on the border just outside. The position is outside the quarters, but the hitbox reaches into the area, then your projectile hits the yak. You can solve this in the findQuarter-function, just requires some creative thinking :smiley: .


Python gone crazy

I wasn’t sure which language, so Javascript was more chosen at random.
Python allows the following (though it’s not the recommended code :wink: )

#Everything before the x=0.8

#To shorten the following line a bit
efhq = enemyForHeroQuarter
hfeq = heroForEnemyQuarter
sDist = self.distanceTo #Jup, storing a function in a variable here
eDist = enemy.distanceTo
fq = findQuarter
canShoot = len([yak for yak in self.findByType("sand-yak") if efhq==fq(yak, self) and hfeq==fq(yak,enemy) and (sDist(yak)<=x*c or eDist(yak)<=x*c)]) == 0

if canShoot:
    self.attack(enemy)