I seem to fail at all fronts to protect Reynaldo

So I thought I was making progress but now I’m feeling defeated :frowning:

I got my peasants to get gold based on distance and value, and tried to make them ignore each others targets, but they still go after each others gold.

I made a function called pickTarget(friend) the plan was to return a warlock if present or closest enemy if not.
I wanted paladins and griffin-riders to be able to use it == fail :frowning:

the last thing that I’m stuck on is I can’t seem to make the paladins heal the weakest. They were at one point but not now. I don’t know what changed. :frowning:

Any help would be great.
on a side note I really like CodeCombat, but sometimes really with I had a debugger, or at least a log of what happened
thanks again

    # Your goal is to protect Reynaldo

    # Find the paladin with the lowest health.
def lowestHealthPaladin():
    lowestHealth = 99999
    lowestFriend = None
    friends = self.findFriends()
    for friend in friends:
        if friend.type != "paladin":
            continue
        if friend.health < lowestHealth and friend.health < friend.maxHealth:
            lowestHealth = friend.health
            lowestFriend = friend

    #self.say("mosthurt "+lowestFriend)
    return lowestFriend

def commandPaladin(paladin):
    # Heal the paladin with the lowest health using lowestHealthPaladin()
    # You can use paladin.canCast("heal") and command(paladin, "cast", "heal", target)
    # Paladins can also shield: command(paladin, "shield")
    mostHurt=lowestHealthPaladin()
    
    if mostHurt and mostHurt.health<400 and paladin.canCast("heal"):
        #self.say(paladin +" can heal "+mostHurt)
        self.command(paladin, "cast", "heal", mostHurt)
    
    if paladin.health < 99:
        self.command(paladin, "shield")
        
    else:
        target=pickTarget(paladin)
        if target:
            self.command(paladin, "attack", target)
            
        
        
def pickTarget(friend):
    
    warlocks=self.findByType("warlock")
    nearEnemy=friend.findNearest(friend.findEnemies())
    targetWarlock=friend.findNearest(warlocks)
    if targetWarlock:
        return targetWarlock
    else:
        if nearEnemy:    
            return nearEnemy


def commandFriends():
    # Command your friends.
    friends = self.findFriends()
    for friend in friends:
        if friend.type == "peasant":
            self.command(friend, "move", targetGold(friend).pos)
        elif friend.type == "griffin-rider":
            target=pickTarget(friend)
            if target:
                self.command(friend, "attack", target)
        
        elif friend.type == "paladin":
            commandPaladin(friend)

def targetGold(friend):
    items=friend.findItems()
    if items:
        
        peasants=self.findByType("peasant")
        activeMoneyTarget=[]
        for peasant in peasants:
            highValue=-1
            if not item in activeMoneyTarget:
                for item in items:
                    distanceToItemValue=(item.value/friend.distanceTo(item))
                    if distanceToItemValue>highValue:
                        target=item
                        highValue=distanceToItemValue
                activeMoneyTarget.append(target)
                return target
               
def summonGriffins():
    if self.costOf("griffin-rider")<self.gold:
        self.summon("griffin-rider")
               
    
loop:
    commandFriends()
    summonGriffins()

@TimmieT

First, please post your code with radiant, harmonious formatting (surrounding it in triple backticks) as explained in the FAQ

Second, please mention the level name – I remember a level about protecting a guy named Reynaldo, but I don’t recall the level name… that makes it harder to help you.


Now, about your code (as much as I can make it out):

Your summonGriffins function is missing essential things. It should be something like:

if self.gold >= costOf(...):       # comparison, colon
    self.summon(...)

The targetGold function is also flawed: you check item before you define it:

    [...]
    highValue=-1
    if not item in activeMoneyTarget:         # 'item' is not yet defined
        for item in items:                    # you only define 'item' here
            distanceToItemValue=(item.value/friend.distanceTo(item))
            [...]

You also don’t return anything if there is no coin at all (just in case).


The pickTarget function seems to be OK (although it could be optimized).


I let the others check the rest :wink:

Please read the FAQ. It teaches you how to format your code properly. I’ve done it for you this time, but learn how to do it yourself.


In commandPaladin:

I believe the attack command overrides the heal. You’d be better off just commanding them to shield where they are, healing when necessary.

In pickTarget:

There will almost always be a nearest warlock, so your minions will head straight for that warlock, before attacking the nearest enemy. Is this intentional?

sorry I had it in my head the name of the level was protect Reynaldo, but it is Grim Determination

i think the part about the griffins was there
def summonGriffins():
if self.costOf(“griffin-rider”)<self.gold:
self.summon(“griffin-rider”)

So I thought I was being clever and redid my get targetGold to use a global array, but found that won’t work with filbert
forum with nick about global

so my goal is to have any number of peasants collect money based on item.value/distanceToItemValue and also make sure they won’t go after the same items. I guess what i"m stuck on is if the item is very far away the loop might run many times, how would i keep my list of active targets? I’m sure it should be obvious, but I seem to be stuck.
Thanks again for the help :slight_smile:

Create your array in the main loop (or even before that) and pass it to your functions.

def findBestCoin(coins):             # note that you expect an input array
    # your code here
    # ...
    return bestCoin, coinsClaimed    # note that you return an item and an array

coins = self.findItems()             # array created before the main loop
                                     # so it's "global" (kind of...)
loop:
    # your code here
    # ...
    bestCoin, coinsClaimed = findBestCoin(coins)
    # note that you store the returned 'bestCoin' and 'coinsClaimed'
    # also note that you send the 'coins' array to the function

so I think I get it.
my main loop had commandFriends() and summonGriffins() in it.
First I added activeMoneyTarget[] to the top of my code.
then added activeMoneyTarget to these lines
commandFriends(activeMoneyTarget)
def commandFriends(activeMoneyTarget):
self.command(friend, “move”, targetGold(friend, activeMoneyTarget).pos)
def targetGold(friend, activeMoneyTarget):
It seems to work without needing to return the array.

does this seem like I’m over complicating things? Should I just be combining more into larger functions?

full code if you would like to take a look
btw I haven’t had a chance to look at the rest, so just look at the money
Thanks for the help. CodeCombat is good times

activeMoneyTarget=[]
# Your goal is to protect Reynaldo

# Find the paladin with the lowest health.
def lowestHealthPaladin():
    lowestHealth = 99999
    lowestFriend = None
    friends = self.findFriends()
    for friend in friends:
        if friend.type != "paladin":
            continue
        if friend.health < lowestHealth and friend.health < friend.maxHealth:
            lowestHealth = friend.health
            lowestFriend = friend

    #self.say("mosthurt "+lowestFriend)
    return lowestFriend

def commandPaladin(paladin):
    # Heal the paladin with the lowest health using lowestHealthPaladin()
    # You can use paladin.canCast("heal") and command(paladin, "cast", "heal", target)
    # Paladins can also shield: command(paladin, "shield")
    mostHurt=lowestHealthPaladin()
    
    if mostHurt and mostHurt.health<400 and paladin.canCast("heal"):
        #self.say(paladin +" can heal "+mostHurt)
        self.command(paladin, "cast", "heal", mostHurt)
    
    if paladin.health < 99:
        self.command(paladin, "shield")
        
    else:
        target=pickTarget(paladin)
        if target:
            self.command(paladin, "attack", target)
            
        
        
def pickTarget(friend):
    
    warlocks=self.findByType("warlock")
    nearEnemy=friend.findNearest(friend.findEnemies())
    targetWarlock=friend.findNearest(warlocks)
    if targetWarlock:
        return targetWarlock
    else:
        if nearEnemy:    
            return nearEnemy


def commandFriends(activeMoneyTarget):
    # Command your friends.
    friends = self.findFriends()
    for friend in friends:
        if friend.type == "peasant":
            self.command(friend, "move", targetGold(friend, activeMoneyTarget).pos)
        elif friend.type == "griffin-rider":
            target=pickTarget(friend)
            if target:
                self.command(friend, "attack", target)
        
        elif friend.type == "paladin":
            commandPaladin(friend)

def targetGold(friend, activeMoneyTarget):
    items=friend.findItems()
    if items:
        
        peasants=self.findByType("peasant")
        for peasant in peasants:
            highValue=-1
            if not item in activeMoneyTarget:
                for item in items:
                    distanceToItemValue=(item.value/friend.distanceTo(item))
                    if distanceToItemValue>highValue:
                        target=item
                        highValue=distanceToItemValue
                activeMoneyTarget.append(target)
                return target
               
def summonGriffins():
    if self.costOf("griffin-rider")<self.gold:
        self.summon("griffin-rider")
               
    
loop:
    commandFriends(activeMoneyTarget)
    summonGriffins()


So I think I found out why I couldn’t attack the warlocks, my glasses can only see 20m.

so I thought I would play some other levels to earn some gems. The first level I tried was zero sum and what I thought would work to get money makes my guy go all crazy. I thought it was almost the same code, but maybe item.value/self.distanceTo(item) is not the way to go.

loop:
    self.move(targetGold())

def targetGold():
    hiVal=-1
    items=self.findItems()
    for item in items:
        disVal=item.value/self.distanceTo(item)
        if disVal>hiVal:
            hiVal==disVal
            currentTarget=item
    return currentTarget.pos

This is simple to fix. Just add an if currentTarget: before you return the position.

not sure if this is what you mean, but like this he seems to go all random and crazy like.

loop:
    self.move(targetGold())

def targetGold():
    hiVal=-1
    items=self.findItems()
    for item in items:
        disVal=item.value/self.distanceTo(item)
        if disVal>hiVal:
            hiVal==disVal
            currentTarget=item
    if currentTarget: 
        return currentTarget.pos
    

Define “all random and crazy like”. I’m afraid I don’t understand what you mean.

Try making the self.move part of the function, like this:

if currentTarget:
    self.move(currentTarget.pos)

I tried that, but the target still jumps all around the map. Sometimes he will get high value right next to him and other times he will get low value all the way across the map passing many high value items. He will also change directions, for unknown reasons. Unknown to me at least.
Thanks again for the help.
BTW I finally beat Grim Determination, didn’t even lose any paladins

This doesn’t seem right :bug:

even when you told me the answer I still had to stare at it until my brain oozed from my ears. Thanks for the help :smile:
hiVal=disVal

By the way, using value/distance is a very dangerous weight and can cause problems when distance “gets close to” 0. I strongly suggest that you get in the habit of using value/(1+distance) instead. Given that the coins get picked up automatically when “too close” it is not likely you will hit a division by zero error, but when you use the same weight with distances to enemies, you can get some strange results. The good news is that 1/(1+distance) and 1/distance really produces the same rank ordering, so the change is one behind the scenes and would not effect which coin/enemy/etc. gets selected.