Infinite loop error (Python)

Programming is easier when the code is organized… although sometimes that means starting over from scratch.


Readability Remodeling (using your code)
# insert a blank line between each function

# added this function, as it appears several times throughout your original code.
# bookmark link: https://discourse.codecombat.com/t/circular-soldier-patrol/12138/4
# example pseudocode usage:
# if enemy is sighted and within attack range, then command units to attack enemy
# else do circularPatrol(units)
def circularPatrol(units, targetPos=hero.pos, clockwise = False, timeScale=0.3):
    radius = 2 + 2.5 * Math.log1p(len(units)) # you might want to fiddle with this.
    angleStep = Math.PI * 2 / len(units)
    for index, unit in enumerate(units):
        angle = angleStep * index + (-1)**(clockwise+1) * hero.time * timeScale
        hero.command(unit, "move", {
            'x': targetPos.x + radius * Math.cos(angle),
            'y': targetPos.y + radius * Math.sin(angle)
        })

def castinvisibility():
    if hero.canCast("invisibility"):
        hero.cast("invisibility", hero)

# add descriptions to a function if you think you are going to forget what it does.
# example description for flag():
# "If I place a green flag on the field, the hero will run there and pick it up"
def flag():
    flag = hero.findFlag("green")
    if flag:
        hero.pickUpFlag(flag)

# another example description:
# This commands all friendly archers.
# They attack enemies that get too close to the hero.
# Otherwise, they patrol around the hero.
def commandArcher(): # maybe rename this to commandArchers()? Since we are dealing with more than 1 archer at a time
    friends = hero.findFriends()
    archers = (friend for friend in friends if friend.type == "archer")
    
    # new code added:
    enemy = hero.findNearestEnemy()
    if enemy and hero.distanceTo(enemy) < 30:
        for archer in archers:
            hero.command(
                archer,
                # notice how the arguments to this function are each on their own separate line?
                "attack",
                archer.findNearestEnemy() # so archers all won't target a single enemy and perform overkill (which wastes time)
            )
    else:
        circularPatrol(
            units = archers,
            targetPos = hero.pos,
            clockwise = True,
            timeScale = 0.40,
        ) # take note of this closing ')' -- it is on the same indentation level as "circularPatrol("
    
    return      # everything else written in the function below this line will never get executed ####################################################
    hey = "this doesn't execute"
    
    # this old code is redundant; you can delete it.
    patrolCenter = {'x': hero.pos.x, 'y': hero.pos.y}
    patrolTimeScale = 0.40
    archerrs = (friend for friend in friends if friend.type == "archer")
    radius = 2 + 2.5 * Math.log1p(len(archers)) 
    angleStep = Math.PI * 2 / len(archers)
    for index, archer in enumerate(archers):
        enemy = archer.findNearestEnemy()
        if enemy and archer.distanceTo(enemy) <= archer.attackRange:
            hero.command(archer, "attack", enemy)
        else:
            hero.command(archer, "move",
                {'x': patrolCenter.x + radius * Math.cos(angleStep * index - hero.time * patrolTimeScale),
                'y': patrolCenter.y + radius * Math.sin(angleStep * index - hero.time * patrolTimeScale)})

# skipping function:
# commandPaladin()
# you can rewrite it yourself

loop:
    enemy = hero.findNearestEnemy()
    castinvisibility()
    flag()
    commandArcher()
    
    # perhaps wrap this section into a function? like heroAction()
    if hero.pos.x != 51 and hero.pos.y != 69 and hero.isReady("blink"):
        hero.blink(Vector(51, 69))
    elif hero.pos.x != 51 and hero.pos.y != 69:
        hero.moveXY(51, 69)
    if enemy and enemy.type == "shaman":
        hero.scattershot(enemy)
    elif enemy and hero.distanceTo(enemy) <= 30:
        hero.scattershot(enemy)
    
    # unneccessary, refer to the case inside commandArcher()
    patrolCenter = {'x': hero.pos.x, 'y': hero.pos.y}
    patrolTimeScale = 0.60
    
    # I would personally put the summonTypes array outside of the loop
    # perhaps even at the top of the program
    summonTypes = ["archer", "archer", "archer", "archer", "paladin", "paladin", "soldier", "soldier", "soldier", "soldier", "soldier", "soldier", "soldier", "archer", "archer", "archer", "archer"]
    
    # can be wrapped into a function. Maybe named it trySummon()?
    # if so, you need to put the summonTypes array above it.
    summonType = summonTypes[len(hero.built) % len(summonTypes)]
    if hero.gold >= hero.costOf(summonType):
        hero.summon(summonType)
    
    # make another function, like commandArcher(), but for soldier ... --> commandSoldiers()
    friends = hero.findFriends()
    soldiers = (friend for friend in friends if friend.type == "soldier")
    radius = 2 + 2.5 * Math.log1p(len(soldiers)) 
    angleStep = Math.PI * 2 / len(soldiers)
    for index, soldier in enumerate(soldiers):
        enemy = soldier.findNearestEnemy()
        if enemy and soldier.distanceTo(enemy) <= 15:
            hero.command(soldier, "attack", enemy)
        else:
            hero.command(soldier, "move",
                {'x': patrolCenter.x + radius * Math.cos(angleStep * index - hero.time * patrolTimeScale),
                'y': patrolCenter.y + radius * Math.sin(angleStep * index - hero.time * patrolTimeScale)})
    
    # fit the rest of this code into commandPaladin()
    for friend in hero.findFriends():
        if friend.type == "paladin":
            enemy = hero.findNearestEnemy()
            
            # unnecessary parentheses: (friend.canCast("heal")) --> friend.canCast("heal")
            if (friend.canCast("heal")) and hero.health < hero.maxHealth:
                self.command(friend, "cast", "heal", self)
            elif (friend.canCast("heal")) and hero.health == hero.maxHealth and soldier and soldier.health < soldier.maxHealth:
                self.command(friend, "cast", "heal", soldier)
            elif (friend.canCast("heal")) and hero.health == hero.maxHealth and archer and archer.health < archer.maxHealth:
                self.command(friend, "cast", "heal", archer)
            elif (friend.canCast("heal")) and hero.health == hero.maxHealth and friend and friend.health < friend.maxHealth:
                self.command(friend, "cast", "heal", friend)
            else:
                
                # commandPaladin() commands all paladins already
                # side note: rename commandPaladin() to commandPaladins()
                commandPaladin()

What a nice loop looks like
loop:
    trySummon()
    commandArchers()
    commandSoldiers()
    commandPaladins()
    heroActions()

# it's so much more readable, and therefore easier to debug!

My Zero-Sum loop, a real example

Link to level

# my functions are already defined; I am only showing the loop:
while True:
    global enemies
    enemies = (x for x in hero.findEnemies() if x.type != "yeti" and x.type != "crate")
    
    trySummon()
    tryCommand()
    
    nearestEnemy = hero.findNearest(enemies)
    if not trySpell(nearestEnemy):
        if not tryAttack(nearestEnemy): pass
        #else:
        #    hero.move(findBestCoin().pos)
    hero.move(findBestCoin().pos)

Nice and tidy :nerd_face:


The technique I’m focusing on:

2 Likes