Exemplo n.º 1
0
 def do_setup(self):
     # initialize data structures after learning the game settings
     self.food_influence = LinearInfluence(self.gamestate)
     self.raze_influence = LinearInfluence(self.gamestate)
     self.defense_influence = LinearInfluence(self.gamestate)
     self.explore_influence = Influence(self.gamestate)
     self.planner = Planner(self.gamestate)
Exemplo n.º 2
0
 def test_diffuse_parent_greater_than_children_single_pass(self):
     'make sure parent is greater or equal to child after single pass'
     pickle_file = open('test_data/influence_test/turn_1.gamestate', 'r')
     gamestate = pickle.load(pickle_file)
     pickle_file.close()
     gamestate.map = np.array(gamestate.map)
     inf = Influence(gamestate)
     
     #print(str(gamestate.food_list))
     for food_loc in gamestate.food_list:
         inf.map[food_loc] = 4
         
     for i in xrange(5):
         inf.diffuse()
         for food_loc in gamestate.food_list:
             food_val = inf.map[food_loc]
             for n_loc in gamestate.get_neighbour_locs(food_loc):
                 #print('%f => %f' % (food_val, inf.map[n_loc]))
                 self.assertTrue(food_val >= inf.map[n_loc])
Exemplo n.º 3
0
 def test_diffuse_parent_greater_than_children_multi_pass(self):
     'make sure each level of parent is greater than its children after multiple passes'
     pickle_file = open('test_data/influence_test/turn_1.gamestate', 'r')
     gamestate = pickle.load(pickle_file)
     pickle_file.close()
     inf = Influence(gamestate)
     
     for food_loc in gamestate.food_list:
         inf.map[food_loc] += 4
              
     for i in xrange(5):
         inf.diffuse()
     for food_loc in gamestate.food_list:
         food_val = inf.map[food_loc]
         for n_loc in gamestate.get_neighbour_locs(food_loc):
             n_val = inf.map[n_loc]
             self.assertTrue(food_val > n_val)
             for nn_loc in [loc for loc in gamestate.get_neighbour_locs(n_loc) if loc != food_loc]:
                 nn_val = inf.map[nn_loc]
                 self.assertTrue(n_val > nn_val)
Exemplo n.º 4
0
 def test_diffuse_total_value_no_change(self):
     'make sure total value of all sources do not change'
     pickle_file = open('test_data/influence_test/turn_1.gamestate', 'r')
     gamestate = pickle.load(pickle_file)
     pickle_file.close()
     inf = Influence(gamestate)
     
     inf.map[(23,18)] = 100
     inf.map[(10,10)] = 100
     inf.map[(26, 18)] = -10
     for i in xrange(50):
         inf.diffuse()
Exemplo n.º 5
0
 def test_diffuse_with_water(self):
     'make sure influence next to water diffuse slower than open locations'
     pickle_file = open('test_data/influence_test/turn_1.gamestate', 'r')
     gamestate = pickle.load(pickle_file)
     pickle_file.close()
     inf = Influence(gamestate)
     
     inf.map[(23,18)] = 10
     inf.map[(10,10)] = 10
     for i in xrange(5):
         inf.diffuse()
         #print ('%f, %f' % (inf.map[(23,18)], inf.map[(10,10)]))
     self.assertTrue(inf.map[(23,18)] < inf.map[(10,10)])            
Exemplo n.º 6
0
 def test_diffuse_multi_source_overshadow(self):
     'make sure larger influence over shadows the smaller opposite influence right next'
     pickle_file = open('test_data/influence_test/turn_1.gamestate', 'r')
     gamestate = pickle.load(pickle_file)
     pickle_file.close()
     inf = Influence(gamestate)
     
     x = 25
     y = 20
     inf.map[(x, y)] = 10
     inf.map[(x+1, y)] = -1
     for i in xrange(5):
         inf.diffuse()
     
     self.assertTrue(inf.map[(x,y)] > inf.map[(x+1, y)])
     self.assertTrue(inf.map[(x+1,y)] > inf.map[(x+2, y)])
     self.assertTrue(inf.map[(x+2,y)] > inf.map[(x+3, y)])
     self.assertTrue(inf.map[(x+3,y)] > inf.map[(x+4, y)])
     self.assertTrue(inf.map[(x+2,y)] > inf.map[(x+2,y+1)])
     self.assertTrue(inf.map[(x+3,y)] > inf.map[(x+3, y-1)])
Exemplo n.º 7
0
class MyBot:
    def __init__(self, gamestate):
        # define class level variables, will be remembered between turns
        self.gamestate = gamestate
        self.diffuse_time = 0
        self.combat_time_history = deque([0, 0, 0, 0, 0])
        self.combat_time = 0
        self.explore_time = 0

    # do_setup is run once at the start of the game
    # after the bot has received the game settings
    def do_setup(self):
        # initialize data structures after learning the game settings
        self.food_influence = LinearInfluence(self.gamestate)
        self.raze_influence = LinearInfluence(self.gamestate)
        self.defense_influence = LinearInfluence(self.gamestate)
        self.explore_influence = Influence(self.gamestate)
        self.planner = Planner(self.gamestate)

    def log_turn(self, turn_no):
        debug_logger.debug('turn ' + str(self.gamestate.current_turn))
        perf_logger.debug('turn ' + str(self.gamestate.current_turn))
        perf_logger.debug('self.diffuse_time = %d' % self.diffuse_time)
        perf_logger.debug('self.combat_time_history = %s' % str(self.combat_time_history))
        perf_logger.debug('self.combat_time = %s' % self.combat_time)
        perf_logger.debug('self.explore_time = %d' % self.explore_time)

    def log_detail(self):
        if DETAIL_LOG and os.path.isdir('pickle') and \
            int(self.gamestate.current_turn) > DETAIL_LOG_START and \
            int(self.gamestate.current_turn) % 10 == 0 \
            :
            # dump gamestate
            pickle_file = open('pickle/turn_' + str(self.gamestate.current_turn) + '.gamestate', 'wb')
            pickle.dump(self.gamestate, pickle_file)
            pickle_file.close()

            # dump influence map value
            # pickle_file = open('pickle/turn_' + str(self.gamestate.current_turn) + '.influence', 'wb')
            # pickle.dump(self.explore_influence, pickle_file)
            # pickle_file.close()

    # do turn is run once per turn
    def do_turn(self):
        # detailed logging
        self.log_turn(self.gamestate.current_turn)

        # razing nearby hill takes precedence
        self.raze_override()

        # update aggressiveness
        self.planner.update_aggressiveness(self.explore_influence)

        # handle combat
        combat_start = self.gamestate.time_remaining()
        self.issue_combat_task()
        self.combat_time_history.append(combat_start - self.gamestate.time_remaining())
        self.combat_time_history.popleft()
        self.combat_time = max(self.combat_time_history)

        # use planner to set new influence
        perf_logger.debug('food_influence.start = %s' % str(self.gamestate.time_elapsed()))
        self.planner.update_food_influence(self.food_influence)
        perf_logger.debug('raze_influence.start = %s' % str(self.gamestate.time_elapsed()))
        debug_logger.debug('razing distance = %d' % -self.planner.enemy_hill_value)
        self.planner.update_raze_influence(self.raze_influence)
        perf_logger.debug('defense_influence.start = %s' % str(self.gamestate.time_elapsed()))
        self.planner.update_defense_influence(self.defense_influence)
        perf_logger.debug('explore_influence.start = %s' % str(self.gamestate.time_elapsed()))
        self.planner.update_explore_influence(self.explore_influence)
        perf_logger.debug('influences.finish = %s' % str(self.gamestate.time_elapsed()))

        # decay strategy influence
        self.explore_influence.decay(DECAY_RATE)

        # diffuse explore_influence, which is the only one using molecular diffusion
        perf_logger.debug('explore_influence.diffuse().start = %s' % str(self.gamestate.time_elapsed()))
        for i in xrange(30):
            if self.gamestate.time_remaining() < self.diffuse_time + self.explore_time + 50:
                perf_logger.debug('bailing diffuse after %d times' % (i))
                break
            diffuse_start = self.gamestate.time_remaining()
            self.explore_influence.diffuse()
            diffuse_duration = diffuse_start - self.gamestate.time_remaining()
            self.diffuse_time = max([diffuse_duration, self.diffuse_time])
        self.diffuse_time -= 1
        perf_logger.debug('explore_influence.diffuse().finish = %s' % str(self.gamestate.time_elapsed()))

        self.log_detail()

        # merge all the influences into a temporary map
        perf_logger.debug('merging map.start = %s' % str(self.gamestate.time_elapsed()))
        merged_map = self.food_influence.map * FOOD_WEIGHT + self.raze_influence.map * RAZE_WEIGHT + \
                    self.defense_influence.map * DEFENSE_WEIGHT + self.explore_influence.map
        perf_logger.debug('merging map.finish = %s' % str(self.gamestate.time_elapsed()))

        explore_start = self.gamestate.time_remaining()
        # avoidance explorer
        self.avoidance_explore(merged_map)
        perf_logger.debug('self.avoidance_explore.finish = %s' % str(self.gamestate.time_elapsed()))

        # normal explore
        self.normal_explore(merged_map)
        self.explore_time = max([explore_start - self.gamestate.time_remaining(), self.explore_time]) - 1
        perf_logger.debug('self.normal_explore.finish = %s' % str(self.gamestate.time_elapsed()))
        perf_logger.debug('endturn: my_ants count = %d, time_elapsed = %s' % (len(self.gamestate.my_ants()), self.gamestate.time_elapsed()))

    def raze_override(self):
        'razing close-by hill takes precedence over combat'
        for hill_loc, owner in self.gamestate.enemy_hills():
            for n_loc in self.gamestate.neighbour_table[hill_loc]:
                if n_loc in self.gamestate.my_ants():
                    self.gamestate.issue_order_by_location(n_loc, hill_loc)

    def issue_combat_task(self):
        'combat logic'
        perf_logger.debug('issue_combat_task.start = ' + str(self.gamestate.time_elapsed()))
        battle.do_combat(self.gamestate)
        perf_logger.debug('issue_combat_task.finish = ' + str(self.gamestate.time_elapsed()))

    def get_desired_moves(self, ant, map):
        desired_moves = []
        neighbours_and_influences = sorted([(map[loc], loc) for loc in self.gamestate.passable_moves(ant)])
        debug_logger.debug('neighbours_and_influences = %s' % str(neighbours_and_influences))
        for inf, n_loc in neighbours_and_influences:
            desired_moves.append(n_loc)

        return desired_moves

    def normal_explore(self, merged_map):
        'only concern influence'
        for my_ant in sorted(self.gamestate.my_unmoved_ants()):
            debug_logger.debug('normal_explore task for %s' % str(my_ant))
            desired_moves = self.get_desired_moves(my_ant, merged_map)
            if len(desired_moves) > 0:
                # do the move
                self.gamestate.issue_order_by_location(my_ant, desired_moves[0])
            else:
                debug_logger.debug('ERROR: no valid move for ant = %s' % str(my_ant))

            # check if we still have time left to calculate more orders
            if self.gamestate.time_remaining() < 10:
                perf_logger.debug('bailing normal explore')
                break

    def avoidance_explore(self, merged_map):
        'explore under enemies presence'
        avoidance_distance = self.gamestate.euclidean_distance_add(self.gamestate.attackradius2, 2)
        for my_ant in self.gamestate.my_unmoved_ants():
            enemy_ants = [enemy_ant for enemy_ant, owner in self.gamestate.enemy_ants()
                        if self.gamestate.euclidean_distance2(my_ant, enemy_ant) <= avoidance_distance]
            if len(enemy_ants) > 0:
                debug_logger.debug('avoidance_explore task for %s' % str(my_ant))
                desired_moves = self.get_desired_moves(my_ant, merged_map)
                move_distances = {move:min([self.gamestate.euclidean_distance2(move, enemy_ant) for enemy_ant in enemy_ants])
                                    for move in desired_moves}
                debug_logger.debug('move_distances = %s' % str(move_distances))

                safe_distance = self.gamestate.euclidean_distance_add(self.gamestate.attackradius2, 1)
                # don't initiate 1 on 1 exchange, but don't be afraid
                desired_distance = self.gamestate.attackradius2
                # be safer if more enemies are around
                # also if only 1 move is within risky zone (risky zone = possible clash if both ants advances)
                if len(enemy_ants) > 1 or len([d for m,d in move_distances.items() if d < safe_distance]) <= 2:
                    desired_distance = safe_distance

                # go for lowest influence that's safe
                best_move = None
                for move in desired_moves:
                    if move_distances[move] > desired_distance:
                        best_move = move
                        break
                # if no safe move try largest distance
                if best_move is None:
                    best_move = max(move_distances, key=move_distances.get)

                # do the move
                directions = self.gamestate.direction(my_ant, best_move) + [None]
                self.gamestate.issue_order_by_location(my_ant, best_move)
                debug_logger.debug('moving %s' % str((my_ant, directions[0])))

            # check if we still have time left to calculate more orders
            if self.gamestate.time_remaining() < 20:
                perf_logger.debug('bailing avoidance explore')
                break

    # static methods are not tied to a class and don't have self passed in
    # this is a python decorator
    @staticmethod
    def run():
        'parse input, update game state and call the bot classes do_turn method'
        gamestate = GameState()
        bot = MyBot(gamestate)
        map_data = ''
        while(True):
            try:
                current_line = sys.stdin.readline().rstrip('\r\n') # string new line char
                if current_line.lower() == 'ready':
                    gamestate.setup(map_data)
                    bot.do_setup()
                    gamestate.finish_turn()
                    map_data = ''
                elif current_line.lower() == 'go':
                    gamestate.update(map_data)
                    # call the do_turn method of the class passed in
                    bot.do_turn()
                    gamestate.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()
Exemplo n.º 8
0
 def do_setup(self):
     # initialize data structures after learning the game settings
     self.strat_influence = Influence(self.gamestate)
     self.planner = Planner(self.gamestate)
Exemplo n.º 9
0
class MyBot:
    def __init__(self, gamestate):
        # define class level variables, will be remembered between turns
        self.gamestate = gamestate
        self.diffuse_time = 0
        self.combat_time_history = deque([0, 0, 0, 0, 0])
        self.combat_time = 0
    
    # do_setup is run once at the start of the game
    # after the bot has received the game settings
    def do_setup(self):
        # initialize data structures after learning the game settings
        self.strat_influence = Influence(self.gamestate)
        self.planner = Planner(self.gamestate)
    
    def log_turn(self, turn_no):
        logging.debug('turn ' + str(self.gamestate.current_turn))
        logging.debug('self.diffuse_time = %d' % self.diffuse_time)
        logging.debug('self.combat_time_history = %s' % str(self.combat_time_history))
        logging.debug('self.combat_time = %s' % self.combat_time)
        # logging.debug('self.strat_influence.map over 0.01 count: %d' % 
            # len([key for key in self.strat_influence.map if math.fabs(self.strat_influence.map[key]) > 0.01]))
            
    def log_detail(self):
        if DETAIL_LOG and os.path.isdir('pickle'):# and int(self.gamestate.current_turn) % 10 == 0:
            # dump gamestate
            pickle_file = open('pickle/turn_' + str(self.gamestate.current_turn) + '.gamestate', 'wb')
            pickle.dump(self.gamestate, pickle_file)
            pickle_file.close()
            
            # dump influence map value
            pickle_file = open('pickle/turn_' + str(self.gamestate.current_turn) + '.influence', 'wb')
            pickle.dump(self.strat_influence, pickle_file)
            pickle_file.close()
    
    # do turn is run once per turn
    def do_turn(self):        
        # detailed logging
        self.log_turn(self.gamestate.current_turn)
        
        # decay strategy influence
        #logging.debug('strat_influence.decay().start = %s' % str(self.gamestate.time_remaining())) 
        self.strat_influence.decay(DECAY_RATE)
        #self.strat_influence = Influence(self.gamestate)
        #logging.debug('strat_influence.decay().finish = %s' % str(self.gamestate.time_remaining())) 
        # use planner to set new influence
        logging.debug('self.planner.do_strategy_plan.start = %s' % str(self.gamestate.time_remaining()))
        self.planner.do_strategy_plan(self.strat_influence)
        # for row in range(self.strat_influence.map.shape[0]):
            # for col in range(self.strat_influence.map.shape[1]):
                # if math.fabs(self.strat_influence.map[row,col]):
                    # logging.debug('%d, %d = %f' % (row, col, self.strat_influence.map[row,col]))
        
        # diffuse strategy influence
        logging.debug('strat_influence.diffuse().start = %s' % str(self.gamestate.time_remaining()))        
        for i in xrange(10):
            if self.gamestate.time_remaining() <  self.combat_time + 100:
                logging.debug('bailing diffuse after %d times' % (i))
                break
            diffuse_start = self.gamestate.time_remaining()
            self.strat_influence.diffuse()
            diffuse_duration = diffuse_start - self.gamestate.time_remaining()
            self.diffuse_time = max([diffuse_duration, self.diffuse_time])
        self.diffuse_time -= 1
        logging.debug('strat_influence.diffuse().finish = %s' % str(self.gamestate.time_remaining())) 

        # razing nearby hill takes precedence
        self.raze_override()
        
        # handle combat
        combat_start = self.gamestate.time_remaining()
        self.issue_combat_task()
        self.combat_time_history.append(combat_start - self.gamestate.time_remaining())
        self.combat_time_history.popleft()
        self.combat_time = max(self.combat_time_history)
        
        self.log_detail()
        # handle explorer
        self.issue_explore_task()
        logging.debug('endturn: my_ants count = %d, time_elapsed = %s' % (len(self.gamestate.my_ants()), self.gamestate.time_elapsed()))

    def raze_override(self):
        'razing close-by hill takes precedence over combat'
        for hill_loc, owner in self.gamestate.enemy_hills():
            for n_loc in self.gamestate.neighbour_table[hill_loc]:
                if n_loc in self.gamestate.my_ants():
                    direction = self.gamestate.direction(n_loc, hill_loc) + [None]
                    self.gamestate.issue_order((n_loc, direction[0]))
        
    def issue_combat_task(self):
        'combat logic'
        logging.debug('issue_combat_task.start = %s' % str(self.gamestate.time_remaining())) 
        zones = battle.get_combat_zones(self.gamestate)
        logging.debug('get_combat_zones.finish = %s' % str(self.gamestate.time_remaining())) 
        
        if zones is not None:
            logging.debug('zones.count = %d' % len(zones))
            for zone in zones:
                if len(zone[0]) > 0:
                    logging.debug('group combat loop for = %s' % str(zone))
                    logging.debug('do_zone_combat.start = %s' % str(self.gamestate.time_remaining())) 
                    battle.do_zone_combat(self.gamestate, zone)
                    logging.debug('do_zone_combat.start = %s' % str(self.gamestate.time_remaining())) 
                
                # check if we still have time left to calculate more orders
                if self.gamestate.time_remaining() < 50:
                    break
                
        logging.debug('issue_combat_task.finish = ' + str(self.gamestate.time_remaining())) 
    
    def normal_explore(self, my_ant):
        'only concern influence'
        loc_influences = {}
        for d in self.gamestate.passable_directions(my_ant):
            loc_influences[d] = self.strat_influence.map[self.gamestate.destination(my_ant, d)]
            
        #logging.debug('my_ant = %s, loc_influences = %s' % (str(my_ant),str(loc_influences)))
        if len(loc_influences) > 0:
            best_directions = min(loc_influences, key=loc_influences.get)
            logging.debug('moving %s to %s' % (str(my_ant), str(best_directions)))
            self.gamestate.issue_order((my_ant, best_directions))
    
    def avoidance_explore(self, my_ant, enemy_ants):
        'explore under enemies presence'
        safe_distance = self.gamestate.euclidean_distance_add(self.gamestate.attackradius2, 1)
        nav_info = []
        for loc in self.gamestate.neighbour_table[my_ant] + [my_ant]:
            influence = self.strat_influence.map[loc]
            distance = min([self.gamestate.euclidean_distance2(loc, enemy_ant) for enemy_ant in enemy_ants])
            nav_info.append((influence, distance, loc))
            
        # go for lowest influence that's safe
        nav_info.sort()
        best_loc = None
        for info in nav_info:
            (influence, distance, loc) = info
            if distance > safe_distance:
                best_loc = loc
                break
        # if no safe move try largest distance
        if best_loc is None:
            (influence, distance, loc) = sorted(nav_info, key=lambda x: x[1])[0]
            best_loc = loc
            
        # do the move
        directions = self.gamestate.direction(my_ant, best_loc) + [None]
        self.gamestate.issue_order((my_ant, directions[0]))
        
    def issue_explore_task(self):
        'explore map based on influence'
        logging.debug('issue_explore_task.start = %s' % str(self.gamestate.time_remaining())) 
        # loop through all my un-moved ants and set them to explore
        # the ant_loc is an ant location tuple in (row, col) form
        avoidance_distance = self.gamestate.euclidean_distance_add(self.gamestate.attackradius2, 2)
        for my_ant in self.gamestate.my_unmoved_ants():
            enemy_ants = [enemy_ant for enemy_ant, owner in self.gamestate.enemy_ants() 
                        if self.gamestate.euclidean_distance2(my_ant, enemy_ant) <= avoidance_distance]
            if len(enemy_ants) > 0:
                self.avoidance_explore(my_ant, enemy_ants)
            else:
                self.normal_explore(my_ant)
            
            # check if we still have time left to calculate more orders
            if self.gamestate.time_remaining() < 10:
                break
        logging.debug('issue_explore_task.finish = ' + str(self.gamestate.time_remaining()))

    # static methods are not tied to a class and don't have self passed in
    # this is a python decorator
    @staticmethod
    def run():
        'parse input, update game state and call the bot classes do_turn method'
        gamestate = GameState()
        bot = MyBot(gamestate)
        map_data = ''
        while(True):
            try:
                current_line = sys.stdin.readline().rstrip('\r\n') # string new line char
                if current_line.lower() == 'ready':
                    gamestate.setup(map_data)
                    bot.do_setup()
                    gamestate.finish_turn()
                    map_data = ''
                elif current_line.lower() == 'go':
                    gamestate.update(map_data)
                    # call the do_turn method of the class passed in
                    bot.do_turn()
                    gamestate.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()
Exemplo n.º 10
0
class MyBot:
    def __init__(self, gamestate):
        # define class level variables, will be remembered between turns
        self.gamestate = gamestate
        self.diffuse_time = 0
        self.combat_time_history = deque([0, 0, 0, 0, 0])
        self.combat_time = 0

    # do_setup is run once at the start of the game
    # after the bot has received the game settings
    def do_setup(self):
        # initialize data structures after learning the game settings
        self.food_influence = LinearInfluence(self.gamestate)
        self.raze_influence = LinearInfluence(self.gamestate)
        self.defense_influence = LinearInfluence(self.gamestate)
        self.explore_influence = Influence(self.gamestate)
        self.planner = Planner(self.gamestate)

    def log_turn(self, turn_no):
        debug_logger.debug("turn " + str(self.gamestate.current_turn))
        perf_logger.debug("turn " + str(self.gamestate.current_turn))
        perf_logger.debug("self.diffuse_time = %d" % self.diffuse_time)
        perf_logger.debug("self.combat_time_history = %s" % str(self.combat_time_history))
        perf_logger.debug("self.combat_time = %s" % self.combat_time)

    def log_detail(self):
        if DETAIL_LOG and os.path.isdir("pickle"):  # and int(self.gamestate.current_turn) % 10 == 0:
            # dump gamestate
            pickle_file = open("pickle/turn_" + str(self.gamestate.current_turn) + ".gamestate", "wb")
            pickle.dump(self.gamestate, pickle_file)
            pickle_file.close()

            # dump influence map value
            pickle_file = open("pickle/turn_" + str(self.gamestate.current_turn) + ".influence", "wb")
            pickle.dump(self.explore_influence, pickle_file)
            pickle_file.close()

    # do turn is run once per turn
    def do_turn(self):
        # detailed logging
        self.log_turn(self.gamestate.current_turn)

        # razing nearby hill takes precedence
        self.raze_override()

        # handle combat
        combat_start = self.gamestate.time_remaining()
        self.issue_combat_task()
        self.combat_time_history.append(combat_start - self.gamestate.time_remaining())
        self.combat_time_history.popleft()
        self.combat_time = max(self.combat_time_history)

        # use planner to set new influence
        perf_logger.debug("self.update_influences.start = %s" % str(self.gamestate.time_elapsed()))
        self.planner.update_food_influence(self.food_influence)
        perf_logger.debug("self.update_food_influence.finish = %s" % str(self.gamestate.time_elapsed()))
        self.planner.update_raze_influence(self.raze_influence)
        perf_logger.debug("self.update_raze_influence.finish = %s" % str(self.gamestate.time_elapsed()))
        self.planner.update_defense_influence(self.defense_influence)
        perf_logger.debug("self.update_defense_influence.finish = %s" % str(self.gamestate.time_elapsed()))
        self.planner.update_explore_influence(self.explore_influence)
        perf_logger.debug("self.update_explore_influence.finish = %s" % str(self.gamestate.time_elapsed()))

        # decay strategy influence
        self.explore_influence.decay(DECAY_RATE)

        # diffuse explore_influence, which is the only one using molecular diffusion
        perf_logger.debug("explore_influence.diffuse().start = %s" % str(self.gamestate.time_elapsed()))
        expected_explore_time = len(self.gamestate.my_ants()) / 2 + 20
        for i in xrange(30):
            if self.gamestate.time_remaining() < self.diffuse_time + expected_explore_time:
                perf_logger.debug("bailing diffuse after %d times" % (i))
                break
            diffuse_start = self.gamestate.time_remaining()
            self.explore_influence.diffuse()
            diffuse_duration = diffuse_start - self.gamestate.time_remaining()
            self.diffuse_time = max([diffuse_duration, self.diffuse_time])
        self.diffuse_time -= 1
        perf_logger.debug("explore_influence.diffuse().finish = %s" % str(self.gamestate.time_elapsed()))

        self.log_detail()

        # avoidance explorer
        self.avoidance_explore()
        perf_logger.debug("self.avoidance_explore.finish = %s" % str(self.gamestate.time_elapsed()))

        # normal explore
        self.normal_explore()
        perf_logger.debug("self.normal_explore.finish = %s" % str(self.gamestate.time_elapsed()))
        perf_logger.debug(
            "endturn: my_ants count = %d, time_elapsed = %s"
            % (len(self.gamestate.my_ants()), self.gamestate.time_elapsed())
        )

    def raze_override(self):
        "razing close-by hill takes precedence over combat"
        for hill_loc, owner in self.gamestate.enemy_hills():
            for n_loc in self.gamestate.neighbour_table[hill_loc]:
                if n_loc in self.gamestate.my_ants():
                    direction = self.gamestate.direction(n_loc, hill_loc) + [None]
                    self.gamestate.issue_order((n_loc, direction[0]))

    def issue_combat_task(self):
        "combat logic"
        perf_logger.debug("issue_combat_task.start = %s" % str(self.gamestate.time_remaining()))
        zones = battle.get_combat_zones(self.gamestate)
        perf_logger.debug("get_combat_zones.finish = %s" % str(self.gamestate.time_remaining()))

        if zones is not None:
            debug_logger.debug("zones.count = %d" % len(zones))
            i = 0
            for zone in zones:
                i += 1
                # debug_logger.debug('group combat loop for = %s' % str(zone))
                # perf_logger.debug('do_zone_combat.start = %s' % str(self.gamestate.time_remaining()))
                battle.do_zone_combat(self.gamestate, zone)
                # perf_logger.debug('do_zone_combat.start = %s' % str(self.gamestate.time_remaining()))

                # check if we still have time left to calculate more orders
                if self.gamestate.time_remaining() < 100:
                    debug_logger.debug("bailing combat zone after %d times" % (i))
                    break

        perf_logger.debug("issue_combat_task.finish = " + str(self.gamestate.time_remaining()))

    def get_desired_moves(self, ant):
        desired_moves = []
        # food
        desired_moves.extend(self.get_desired_move_from_linear_influence(ant, self.food_influence))
        debug_logger.debug("desired_moves.food = %s" % str(desired_moves))

        # defend hill
        desired_moves.extend(self.get_desired_move_from_linear_influence(ant, self.defense_influence))
        debug_logger.debug("desired_moves.defense = %s" % str(desired_moves))

        # raze hill
        desired_moves.extend(self.get_desired_move_from_linear_influence(ant, self.raze_influence))
        debug_logger.debug("desired_moves.raze = %s" % str(desired_moves))

        # explore
        desired_moves.extend(self.get_desired_move_from_molecular_influence(ant, self.explore_influence))
        debug_logger.debug("desired_moves.explore = %s" % str(desired_moves))

        # uniquify
        seen = set()
        seen_add = seen.add
        desired_moves = [move for move in desired_moves if move not in seen and not seen_add(move)]
        return desired_moves

    def get_desired_move_from_molecular_influence(self, ant, influence):
        desired_moves = []
        neighbours_and_influences = sorted(
            [(influence.map[loc], loc) for loc in [ant] + self.gamestate.passable_neighbours(ant)]
        )
        debug_logger.debug("neighbours_and_influences = %s" % str(neighbours_and_influences))
        for inf, n_loc in neighbours_and_influences:
            desired_moves.append(n_loc)

        return desired_moves

    def get_desired_move_from_linear_influence(self, ant, influence):
        desired_moves = []
        if influence.map[ant] != 0:
            neighbours_and_influences = sorted(
                [(influence.map[loc], loc) for loc in [ant] + self.gamestate.passable_neighbours(ant)]
            )
            debug_logger.debug("neighbours_and_influences = %s" % str(neighbours_and_influences))
            for inf, n_loc in neighbours_and_influences:
                if inf < influence.map[ant]:
                    desired_moves.append(n_loc)

        return desired_moves

    def normal_explore(self):
        "only concern influence"
        for my_ant in self.gamestate.my_unmoved_ants():
            debug_logger.debug("normal explore task for %s" % str(my_ant))
            desired_moves = self.get_desired_moves(my_ant)
            if len(desired_moves) > 0:
                move = desired_moves[0]
                # do the move
                directions = self.gamestate.direction(my_ant, move) + [None]
                self.gamestate.issue_order((my_ant, directions[0]))

            # check if we still have time left to calculate more orders
            if self.gamestate.time_remaining() < 10:
                break

    def avoidance_explore(self):
        "explore under enemies presence"
        avoidance_distance = self.gamestate.euclidean_distance_add(self.gamestate.attackradius2, 2)
        for my_ant in self.gamestate.my_unmoved_ants():
            enemy_ants = [
                enemy_ant
                for enemy_ant, owner in self.gamestate.enemy_ants()
                if self.gamestate.euclidean_distance2(my_ant, enemy_ant) <= avoidance_distance
            ]
            if len(enemy_ants) > 0:
                # don't initiate 1 on 1 exchange, but don't be afraid
                safe_distance = self.gamestate.attackradius2
                if len(enemy_ants) > 1:
                    # be safer if more enemies are around
                    safe_distance = self.gamestate.euclidean_distance_add(self.gamestate.attackradius2, 1)
                desired_moves = self.get_desired_moves(my_ant)
                move_distances = {
                    move: min([self.gamestate.euclidean_distance2(move, enemy_ant) for enemy_ant in enemy_ants])
                    for move in desired_moves
                }
                debug_logger.debug("move_distances = %s" % str(move_distances))
                # go for lowest influence that's safe
                best_move = None
                for move in desired_moves:
                    if move_distances[move] > safe_distance:
                        best_move = move
                        break
                # if no safe move try largest distance
                if best_move is None:
                    best_move = max(move_distances, key=move_distances.get)

                # do the move
                directions = self.gamestate.direction(my_ant, best_move) + [None]
                self.gamestate.issue_order((my_ant, directions[0]))
                debug_logger.debug("moving %s" % str((my_ant, directions[0])))

            # check if we still have time left to calculate more orders
            if self.gamestate.time_remaining() < 30:
                break

    # static methods are not tied to a class and don't have self passed in
    # this is a python decorator
    @staticmethod
    def run():
        "parse input, update game state and call the bot classes do_turn method"
        gamestate = GameState()
        bot = MyBot(gamestate)
        map_data = ""
        while True:
            try:
                current_line = sys.stdin.readline().rstrip("\r\n")  # string new line char
                if current_line.lower() == "ready":
                    gamestate.setup(map_data)
                    bot.do_setup()
                    gamestate.finish_turn()
                    map_data = ""
                elif current_line.lower() == "go":
                    gamestate.update(map_data)
                    # call the do_turn method of the class passed in
                    bot.do_turn()
                    gamestate.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()