Ant Wars

P

Phil Tomson

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
 
J

James Edward Gray II

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

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
 
J

James Edward Gray II

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

# copyright 2004 James Edward Gray II <[email protected]>
# 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 = 1
else
@black_markers = 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 == 1
return true
else
return false
end
else
if @black_markers == 1
return true
else
return false
end
end
end
end

def unmark(color, i)
if color == "red"
@red_markers = 0
else
@black_markers = 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][x]
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,159
Messages
2,570,879
Members
47,413
Latest member
ReeceDorri

Latest Threads

Top