Skip to content

Commit

Permalink
ants starter package
Browse files Browse the repository at this point in the history
  • Loading branch information
Swizec committed Oct 20, 2011
1 parent 5aec911 commit b8dac92
Show file tree
Hide file tree
Showing 138 changed files with 41,313 additions and 0 deletions.
1 change: 1 addition & 0 deletions ants/.gitignore
@@ -0,0 +1 @@
*.pyc
56 changes: 56 additions & 0 deletions ants/MyBot.py
@@ -0,0 +1,56 @@
#!/usr/bin/env python
from ants import *

# define a class with a do_turn method
# the Ants.run method will parse and update bot input
# it will also run the do_turn method for us
class MyBot:
def __init__(self):
# define class level variables, will be remembered between turns
pass

# do_setup is run once at the start of the game
# after the bot has received the game settings
# the ants class is created and setup by the Ants.run method
def do_setup(self, ants):
# initialize data structures after learning the game settings
pass

# do turn is run once per turn
# the ants class has the game state and is updated by the Ants.run method
# it also has several helper methods to use
def do_turn(self, ants):
# loop through all my ants and try to give them orders
# the ant_loc is an ant location tuple in (row, col) form
for ant_loc in ants.my_ants():
# try all directions in given order
directions = ('n','e','s','w')
for direction in directions:
# the destination method will wrap around the map properly
# and give us a new (row, col) tuple
new_loc = ants.destination(ant_loc, direction)
# passable returns true if the location is land
if (ants.passable(new_loc)):
# an order is the location of a current ant and a direction
ants.issue_order((ant_loc, direction))
# stop now, don't give 1 ant multiple orders
break
# check if we still have time left to calculate more orders
if ants.time_remaining() < 10:
break

if __name__ == '__main__':
# psyco will speed up python a little, but is not needed
try:
import psyco
psyco.full()
except ImportError:
pass

try:
# if run is passed a class with a do_turn method, it will do the work
# this is not needed, in which case you will need to write your own
# parsing function and your own game state class
Ants.run(MyBot())
except KeyboardInterrupt:
print('ctrl-c, leaving ...')
287 changes: 287 additions & 0 deletions ants/ants.py
@@ -0,0 +1,287 @@
#!/usr/bin/env python
import sys
import traceback
import random
import time
from collections import defaultdict
from math import sqrt

MY_ANT = 0
ANTS = 0
DEAD = -1
LAND = -2
FOOD = -3
WATER = -4

PLAYER_ANT = 'abcdefghij'
HILL_ANT = string = 'ABCDEFGHI'
PLAYER_HILL = string = '0123456789'
MAP_OBJECT = '?%*.!'
MAP_RENDER = PLAYER_ANT + HILL_ANT + PLAYER_HILL + MAP_OBJECT

AIM = {'n': (-1, 0),
'e': (0, 1),
's': (1, 0),
'w': (0, -1)}
RIGHT = {'n': 'e',
'e': 's',
's': 'w',
'w': 'n'}
LEFT = {'n': 'w',
'e': 'n',
's': 'e',
'w': 's'}
BEHIND = {'n': 's',
's': 'n',
'e': 'w',
'w': 'e'}

class Ants():
def __init__(self):
self.cols = None
self.rows = None
self.map = None
self.hill_list = {}
self.ant_list = {}
self.dead_list = defaultdict(list)
self.food_list = []
self.turntime = 0
self.loadtime = 0
self.turn_start_time = None
self.vision = None
self.viewradius2 = 0
self.attackradius2 = 0
self.spawnradius2 = 0
self.turns = 0

def setup(self, data):
'parse initial input and setup starting game state'
for line in data.split('\n'):
line = line.strip().lower()
if len(line) > 0:
tokens = line.split()
key = tokens[0]
if key == 'cols':
self.cols = int(tokens[1])
elif key == 'rows':
self.rows = int(tokens[1])
elif key == 'player_seed':
random.seed(int(tokens[1]))
elif key == 'turntime':
self.turntime = int(tokens[1])
elif key == 'loadtime':
self.loadtime = int(tokens[1])
elif key == 'viewradius2':
self.viewradius2 = int(tokens[1])
elif key == 'attackradius2':
self.attackradius2 = int(tokens[1])
elif key == 'spawnradius2':
self.spawnradius2 = int(tokens[1])
elif key == 'turns':
self.turns = int(tokens[1])
self.map = [[LAND for col in range(self.cols)]
for row in range(self.rows)]

def update(self, data):
'parse engine input and update the game state'
# start timer
self.turn_start_time = time.clock()

# reset vision
self.vision = None

# clear hill, ant and food data
self.hill_list = {}
for row, col in self.ant_list.keys():
self.map[row][col] = LAND
self.ant_list = {}
for row, col in self.dead_list.keys():
self.map[row][col] = LAND
self.dead_list = defaultdict(list)
for row, col in self.food_list:
self.map[row][col] = LAND
self.food_list = []

# update map and create new ant and food lists
for line in data.split('\n'):
line = line.strip().lower()
if len(line) > 0:
tokens = line.split()
if len(tokens) >= 3:
row = int(tokens[1])
col = int(tokens[2])
if tokens[0] == 'w':
self.map[row][col] = WATER
elif tokens[0] == 'f':
self.map[row][col] = FOOD
self.food_list.append((row, col))
else:
owner = int(tokens[3])
if tokens[0] == 'a':
self.map[row][col] = owner
self.ant_list[(row, col)] = owner
elif tokens[0] == 'd':
# food could spawn on a spot where an ant just died
# don't overwrite the space unless it is land
if self.map[row][col] == LAND:
self.map[row][col] = DEAD
# but always add to the dead list
self.dead_list[(row, col)].append(owner)
elif tokens[0] == 'h':
owner = int(tokens[3])
self.hill_list[(row, col)] = owner

def time_remaining(self):
return self.turntime - int(1000 * (time.clock() - self.turn_start_time))

def issue_order(self, order):
'issue an order by writing the proper ant location and direction'
(row, col), direction = order
sys.stdout.write('o %s %s %s\n' % (row, col, direction))
sys.stdout.flush()

def finish_turn(self):
'finish the turn by writing the go line'
sys.stdout.write('go\n')
sys.stdout.flush()

def my_hills(self):
return [loc for loc, owner in self.hill_list.items()
if owner == MY_ANT]

def enemy_hills(self):
return [(loc, owner) for loc, owner in self.hill_list.items()
if owner != MY_ANT]

def my_ants(self):
'return a list of all my ants'
return [(row, col) for (row, col), owner in self.ant_list.items()
if owner == MY_ANT]

def enemy_ants(self):
'return a list of all visible enemy ants'
return [((row, col), owner)
for (row, col), owner in self.ant_list.items()
if owner != MY_ANT]

def food(self):
'return a list of all food locations'
return self.food_list[:]

def passable(self, loc):
'true if not water'
row, col = loc
return self.map[row][col] != WATER

def unoccupied(self, loc):
'true if no ants are at the location'
row, col = loc
return self.map[row][col] in (LAND, DEAD)

def destination(self, loc, direction):
'calculate a new location given the direction and wrap correctly'
row, col = loc
d_row, d_col = AIM[direction]
return ((row + d_row) % self.rows, (col + d_col) % self.cols)

def distance(self, loc1, loc2):
'calculate the closest distance between to locations'
row1, col1 = loc1
row2, col2 = loc2
d_col = min(abs(col1 - col2), self.cols - abs(col1 - col2))
d_row = min(abs(row1 - row2), self.rows - abs(row1 - row2))
return d_row + d_col

def direction(self, loc1, loc2):
'determine the 1 or 2 fastest (closest) directions to reach a location'
row1, col1 = loc1
row2, col2 = loc2
height2 = self.rows//2
width2 = self.cols//2
d = []
if row1 < row2:
if row2 - row1 >= height2:
d.append('n')
if row2 - row1 <= height2:
d.append('s')
if row2 < row1:
if row1 - row2 >= height2:
d.append('s')
if row1 - row2 <= height2:
d.append('n')
if col1 < col2:
if col2 - col1 >= width2:
d.append('w')
if col2 - col1 <= width2:
d.append('e')
if col2 < col1:
if col1 - col2 >= width2:
d.append('e')
if col1 - col2 <= width2:
d.append('w')
return d

def visible(self, loc):
' determine which squares are visible to the given player '

if self.vision == None:
if not hasattr(self, 'vision_offsets_2'):
# precalculate squares around an ant to set as visible
self.vision_offsets_2 = []
mx = int(sqrt(self.viewradius2))
for d_row in range(-mx,mx+1):
for d_col in range(-mx,mx+1):
d = d_row**2 + d_col**2
if d <= self.viewradius2:
self.vision_offsets_2.append((
d_row%self.rows-self.rows,
d_col%self.cols-self.cols
))
# set all spaces as not visible
# loop through ants and set all squares around ant as visible
self.vision = [[False]*self.cols for row in range(self.rows)]
for ant in self.my_ants():
a_row, a_col = ant
for v_row, v_col in self.vision_offsets_2:
self.vision[a_row+v_row][a_col+v_col] = True
row, col = loc
return self.vision[row][col]

def render_text_map(self):
'return a pretty string representing the map'
tmp = ''
for row in self.map:
tmp += '# %s\n' % ''.join([MAP_RENDER[col] for col in row])
return tmp

# static methods are not tied to a class and don't have self passed in
# this is a python decorator
@staticmethod
def run(bot):
'parse input, update game state and call the bot classes do_turn method'
ants = Ants()
map_data = ''
while(True):
try:
current_line = sys.stdin.readline().rstrip('\r\n') # string new line char
if current_line.lower() == 'ready':
ants.setup(map_data)
bot.do_setup(ants)
ants.finish_turn()
map_data = ''
elif current_line.lower() == 'go':
ants.update(map_data)
# call the do_turn method of the class passed in
bot.do_turn(ants)
ants.finish_turn()
map_data = ''
else:
map_data += current_line + '\n'
except EOFError:
break
except KeyboardInterrupt:
raise
except:
# don't raise error or return so that bot attempts to stay alive
traceback.print_exc(file=sys.stderr)
sys.stderr.flush()

0 comments on commit b8dac92

Please sign in to comment.