#!/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()
engine.py
#!/usr/bin/env python
from __future__ import print_function
import time
import traceback
import os
import random
import sys
import json
import io
if sys.version_info >= (3,):
def unicode(s):
return s
from sandbox import get_sandbox
class HeadTail(object):
'Capture first part of file write and discard remainder'
def __init__(self, file, max_capture=510):
self.file = file
self.max_capture = max_capture
self.capture_head_len = 0
self.capture_head = unicode('')
self.capture_tail = unicode('')
def write(self, data):
if self.file:
self.file.write(data)
capture_head_left = self.max_capture - self.capture_head_len
if capture_head_left > 0:
data_len = len(data)
if data_len <= capture_head_left:
self.capture_head += data
self.capture_head_len += data_len
else:
self.capture_head += data[:capture_head_left]
self.capture_head_len = self.max_capture
self.capture_tail += data[capture_head_left:]
self.capture_tail = self.capture_tail[-self.max_capture:]
else:
self.capture_tail += data
self.capture_tail = self.capture_tail[-self.max_capture:]
def flush(self):
if self.file:
self.file.flush()
def close(self):
if self.file:
self.file.close()
def head(self):
return self.capture_head
def tail(self):
return self.capture_tail
def headtail(self):
if self.capture_head != '' and self.capture_tail != '':
sep = unicode('\n..\n')
else:
sep = unicode('')
return self.capture_head + sep + self.capture_tail
def run_game(game, botcmds, options):
# file descriptors for replay and streaming formats
replay_log = options.get('replay_log', None)
stream_log = options.get('stream_log', None)
verbose_log = options.get('verbose_log', None)
# file descriptors for bots, should be list matching # of bots
input_logs = options.get('input_logs', [None]*len(botcmds))
output_logs = options.get('output_logs', [None]*len(botcmds))
error_logs = options.get('error_logs', [None]*len(botcmds))
capture_errors = options.get('capture_errors', False)
capture_errors_max = options.get('capture_errors_max', 510)
turns = int(options['turns'])
loadtime = float(options['loadtime']) / 1000
turntime = float(options['turntime']) / 1000
strict = options.get('strict', False)
end_wait = options.get('end_wait', 0.0)
location = options.get('location', 'localhost')
game_id = options.get('game_id', 0)
error = ''
bots = []
bot_status = []
bot_turns = []
if capture_errors:
error_logs = [HeadTail(log, capture_errors_max) for log in error_logs]
try:
# create bot sandboxes
for b, bot in enumerate(botcmds):
bot_cwd, bot_cmd = bot
sandbox = get_sandbox(bot_cwd,
secure=options.get('secure_jail', None))
sandbox.start(bot_cmd)
bots.append(sandbox)
bot_status.append('survived')
bot_turns.append(0)
# ensure it started
if not sandbox.is_alive:
bot_status[-1] = 'crashed 0'
bot_turns[-1] = 0
if verbose_log:
verbose_log.write('bot %s did not start\n' % b)
game.kill_player(b)
sandbox.pause()
if stream_log:
stream_log.write(game.get_player_start())
stream_log.flush()
if verbose_log:
verbose_log.write('running for %s turns\n' % turns)
for turn in range(turns+1):
if turn == 0:
game.start_game()
# send game state to each player
for b, bot in enumerate(bots):
if game.is_alive(b):
if turn == 0:
start = game.get_player_start(b) + 'ready\n'
bot.write(start)
if input_logs and input_logs[b]:
input_logs[b].write(start)
input_logs[b].flush()
else:
state = 'turn ' + str(turn) + '\n' + game.get_player_state(b) + 'go\n'
bot.write(state)
if input_logs and input_logs[b]:
input_logs[b].write(state)
input_logs[b].flush()
bot_turns[b] = turn
if turn > 0:
if stream_log:
stream_log.write('turn %s\n' % turn)
stream_log.write('score %s\n' % ' '.join([str(s) for s in game.get_scores()]))
stream_log.write(game.get_state())
stream_log.flush()
game.start_turn()
# get moves from each player
if turn == 0:
time_limit = loadtime
else:
time_limit = turntime
if options.get('serial', False):
simul_num = int(options['serial']) # int(True) is 1
else:
simul_num = len(bots)
bot_moves = [[] for b in bots]
error_lines = [[] for b in bots]
statuses = [None for b in bots]
bot_list = [(b, bot) for b, bot in enumerate(bots)
if game.is_alive(b)]
random.shuffle(bot_list)
for group_num in range(0, len(bot_list), simul_num):
pnums, pbots = zip(*bot_list[group_num:group_num + simul_num])
moves, errors, status = get_moves(game, pbots, pnums,
time_limit, turn)
for p, b in enumerate(pnums):
bot_moves[b] = moves[p]
error_lines[b] = errors[p]
statuses[b] = status[p]
# handle any logs that get_moves produced
for b, errors in enumerate(error_lines):
if errors:
if error_logs and error_logs[b]:
error_logs[b].write(unicode('\n').join(errors)+unicode('\n'))
# set status for timeouts and crashes
for b, status in enumerate(statuses):
if status != None:
bot_status[b] = status
bot_turns[b] = turn
# process all moves
bot_alive = [game.is_alive(b) for b in range(len(bots))]
if turn > 0 and not game.game_over():
for b, moves in enumerate(bot_moves):
if game.is_alive(b):
valid, ignored, invalid = game.do_moves(b, moves)
if output_logs and output_logs[b]:
output_logs[b].write('# turn %s\n' % turn)
if valid:
if output_logs and output_logs[b]:
output_logs[b].write('\n'.join(valid)+'\n')
output_logs[b].flush()
if ignored:
if error_logs and error_logs[b]:
error_logs[b].write('turn %4d bot %s ignored actions:\n' % (turn, b))
error_logs[b].write('\n'.join(ignored)+'\n')
error_logs[b].flush()
if output_logs and output_logs[b]:
output_logs[b].write('\n'.join(ignored)+'\n')
output_logs[b].flush()
if invalid:
if strict:
game.kill_player(b)
bot_status[b] = 'invalid'
bot_turns[b] = turn
if error_logs and error_logs[b]:
error_logs[b].write('turn %4d bot %s invalid actions:\n' % (turn, b))
error_logs[b].write('\n'.join(invalid)+'\n')
error_logs[b].flush()
if output_logs and output_logs[b]:
output_logs[b].write('\n'.join(invalid)+'\n')
output_logs[b].flush()
if turn > 0:
game.finish_turn()
# send ending info to eliminated bots
bots_eliminated = []
for b, alive in enumerate(bot_alive):
if alive and not game.is_alive(b):
bots_eliminated.append(b)
for b in bots_eliminated:
if verbose_log:
verbose_log.write('turn %4d bot %s eliminated\n' % (turn, b))
if bot_status[b] == 'survived': # could be invalid move
bot_status[b] = 'eliminated'
bot_turns[b] = turn
score_line ='score %s\n' % ' '.join([str(s) for s in game.get_scores(b)])
status_line = 'status %s\n' % ' '.join(map(str, game.order_for_player(b, bot_status)))
status_line += 'playerturns %s\n' % ' '.join(map(str, game.order_for_player(b, bot_turns)))
end_line = 'end\nplayers %s\n' % len(bots) + score_line + status_line
state = end_line + game.get_player_state(b) + 'go\n'
bots[b].write(state)
if input_logs and input_logs[b]:
input_logs[b].write(state)
input_logs[b].flush()
if end_wait:
bots[b].resume()
if bots_eliminated and end_wait:
if verbose_log:
verbose_log.write('waiting {0} seconds for bots to process end turn\n'.format(end_wait))
time.sleep(end_wait)
for b in bots_eliminated:
bots[b].kill()
if verbose_log:
stats = game.get_stats()
stat_keys = sorted(stats.keys())
s = 'turn %4d stats: ' % turn
if turn % 50 == 0:
verbose_log.write(' '*len(s))
for key in stat_keys:
values = stats[key]
verbose_log.write(' {0:^{1}}'.format(key, max(len(key), len(str(values)))))
verbose_log.write('\n')
verbose_log.write(s)
for key in stat_keys:
values = stats[key]
if type(values) == list:
values = '[' + ','.join(map(str,values)) + ']'
verbose_log.write(' {0:^{1}}'.format(values, max(len(key), len(str(values)))))
verbose_log.write('\n')
#alive = [game.is_alive(b) for b in range(len(bots))]
#if sum(alive) <= 1:
if game.game_over():
break
# send bots final state and score, output to replay file
game.finish_game()
score_line ='score %s\n' % ' '.join(map(str, game.get_scores()))
status_line = 'status %s\n' % ' '.join(bot_status)
status_line += 'playerturns %s\n' % ' '.join(map(str, bot_turns))
end_line = 'end\nplayers %s\n' % len(bots) + score_line + status_line
if stream_log:
stream_log.write(end_line)
stream_log.write(game.get_state())
stream_log.flush()
if verbose_log:
verbose_log.write(score_line)
verbose_log.write(status_line)
verbose_log.flush()
for b, bot in enumerate(bots):
if game.is_alive(b):
score_line ='score %s\n' % ' '.join([str(s) for s in game.get_scores(b)])
status_line = 'status %s\n' % ' '.join(map(str, game.order_for_player(b, bot_status)))
status_line += 'playerturns %s\n' % ' '.join(map(str, game.order_for_player(b, bot_turns)))
end_line = 'end\nplayers %s\n' % len(bots) + score_line + status_line
state = end_line + game.get_player_state(b) + 'go\n'
bot.write(state)
if input_logs and input_logs[b]:
input_logs[b].write(state)
input_logs[b].flush()
except Exception as e:
# TODO: sanitize error output, tracebacks shouldn't be sent to workers
error = traceback.format_exc()
if verbose_log:
verbose_log.write(traceback.format_exc())
# error = str(e)
finally:
if end_wait:
for bot in bots:
bot.resume()
if verbose_log:
verbose_log.write('waiting {0} seconds for bots to process end turn\n'.format(end_wait))
time.sleep(end_wait)
for bot in bots:
if bot.is_alive:
bot.kill()
bot.release()
if error:
game_result = { 'error': error }
else:
scores = game.get_scores()
game_result = {
'challenge': game.__class__.__name__.lower(),
'location': location,
'game_id': game_id,
'status': bot_status,
'playerturns': bot_turns,
'score': scores,
'rank': [sorted(scores, reverse=True).index(x) for x in scores],
'replayformat': 'json',
'replaydata': game.get_replay(),
'game_length': turn
}
if capture_errors:
game_result['errors'] = [head.headtail() for head in error_logs]
if replay_log:
json.dump(game_result, replay_log, sort_keys=True)
return game_result
def get_moves(game, bots, bot_nums, time_limit, turn):
bot_finished = [not game.is_alive(bot_nums[b]) for b in range(len(bots))]
bot_moves = [[] for b in bots]
error_lines = [[] for b in bots]
statuses = [None for b in bots]
start_time = time.time()
# resume all bots
for bot in bots:
if bot.is_alive:
bot.resume()
# loop until received all bots send moves or are dead
# or when time is up
while (sum(bot_finished) < len(bot_finished) and
time.time() - start_time < time_limit):
time.sleep(0.01)
for b, bot in enumerate(bots):
if bot_finished[b]:
continue # already got bot moves
if not bot.is_alive:
error_lines[b].append(unicode('turn %4d bot %s crashed') % (turn, bot_nums[b]))
statuses[b] = 'crashed'
line = bot.read_error()
while line != None:
error_lines[b].append(line)
line = bot.read_error()
bot_finished[b] = True
game.kill_player(bot_nums[b])
continue # bot is dead
# read a maximum of 100 lines per iteration
for x in range(100):
line = bot.read_line()
if line is None:
# stil waiting for more data
break
line = line.strip()
if line.lower() == 'go':
bot_finished[b] = True
# bot finished sending data for this turn
break
bot_moves[b].append(line)
for x in range(100):
line = bot.read_error()
if line is None:
break
error_lines[b].append(line)
# pause all bots again
for bot in bots:
if bot.is_alive:
bot.pause()
# kill timed out bots
for b, finished in enumerate(bot_finished):
if not finished:
error_lines[b].append(unicode('turn %4d bot %s timed out') % (turn, bot_nums[b]))
statuses[b] = 'timeout'
bot = bots[b]
for x in range(100):
line = bot.read_error()
if line is None:
break
error_lines[b].append(line)
game.kill_player(bot_nums[b])
bots[b].kill()
return bot_moves, error_lines, statuses
game.py
#!/usr/bin/env python
# Games used by the engine should implement the following methods
class Game:
def __init__(self):
pass
# load starting map or game board positions
def load_map(self, filename):
pass
# common functions for all games used by engine
def start_game(self):
pass
# do things needed for start of turn (cleanup, etc...)
def start_turn(self):
pass
# do things needed for finishing a turn (resolving orders, scoring, etc...)
def finish_turn(self):
pass
# do things needed for finishing a game (scoring, etc...)
def finish_game(self):
pass
# remove a player from the game, may be a crashed/timed out bot
def kill_player(self, player):
pass
# return if a player is alive, might be removed by game mechanics
def is_alive(self, player):
pass
# returns if the game is over due to a win condition
def game_over(self): # returns boolean
pass
# used by engine to get the current game state for the streaming format
def get_state(self):
pass
# used for turn 0, sending minimal info for bot to load
# when passed none, the output is used at the start of the streaming format
def get_player_start(self, player=None):
pass
# used for sending state to bots for each turn
def get_player_state(self, player):
pass
# process a single player's moves, may be appropriate to resolve during finish turn
def do_moves(self, player, moves):
# returns valid, ignored, invalid
# [''], [('','')], [('','')]
pass
def do_all_moves(self, bot_moves):
return [self.do_moves(b, moves) for b, moves in enumerate(bot_moves)]
# used for ranking
def get_scores(self):
pass
# can be used to determine fairness of game and other stuff for visualizers
def get_stats(self):
pass
# used for getting a compact replay of the game
def get_replay(self):
pass
playgame.py
#!/usr/bin/env python
from __future__ import print_function
import traceback
import sys
import os
import time
from optparse import OptionParser, OptionGroup
import random
import cProfile
import visualizer.visualize_locally
import json
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from ants import Ants
# get engine from worker dir
sys.path.append("../worker")
from engine import run_game
# make stderr red text
try:
import colorama
colorama.init()
colorize = True
color_default = (colorama.Fore.RED)
color_reset = (colorama.Style.RESET_ALL)
except:
colorize = False
color_default = None
color_reset = None
class Colorize(object):
def __init__(self, file, color=color_default):
self.file = file
self.color = color
self.reset = color_reset
def write(self, data):
if self.color:
self.file.write(''.join(self.color))
self.file.write(data)
if self.reset:
self.file.write(''.join(self.reset))
def flush(self):
self.file.flush()
def close(self):
self.file.close()
if colorize:
stderr = Colorize(sys.stderr)
else:
stderr = sys.stderr
class Comment(object):
def __init__(self, file):
self.file = file
self.last_char = '\n'
def write(self, data):
for char in data:
if self.last_char == '\n':
self.file.write('# ')
self.file.write(char)
self.last_char = char
def flush(self):
self.file.flush()
def close(self):
self.file.close()
class Tee(object):
''' Write to multiple files at once '''
def __init__(self, *files):
self.files = files
def write(self, data):
for file in self.files:
file.write(data)
def flush(self):
for file in self.files:
file.flush()
def close(self):
for file in self.files:
file.close()
def main(argv):
usage ="Usage: %prog [options] map bot1 bot2\n\nYou must specify a map file."
parser = OptionParser(usage=usage)
# map to be played
# number of players is determined by the map file
parser.add_option("-m", "--map_file", dest="map",
help="Name of the map file")
# maximum number of turns that the game will be played
parser.add_option("-t", "--turns", dest="turns",
default=1000, type="int",
help="Number of turns in the game")
parser.add_option("--serial", dest="serial",
action="store_true",
help="Run bots in serial, instead of parallel.")
parser.add_option("--turntime", dest="turntime",
default=1000, type="int",
help="Amount of time to give each bot, in milliseconds")
parser.add_option("--loadtime", dest="loadtime",
default=3000, type="int",
help="Amount of time to give for load, in milliseconds")
parser.add_option("-r", "--rounds", dest="rounds",
default=1, type="int",
help="Number of rounds to play")
parser.add_option("--player_seed", dest="player_seed",
default=None, type="int",
help="Player seed for the random number generator")
parser.add_option("--engine_seed", dest="engine_seed",
default=None, type="int",
help="Engine seed for the random number generator")
parser.add_option('--strict', dest='strict',
action='store_true', default=False,
help='Strict mode enforces valid moves for bots')
parser.add_option('--capture_errors', dest='capture_errors',
action='store_true', default=False,
help='Capture errors and stderr in game result')
parser.add_option('--end_wait', dest='end_wait',
default=0, type="float",
help='Seconds to wait at end for bots to process end')
parser.add_option('--secure_jail', dest='secure_jail',
action='store_true', default=False,
help='Use the secure jail for each bot (*nix only)')
parser.add_option('--fill', dest='fill',
action='store_true', default=False,
help='Fill up extra player starts with last bot specified')
parser.add_option('-p', '--position', dest='position',
default=0, type='int',
help='Player position for first bot specified')
# ants specific game options
game_group = OptionGroup(parser, "Game Options", "Options that affect the game mechanics for ants")
game_group.add_option("--attack", dest="attack",
default="focus",
help="Attack method to use for engine. (closest, focus, support, damage)")
game_group.add_option("--kill_points", dest="kill_points",
default=2, type="int",
help="Points awarded for killing an ant shared by all ants involved")
game_group.add_option("--food", dest="food",
default="symmetric",
help="Food spawning method. (none, random, sections, symmetric)")
game_group.add_option("--viewradius2", dest="viewradius2",
default=77, type="int",
help="Vision radius of ants squared")
game_group.add_option("--spawnradius2", dest="spawnradius2",
default=1, type="int",
help="Spawn radius of ants squared")
game_group.add_option("--attackradius2", dest="attackradius2",
default=5, type="int",
help="Attack radius of ants squared")
game_group.add_option("--food_rate", dest="food_rate", nargs=2, type="int", default=(2,8),
help="Numerator of food per turn per player rate")
game_group.add_option("--food_turn", dest="food_turn", nargs=2, type="int", default=(12,30),
help="Denominator of food per turn per player rate")
game_group.add_option("--food_start", dest="food_start", nargs=2, type="int", default=(75,175),
help="One over percentage of land area filled with food at start")
game_group.add_option("--food_visible", dest="food_visible", nargs=2, type="int", default=(1,3),
help="Amount of food guaranteed to be visible to starting ants")
game_group.add_option("--cutoff_turn", dest="cutoff_turn", type="int", default=100,
help="Number of turns cutoff percentage is maintained to end game early")
game_group.add_option("--cutoff_percent", dest="cutoff_percent", type="float", default=0.90,
help="Number of turns cutoff percentage is maintained to end game early")
game_group.add_option("--scenario", dest="scenario",
action='store_true', default=False)
parser.add_option_group(game_group)
# the log directory must be specified for any logging to occur, except:
# bot errors to stderr
# verbose levels 1 & 2 to stdout and stderr
# profiling to stderr
# the log directory will contain
# the replay or stream file used by the visualizer, if requested
# the bot input/output/error logs, if requested
log_group = OptionGroup(parser, "Logging Options", "Options that control the logging")
log_group.add_option("-g", "--game", dest="game_id", default=0, type='int',
help="game id to start at when numbering log files")
log_group.add_option("-l", "--log_dir", dest="log_dir", default=None,
help="Directory to dump replay files to.")
log_group.add_option('-R', '--log_replay', dest='log_replay',
action='store_true', default=False),
log_group.add_option('-S', '--log_stream', dest='log_stream',
action='store_true', default=False),
log_group.add_option("-I", "--log_input", dest="log_input",
action="store_true", default=False,
help="Log input streams sent to bots")
log_group.add_option("-O", "--log_output", dest="log_output",
action="store_true", default=False,
help="Log output streams from bots")
log_group.add_option("-E", "--log_error", dest="log_error",
action="store_true", default=False,
help="log error streams from bots")
log_group.add_option('-e', '--log_stderr', dest='log_stderr',
action='store_true', default=False,
help='additionally log bot errors to stderr')
log_group.add_option('-o', '--log_stdout', dest='log_stdout',
action='store_true', default=False,
help='additionally log replay/stream to stdout')
# verbose will not print bot input/output/errors
# only info+debug will print bot error output
log_group.add_option("-v", "--verbose", dest="verbose",
action='store_true', default=False,
help="Print out status as game goes.")
log_group.add_option("--profile", dest="profile",
action="store_true", default=False,
help="Run under the python profiler")
parser.add_option("--nolaunch", dest="nolaunch",
action='store_true', default=False,
help="Prevent visualizer from launching")
log_group.add_option("--html", dest="html_file",
default=None,
help="Output file name for an html replay")
parser.add_option_group(log_group)
(opts, args) = parser.parse_args(argv)
if opts.map is None or not os.path.exists(opts.map):
parser.print_help()
return -1
try:
if opts.profile:
# put profile file into output dir if we can
prof_file = "ants.profile"
if opts.log_dir:
prof_file = os.path.join(opts.log_dir, prof_file)
# cProfile needs to be explitly told about out local and global context
print("Running profile and outputting to {0}".format(prof_file,), file=stderr)
cProfile.runctx("run_rounds(opts,args)", globals(), locals(), prof_file)
else:
# only use psyco if we are not profiling
# (psyco messes with profiling)
try:
import psyco
psyco.full()
except ImportError:
pass
run_rounds(opts,args)
return 0
except Exception:
traceback.print_exc()
return -1
def run_rounds(opts,args):
def get_cmd_wd(cmd):
''' get the proper working directory from a command line '''
new_cmd = []
wd = None
for i, part in enumerate(reversed(cmd.split())):
if wd == None and os.path.exists(part):
wd = os.path.split(os.path.realpath(part))[0]
if i == 0:
new_cmd.insert(0, os.path.join(".", os.path.basename(part)))
else:
new_cmd.insert(0, os.path.basename(part))
else:
new_cmd.insert(0, part)
return wd, ' '.join(new_cmd)
def get_cmd_name(cmd):
''' get the name of a bot from the command line '''
for i, part in enumerate(reversed(cmd.split())):
if os.path.exists(part):
return os.path.basename(part)
# this split of options is not needed, but left for documentation
game_options = {
"map": opts.map,
"attack": opts.attack,
"kill_points": opts.kill_points,
"food": opts.food,
"viewradius2": opts.viewradius2,
"attackradius2": opts.attackradius2,
"spawnradius2": opts.spawnradius2,
"loadtime": opts.loadtime,
"turntime": opts.turntime,
"turns": opts.turns,
"food_rate": opts.food_rate,
"food_turn": opts.food_turn,
"food_start": opts.food_start,
"food_visible": opts.food_visible,
"cutoff_turn": opts.cutoff_turn,
"cutoff_percent": opts.cutoff_percent,
"scenario": opts.scenario }
if opts.player_seed != None:
game_options['player_seed'] = opts.player_seed
if opts.engine_seed != None:
game_options['engine_seed'] = opts.engine_seed
engine_options = {
"loadtime": opts.loadtime,
"turntime": opts.turntime,
"map_file": opts.map,
"turns": opts.turns,
"log_replay": opts.log_replay,
"log_stream": opts.log_stream,
"log_input": opts.log_input,
"log_output": opts.log_output,
"log_error": opts.log_error,
"serial": opts.serial,
"strict": opts.strict,
"capture_errors": opts.capture_errors,
"secure_jail": opts.secure_jail,
"end_wait": opts.end_wait }
for round in range(opts.rounds):
# initialize game
game_id = round + opts.game_id
with open(opts.map, 'r') as map_file:
game_options['map'] = map_file.read()
if opts.engine_seed:
game_options['engine_seed'] = opts.engine_seed + round
game = Ants(game_options)
# initialize bots
bots = [get_cmd_wd(arg) for arg in args]
bot_count = len(bots)
# insure correct number of bots, or fill in remaining positions
if game.num_players != len(bots):
if game.num_players > len(bots) and opts.fill:
extra = game.num_players - len(bots)
for _ in range(extra):
bots.append(bots[-1])
else:
print("Incorrect number of bots for map. Need {0}, got {1}"
.format(game.num_players, len(bots)), file=stderr)
for arg in args:
print("Bot Cmd: {0}".format(arg), file=stderr)
break
bot_count = len(bots)
# move position of first bot specified
if opts.position > 0 and opts.position <= len(bots):
first_bot = bots[0]
bots = bots[1:]
bots.insert(opts.position, first_bot)
# initialize file descriptors
if opts.log_dir and not os.path.exists(opts.log_dir):
os.mkdir(opts.log_dir)
if not opts.log_replay and not opts.log_stream and (opts.log_dir or opts.log_stdout):
opts.log_replay = True
replay_path = None # used for visualizer launch
if opts.log_replay:
if opts.log_dir:
replay_path = os.path.join(opts.log_dir, '{0}.replay'.format(game_id))
engine_options['replay_log'] = open(replay_path, 'w')
if opts.log_stdout:
if 'replay_log' in engine_options and engine_options['replay_log']:
engine_options['replay_log'] = Tee(sys.stdout, engine_options['replay_log'])
else:
engine_options['replay_log'] = sys.stdout
else:
engine_options['replay_log'] = None
if opts.log_stream:
if opts.log_dir:
engine_options['stream_log'] = open(os.path.join(opts.log_dir, '{0}.stream'.format(game_id)), 'w')
if opts.log_stdout:
if engine_options['stream_log']:
engine_options['stream_log'] = Tee(sys.stdout, engine_options['stream_log'])
else:
engine_options['stream_log'] = sys.stdout
else:
engine_options['stream_log'] = None
if opts.log_input and opts.log_dir:
engine_options['input_logs'] = [open(os.path.join(opts.log_dir, '{0}.bot{1}.input'.format(game_id, i)), 'w')
for i in range(bot_count)]
else:
engine_options['input_logs'] = None
if opts.log_output and opts.log_dir:
engine_options['output_logs'] = [open(os.path.join(opts.log_dir, '{0}.bot{1}.output'.format(game_id, i)), 'w')
for i in range(bot_count)]
else:
engine_options['output_logs'] = None
if opts.log_error and opts.log_dir:
if opts.log_stderr:
if opts.log_stdout:
engine_options['error_logs'] = [Tee(Comment(stderr), open(os.path.join(opts.log_dir, '{0}.bot{1}.error'.format(game_id, i)), 'w'))
for i in range(bot_count)]
else:
engine_options['error_logs'] = [Tee(stderr, open(os.path.join(opts.log_dir, '{0}.bot{1}.error'.format(game_id, i)), 'w'))
for i in range(bot_count)]
else:
engine_options['error_logs'] = [open(os.path.join(opts.log_dir, '{0}.bot{1}.error'.format(game_id, i)), 'w')
for i in range(bot_count)]
elif opts.log_stderr:
if opts.log_stdout:
engine_options['error_logs'] = [Comment(stderr)] * bot_count
else:
engine_options['error_logs'] = [stderr] * bot_count
else:
engine_options['error_logs'] = None
if opts.verbose:
if opts.log_stdout:
engine_options['verbose_log'] = Comment(sys.stdout)
else:
engine_options['verbose_log'] = sys.stdout
engine_options['game_id'] = game_id
if opts.rounds > 1:
print('# playgame round {0}, game id {1}'.format(round, game_id))
# intercept replay log so we can add player names
if opts.log_replay:
intcpt_replay_io = StringIO()
real_replay_io = engine_options['replay_log']
engine_options['replay_log'] = intcpt_replay_io
result = run_game(game, bots, engine_options)
# add player names, write to proper io, reset back to normal
if opts.log_replay:
replay_json = json.loads(intcpt_replay_io.getvalue())
replay_json['playernames'] = [get_cmd_name(arg) for arg in args]
real_replay_io.write(json.dumps(replay_json))
intcpt_replay_io.close()
engine_options['replay_log'] = real_replay_io
# close file descriptors
if engine_options['stream_log']:
engine_options['stream_log'].close()
if engine_options['replay_log']:
engine_options['replay_log'].close()
if engine_options['input_logs']:
for input_log in engine_options['input_logs']:
input_log.close()
if engine_options['output_logs']:
for output_log in engine_options['output_logs']:
output_log.close()
if engine_options['error_logs']:
for error_log in engine_options['error_logs']:
error_log.close()
if replay_path:
if opts.nolaunch:
if opts.html_file:
visualizer.visualize_locally.launch(replay_path, True, opts.html_file)
else:
if opts.html_file == None:
visualizer.visualize_locally.launch(replay_path,
generated_path="replay.{0}.html".format(game_id))
else:
visualizer.visualize_locally.launch(replay_path,
generated_path=opts.html_file)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
sandbox.py
#!/usr/bin/python
from __future__ import print_function
import os
import shlex
import signal
import subprocess
import sys
import time
from optparse import OptionParser
from threading import Thread
try:
from Queue import Queue, Empty
except ImportError:
from queue import Queue, Empty
# make python 3.x compatible with python 2.x
if sys.version_info >= (3,):
def unicode(s, errors="strict"):
if isinstance(s, str):
return s
elif isinstance(s, bytes) or isinstance(s, bytearray):
return s.decode("utf-8", errors)
raise SandboxError("Tried to convert unrecognized type to unicode")
try:
from server_info import server_info
_SECURE_DEFAULT = server_info.get('secure_jail', True)
except ImportError:
_SECURE_DEFAULT = False
class SandboxError(Exception):
pass
def _guard_monitor(jail):
guard_out = jail.command_process.stdout
while True:
line = guard_out.readline()
if not line:
end_item = (time.time(), None)
jail.resp_queue.put(end_item)
jail.stdout_queue.put(end_item)
jail.stderr_queue.put(end_item)
break
line = line.rstrip("\r\n")
words = line.split(None, 2)
if len(words) < 3:
msg, ts = words
data = ""
else:
msg, ts, data = words
ts = float(ts)
data = unicode(data, errors="replace")
if msg == "STDOUT":
jail.stdout_queue.put((time, data))
elif msg == "STDERR":
jail.stderr_queue.put((time, data))
elif msg == "SIGNALED":
jail.resp_queue.put((time, data))
class Jail(object):
""" Provide a secure sandbox to run arbitrary commands in.
This will only function on specially prepared Ubuntu systems.
"""
def __init__(self, working_directory):
"""Initialize a new sandbox for the given working directory.
working_directory: the directory in which the shell command should
be launched. Files from this directory are copied
into the secure space before the shell command is
executed.
"""
self.locked = False
jail_base = "/srv/chroot"
all_jails = os.listdir(jail_base)
all_jails = [j for j in all_jails if j.startswith("jailuser")]
for jail in all_jails:
lock_dir = os.path.join(jail_base, jail, "locked")
try:
os.mkdir(lock_dir)
except OSError:
# if the directory could not be created, that should mean the
# jail is already locked and in use
continue
with open(os.path.join(lock_dir, "lock.pid"), "w") as pid_file:
pid_file.write(str(os.getpid()))
self.locked = True
self.name = jail
break
else:
raise SandboxError("Could not find an unlocked jail")
self.jchown = os.path.join(server_info["repo_path"], "worker/jail_own")
self.base_dir = os.path.join(jail_base, jail)
self.number = int(jail[len("jailuser"):])
self.chroot_cmd = "sudo -u {0} schroot -u {0} -c {0} -d {1} -- jailguard.py ".format(
self.name, "/home/jailuser")
self._is_alive = False
self.command_process = None
self.resp_queue = Queue()
self.stdout_queue = Queue()
self.stderr_queue = Queue()
self._prepare_with(working_directory)
def __del__(self):
if self.locked:
raise SandboxError("Jail object for %s freed without being released"
% (self.name))
@property
def is_alive(self):
"""Indicates whether a command is currently running in the sandbox"""
if self._is_alive:
sub_result = self.command_process.poll()
if sub_result is None:
return True
self._is_alive = False
return False
def release(self):
"""Release the sandbox for further use
Unlocks and releases the jail for reuse by others.
Must be called exactly once after Jail.is_alive == False.
"""
if self.is_alive:
raise SandboxError("Sandbox released while still alive")
if not self.locked:
raise SandboxError("Attempt to release jail that is already unlocked")
if os.system("sudo umount %s" % (os.path.join(self.base_dir, "root"),)):
raise SandboxError("Error returned from umount of jail %d"
% (self.number,))
lock_dir = os.path.join(self.base_dir, "locked")
pid_filename = os.path.join(lock_dir, "lock.pid")
with open(pid_filename, 'r') as pid_file:
lock_pid = int(pid_file.read())
if lock_pid != os.getpid():
# if we ever get here something has gone seriously wrong
# most likely the jail locking mechanism has failed
raise SandboxError("Jail released by different pid, name %s, lock_pid %d, release_pid %d"
% (self.name, lock_pid, os.getpid()))
os.unlink(pid_filename)
os.rmdir(lock_dir)
self.locked = False
def _prepare_with(self, command_dir):
if os.system("%s c %d" % (self.jchown, self.number)) != 0:
raise SandboxError("Error returned from jail_own c %d in prepare"
% (self.number,))
scratch_dir = os.path.join(self.base_dir, "scratch")
if os.system("rm -rf %s" % (scratch_dir,)) != 0:
raise SandboxError("Could not remove old scratch area from jail %d"
% (self.number,))
home_dir = os.path.join(scratch_dir, "home/jailuser")
os.makedirs(os.path.join(scratch_dir, "home"))
if os.system("cp -r %s %s" % (command_dir, home_dir)) != 0:
raise SandboxError("Error copying working directory '%s' to jail %d"
% (command_dir, self.number))
if os.system("sudo mount %s" % (os.path.join(self.base_dir, "root"),)):
raise SandboxError("Error returned from mount of %d in prepare"
% (self.number,))
if os.system("%s j %d" % (self.jchown, self.number)) != 0:
raise SandboxError("Error returned from jail_own j %d in prepare"
% (self.number,))
self.home_dir = home_dir
self.command_dir = command_dir
def retrieve(self):
"""Copy the working directory back out of the sandbox."""
if self.is_alive:
raise SandboxError("Tried to retrieve sandbox while still alive")
os.system("rm -rf %s" % (self.command_dir,))
if os.system("%s c %d" % (self.jchown, self.number)) != 0:
raise SandboxError("Error returned from jail_own c %d in prepare"
% (self.number,))
os.system("cp -r %s %s" % (self.home_dir, self.command_dir))
def start(self, shell_command):
"""Start a command running in the sandbox"""
if self.is_alive:
raise SandboxError("Tried to run command with one in progress.")
shell_command = self.chroot_cmd + shell_command
shell_command = shlex.split(shell_command.replace('\\','/'))
try:
self.command_process = subprocess.Popen(shell_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
except OSError:
raise SandboxError('Failed to start {0}'.format(shell_command))
self._is_alive = True
monitor = Thread(target=_guard_monitor, args=(self,))
monitor.daemon = True
monitor.start()
def _signal(self, signal):
if not self.locked:
raise SandboxError("Attempt to send %s to unlocked jail" % (signal,))
result = subprocess.call("sudo -u {0} kill -{1} -1".format(
self.name, signal), shell=True)
if result != 0:
raise SandboxError("Error returned from jail %s sending signal %s"
% (self.name, signal))
def kill(self):
"""Stops the sandbox.
Stops down the sandbox, cleaning up any spawned processes, threads, and
other resources. The shell command running inside the sandbox may be
suddenly terminated.
"""
try:
self.command_process.stdin.write("KILL\n")
self.command_process.stdin.flush()
except IOError as exc:
if exc.errno != 32:
raise
try:
item = self.resp_queue.get(timeout=5)
if item[1] != "KILL" and item[1] is not None:
raise SandboxError("Bad response from jailguard after kill, %s"
% (item,))
except Empty:
pass
self._signal("CONT")
for i in range(20):
if self.command_process.poll() != None:
break
if i == 10:
self._signal("KILL")
time.sleep(0.1)
# final check to make sure processes are died and raise error if not
if self.is_alive:
raise SandboxError("Could not kill sandbox children")
def pause(self):
"""Pause the process by sending a SIGSTOP to the child"""
try:
self.command_process.stdin.write("STOP\n")
self.command_process.stdin.flush()
except IOError as exc:
if exc.errno == 32: # Broken pipe, guard exited
return
raise
item = self.resp_queue.get()
if item[1] != "STOP" and item[1] is not None:
raise SandboxError("Bad response from jailguard after pause, %s"
% (item,))
def resume(self):
"""Resume the process by sending a SIGCONT to the child"""
try:
self.command_process.stdin.write("CONT\n")
self.command_process.stdin.flush()
except IOError as exc:
if exc.errno == 32: # Broken pipe, guard exited
return
raise
item = self.resp_queue.get()
if item[1] != "CONT" and item[1] is not None:
raise SandboxError("Bad response from jailguard after resume, %s"
% (item,))
def write(self, data):
"""Write str to stdin of the process being run"""
for line in data.splitlines():
self.write_line(line)
def write_line(self, line):
"""Write line to stdin of the process being run
A newline is appended to line and written to stdin of the child process
"""
if not self.is_alive:
return False
try:
self.command_process.stdin.write("SEND %s\n" % (line,))
self.command_process.stdin.flush()
except (OSError, IOError):
self.kill()
def read_line(self, timeout=0):
"""Read line from child process
Returns a line of the child process' stdout, if one isn't available
within timeout seconds it returns None. Also guaranteed to return None
at least once after each command that is run in the sandbox.
"""
if not self.is_alive:
timeout=0
try:
time, line = self.stdout_queue.get(block=True, timeout=timeout)
return line
except Empty:
return None
def read_error(self, timeout=0):
"""Read line from child process' stderr
Returns a line of the child process' stderr, if one isn't available
within timeout seconds it returns None. Also guaranteed to return None
at least once after each command that is run in the sandbox.
"""
if not self.is_alive:
timeout=0
try:
time, line = self.stderr_queue.get(block=True, timeout=timeout)
return line
except Empty:
return None
def check_path(self, path, errors):
resolved_path = os.path.join(self.home_dir, path)
if not os.path.exists(resolved_path):
errors.append("Output file " + str(path) + " was not created.")
return False
else:
return True
def _monitor_file(fd, q):
while True:
line = fd.readline()
if not line:
q.put(None)
break
line = unicode(line, errors="replace")
line = line.rstrip('\r\n')
q.put(line)
class House:
"""Provide an insecure sandbox to run arbitrary commands in.
The sandbox class is used to invoke arbitrary shell commands.
This class provides the same interface as the secure Sandbox but doesn't
provide any actual security or require any special system setup.
"""
def __init__(self, working_directory):
"""Initialize a new sandbox for the given working directory.
working_directory: the directory in which the shell command should
be launched.
"""
self._is_alive = False
self.command_process = None
self.stdout_queue = Queue()
self.stderr_queue = Queue()
self.working_directory = working_directory
@property
def is_alive(self):
"""Indicates whether a command is currently running in the sandbox"""
if self._is_alive:
sub_result = self.command_process.poll()
if sub_result is None:
return True
self.child_queue.put(None)
self._is_alive = False
return False
def start(self, shell_command):
"""Start a command running in the sandbox"""
if self.is_alive:
raise SandboxError("Tried to run command with one in progress.")
working_directory = self.working_directory
self.child_queue = Queue()
shell_command = shlex.split(shell_command.replace('\\','/'))
try:
self.command_process = subprocess.Popen(shell_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
cwd=working_directory)
except OSError:
raise SandboxError('Failed to start {0}'.format(shell_command))
self._is_alive = True
stdout_monitor = Thread(target=_monitor_file,
args=(self.command_process.stdout, self.stdout_queue))
stdout_monitor.daemon = True
stdout_monitor.start()
stderr_monitor = Thread(target=_monitor_file,
args=(self.command_process.stderr, self.stderr_queue))
stderr_monitor.daemon = True
stderr_monitor.start()
Thread(target=self._child_writer).start()
def kill(self):
"""Stops the sandbox.
Shuts down the sandbox, cleaning up any spawned processes, threads, and
other resources. The shell command running inside the sandbox may be
suddenly terminated.
"""
if self.is_alive:
try:
self.command_process.kill()
except OSError:
pass
self.command_process.wait()
self.child_queue.put(None)
def retrieve(self):
"""Copy the working directory back out of the sandbox."""
if self.is_alive:
raise SandboxError("Tried to retrieve sandbox while still alive")
pass
def release(self):
"""Release the sandbox for further use
If running in a jail unlocks and releases the jail for reuse by others.
Must be called exactly once after Sandbox.kill has been called.
"""
if self.is_alive:
raise SandboxError("Sandbox released while still alive")
pass
def pause(self):
"""Pause the process by sending a SIGSTOP to the child
A limitation of the method is it will only pause the initial
child process created any further (grandchild) processes created
will not be paused.
This method is a no-op on Windows.
"""
try:
self.command_process.send_signal(signal.SIGSTOP)
except (ValueError, AttributeError, OSError):
pass
def resume(self):
"""Resume the process by sending a SIGCONT to the child
This method is a no-op on Windows
"""
try:
self.command_process.send_signal(signal.SIGCONT)
except (ValueError, AttributeError, OSError):
pass
def _child_writer(self):
queue = self.child_queue
stdin = self.command_process.stdin
while True:
ln = queue.get()
if ln is None:
break
try:
stdin.write(ln)
stdin.flush()
except (OSError, IOError):
self.kill()
break
def write(self, str):
"""Write str to stdin of the process being run"""
if not self.is_alive:
return False
self.child_queue.put(str)
def write_line(self, line):
"""Write line to stdin of the process being run
A newline is appended to line and written to stdin of the child process
"""
if not self.is_alive:
return False
self.child_queue.put(line + "\n")
def read_line(self, timeout=0):
"""Read line from child process
Returns a line of the child process' stdout, if one isn't available
within timeout seconds it returns None. Also guaranteed to return None
at least once after each command that is run in the sandbox.
"""
if not self.is_alive:
timeout=0
try:
return self.stdout_queue.get(block=True, timeout=timeout)
except Empty:
return None
def read_error(self, timeout=0):
"""Read line from child process' stderr
Returns a line of the child process' stderr, if one isn't available
within timeout seconds it returns None. Also guaranteed to return None
at least once after each command that is run in the sandbox.
"""
if not self.is_alive:
timeout=0
try:
return self.stderr_queue.get(block=True, timeout=timeout)
except Empty:
return None
def check_path(self, path, errors):
resolved_path = os.path.join(self.working_directory, path)
if not os.path.exists(resolved_path):
errors.append("Output file " + str(path) + " was not created.")
return False
else:
return True
def get_sandbox(working_dir, secure=None):
if secure is None:
secure = _SECURE_DEFAULT
if secure:
return Jail(working_dir)
else:
return House(working_dir)
def main():
parser = OptionParser(usage="usage: %prog [options] <command to run>")
parser.add_option("-d", "--directory", action="store", dest="working_dir",
default=".",
help="Working directory to run command in (copied in secure mode)")
parser.add_option("-l", action="append", dest="send_lines", default=list(),
help="String to send as a line on commands stdin")
parser.add_option("-s", "--send-delay", action="store", dest="send_delay",
type="float", default=0.0,
help="Time in seconds to sleep after sending a line")
parser.add_option("-r", "--receive-wait", action="store",
dest="resp_wait", type="float", default=600,
help="Time in seconds to wait for another response line")
parser.add_option("-j", "--jail", action="store_true", dest="secure",
default=_SECURE_DEFAULT,
help="Run in a secure jail")
parser.add_option("-o", "--open", action="store_false", dest="secure",
help="Run without using a secure jail")
options, args = parser.parse_args()
if len(args) == 0:
parser.error("Must include a command to run.\
\nRun with --help for more information.")
print("Using secure sandbox: %s" % (options.secure))
print("Sandbox working directory: %s" % (options.working_dir))
sandbox = get_sandbox(options.working_dir, secure=options.secure)
try:
print()
sandbox.start(" ".join(args))
for line in options.send_lines:
sandbox.write_line(line)
print("sent: " + line)
time.sleep(options.send_delay)
while True:
response = sandbox.read_line(options.resp_wait)
if response is None:
print()
print("No more responses. Terminating.")
break
print("response: " + response)
sandbox.kill()
finally:
sandbox.release()
if __name__ == "__main__":
main()
playonegame.cmd
@echo off
python "%~dp0playgame.py" --engine_seed 42 --player_seed 42 --end_wait=0.25 --verbose --log_dir game_logs --turns 500 --map_file "%~dp0maps\maze\maze_04p_01.map" %* "python ""%~dp0sample_bots\python\GreedyBot.py""" "python ""%~dp0sample_bots\python\LeftyBot.py""" "python ""%~dp0sample_bots\python\HunterBot.py""" "python ""%~dp0sample_bots\python\RandomBot.py"""