Marauder: complicated way is only slightly better


#1

In marauder level, an elaborate way of collecting the coins is suggested:

while True:
    coin = hero.findNearestItem()
    # Solange eine Münze existiert:
    while coin:
        # Geh zur Münze
        hero.moveXY(coin.pos.x, coin.pos.y)
        # Weise die Münzen-Variable dem nächsten Item zu.
        coin = hero.findNearestItem()

I can’t help but notice this used to be done in a simpler way in previous levels:

while True:
    coin = hero.findNearestItem()
    if coin:
        hero.moveXY(coin.pos.x, coin.pos.y)

But the hero keeps missing a few coins (while level goals are still achieved).
Since this runs in a while True loop, the variable should be updated after each run anyways.

The same goes for attacking the enemies:

while True:
    enemy = hero.findNearestEnemy()
    if enemy:
        # Solange die Gesundheit des Gegners größer als 0 ist.
        while enemy.health > 0:
            # Attackiere Gegner
            hero.attack(enemy)
        pass

In previous levels, the enemies’ health was never checked, but the hero did keep attacking until they were dead anyways:

while True:
    enemy = hero.findNearestEnemy()
    if enemy:
        hero.attack(enemy)

So why were these tasks (collecting coins, killing enemies) addressed in different ways?


#2

Sometimes, if you have different ways of killing an enemy, (e.g. chain-lightning, bash, attack), then you would need to check that the enemy isn’t dead, because there is a chance that while you are running through your if loop, you have already killed the enemy using one method, but the loop continues.

For example, if you use the following code to kill a munchkin:

while True:
    enemy = hero.findNearestEnemy()
    if enemy:
        if hero.canCast("chain-lightning", enemy):
            hero.cast("chain-lightning", enemy)
        elif hero.isReady("bash"):
            hero.bash(enemy)
        else:
            hero.attack(enemy)

If you used the above code to kill a munchkin, after the hero chain-lightnings the munchkin, it would say “But it’s dead!” and the code stops going. However, if you check that the enemy.health > 0 in each of the if and elif and else, then the hero would not say “But it’s dead!” and stop working.


#3

Wouldn’t you rather do that by putting the whole if/elif/else block in a while enemy.health > 0 statement instead of after each part of the block?


#4

That wouldn’t work. Because the while statement checks the condition BEFORE it goes into the if loops, so after you kill the enemy, the while condition was already checked, so it wouldn’t work.


#5

Just though of this, I’ll try this when ill get home

def action(acname, target, atype = "none"):
  if target.health > 0:
    if atype == "spell":
      if hero.canCast(acname, target):
        hero.cast(acname, target)
    elif atype == "skill":
      if hero.isReady(acname):
        hero.acname(target)
    elif acname == "attack":
      hero.attack(target)
    else: hero.say("unexpected error")
    
while True:
enemy = hero.findNearestEnemy()
if enemy:
  action("chain-lightning", enemy, "spell")
  action("bash", enemy, "skill")
  action("attack", enemy)

#6

This code will pass without the so hated “But it’s dead!” message. The ‘if’ will execute, the ‘elif’ and ‘else’ not. Then in the new execution of the loop there will be another enemy.
The problem will arise if you substitute elif with if

        if hero.isReady("bash"):
            hero.bash(enemy)

or your soldiers had killed the targeted enemy.


#7

I also will try it. Cool idea!


#8

Has anyone here got a reaction to my first question though? Just asking.
So the point of the while condition here is not to make sure hero keeps attacking the enemy until it’s dead (because the hero.attack() function achieves that by itself), but to make sure it stops attacking when it’s dead?
If so, that also doesn’t make sense, because hero.attack() achieves both (saying “But it’s dead!” if applicable - but I also fail to see what the problem is with that) without the while condition.


#9

I get an error when I try https://codecombat.com/play/level/plunderer. Where is the level?


#10

Sorry about that - it’s Marauder. corrected the title now. I was only translating the German word with a different term.
However, you’ve just shown me a great way of referencing the levels, I wasn’t aware they had these simple URLs!


#11

Hmm, maybe I was wrong about that. Possibly it looked like that in previous levels only because the attack() function was always in some kind of loop.
In Master of names and previous levels, hero.attack(enemy) only makes hero strike once, even if enemy is still alive after that.
That’s why it takes a second iteration if the weapon isn’t powerful enough:

enemy1 = hero.findNearestEnemy()
hero.attack(enemy1)
hero.attack(enemy1)

This makes it unnecessary to have a separate device for making the attacks stop - it’ll do so after one attack. If the code orders an attack on a dead opponent by virtue of a loop, that is abandoned with “but it’s dead!”. What would it say in a console? Does it produce an error?
It also makes a separate device necessary for making the attack continue if the enemy is still alive, and we’ve been doing this with loops all along. Maybe I need to take a closer look at all those previous levels again.


#12

No message “but it’s dead!” in the console with the wrong code on the " Master of names" picture. You can display custom messages in the console before or after the intended action with:

            print("chain-lightning " + enemy.id)
            print("bash " + enemy.id)
            print("attack " + enemy.id)
chain-lightning Kraster
attack Kraster
attack Trog
attack Trog
attack Thabt

#13

There are 4 possibilities: (e+, c+), (e+, c-), (e-, c+), (e-, c-)
Code with enemy priority runs for 34.3s in my case:

while True:
   coin = hero.findNearestItem()
   enemy = hero.findNearestEnemy()
   if enemy:                   
       if coin:
           # hero attacks enemy # (e+, c+)
       else:
           # hero attacks enemy # (e+, c-)
   elif coin:
       # hero moves to coin     # (e-, c+)
   else:
       # pass                   # (e-, c-)

Code with coin priority runs for 34.1s in my case:

while True:
    coin = hero.findNearestItem()
    enemy = hero.findNearestEnemy()
    
    if coin:
        if enemy:
            # hero moves to coin  # (e+, c+)  
        else:
            # hero moves to coin  # (e-, c+)
    elif enemy:
        # hero attacks enemy      # (e+, c-)
    else:
        # pass                    # (e-, c-)  

Original Code runs for 34.1s in my case:

I can be wrong, but imo there is no gain in this technique in this level and if the hero is a accomplishing an action in a loop inside the main loop the allied minions will freeze.


#14

Okay, but I still haven’t reached a conclusive answer with my topic. Maybe my question wasn’t concise enough:
When hero attacks enemies we want to make sure of 2 things:

  1. He keeps attacking until the enemy’s health is < 1

  2. He stops trying to attack once the enemy’s health goes < 1

In Marauder level, a while condition is used to check the enemy’s health while attacking.
Question:
Why wasn’t this used in previous levels (but both of the above goals were achieved regardless)?
Why use it now?