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.