Sharing my code for Colossus Clash (SM Loh)

For any who are interested, here is the (slightly cleaned-up) code I submitted for Colossus Clash. I have some comments below. Feel free to ask any questions!

One thing I do ask, if you copy and paste this code and submit it without making any changes of your own, please don’t leave it up there for longer than a day, as I don’t want many copies of this code on the leaderboard messing up the rankings for everyone else who might be trying their own strategies.

BIG_GUYS_LIST = ['cronus','hyperion','atlas','talos']
UNITS_LIST = ['munchkin', 'thrower', 'ogre', 'shaman', 'brawler', 'warlock']
IGNORED_TYPES = ['base', 'atlas', 'cronus', 'hyperion', 'talos']
FB_INC_DISTSQ = 400

def sortedEnemies():
    es = [i for i in filterFodder(hero.findEnemies()) if i.type not in IGNORED_TYPES]
    # Using dict bc complex sort/sorted doesn't really work
    edict = {}
    for e in es: edict[int(e.x*10000)] = e
    xs = sorted([int(i.x*10000) for i in es])
    esorted = [edict[x] for x in xs]
    return esorted    

def c(spell, loc):
    hero.cast(spell, loc.x, loc.y)

# Returns the distance-squared for calculation efficiency
def myDist(loc,loc2):
    return (loc.x-loc2.x)**2 + (loc.y-loc2.y)**2

def fireball():
    es = sortedEnemies()
    if len(es)>1:
        ex = es[0].x
        ey = es[0].y
        fx = ex
        fy = ey
        tc=1
        for i in range(1, len(es)):
            if es[i].x - ex > 20: break
            if myDist(es[0],es[i])<=FB_INC_DISTSQ:
                fx=es[i].x
                fy=es[i].y
                tc+=1
        if tc>3:
            c("fireball", Vector((ex+fx)/2, (ey+fy)/2))
            return True
    return False

# Removes any units further away than maxDist, and also returns the closest and
# furthest units (within maxDist)
def filterByMaxDist(aList, b, maxDist):
    result = []
    maxFoundSq = 0
    furthest = None
    minFoundSq = 99999
    nearest = None
    maxDistSq = maxDist * maxDist
    for a in aList:
        x = a.x - b.x
        y = a.y - b.y
        if x > maxDist or x < -maxDist or y > maxDist or y < -maxDist: continue
        distSq =  x*x + y*y
        if distSq <= maxDistSq:
            result.append(a)
            if distSq > maxFoundSq:
                maxFoundSq = distSq
                furthest = a
            if distSq < minFoundSq:
                minFoundSq = distSq
                nearest = a
    return result, nearest, furthest

def filterTypeByList(aList, checkList):
    result = []
    for a in aList:
        if a.type in checkList:
            result.append(a)
    return result

def onlyCols(aList):
    return filterTypeByList(aList, BIG_GUYS_LIST)

def onlyUnits(aList):
    return filterTypeByList(aList, UNITS_LIST)

def getMinHealthGuy(aList):
    minHealth = 999999
    result = None
    for a in aList:
        if a.health < minHealth:
            minHealth = a.health
            result = a
    return result

def runHype(e):
    col = e.colossus
    while True:
        elist = hero.findEnemies()
        clist = onlyCols(elist)
        if col.isReady():
            slist, nearest, furthest = filterByMaxDist(elist, col, 30)
            if len(slist)>1:
                # Do special on furthest enemy unit within a certain close distance.
                # Check at least X units within certain distance. Not doing more complex
                # directional checking since that's expensive and units generally cluster.
                # If there's a colossus, make sure we don't miss it.
                if myDist(col, nearest)<=25:
                    col.special(nearest)
                    continue
                sclist, nearestc, _ = filterByMaxDist(clist, col, 30)
                if len(sclist)>0:
                    col.special(getMinHealthGuy(sclist))
                    continue
                col.special(furthest)
                continue
        # Regular attacks on any immediately in-range colossi
        closeclist, nearest, _ = filterByMaxDist(clist, col, 22)
        if len(closeclist)>0:
            if len(closeclist)==1:
                col.attack(closeclist[0])
                continue
            else:
                # Maybe also check for 1-hit targets?
                col.attack(getMinHealthGuy(closeclist))
                continue
        # Next level of closeness
        closeclist, nearest, _ = filterByMaxDist(clist, col, 55)
        if len(closeclist)>0:
            col.moveTo(nearest.x, nearest.y)
            continue
        # Then attack regular units
        ulist = onlyUnits(elist)
        closelist, nearest, _ = filterByMaxDist(ulist, col, 22)
        if len(closelist)>0:
            col.attack(nearest)
            continue
        if col.x>80:
            e = col.findNearestEnemy()
            if e:
                col.attack(e)
                continue
        col.moveTo(95,40)


def runCols(e):
    colossus = e.colossus
    place = e.place
    if colossus.type == 'hyperion':
        # Run Hyperion handler
        runHype(e)

def choose(place):
    return {
        'B': 'hyperion',
        'C': 'hyperion',
    }[place]

hero.chooseColossus = choose
hero.on('spawn-colossus', runCols)

sy = 10
while True:
    if hero.mana >= 1.3 and fireball():
        continue
    if hero.mana > 5.0:
        hero.summon('brawler', 10, sy)
        sy=(sy+30) % 90
        continue

Commentary:

  • Colossus:
    Hyperions were chosen mainly for their special, which have a longer range than the Atlas’ stomp, even though it only has a 90-degree spread. In order to maximize the number of units damaged by the special attack, they target units up to 30 distance away. There is a priority for trying to get enemy colossi within the range of the special as well.
    Simplifying assumption: there is no check for what direction the opponent units are in, but it usually works well enough as the enemies usually all come from the same direction.

  • Fireball:
    This was copy-pasted over from my code for Giant’s Gate. It’s actually a bit unnecessarily complex for what it currently does, which can be accomplished with just two for loops. The sorting was performed in order to more efficiently do a more complete search to find any clumps of enemy units (there would be an additional for loop for the first unit). However, that was removed due to performance concerns, and always including the leftmost enemy unit turned out to work well enough.

4 Likes