Ant Wars

Did anyone work on a Ruby entry to the ICFP contest this year?

Apparently it was an ant colony simulation. It looks like they've decided to
continue on with an ant-wars site where you can enter your colony to compete
with others. Did anyone work on any infrastructure code for this already?
(there is an assembly language and a high-level Ant language that is defined)

see:
http://ant-wars.com/

Phil

Your question raised my interest, so I went back and browsed through the archives. I don't immediately see a Ruby entry, but my search was not exhaustive.

This site may help your own efforts:

http://www.kingsrook.com/icfp/homePage.html?page=/icfp/2004/otherSites.html

I entered with Perl this year (Team Road Crew) as I hadn't yet found Ruby then. It would be interesting to see a Ruby simulator though I think.

Please keep us looped if you find one or decide to build one yourself.

James Edward Gray II

···

On Sep 12, 2004, at 11:39 AM, Phil Tomson wrote:

Did anyone work on a Ruby entry to the ICFP contest this year?

Did anyone work on a Ruby entry to the ICFP contest this year?

This proved too great a temptation to ignore. Below is my new Ruby simulator. Feel free to play with it and/or enhance it.

Interface is practically non-existant so far, but I'm thinking of hooking it into OpenGL when I have time.

You would have to make changes if you wanted to use it with "Ant Wars" but it is fully compliant with the original contest.

Enjoy.

James Edward Gray II

#!/usr/bin/ruby -w

# a simulator for ICFP 2004 ant game

···

On Sep 12, 2004, at 11:39 AM, Phil Tomson wrote:

# copyright 2004 James Edward Gray II <james@grayproductions.net>
# permission given to modify and use

# reads ant files from "ants" directory and world files from "worlds"

# a simple ant data structure
class Ant
  @@brains = { } # store brains at class level to save on memory

  attr_reader :id, :color
  attr_writer :alive, :state, :resting, :has_food
  attr_accessor :direction, :x, :y
  
  def initialize(id, color, brain_file, x, y)
    @alive = true
    
    @id = id
    @color = color
    
    unless @@brains.include? color
      unless brain_file =~ /^ants/
        brain_file = File.join("ants", brain_file)
      end
      @@brains[color] =
      IO.foreach brain_file do |line| # ant file parser
        if line =~ / ^\s*(Sense)\s+
               (Here|Ahead|(?:Left|Right)Ahead)\s+
               (\d+)\s+(\d+)\s+
              (Friend(?:WithFood)?|Foe(?:WithFood|Marker|Home)?|
               Food>Rock>Marker\s+[0-5]|Home)\s*$ /ix
          @@brains[color].push [ $1.downcase, $2.downcase,
                       $3.to_i, $4.to_i,
                       $5.downcase ]
        elsif line =~ /^\s*((?:Un)?mark)\s+([0-5])\s+(\d+)\s*$/i or
            line =~ /^\s*(PickUp|Move)\s+(\d+)\s+(\d+)\s*$/i
          @@brains[color].push [ $1.downcase, $2.to_i, $3.to_i ]
        elsif line =~ /^\s*(Turn)\s+(Left|Right)\s+(\d+)\s*$/i
          @@brains[color].push [ $1.downcase, $2.downcase, $3.to_i ]
        elsif line =~ /^\s*(Drop)\s+(\d+)\s*$/i
          @@brains[color].push [ $1.downcase, $2.to_i ]
        elsif line =~ /^\s*(Flip)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/i
          @@brains[color].push [ $1.downcase, $2.to_i,
                       $3.to_i, $4.to_i ]
        elsif line =~ /\S/
          raise "Corrupt ant brain. State #{$. - 1}: #{line}."
        end
      end
    end
    @brain = @@brains[color]
    @state = 0
    
    @resting = 0
    @direction = 0
    @has_food = 0
    
    @x = x
    @y = y
  end

  def alive?
    return @alive
  end
  
  def enemy
    if color == "red"
      return "black"
    else
      return "red"
    end
  end
  
  def has_food?
    if @has_food == 1
      return true
    else
      return false
    end
  end
  
  def resting?
    if @resting > 0
      @resting -= 1
      return true
    else
      return false
    end
  end
  
  def state
    return @brain[@state]
  end
  
  def inspect # for test dumps
    return "#@color ant of id #@id, dir #@direction, " +
         "food #@has_food, state #@state, resting #@resting"
  end
end

# a simple data structure for representing world spaces
class Cell
  attr_reader :x, :y
  attr_writer :ant
  attr_accessor :food

  def initialize(x, y, rocky = false, hill = nil, food = 0, ant = nil)
    @x = x
    @y = y
    
    @rocky = rocky
    @hill = if hill.kind_of? String then hill.downcase else hill end
    @food = food
    @ant = ant
    
    @red_markers = [ 0, 0, 0, 0, 0, 0 ]
    @black_markers = [ 0, 0, 0, 0, 0, 0 ]
  end
  
  def adjacent(direction)
    case direction
      when 0
        return @x + 1, @y
      when 1
        if @y % 2 == 0
          return @x, @y + 1
        else
          return @x + 1, @y + 1
        end
      when 2
        if @y % 2 == 0
          return @x - 1, @y + 1
        else
          return @x, @y + 1
        end
      when 3
        return @x - 1, @y
      when 4
        if @y % 2 == 0
          return @x - 1, @y - 1
        else
          return @x, @y - 1
        end
      when 5
        if @y % 2 == 0
          return @x, @y - 1
        else
          return @x + 1, @y - 1
        end
    end
  end
  
  def ant?(color = nil)
    color.downcase! if color.kind_of? String
    if not color.nil? and not @ant.nil? and @ant.color == color
      return @ant
    elsif color.nil? and not @ant.nil?
      return @ant
    else
      return false
    end
  end
  
  def rocky?
    return @rocky
  end
  
  def hill?(color = nil)
    color.downcase! if color.kind_of? String
    if not color.nil? and @hill == color
      return true
    elsif color.nil? and not hill.nil?
      return true
    else
      return false
    end
  end
  
  def mark(color, i)
    if color == "red"
      @red_markers[i] = 1
    else
      @black_markers[i] = 1
    end
  end
  
  def mark?(color, i = nil)
    if i.nil?
      if color == "red"
        @red_markers.each { |e| return true if e == 1 }
        return false
      else
        @black_markers.each { |e| return true if e == 1 }
        return false
      end
    else
      if color == "red"
        if @red_markers[i] == 1
          return true
        else
          return false
        end
      else
        if @black_markers[i] == 1
          return true
        else
          return false
        end
      end
    end
  end
  
  def unmark(color, i)
    if color == "red"
      @red_markers[i] = 0
    else
      @black_markers[i] = 0
    end
  end
  
  def inspect # for test dumps
    dump = "cell (#@x, #@y): "

    if @rocky
      dump += "rock; "
    end
    
    if @food > 0
      dump += "#@food food; "
    end
    
    if not @hill.nil?
      dump += "#@hill hill; "
    end

    if @red_markers.include? 1
      dump += "red marks: "
      @red_markers.each_with_index do |mark, i|
        dump += i.to_s if mark == 1
      end
      dump += "; "
    end
    if @black_markers.include? 1
      dump += "black marks: "
      @black_markers.each_with_index do |mark, i|
        dump += i.to_s if mark == 1
      end
      dump += "; "
    end
    
    if not @ant.nil?
      dump += @ant.inspect
    end
    
    dump.sub!(/(rock);\s*$/, '\1') # ugly hack to match their dump format
    
    return dump
  end
end

# primary game logic object
class Simulator
  def initialize( red_brain_file, black_brain_file, world_file = nil,
          final_round = 100000, test_mode = false )
    @world =
    @ants =
    if world_file.nil?
      worlds =
      Dir.foreach "worlds" do |file_name|
        worlds.push file_name unless file_name[0, 1] == "."
      end
      world_file = worlds[rand(worlds.size)]
    end
    unless world_file =~ /^worlds/
      world_file = File.join("worlds", world_file)
    end
    IO.foreach world_file do |line| # world file parser
      next if $. < 3
      row =
      line.split(" ").each do |e|
        case e
          when "#"
            row.push Cell.new(row.length, @world.length, true)
          when "."
            row.push Cell.new(row.length, @world.length)
          when "1".."9"
            row.push Cell.new( row.length, @world.length,
                       false, nil, e.to_i )
          when "+", "-"
            color = if e == "+" then "red" else "black" end
            brain = if e == "+"
                  red_brain_file
                else
                  black_brain_file
                end
            @ants.push Ant.new( @ants.length, color, brain,
                      row.length, @world.length )
            row.push Cell.new( row.length, @world.length,
                       false, color, 0, @ants[-1] )
          else
            raise "Corrupt world. Unknown symbol: #{e}."
        end
      end
      @world.push row
    end
    
    @final_round = final_round.to_i
    
    # the following was just for testing the sim against the spec
    @test_mode = test_mode
    @random_seed = 12345
    3.times { @random_seed = (@random_seed * 22695477 + 1) % 1073741824 }
  end
  
  # main event loop - not broken down on purpose, for speed
  # extended interface calls should be interleaved in here
  def run
    test_dump 0 if @test_mode

    1.upto @final_round do |round|
      deaths = # for removing ants after each() iteration
      @ants.each do |ant|
        next if ant.resting? or not ant.alive?
        
        action = ant.state
        cell = @world[ant.y][ant.x]
        case action[0]
          when "sense"
            check = cell
            case action[1]
              when "ahead"
                look_x, look_y = cell.adjacent ant.direction
                check = @world[look_y][look_x]
              when "leftahead"
                look_x, look_y =
                    cell.adjacent((ant.direction + 5) % 6)
                check = @world[look_y][look_x]
              when "rightahead"
                look_x, look_y =
                    cell.adjacent((ant.direction + 1) % 6)
                check = @world[look_y][look_x]
            end
            case action[4]
              when "friend"
                if check.ant? ant.color
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "foe"
                if check.ant? ant.enemy
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "friendwithfood"
                if check.ant? ant.color and check.ant?.has_food?
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "foewithfood"
                if check.ant? ant.enemy and check.ant?.has_food?
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "food"
                if check.food > 0
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "rock"
                if check.rocky?
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "foemarker"
                if check.mark? ant.enemy
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "home"
                if check.hill? ant.color
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              when "foehome"
                if check.hill? ant.enemy
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
              else # marker 0-5 - trick to avoid regex
                if check.mark? ant.color, action[4][-1, 1].to_i
                  ant.state = action[2]
                else
                  ant.state = action[3]
                end
            end
          when "mark"
            cell.mark(ant.color, action[1])
            ant.state = action[2]
          when "unmark"
            cell.unmark(ant.color, action[1])
            ant.state = action[2]
          when "pickup"
            if ant.has_food? or cell.food == 0
              ant.state = action[2]
            else
              cell.food = cell.food - 1
              ant.has_food = 1
              ant.state = action[1]
            end
          when "drop"
            if ant.has_food?
              ant.has_food = 0
              cell.food = cell.food + 1
            end
            ant.state = action[1]
          when "move"
            new_x, new_y = cell.adjacent ant.direction
            to = @world[new_y][new_x]
            if to.rocky? or to.ant?
              ant.state = action[2]
            else
              cell.ant = nil
              to.ant = ant
              ant.x = to.x
              ant.y = to.y
              ant.resting = 14
              ant.state = action[1]

              check_surround = [ to ]
              0.upto 5 do |direction|
                x, y = to.adjacent direction
                check_surround.push @world[y]
              end
              check_surround.each do |e|
                next unless e.ant?
                enemy_count = 0
                0.upto 5 do |direction|
                  test_x, test_y = e.adjacent direction
                  next if test_x < 0 or test_y < 0 or
                      test_x >= @world[0].size or
                      test_y >= @world.size
                  if @world[test_y][test_x].ant? e.ant?.enemy
                    enemy_count += 1
                  end
                  break if enemy_count == 5
                  break if enemy_count < direction
                end
                if enemy_count == 5
                  e.ant?.alive = false
                  deaths.push e.ant?.id
                  e.ant = nil
                  e.food += 3
                end
              end
            end
          when "turn"
            if action[1] == "left"
              ant.direction = (ant.direction + 5) % 6
            else
              ant.direction = (ant.direction + 1) % 6
            end
            ant.state = action[2]
          when "flip"
            if @test_mode
              if test_random(action[1]) == 0
                ant.state = action[2]
              else
                ant.state = action[3]
              end
            else # for speed
              if rand(action[1]) == 0
                ant.state = action[2]
              else
                ant.state = action[3]
              end
            end
        end
      end
      deaths.each do |e| # remove ants that died this turn
        @ants.delete(@ants.find { |ant| ant.id == e })
      end

      test_dump round if @test_mode
    end
    
    print score unless @test_mode
  end
  
  # super basic results printout
  def score
    red_score = black_score = 0
    @world.each do |row|
      row.each do |cell|
        if cell.hill? "red"
          red_score += cell.food
        elsif cell.hill? "black"
          black_score += cell.food
        end
      end
    end
    score = "Final Score:\n\n" +
        "\tRed: #{red_score}\n\tBlack: #{black_score}\n\n"
    if red_score > black_score
      score += "Red wins!\n"
    elsif red_score == black_score
      score += "Draw.\n"
    else
      score += "Black wins!\n"
    end
  end
  
  # these final two method were just for validation - done to 10,000 turns
  def test_dump(round)
    print "random seed: 12345\n\n" if round == 0
    puts "After round #{round}..."
    @world.each do |row|
      row.each { |cell| p cell }
    end
    puts
  end
  
  def test_random(limit)
    @random_seed = (@random_seed * 22695477 + 1) % 1073741824
    
    return ((@random_seed / 65536) % 16384) % limit
  end
end

unless ARGV.size >= 2
  puts "Usage: ant_sim.rb RED_ANT_FILE BLACK_ANT_FILE [ TURNS TEST_MODE ]"
  exit
end

game = Simulator.new(*ARGV)
game.run