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
# 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