ICFP 2004: Team Sealab2021

V 1.0, updated June 7, 2004

Why Sealab 2021? Because my 2002 entry was 'Aqua Team Hunger Farce'
I wrote a great mini language, preprocessor and simulator.
Which left no time for writing ants. Nuts.

My Ant as submitted can be found Here (same ant for lightning and final)
The code that is it generated from is Here

Python was used to write the simulator, the GUI, and the mini language that gets translated into ant-code. All that in under a thousand lines of code (including comments). In short we used python because it rules.

The 'real' problem of this year's contest was writing a thing to let you write good ants. Here is a screenshot of my simulator in action (click for full size):

Green is food, Black is rock (so Black ants are actually Blue). The center color of the ant is it's kind, the outer ring changes colors depending on what job the ant is doing to help with debugging. The hexes are actually 7x7 squares, pasted over and over again and filled with the appropriate color. The dropdowns on the left hand side change the board and the red/black programs. The buttons step 1/10/100/1000/100k turns. The buttons on the bottom change the image to a pheremone view.

The original simulator took about four hours to write, and was 600 lines of python. The execute function is dead simple:

  def execute(self, ant):
    if (ant.resting):
      ant.resting -= 1
    if (ant.resting): # still has leftover
      return
    func = self.states[ant.state]
    func(self.board, ant) # modifies ant, and maybe board
    ant.stepped += 1
    return
The self.states[] array is just a bunch of function closures generated like so:
def mk_flip(maxint, st1, st2):
  """ Make a flip function for the maxint/st1/st2 combo and return it """
  if (st1 == st2): # No-Op, optimize it to skip the call to random
    def skip_flip(bob, ant):
      ant.state = st1
      return
    return skip_flip
  def flip(bob, ant):
    if (random.randint(0,maxint) == 0):
      ant.state = st1
    else:
      ant.state = st2
    return
  return flip
It was easiest to write it this way, but psyco (python just in time compiler optimizer) gets very confused and only provided 10% speedup when it didn't cause a coredump all together. It took about five minutes to run a full scale ant war, after a half an hour of optimization (using native random for Flip, not updating the screen every time an ant moved) it took 30 seconds to run a full scale war. Good enough, so I stopped optimizing.

Once that was written we tried to write a simple Ant in raw ant-code. We failed miserably, it was impossible to keep track of state numbers. Then 'PASS' and 'REPEAT' were added as targets. That was still too hard, so labels and Goto's were added.

  top:
    Turn Right PASS
    Turn Right PASS
    Turn Right PASS
  Move REPEAT PASS # move until we fail
  Mark 0 PASS
  Goto @top+1
This is all the functionality you need to write a decent ant, and was used to write the lightning entry. Later I added some useful debugging stuff, like changing the ant's color. If run with a single ant, it also has a window with the state numbers and orignal program text so you can watch what un-munged code is being executed (picture of step-debugger).
  top:
    # change the ant's color ring
    Type 1
    
    # eval, raise an exception if false
    Assert ant.type==1 

    # eval, and print to stdout
    Print "eval() and print this line", ant.type, ant.dead, ant.state

    Turn Around @top # expands to 'Turn Right PASS, Turn Right PASS, Turn Right @top'
    Sleep 7 # expands to seven lines of 'Flip 1 PASS PASS'
    Goto @fancy
  
  fancy: 
  # these took hours to implement and were almost never used
    Do 10
      Move PASS REPEAT
    EndDo

    # simple var substitution
    VAR use_marker=2
    Mark $use_marker

    # include other source files, with a set of VARS
    include other.ant {'use_marker':2}
The preprocessor also makes some attempts to optimize Goto's that just goto the next line, and things like that.

So that was the end of Saturday, on Sunday I started to write very smart ants. It turns out very smart ants are incredibly slow at finding and gathering food. In the simulator picture above you can see a 'Kill Ring' of ants in the center of the Red hill, these ants never move. The two white ringed ants on the hill stay on the hill and move any food they find into the middle of the kill ring. They can't do this as fast as other ants put food on the hill.

In order to do that we have to figure out which ants are on what edge, and assign them duties depending on where they are. Then we peel the onion a couple more times until we are in the center. The pheremones on the hill end up looking like:

Phermone view

Ants and Objects View


The gather ants roam just inside the light green marks, sensing ahead right for food. If found they grab it and drop it in the kill ring (the dark green marks in the center). All the gathering ants drop food on the edge of the hill. Great in theory, but in practice it is just seven fewer ants to gather food. Many hours wasted on the Gather Ants program and the Onion peeler program. The smarter the ants got, the worse they performed. I originally doubted a geneticly evolved ant could win, or that anyone could write one in time but if my sample ants did better than the smart ones perhaps a GA ant could be eloved that is simpler and better still.

That's about it, if you want to run the simulator you'll need python2.3 with Tk installed and PIL (python imaging library).

Tarball of everything 12k (yes 12k for a GUI, simulator, and ant programs. python rules)

If you just want to peek, board.py The board parser and Tk GUI, program.py The program parser and executer