Esempio n. 1
0
class MyBot(BaseBot):

    def zeros(self,rows,cols):
        row = []
        data = []
        for i in range(cols):
            row.append(0)
        for i in range(rows):
            data.append(row[:])
        return data

    # v = list of item values or profit
    # w = list of item weight or cost
    # W = max weight or max cost for the knapsack
    def zeroOneKnapsack(self, v, w, W):
        # c is the cost matrix
        c = []
        n = len(v)
        c = self.zeros(n,W+1)
        for i in range(0,n):
            #for ever possible weight
            for j in range(0,W+1):
                        #can we add this item to this?
                if (w[i] > j):
                    c[i][j] = c[i-1][j]
                else:
                    c[i][j] = max(c[i-1][j],v[i] +c[i-1][j-w[i]])
        return [c[n-1][W], self.getUsedItems(w,c)]

    # w = list of item weight or cost
    # c = the cost matrix created by the dynamic programming solution
    def getUsedItems(self,w,c):
        # item count
        i = len(c)-1
        currentW =  len(c[0])-1
        # set everything to not marked
        marked = []
        for i in range(i+1):
            marked.append(0)
        while (i >= 0 and currentW >=0):
            if (i==0 and c[i][currentW] >0 )or c[i][currentW] != c[i-1][currentW]:
                marked[i] =1
                currentW = currentW-w[i]
            i = i-1
        return marked

    def total_fleet_ship_count(self, owner):
        return sum( [ fleet.ship_count for fleet in self.universe.find_fleets(owner) ] )

    def closest_enemy_planet_distance(self, p):
        return min((lambda ep:ep.distance(p))(ep) for ep in self.enemy_planets)

    def my_fleets_attacking(self, planet):
        return sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner == player.ME] )

    def closest_to_enemy_neutral_under_my_attack(self):
        best_distance = 1000000
        result_planet = None
        for planet in self.nobodies_planets:
            if self.my_fleets_attacking(planet) > 0:
                distance = self.enemy_com.distance(planet)
                if distance < best_distance:
                    best_distance = distance
                    result_planet = planet
        return result_planet

    def get_attack_ship_count_first_turn(self, planet_to_attack, my_home, enemy_home):
        my_dist = my_home.distance(planet_to_attack)
        enemy_dist = enemy_home.distance(planet_to_attack)
        #log.info("Distances for %s are %s %s" % (planet_to_attack, my_dist, enemy_dist))
        if my_dist < enemy_dist:
            return planet_to_attack.ship_count+1
        if my_dist == enemy_dist and planet_to_attack.ship_count <= planet_to_attack.growth_rate:
            return planet_to_attack.ship_count+1
        return 1000000

    def get_neutrals_under_enemy_attack(self):
        result = []
        for planet in self.nobodies_planets:
            if sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner in player.NOT_ME ] ) > 0:
                result.append(planet)
        return result

    def get_neutrals_under_my_attack(self):
        result = []
        for planet in self.nobodies_planets:
            if sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner == player.ME ] ) > 0:
                result.append(planet)
        return result

    def get_sum_enemy_growth_within_dist(self, planet, distance):
        result = 0
        for p in self.enemy_planets:
            if p.id != planet.id and p.distance(planet) <= distance:
                result += p.growth_rate
        return result

    def doPrep(self):
        log.info("Prep phase")

        self.max_distance_between_planets = 0
        for p1 in self.all_planets:
            for p2 in self.all_planets:
                self.max_distance_between_planets = max(self.max_distance_between_planets, p1.distance(p2))
        #log.info("Max distance: %s" % self.max_distance_between_planets)

        # calculate current high level metrics
        self.my_total_ships_available = 0
        self.my_total_ships = 0
        self.my_total_growth_rate = 0
        self.enemy_total_ships_available = 0
        self.enemy_total_ships = 0
        self.enemy_total_growth_rate = 0
        self.ships_available = {}
        self.ships_needed = {}
        self.planet_timeline = {}
        for planet in self.all_planets:
            if len(planet.attacking_fleets) == 0:
                self.ships_available[planet] = planet.ship_count
                self.ships_needed[planet] = 0
                simulation_distance = self.max_distance_between_planets
                self.planet_timeline[planet] = planet.in_future_timeline(simulation_distance)
            else:
                simulation_distance = self.max_distance_between_planets
                self.planet_timeline[planet] = planet.in_future_timeline(simulation_distance)
                max_needed = 0
                min_available = 1000000
                #log.info("timeline for %s: %s" % (planet, self.planet_timeline[planet]))
                for step in self.planet_timeline[planet]:
                    owner = step[0]
                    ship_count = step[1]
                    if owner != planet.owner:
                        max_needed = max(max_needed, ship_count)
                    else:
                        min_available = min(min_available, ship_count)
                if max_needed > 0:
                    # do we bail if we are going to lose this planet anyway?
                    self.ships_available[planet] = 0
                    self.ships_needed[planet] = max_needed
                else:
                    self.ships_available[planet] = min_available
                    self.ships_needed[planet] = 0

            if (planet.owner == player.ME):
                self.my_total_ships_available += self.ships_available[planet]
                self.my_total_growth_rate += planet.growth_rate
                self.my_total_ships += planet.ship_count
            else:
                self.enemy_total_ships_available += self.ships_available[planet]
                self.enemy_total_growth_rate += planet.growth_rate
                self.enemy_total_ships += planet.ship_count
            #log.info("avail ships for %s: %s" % (planet, self.ships_available[planet]))

        # prevent initial overexpansion
        if self.universe.game.turn_count <= 2:
            for my_planet in self.my_planets:
                for enemy_planet in self.enemy_planets:
                    max_enemy_fleet = self.ships_available[enemy_planet]
                    distance = my_planet.distance(enemy_planet)
                    ships_needed_for_safety = max_enemy_fleet-distance*my_planet.growth_rate
                    if ships_needed_for_safety > (my_planet.ship_count - self.ships_available[my_planet]):
                        deficit = ships_needed_for_safety - (my_planet.ship_count - self.ships_available[my_planet])
                        #log.info("deficit for %s: %s" % (my_planet, deficit))
                        if deficit > self.ships_available[my_planet]:
                            deficit = self.ships_available[my_planet]

                        self.ships_available[my_planet] -= deficit
                        self.my_total_ships_available -= deficit

        self.my_total_ships += self.total_fleet_ship_count(player.ME)
        self.enemy_total_ships += self.total_fleet_ship_count(player.NOT_ME)

        # calculate enemy's center of mass
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.enemy_planets:
            weighted_x += planet.position.x * (self.ships_available[planet] + planet.growth_rate)
            weighted_y += planet.position.y * (self.ships_available[planet] + planet.growth_rate)
            div += self.ships_available[planet] + planet.growth_rate
        if div == 0:
            div = 1

        self.enemy_com = Planet(self.universe, 666, weighted_x/div, weighted_y/div, 2, 0, 0)
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.enemy_planets:
            weighted_x += planet.position.x * (planet.growth_rate+1)
            weighted_y += planet.position.y * (planet.growth_rate+1)
            div += planet.growth_rate+1
        self.enemy_growth_com = Planet(self.universe, 666, weighted_x/div, weighted_y/div, 2, 0, 0)


        # For every planet, and every turn, calculate how many ships the enemy CAN sent to it's aid
        self.max_aid_at_turn = {}
        self.max_aid_at_turn_single = {}
        enemy_planets_incl_candidates = list(self.enemy_planets) + self.get_neutrals_under_enemy_attack()
        for planet in self.all_planets:
            self.max_aid_at_turn[planet] = {}
            self.max_aid_at_turn_single[planet] = {}
            for turn in range(1, self.max_distance_between_planets+1):
                max_aid = 0
                max_aid_single = 0
                #for enemy_planet in self.all_planets:
                for enemy_planet in enemy_planets_incl_candidates:
                    if enemy_planet.id != planet.id and planet.distance(enemy_planet) < turn:
                        enemy_planet_time_step = self.planet_timeline[enemy_planet][turn - planet.distance(enemy_planet)]
                        if (enemy_planet_time_step[0] in player.ENEMIES):
                            max_aid += enemy_planet_time_step[1]
                            max_aid_single = max(max_aid_single, enemy_planet_time_step[1])
                            #log.info("adding to max aid: %s" %  enemy_planet_time_step[1])
                    else:
                        if enemy_planet.id != planet.id and planet.distance(enemy_planet) == turn:
                            enemy_planet_time_step = self.planet_timeline[enemy_planet][0]
                            if (enemy_planet_time_step[0] in player.ENEMIES):
                                max_aid += enemy_planet.ship_count
                                max_aid_single = max(max_aid_single, enemy_planet.ship_count)
                if self.planet_timeline[planet][turn-1][0] in player.ENEMIES:
                    max_aid += self.planet_timeline[planet][turn-1][1]
                    max_aid_single = max(max_aid_single, self.planet_timeline[planet][turn-1][1])
                    #log.info("self aid: %s" % self.planet_timeline[planet][turn-1][1])
                self.max_aid_at_turn[planet][turn] = max_aid
                self.max_aid_at_turn_single[planet][turn] = max_aid_single
                #log.info("Max aid for %s at %s: %s" % (planet.id, turn, self.max_aid_at_turn[planet][turn]))
        #log.info("Max aid: %s" % self.max_aid_at_turn)

        # For every planet, and every turn, calculate how many ships I CAN send to its aid
        self.my_max_aid_at_turn = {}
        my_planets_incl_candidates = list(self.my_planets) + self.get_neutrals_under_my_attack()
        for planet in self.all_planets:
            self.my_max_aid_at_turn[planet] = {}
            for turn in range(1, self.max_distance_between_planets+1):
                max_aid = 0
                #for my_planet in self.my_planets:
                for my_planet in my_planets_incl_candidates:
                    if my_planet.id != planet.id and planet.distance(my_planet) < turn:
                        my_planet_time_step = self.planet_timeline[my_planet][turn - planet.distance(my_planet)]
                        if (my_planet_time_step[0] == player.ME):
                            max_aid += my_planet_time_step[1]
                            #max_aid = max(max_aid, my_planet_time_step[1])
                            #log.info("adding to my max aid: %s" %  my_planet_time_step[1])
                    else:
                        if my_planet.id != planet.id and planet.distance(my_planet) == turn:
                            my_planet_time_step = self.planet_timeline[my_planet][0]
                            if (my_planet_time_step[0] == player.ME):
                                max_aid += my_planet.ship_count
                                #max_aid = max(max_aid, my_planet.ship_count)
                #if self.planet_timeline[planet][turn-1][0] == player.ME:
                    #max_aid += self.planet_timeline[planet][turn-1][1]
                    #log.info("self aid: %s" % self.planet_timeline[planet][turn-1][1])
                self.my_max_aid_at_turn[planet][turn] = max_aid
                #log.info("My Max aid for %s at %s: %s" % (planet.id, turn, self.my_max_aid_at_turn[planet][turn]))
        #log.info("My Max aid: %s" % self.my_max_aid_at_turn)

        log.info("MY STATUS: %s/%s - %s available" % (self.my_total_ships, self.my_total_growth_rate, self.my_total_ships_available))
        log.info("ENEMY STATUS: %s/%s - %s available" % (self.enemy_total_ships, self.enemy_total_growth_rate, self.enemy_total_ships_available))
        #log.info("ENEMY COM: %s, %s" % (self.enemy_com.position.x, self.enemy_com.position.y))

    def doDefenseOffense(self):
        log.info("Offense/Defense phase")

        possible_moves = []
        for my_planet in self.my_planets:
            for planet_to_attack in self.all_planets:
                if planet_to_attack.id == my_planet.id:
                    continue
                attack_distance = my_planet.distance(planet_to_attack)
                planet_to_attack_future = self.planet_timeline[planet_to_attack][attack_distance-1]
                planet_to_attack_future_owner = planet_to_attack_future[0]
                cost_to_conquer = -1
                time_to_profit = 0
                if planet_to_attack_future_owner == player.NOBODY:
                    cost_to_conquer = planet_to_attack_future[1]
                    if planet_to_attack.growth_rate > 0:
                        time_to_profit = int(ceil((cost_to_conquer+0.001)/planet_to_attack.growth_rate))
                    else:
                        time_to_profit = 1000000
                    if (time_to_profit+attack_distance) >= self.max_distance_between_planets:
                        time_to_profit = self.max_distance_between_planets - attack_distance
                    #log.info("Time to profit for %s is %s" % (planet_to_attack, time_to_profit))
                else:
                    if planet_to_attack_future_owner in player.ENEMIES:
                        cost_to_conquer = 0

                can_hold = True
                for turn in range(attack_distance,attack_distance+time_to_profit):
                    max_aid = self.max_aid_at_turn[planet_to_attack][turn]
                    my_max_aid = self.my_max_aid_at_turn[planet_to_attack][turn] - (cost_to_conquer + 1)
                    if max_aid > my_max_aid:
                        can_hold = False
                        break
                if not can_hold:
                    continue

                max_aid = self.max_aid_at_turn[planet_to_attack][attack_distance+time_to_profit]
                my_max_aid = self.my_max_aid_at_turn[planet_to_attack][attack_distance+time_to_profit] - (cost_to_conquer + 1)
                if planet_to_attack_future_owner in player.ENEMIES:
                    my_max_aid = 0
                ships_to_send = cost_to_conquer + max(max_aid - my_max_aid, 0) + 1
                #log.info("Evaluating attack of %s from %s, max %s, mymax %s, cost %s, ships %s" % (planet_to_attack, my_planet, max_aid, my_max_aid, cost_to_conquer, ships_to_send))
                if planet_to_attack_future_owner != player.ME and ships_to_send > 0 and ships_to_send <= self.ships_available[my_planet]:
                    if self.planet_timeline[planet_to_attack][attack_distance-1][0] in player.ENEMIES and self.planet_timeline[planet_to_attack][attack_distance-2][0] == player.NOBODY:
                        continue
                    attack_score = (self.max_distance_between_planets - attack_distance + 40) * planet_to_attack.growth_rate
                    if planet_to_attack_future_owner in player.ENEMIES:
                        #attack_score += self.get_sum_enemy_growth_within_dist(planet_to_attack, self.max_distance_between_planets/5)*5
                        attack_score *= 2

#                    can_defend_source = True
#                    for enemy_planet in self.enemy_planets:
#                        if enemy_planet.id == planet_to_attack.id:
#                            continue
#                        dist = enemy_planet.distance(my_planet)
#                        max_aid = self.max_aid_at_turn_single[my_planet][dist]
#                        my_max_aid = self.my_max_aid_at_turn[my_planet][dist] + (my_planet.ship_count-ships_to_send) + my_planet.growth_rate*dist
#                        if attack_distance*2<dist and planet_to_attack_future_owner == player.NOBODY:
#                            my_max_aid += (dist-attack_distance*2) * planet_to_attack.growth_rate
#                        if my_max_aid < max_aid:
#                            can_defend_source = False
#                            break

                    if planet_to_attack_future_owner in player.ENEMIES or (attack_score-cost_to_conquer) >= 140:
                        possible_moves.append((my_planet, planet_to_attack, ships_to_send, attack_score))
                    #log.info("Attack score of %s from %s is: %s - %s ships" % (planet_to_attack, my_planet, attack_score, ships_to_send))

        # execute the best moves
        planets_attacked = []
        sorted_moves = sorted(possible_moves, key=lambda m : m[3] + m[1].growth_rate/1000.0 + m[1].id/1000000.0, reverse=True)
        log.info("Best moves: %s" % len(sorted_moves))

        if self.universe.game.turn_count == 1:
            candidates = []
            candidate_map = {}
            my_home = list(self.my_planets)[0]
            enemy_home = list(self.enemy_planets)[0]
            home_planet_distance = my_home.distance(enemy_home)
            ships_available = min(my_home.ship_count, my_home.growth_rate * home_planet_distance)

            i = 0
            max_attack_distance=0
            for p in sorted(self.nobodies_planets, key=lambda p : self.get_attack_ship_count_first_turn(p, my_home, enemy_home) + p.id/1000000.0):
              if p.distance(my_home) < p.distance(enemy_home) or p.distance(my_home) == p.distance(enemy_home):
                if p.distance(my_home) == p.distance(enemy_home) and p.ship_count > 10:
                    continue
                candidates.append(p)
                candidate_map[i] = p
                max_attack_distance = max(max_attack_distance, p.distance(my_home))
                i += 1

            weights = []
            profits = []
            for c in candidates:
                attack_score = (self.max_distance_between_planets - c.distance(my_home) + 40) * c.growth_rate
                if (attack_score-c.ship_count) < 50:
                    attack_score = 0    
                weight = self.get_attack_ship_count_first_turn(c, my_home, enemy_home)
                weights.append(weight)
                profits.append(attack_score)

            best_planets_to_attack = self.zeroOneKnapsack(profits,weights,ships_available)
            #log.info("best planets: %s" % best_planets_to_attack)

            sorted_moves = []
            for i in range(len(best_planets_to_attack[1])):
                if (best_planets_to_attack[1][i] != 0):
                    planet_to_attack = candidate_map[i]
                    sorted_moves.append((my_home, planet_to_attack, planet_to_attack.ship_count+1, 0))

        for move in sorted_moves:
            ships_to_send = move[2]
            planet_to_attack = move[1]
            my_planet = move[0]
            if ships_to_send <= self.ships_available[my_planet] and planet_to_attack not in planets_attacked:
                my_planet.send_fleet(planet_to_attack, ships_to_send)
                self.ships_available[my_planet] -= ships_to_send
                planets_attacked.append(planet_to_attack)

    def doPostOffense(self):
        log.info("Post-Offense phase")
        if len(self.enemy_planets) == 0:
            return

        planets_to_send_to = self.my_planets
#        neutral_candidate = self.closest_to_enemy_neutral_under_my_attack()
#        if neutral_candidate is not None and neutral_candidate.ship_count <= 10:
#           planets_to_send_to = planets_to_send_to + neutral_candidate

        # cache closest and com enemy planet distances
        closest_enemy_planet_distance_map = {}
        com_enemy_planet_distance_map = {}
        for planet in planets_to_send_to:
            closest_enemy_planet_distance_map[planet] = self.closest_enemy_planet_distance(planet)
            com_enemy_planet_distance_map[planet] = self.enemy_com.distance(planet)

        my_nearest_to_enemy_planets = sorted(planets_to_send_to, key=lambda p : p.distance(self.enemy_com) +  p.id/1000000.0)

        for source_planet in self.my_planets:
            if self.ships_available[source_planet] > 0:
                #log.info("Post-Offense for %s" % source_planet)
                for dest_planet in my_nearest_to_enemy_planets:
                    distance = source_planet.distance(dest_planet)
                    if distance > 0 and distance < com_enemy_planet_distance_map[source_planet]:
                        if com_enemy_planet_distance_map[dest_planet] < com_enemy_planet_distance_map[source_planet] and \
                          closest_enemy_planet_distance_map[dest_planet] <= closest_enemy_planet_distance_map[source_planet]:
                            source_planet.send_fleet(dest_planet, self.ships_available[source_planet])
                            self.ships_available[source_planet] = 0
                            break


    def do_turn(self):
        self.all_planets = self.universe.all_planets
        self.my_planets = self.universe.my_planets
        self.enemy_planets = self.universe.enemy_planets
        self.nobodies_planets = self.universe.nobodies_planets

        if len(self.my_planets) == 0:
            return

        self.doPrep()
        self.doDefenseOffense()
        self.doPostOffense()
Esempio n. 2
0
class MyBot(BaseBot):

    def __init__(self, universe):
        self.universe = universe
        self.scheduled_moves_at_turn= {}

    def total_fleet_ship_count(self, owner):
        return sum( [ fleet.ship_count for fleet in self.universe.find_fleets(owner) ] )

    def get_neutrals_under_player_attack(self, player):
        result = []
        for planet in self.nobodies_planets:
            if sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner == player ] ) > 0:
                result.append(planet)
        return result

    def get_available_ships_within_distance(self, planet_to_attack, player, distance):
        result = 0
        for planet in (list(self.universe.find_planets(player)) + self.get_neutrals_under_player_attack(player)):
            if planet.id != planet_to_attack.id and planet.distance(planet_to_attack) <= distance and self.ships_needed[planet] == 0:
                ships_avail = self.ships_available_at_turn[planet][distance-planet.distance(planet_to_attack)]
#                if planet_to_attack.id == 0:
#                    log.info("get avail from %s = %s" % (planet, ships_avail))
                result += ships_avail
        return result

    def get_attack_score(self, planet_to_attack, future_owner, distance):
        turns = self.max_distance_between_planets - distance + HORIZON
        attack_score = turns * planet_to_attack.growth_rate
        if future_owner in player.ENEMIES:
            attack_score *= ATTACK_SCORE_ENEMY_MULTIPLIER
        return attack_score

    def get_attack_score_200(self, planet_to_attack, future_owner, distance):
        profit_turns = max(200 - self.current_turn - distance, 0)
        attack_score = profit_turns * planet_to_attack.growth_rate
        if future_owner in player.ENEMIES:
            attack_score *= 2
        return attack_score

    def get_scheduled_fleets_to(self, planet):
        result = []
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.target == planet:
                    distance = move.source.distance(move.target)
                    turns_remaining = distance + (move.turn - self.universe.game.turn_count)
                    fleet = Fleet(self.universe,random.randint(1,1000000),1, move.ship_count, move.source.id, move.target.id, distance, turns_remaining)
                    result.append(fleet)
        return result

    def get_scheduled_fleets_from(self, planet):
        result = []
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.source == planet:
                    turns_remaining = move.turn - self.universe.game.turn_count
                    fleet = Fleet(self.universe,random.randint(1,1000000),1, move.ship_count, move.source.id, move.target.id, turns_remaining, turns_remaining)
                    result.append(fleet)
        return result

    def get_scheduled_fleets_shipcount_from_within_distance(self, planet, turns):
        result = 0
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.source == planet:
                    turns_remaining = move.turn - self.universe.game.turn_count
                    if turns_remaining == turns:
                        result += move.ship_count
        return result

    def get_attack_ship_count_first_turn(self, planet_to_attack, my_home, enemy_home):
        my_dist = my_home.distance(planet_to_attack)
        enemy_dist = enemy_home.distance(planet_to_attack)
        if my_dist < enemy_dist:
            return planet_to_attack.ship_count+1
        if my_dist == enemy_dist and planet_to_attack.ship_count <= planet_to_attack.growth_rate:
            return planet_to_attack.ship_count+1
        return 1000000

    def closest_enemy_planet(self, p):
        if len(self.enemy_planets) == 0:
            return None

        sorted_planets = sorted(self.enemy_planets, key=lambda ep : p.distance(ep) + ep.id/1000000.0)
        return sorted_planets[0]

    def closest_enemy_planet_distance(self, p):
        return min((lambda ep:ep.distance(p))(ep) for ep in self.enemy_planets)

    def my_fleets_attacking(self, planet):
        return sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner == player.ME] )

    def closest_to_enemy_neutral_under_my_attack(self):
        best_distance = 1000000
        result_planet = None
        for planet in self.nobodies_planets:
            if self.my_fleets_attacking(planet) > 0:
                distance = self.enemy_com.distance(planet)
                if distance < best_distance:
                    best_distance = distance
                    result_planet = planet
        return result_planet

    def decrease_ships_available(self, planet, start_turn, ship_count):
        for turn in range(start_turn, self.max_distance_between_planets + 21):
            self.ships_available_at_turn[planet][turn] -= ship_count

    def send_fleet(self, source, target, ship_count):
        if source.owner == PLAYER1 and ship_count > 0 and ship_count <= source.ship_count:
            source.send_fleet(target, ship_count)
        else:
            log.info("Error sending fleet from %s to %s with % ships" % (source, target, ship_count))

    def doScheduled(self):
        log.info("Scheduled move phase")
        # execute delayed moves first
        if self.scheduled_moves_at_turn.has_key(self.current_turn):
            for move in self.scheduled_moves_at_turn[self.current_turn]:
                #if move.ship_count <= move.source.ship_count and move.ship_count > 0 and move.source.owner == PLAYER1 and self.ships_available_at_turn[move.source][0] >= move.ship_count:
                #if move.ship_count <= move.source.ship_count and move.ship_count > 0 and move.source.owner == PLAYER1 and move.source.ship_count >= move.ship_count:
                if move.ship_count <= move.source.ship_count and move.ship_count > 0 and move.source.owner == PLAYER1 and move.source.ship_count >= move.ship_count and self.ships_available_at_turn[move.source][0] >= move.ship_count:
                    self.send_fleet(move.source, move.target, move.ship_count)
                    self.decrease_ships_available(move.source, 0, move.ship_count)
                    #self.cumulative_ships_sent += move.ship_count
                    #self.ships_available[move.source] -= move.ship_count
                else:
                    log.info("Can't execute move: %s,  ships avail: %s" % (move, self.ships_available_at_turn[move.source][0]))
            del self.scheduled_moves_at_turn[self.current_turn]

    def doPrep(self):
        log.info("Prep phase")
        if self.current_turn == 1:
            self.my_home = list(self.my_planets)[0]
            self.enemy_home = list(self.enemy_planets)[0]

        self.max_distance_between_planets = 0
        for p1 in self.all_planets:
            for p2 in self.all_planets:
                self.max_distance_between_planets = max(self.max_distance_between_planets, p1.distance(p2))
        #log.info("Max distance: %s" % self.max_distance_between_planets)

        # calculate current high level metrics
        self.total_ships = {PLAYER1:0, PLAYER2:0}
        self.total_growth_rate = {PLAYER1:0, PLAYER2:0}
        self.ships_available_at_turn = {}
        self.ships_needed = {}
        self.ships_needed_at_turn = {}
        self.ships_needed_later = {}
        self.planet_timeline = {}

        for planet in self.all_planets:
            self.ships_available_at_turn[planet] = {}
            scheduled_fleets_to_planet = self.get_scheduled_fleets_to(planet)
            scheduled_fleets_from_planet = self.get_scheduled_fleets_from(planet)
            self.planet_timeline[planet] = planet.in_future_timeline(self.max_distance_between_planets + 20, scheduled_fleets_to_planet, scheduled_fleets_from_planet)
            need_help = False
            if planet.id == 7:
                log.info("timeline for %s: %s" % (planet, self.planet_timeline[planet]))
                #log.info("attacking fleets by me: %s" % (self.universe.find_fleets(PLAYER1, destination=planet)))
            prev_owner = planet.owner
            for step in self.planet_timeline[planet]:
                owner = step[0]
                ship_count = step[1]
                if owner != prev_owner and prev_owner == planet.owner and prev_owner != NOBODY and not need_help:
                    self.ships_needed[planet] = ship_count
                    self.ships_needed_at_turn[planet] = self.planet_timeline[planet].index(step) + 1
                    need_help = True
                    self.ships_needed_later[planet] = []
                    #log.info("Planet %s needs help %s at %s" % (planet, ship_count, self.ships_needed_at_turn[planet]))
                prev_owner = owner
            if not need_help:
                self.ships_needed[planet] = 0
                min_available = 1000000
                step_index = len(self.planet_timeline[planet])
                for step in reversed(self.planet_timeline[planet]):
                    ship_count = step[1]
                    min_available = min(min_available, ship_count)
                    if step[0] == NOBODY:
                        min_available = 0
                    if min_available < 0:
                        log.info("Negative min_available: %s for %s" % (min_available, planet))
                        min_available = 0
                    self.ships_available_at_turn[planet][step_index] = min_available
                    #log.info("avail for %s at %s: %s" % (planet, step_index, min_available))
                    step_index -= 1
                self.ships_available_at_turn[planet][0] = max(0,min(planet.ship_count, self.ships_available_at_turn[planet][1] - planet.growth_rate))
            else:
                for step_index in range(0, len(self.planet_timeline[planet])+1):
                    self.ships_available_at_turn[planet][step_index] = 0
            if planet.owner != NOBODY:
                self.total_ships[planet.owner] += planet.ship_count
                self.total_growth_rate[planet.owner] += planet.growth_rate
#            if planet.id == 14:
#                log.info("avail timeline for %s is: %s" % (planet, self.ships_available_at_turn[planet]))
        self.total_ships[PLAYER1] += self.total_fleet_ship_count(PLAYER1)
        self.total_ships[PLAYER2] += self.total_fleet_ship_count(PLAYER2)

        for my_planet in [self.my_home]:
            for enemy_planet in [self.enemy_home]:
#                if self.ships_available_at_turn[enemy_planet][0] < self.ships_available_at_turn[my_planet][0]:
#                    continue
                if my_planet.owner != PLAYER1 or enemy_planet.owner != PLAYER2:
                    continue
                max_enemy_fleet = self.ships_available_at_turn[enemy_planet][0]
                distance = my_planet.distance(enemy_planet)
                ships_needed_for_safety = max_enemy_fleet-(self.planet_timeline[my_planet][distance-1][1] - my_planet.ship_count) - enemy_planet.growth_rate
                #ships_needed_for_safety = max_enemy_fleet-(self.planet_timeline[my_planet][distance-1][1] - my_planet.ship_count)
                if ships_needed_for_safety > (my_planet.ship_count - self.ships_available_at_turn[my_planet][0]):
                    deficit = ships_needed_for_safety - (my_planet.ship_count - self.ships_available_at_turn[my_planet][0])
                    #log.info("deficit for %s: %s, max enemy fleet %s" % (my_planet, deficit, max_enemy_fleet))
                    if deficit > self.ships_available_at_turn[my_planet][0]:
                        deficit = self.ships_available_at_turn[my_planet][0]
                    self.decrease_ships_available(my_planet, 0, deficit)

        # calculate enemy's center of mass
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.enemy_planets:
            weighted_x += planet.position.x * (self.ships_available_at_turn[planet][0] + planet.growth_rate)
            weighted_y += planet.position.y * (self.ships_available_at_turn[planet][0] + planet.growth_rate)
            div += self.ships_available_at_turn[planet][0] + planet.growth_rate
        if div == 0:
            div = 1

        self.enemy_com = Planet(self.universe, 666, weighted_x/div, weighted_y/div, 2, 0, 0)

        # For every planet, and every turn, calculate how many ships each player can send to it
        # TODO should we use ships_available_at_turn here?
        self.max_aid_at_turn = {PLAYER1:{}, PLAYER2:{}}
        for player in (PLAYER1 | PLAYER2):
            source_planets = list(self.universe.find_planets(player)) + self.get_neutrals_under_player_attack(player)
            for planet in self.all_planets:
                self.max_aid_at_turn[player][planet] = {}
                for turn in range(1, self.max_distance_between_planets+21):
                    max_aid = 0
                    for source_planet in source_planets:
                        if source_planet.id != planet.id and planet.distance(source_planet) < turn:
                            source_planet_time_step = self.planet_timeline[source_planet][turn - planet.distance(source_planet) - 1]
                            if (source_planet_time_step[0] == player):
                                #log.info("Max aid by %s for %s from %s at %s: %s" % (player.id, planet.id, source_planet.id, turn, source_planet_time_step[1]))
                                max_aid += source_planet_time_step[1]
                        else:
                            if source_planet.id != planet.id and planet.distance(source_planet) == turn:
                                if (source_planet.owner == player):
                                    max_aid += source_planet.ship_count
                    self.max_aid_at_turn[player][planet][turn] = max_aid
                    #log.info("Max aid by %s for %s at %s: %s" % (player.id, planet.id, turn, self.max_aid_at_turn[player][planet][turn]))

        log.info("MY STATUS: %s/%s" % (self.total_ships[PLAYER1], self.total_growth_rate[PLAYER1]))
        log.info("ENEMY STATUS: %s/%s" % (self.total_ships[PLAYER2], self.total_growth_rate[PLAYER2]))

    def doDefense(self):
        log.info("Defense phase")

        for planet_to_defend in self.all_planets:
            ships_to_send = self.ships_needed[planet_to_defend]
            if ships_to_send <= 0:
                continue
            min_distance = self.max_distance_between_planets
            max_distance = self.ships_needed_at_turn[planet_to_defend]
            for my_planet in self.my_planets:
                distance = my_planet.distance(planet_to_defend)
                min_distance = min(min_distance, distance)
            min_distance = max(min_distance, 1)
            defended = False
            for distance in range(min_distance, max_distance+1):
                # calculate if we can get enough ships from my planets to planet_to_defend within 'distance' turns
                ships_avail_to_defend = self.get_available_ships_within_distance(planet_to_defend, PLAYER1, distance)
                #log.info("Ships avail to defend %s within %s dist: %s" % (planet_to_defend, distance, ships_avail_to_defend))
                if ships_avail_to_defend >= ships_to_send:
                    ships_left_to_send = ships_to_send
                    for source_planet in sorted(list(self.my_planets) + self.get_neutrals_under_player_attack(PLAYER1), key=lambda p : p.distance(planet_to_defend) + p.id/1000000.0):
                        if self.ships_needed[source_planet] > 0:
                            continue
                        log.info("evaluating for D: %s" % (source_planet))
                        current_distance = source_planet.distance(planet_to_defend)
                        ships_avail = self.ships_available_at_turn[source_planet][distance-current_distance]
                        if source_planet.id != planet_to_defend.id and ships_avail > 0:
                            log.info("Ships avail from %s: %s  at dist %s, dist = %s" % (source_planet, ships_avail, current_distance, distance))
                            ships_to_send = min(ships_left_to_send, ships_avail)
                            if current_distance == distance:
                                log.info("defending avail from %s: %s  at dist %s" % (source_planet, ships_to_send, current_distance))
                                self.send_fleet(source_planet, planet_to_defend, ships_to_send)
                                #self.cumulative_ships_sent += ships_to_send
                            if current_distance < distance:
                                future_turn = self.current_turn + (distance - current_distance)
                                future_move = Move(source_planet, planet_to_defend, future_turn, ships_to_send)
                                log.info("Scheduled move: %s" % future_move)
                                if not self.scheduled_moves_at_turn.has_key(future_turn):
                                    self.scheduled_moves_at_turn[future_turn] = []
                                self.scheduled_moves_at_turn[future_turn].append(future_move)
                            ships_left_to_send -= ships_to_send
                            self.decrease_ships_available(source_planet, 0, ships_to_send)
                            if ships_left_to_send == 0:
                                defended = True
                                break
                if defended:
                    break

    def doFirstTurnOffense(self):
        candidates = []
        candidate_map = {}
        home_planet_distance = self.my_home.distance(self.enemy_home)
        ships_available = min(self.my_home.ship_count, self.my_home.growth_rate * (home_planet_distance+0))

        i = 0
        max_attack_distance=0
        for p in sorted(self.nobodies_planets, key=lambda p : self.get_attack_ship_count_first_turn(p, self.my_home, self.enemy_home) + p.id/1000000.0):
          if p.distance(self.my_home) < p.distance(self.enemy_home) or p.distance(self.my_home) == p.distance(self.enemy_home):
            if p.distance(self.my_home) == p.distance(self.enemy_home) and p.ship_count > 10:
                continue
            candidates.append(p)
            candidate_map[i] = p
            max_attack_distance = max(max_attack_distance, p.distance(self.my_home))
            i += 1

        weights = []
        profits = []
        for c in candidates:
            weight = self.get_attack_ship_count_first_turn(c, self.my_home, self.enemy_home)
            attack_score = (self.max_distance_between_planets - c.distance(self.my_home) + HORIZON_FIRST) * c.growth_rate - (weight - 1)
            if attack_score < ATTACK_SCORE_THRESHOLD_FIRST:
                attack_score = 0
            weights.append(weight)
            profits.append(attack_score)
            #log.info("candidate %s: score %s, weight %s" % (c, attack_score, weight))

        best_planets_to_attack = zeroOneKnapsack(profits,weights,ships_available)
        #log.info("best planets: %s, ships_avail: %s" % (best_planets_to_attack,ships_available))

        sorted_moves = []
        for i in range(len(best_planets_to_attack[1])):
            if (best_planets_to_attack[1][i] != 0):
                planet_to_attack = candidate_map[i]
                self.send_fleet(self.my_home, planet_to_attack, planet_to_attack.ship_count+1)

    def doOffense(self):
        log.info("Offense phase")
        if self.current_turn == 1:
            self.doFirstTurnOffense()
            return

        planets_attacked = []
        best_planet_to_attack = None
        while True:
            best_planet_to_attack = None
            best_planet_to_attack_score = 0
            best_planet_to_attack_distance = 0
            best_planet_to_attack_ships_to_send = 0
            for planet_to_attack in self.all_planets:
                if planet_to_attack in planets_attacked:
                    continue
                min_distance = self.max_distance_between_planets
                max_distance = 0
                for my_planet in self.my_planets:
                    distance = my_planet.distance(planet_to_attack)
                    min_distance = min(min_distance, distance)
                    max_distance = max(max_distance, distance)
                for fleet in self.universe.find_fleets(owner=PLAYER2, destination=planet_to_attack):
                    max_distance = max(max_distance, fleet.turns_remaining)
                #log.info("Max distance for %s: %s" % (planet_to_attack, max_distance))
                min_distance = max(min_distance, 1)
                for distance in range(min_distance, max_distance+1):
                    # calculate how many ships we need to get from my planets to planet_to_attack within 'distance' turns
                    planet_to_attack_future = self.planet_timeline[planet_to_attack][distance-1]
                    planet_to_attack_future_owner = planet_to_attack_future[0]
                    if planet_to_attack_future_owner == PLAYER1:
                        break
                    cost_to_conquer = 0 if planet_to_attack_future_owner == PLAYER2 else -1
                    time_to_profit = 0
                    if planet_to_attack_future_owner == player.NOBODY:
                        cost_to_conquer = planet_to_attack_future[1]
                        time_to_profit = int(ceil((cost_to_conquer+0.001)/planet_to_attack.growth_rate)) if planet_to_attack.growth_rate > 0 else 1000000
                        if planet_to_attack_future_owner == NOBODY and self.enemy_com.distance(planet_to_attack) < distance:
                            break
                    #log.info("Time to profit for %s is %s" % (planet_to_attack, time_to_profit))

#                    if (distance+time_to_profit) >= self.max_distance_between_planets:
#                        break

                    can_hold = True
                    for turn in range(distance, min(distance+time_to_profit+1, self.max_distance_between_planets + 20)):
                        enemy_max_aid = self.max_aid_at_turn[PLAYER2][planet_to_attack][turn]
                        if planet_to_attack_future_owner == player.PLAYER2:
                            enemy_max_aid += self.planet_timeline[planet_to_attack][turn+time_to_profit-1][1]
                        my_max_aid = self.max_aid_at_turn[PLAYER1][planet_to_attack][turn] - cost_to_conquer + planet_to_attack.growth_rate * (turn-distance) - self.cumulative_ships_sent
                        if enemy_max_aid > my_max_aid:
                            can_hold = False
                            #log.info("can't hold %s at turn %s, enemy %s, me %s" % (planet_to_attack, turn, enemy_max_aid, my_max_aid))
                            break
                    if not can_hold:
                        continue

                    simulation_distance = min(distance+time_to_profit, self.max_distance_between_planets + 20)
                    if simulation_distance <= 0:
                        continue
                    enemy_max_aid = self.max_aid_at_turn[PLAYER2][planet_to_attack][simulation_distance]
                    if planet_to_attack_future_owner == player.PLAYER2:
                        enemy_max_aid += self.planet_timeline[planet_to_attack][simulation_distance-1][1]
                    my_max_aid = self.max_aid_at_turn[PLAYER1][planet_to_attack][simulation_distance] - (cost_to_conquer + 1) - self.cumulative_ships_sent if planet_to_attack_future_owner == NOBODY else 0
                    ships_to_send = cost_to_conquer + max(enemy_max_aid - my_max_aid, 0) + 1
                    log.info("aids for %s at distance %s: enemy %s , me %s, cost %s" % (planet_to_attack, distance, enemy_max_aid, my_max_aid, cost_to_conquer))

                    # calculate if we can get enough ships from my planets to planet_to_attack within 'distance' turns
                    ships_avail_to_attack = self.get_available_ships_within_distance(planet_to_attack, PLAYER1, distance)
                    log.info("avail to attack: %s, need to send %s" % (ships_avail_to_attack, ships_to_send))
                    if ships_avail_to_attack >= ships_to_send:
                        if self.planet_timeline[planet_to_attack][distance-1][0] in player.ENEMIES and self.planet_timeline[planet_to_attack][distance-2][0] == player.NOBODY:
                            continue

                        attack_score = self.get_attack_score(planet_to_attack, planet_to_attack_future_owner, distance)
                        log.info("Attack score of %s at dist %s is: %s - %s ships, cost %s" % (planet_to_attack, distance, attack_score, ships_to_send, cost_to_conquer))
                        if planet_to_attack_future_owner in player.ENEMIES or (attack_score-cost_to_conquer) >= ATTACK_SCORE_THRESHOLD:
                            if attack_score > best_planet_to_attack_score:
                                best_planet_to_attack_score = attack_score
                                best_planet_to_attack = planet_to_attack
                                best_planet_to_attack_distance = distance
                                best_planet_to_attack_ships_to_send = ships_to_send
                        break


            if best_planet_to_attack is None:
                return

            log.info("Best planet to attack: %s at dist %s with score %s" % (best_planet_to_attack, best_planet_to_attack_distance, best_planet_to_attack_score))

            ships_left_to_send = best_planet_to_attack_ships_to_send
            source_planets = list(self.my_planets) + self.get_neutrals_under_player_attack(PLAYER1)
            for source_planet in sorted(source_planets, key=lambda p : p.distance(best_planet_to_attack) + p.id/1000000.0):
                distance = source_planet.distance(best_planet_to_attack)
                if distance > best_planet_to_attack_distance:
                    continue
                ships_avail = self.ships_available_at_turn[source_planet][best_planet_to_attack_distance-distance]
                log.info("ships avail to attack from %s at dist %s: %s" % (source_planet, best_planet_to_attack_distance-distance, ships_avail))
                if self.ships_needed[source_planet] > 0:
                    ships_avail = 0
                if source_planet.id != best_planet_to_attack.id and ships_avail > 0:
                    ships_to_send = min(ships_left_to_send, ships_avail)
                    log.info("ships to send from %s: %s" % (source_planet, ships_to_send))
                    if distance == best_planet_to_attack_distance and source_planet.owner == PLAYER1:
                        self.send_fleet(source_planet, best_planet_to_attack, ships_to_send)
                        #self.cumulative_ships_sent += ships_to_send
                    if distance < best_planet_to_attack_distance:
                        future_turn = self.current_turn + (best_planet_to_attack_distance - distance)
                        future_move = Move(source_planet, best_planet_to_attack, future_turn, ships_to_send)
                        log.info("Scheduled move: %s" % future_move)
                        if not self.scheduled_moves_at_turn.has_key(future_turn):
                            self.scheduled_moves_at_turn[future_turn] = []
                        self.scheduled_moves_at_turn[future_turn].append(future_move)
                    ships_left_to_send -= ships_to_send
                    self.decrease_ships_available(source_planet, 0, ships_to_send)
                    if ships_left_to_send == 0:
                        break
            planets_attacked.append(best_planet_to_attack)

    def doPostOffense2(self):
        log.info("Post-Offense phase")
        if len(self.enemy_planets) == 0:
            return

        planets_to_send_to = copy(self.my_planets)
        neutral_candidate = self.closest_to_enemy_neutral_under_my_attack()
        if neutral_candidate is not None:
           planets_to_send_to = planets_to_send_to | neutral_candidate

        for source_planet in self.my_planets:
            closest_enemy_planet = self.closest_enemy_planet(source_planet)
            #log.info("Eval Post-Offense for %s: closest enemy is %s" % (source_planet, closest_enemy_planet))
            min_distance_to_enemy = 1000000
            dest_planet = None
            for planet_to_send_to in sorted(planets_to_send_to, key=lambda p : p.id if p.id != source_planet.id else 1000000):
                if source_planet.distance(planet_to_send_to) < source_planet.distance(closest_enemy_planet) \
                  and planet_to_send_to.distance(closest_enemy_planet) < min_distance_to_enemy:
                    min_distance_to_enemy = planet_to_send_to.distance(closest_enemy_planet)
                    dest_planet = planet_to_send_to
            if dest_planet is not None and source_planet.id != dest_planet.id and self.ships_available_at_turn[source_planet][0] > 0:
                ships_to_send = min(self.ships_available_at_turn[source_planet][0], source_planet.ship_count)
                self.send_fleet(source_planet, dest_planet, ships_to_send)
                self.decrease_ships_available(source_planet, 0, ships_to_send)

    def doPostOffense(self):
        log.info("Post-Offense phase")
        if len(self.enemy_planets) == 0:
            return

        planets_to_send_to = copy(self.my_planets)
        neutral_candidate = self.closest_to_enemy_neutral_under_my_attack()
        if neutral_candidate is not None:
           planets_to_send_to = planets_to_send_to | neutral_candidate

        # cache closest and com enemy planet distances
        closest_enemy_planet_distance_map = {}
        com_enemy_planet_distance_map = {}
        for planet in planets_to_send_to:
            closest_enemy_planet_distance_map[planet] = self.closest_enemy_planet_distance(planet)
            com_enemy_planet_distance_map[planet] = self.enemy_com.distance(planet)

        my_nearest_to_enemy_planets = sorted(planets_to_send_to, key=lambda p : p.distance(self.enemy_com) +  p.id/1000000.0)

        for source_planet in self.my_planets:
            if self.ships_needed[source_planet] == 0 and self.ships_available_at_turn[source_planet][0] > 0:
                #log.info("Post-Offense for %s" % source_planet)
                for dest_planet in my_nearest_to_enemy_planets:
                    distance = source_planet.distance(dest_planet)
                    if distance > 0 and distance < com_enemy_planet_distance_map[source_planet]:
                        if com_enemy_planet_distance_map[dest_planet] < com_enemy_planet_distance_map[source_planet] and \
                          closest_enemy_planet_distance_map[dest_planet] <= closest_enemy_planet_distance_map[source_planet]:
                            self.send_fleet(source_planet, dest_planet, self.ships_available_at_turn[source_planet][0])
                            self.decrease_ships_available(source_planet, 0, self.ships_available_at_turn[source_planet][0])
                            break


    def do_turn(self):
        self.all_planets = self.universe.all_planets
        self.my_planets = self.universe.my_planets
        self.enemy_planets = self.universe.enemy_planets
        self.nobodies_planets = self.universe.nobodies_planets
        self.not_my_planets = self.universe.not_my_planets
        self.current_turn = self.universe.game.turn_count

        if len(self.my_planets) == 0:
            return

        self.cumulative_ships_sent = 0

        self.doPrep()
        self.doScheduled()
        self.doDefense()
        self.doOffense()
        self.doPostOffense2()
Esempio n. 3
0
class MyBot(BaseBot):

    def __init__(self, universe):
        self.universe = universe
        self.scheduled_moves_at_turn= {}

    def total_fleet_ship_count(self, owner):
        return sum( [ fleet.ship_count for fleet in self.universe.find_fleets(owner) ] )

    def get_neutrals_under_player_attack(self, player):
        result = []
        for planet in self.nobodies_planets:
            if sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner == player ] ) > 0:
                result.append(planet)
        return result

    def get_available_ships_within_distance(self, planet_to_attack, player, distance):
        result = 0
        for planet in self.universe.find_planets(player):
            if planet.id != planet_to_attack.id and planet.distance(planet_to_attack) <= distance and self.ships_needed[planet] == 0:
                ships_avail = self.ships_available[planet]
                if len(planet.attacking_fleets) == 0 and len(self.get_scheduled_fleets_from(planet)) == 0:
                    ships_avail += (distance-planet.distance(planet_to_attack)) * planet.growth_rate
                result += ships_avail
        return result

    def get_attack_score(self, planet_to_attack, future_owner, distance):
        attack_score = (self.max_distance_between_planets - distance + 40) * planet_to_attack.growth_rate
        if future_owner in player.ENEMIES:
            attack_score *= 2
        return attack_score

    def get_scheduled_fleets_to(self, planet):
        result = []
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.target == planet:
                    distance = move.source.distance(move.target)
                    turns_remaining = distance + (move.turn - self.universe.game.turn_count)
                    fleet = Fleet(self.universe,12345,1, move.ship_count, move.source.id, move.target.id, distance, turns_remaining)
                    result.append(fleet)
        return result

    def get_scheduled_fleets_from(self, planet):
        result = []
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.source == planet:
                    turns_remaining = move.turn - self.universe.game.turn_count
                    fleet = Fleet(self.universe,12345,1, move.ship_count, move.source.id, move.target.id, turns_remaining, turns_remaining)
                    result.append(fleet)
        return result

    def get_attack_ship_count_first_turn(self, planet_to_attack, my_home, enemy_home):
        my_dist = my_home.distance(planet_to_attack)
        enemy_dist = enemy_home.distance(planet_to_attack)
        if my_dist < enemy_dist:
            return planet_to_attack.ship_count+1
        if my_dist == enemy_dist and planet_to_attack.ship_count <= planet_to_attack.growth_rate:
            return planet_to_attack.ship_count+1
        return 1000000

    def closest_enemy_planet_distance(self, p):
        return min((lambda ep:ep.distance(p))(ep) for ep in self.enemy_planets)

    def my_fleets_attacking(self, planet):
        return sum( [ 1 for fleet in planet.attacking_fleets if fleet.owner == player.ME] )

    def closest_to_enemy_neutral_under_my_attack(self):
        best_distance = 1000000
        result_planet = None
        for planet in self.nobodies_planets:
            if self.my_fleets_attacking(planet) > 0:
                distance = self.enemy_com.distance(planet)
                if distance < best_distance:
                    best_distance = distance
                    result_planet = planet
        return result_planet


    def doScheduled(self):
        log.info("Scheduled move phase")
        # execute delayed moves first
        if self.scheduled_moves_at_turn.has_key(self.current_turn):
            for move in self.scheduled_moves_at_turn[self.current_turn]:
                if move.ship_count <= move.source.ship_count and move.ship_count > 0 and move.source.owner == PLAYER1 and self.ships_available[move.source] >= move.ship_count:
                    move.source.send_fleet(move.target, move.ship_count)
                    self.ships_available[move.source] -= move.ship_count
                else:
                    log.info("Can't execute move: %s,  ships avail: %s" % (move, self.ships_available[move.source]))


    def doPrep(self):
        log.info("Prep phase")

        self.max_distance_between_planets = 0
        for p1 in self.all_planets:
            for p2 in self.all_planets:
                self.max_distance_between_planets = max(self.max_distance_between_planets, p1.distance(p2))
        #log.info("Max distance: %s" % self.max_distance_between_planets)

        # calculate current high level metrics
        self.total_ships = {PLAYER1:0, PLAYER2:0}
        self.total_growth_rate = {PLAYER1:0, PLAYER2:0}
        self.ships_available = {}
        self.ships_needed = {}
        self.ships_needed_at_turn = {}
        self.planet_timeline = {}

        for planet in self.all_planets:
            scheduled_fleets_to_planet = self.get_scheduled_fleets_to(planet)
            scheduled_fleets_from_planet = self.get_scheduled_fleets_from(planet)
            self.planet_timeline[planet] = planet.in_future_timeline(self.max_distance_between_planets, scheduled_fleets_to_planet, scheduled_fleets_from_planet)
            need_help = False
            min_available = 1000000
            #log.info("timeline for %s: %s" % (planet, self.planet_timeline[planet]))
            prev_owner = planet.owner
            for step in self.planet_timeline[planet]:
                owner = step[0]
                ship_count = step[1]
                if owner == PLAYER2 and prev_owner == PLAYER1 and not need_help:
                    self.ships_needed[planet] = ship_count
                    self.ships_needed_at_turn[planet] = self.planet_timeline[planet].index(step) + 1
                    need_help = True
                    log.info("Planet %s needs help %s at %s" % (planet, ship_count, self.ships_needed_at_turn[planet]))
                if owner == planet.owner:
                    min_available = min(min_available, ship_count)
                    if min_available < 0:
                        log.info("Negative min_available: %s for %s" % (min_available, planet))
                        min_available = 0
                prev_owner = owner
            if need_help:
                self.ships_available[planet] = 0
            else:
                self.ships_available[planet] = min(min_available, planet.ship_count)
                self.ships_needed[planet] = 0

            if planet.owner != NOBODY:
                self.total_ships[planet.owner] += planet.ship_count
                self.total_growth_rate[planet.owner] += planet.growth_rate

        self.total_ships[PLAYER1] += self.total_fleet_ship_count(PLAYER1)
        self.total_ships[PLAYER2] += self.total_fleet_ship_count(PLAYER2)

        # calculate enemy's center of mass
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.enemy_planets:
            weighted_x += planet.position.x * (self.ships_available[planet] + planet.growth_rate)
            weighted_y += planet.position.y * (self.ships_available[planet] + planet.growth_rate)
            div += self.ships_available[planet] + planet.growth_rate
        if div == 0:
            div = 1

        self.enemy_com = Planet(self.universe, 666, weighted_x/div, weighted_y/div, 2, 0, 0)

        # For every planet, and every turn, calculate how many ships each player can send to it
        self.max_aid_at_turn = {PLAYER1:{}, PLAYER2:{}}
        for player in (PLAYER1 | PLAYER2):
            source_planets = list(self.universe.find_planets(player)) + self.get_neutrals_under_player_attack(player)
            for planet in self.all_planets:
                self.max_aid_at_turn[player][planet] = {}
                for turn in range(1, self.max_distance_between_planets+1):
                    max_aid = 0
                    for source_planet in source_planets:
                        if source_planet.id != planet.id and planet.distance(source_planet) < turn:
                            source_planet_time_step = self.planet_timeline[source_planet][turn - planet.distance(source_planet)]
                            if (source_planet_time_step[0] == player):
                                max_aid += source_planet_time_step[1]
                        else:
                            if source_planet.id != planet.id and planet.distance(source_planet) == turn:
                                source_planet_time_step = self.planet_timeline[source_planet][0]
                                if (source_planet_time_step[0] == player):
                                    max_aid += source_planet.ship_count
                    self.max_aid_at_turn[player][planet][turn] = max_aid
                    #log.info("Max aid by %s for %s at %s: %s" % (player.id, planet.id, turn, self.max_aid_at_turn[player][planet][turn]))

        log.info("MY STATUS: %s/%s" % (self.total_ships[PLAYER1], self.total_growth_rate[PLAYER1]))
        log.info("ENEMY STATUS: %s/%s" % (self.total_ships[PLAYER2], self.total_growth_rate[PLAYER2]))

    def doDefense(self):
        log.info("Defense phase")

        for planet_to_defend in self.all_planets:
            ships_to_send = self.ships_needed[planet_to_defend]
            if ships_to_send <= 0:
                continue
            min_distance = self.max_distance_between_planets
            max_distance = self.ships_needed_at_turn[planet_to_defend]
            for my_planet in self.my_planets:
                distance = my_planet.distance(planet_to_defend)
                min_distance = min(min_distance, distance)
            min_distance = max(min_distance, 1)
            defended = False
            for distance in range(min_distance, max_distance+1):
                # calculate if we can get enough ships from my planets to planet_to_defend within 'distance' turns
                ships_avail_to_defend = self.get_available_ships_within_distance(planet_to_defend, PLAYER1, distance)
                log.info("Ships avail to defend %s within %s dist: %s" % (planet_to_defend, distance, ships_avail_to_defend))
                if ships_avail_to_defend >= ships_to_send:
                    ships_left_to_send = ships_to_send
                    for source_planet in sorted(self.my_planets, key=lambda p : p.distance(planet_to_defend) + p.id/1000000.0):
                        current_distance = source_planet.distance(planet_to_defend)
                        ships_avail = self.ships_available[source_planet] + (distance-current_distance) * source_planet.growth_rate
                        if source_planet.id != planet_to_defend.id and ships_avail > 0:
                            log.info("Ships avail from %s: %s  at dist %s, dist = %s" % (source_planet, ships_avail, current_distance, distance))
                            ships_to_send = min(ships_left_to_send, ships_avail)
                            if current_distance == distance:
                                log.info("defending avail from %s: %s  at dist %s" % (source_planet, ships_to_send, current_distance))
                                source_planet.send_fleet(planet_to_defend, ships_to_send)
                            if current_distance < distance:
                                future_turn = self.current_turn + (distance - current_distance)
                                future_move = Move(source_planet, planet_to_defend, future_turn, ships_to_send)
                                log.info("Scheduled move: %s" % future_move)
                                if not self.scheduled_moves_at_turn.has_key(future_turn):
                                    self.scheduled_moves_at_turn[future_turn] = []
                                self.scheduled_moves_at_turn[future_turn].append(future_move)
                            ships_left_to_send -= ships_to_send
                            self.ships_available[source_planet] -= ships_to_send
                            if ships_left_to_send == 0:
                                defended = True
                                break
                if defended:
                    break

    def doFirstTurnOffense(self):
        candidates = []
        candidate_map = {}
        my_home = list(self.my_planets)[0]
        enemy_home = list(self.enemy_planets)[0]
        home_planet_distance = my_home.distance(enemy_home)
        ships_available = min(my_home.ship_count, my_home.growth_rate * home_planet_distance)

        i = 0
        max_attack_distance=0
        for p in sorted(self.nobodies_planets, key=lambda p : self.get_attack_ship_count_first_turn(p, my_home, enemy_home) + p.id/1000000.0):
          if p.distance(my_home) < p.distance(enemy_home) or p.distance(my_home) == p.distance(enemy_home):
            if p.distance(my_home) == p.distance(enemy_home) and p.ship_count > 10:
                continue
            candidates.append(p)
            candidate_map[i] = p
            max_attack_distance = max(max_attack_distance, p.distance(my_home))
            i += 1

        weights = []
        profits = []
        for c in candidates:
            attack_score = (self.max_distance_between_planets - c.distance(my_home) + 40) * c.growth_rate
            weight = self.get_attack_ship_count_first_turn(c, my_home, enemy_home)
            weights.append(weight)
            profits.append(attack_score)

        best_planets_to_attack = zeroOneKnapsack(profits,weights,ships_available)
        #log.info("best planets: %s" % best_planets_to_attack)

        sorted_moves = []
        for i in range(len(best_planets_to_attack[1])):
            if (best_planets_to_attack[1][i] != 0):
                planet_to_attack = candidate_map[i]
                my_home.send_fleet(planet_to_attack, planet_to_attack.ship_count+1)

    def doOffense(self):
        log.info("Offense phase")
        if self.current_turn == 1:
            self.doFirstTurnOffense()
            return

        best_planet_to_attack = None
        while True:
            best_planet_to_attack = None
            best_planet_to_attack_score = 0
            best_planet_to_attack_distance = 0
            best_planet_to_attack_ships_to_send = 0
            for planet_to_attack in self.not_my_planets:
                min_distance = self.max_distance_between_planets
                max_distance = 0
                for my_planet in self.my_planets:
                    distance = my_planet.distance(planet_to_attack)
                    min_distance = min(min_distance, distance)
                    max_distance = max(max_distance, distance)
                for fleet in self.universe.find_fleets(owner=PLAYER2, destination=planet_to_attack):
                    max_distance = max(max_distance, fleet.turns_remaining)
                #log.info("Max distance for %s: %s" % (planet_to_attack, max_distance))
                min_distance = max(min_distance, 1)
                for distance in range(min_distance, max_distance+1):
                    # calculate how many ships we need to get from my planets to planet_to_attack within 'distance' turns
                    planet_to_attack_future = self.planet_timeline[planet_to_attack][distance-1]
                    planet_to_attack_future_owner = planet_to_attack_future[0]
                    if planet_to_attack_future_owner == PLAYER1:
                        break
                    cost_to_conquer = 0 if planet_to_attack_future_owner == PLAYER2 else -1
                    time_to_profit = 0
                    if planet_to_attack_future_owner == player.NOBODY:
                        cost_to_conquer = planet_to_attack_future[1]
                        time_to_profit = int(ceil((cost_to_conquer+0.001)/planet_to_attack.growth_rate)) if planet_to_attack.growth_rate > 0 else 1000000
                    #log.info("Time to profit for %s is %s" % (planet_to_attack, time_to_profit))

                    if (distance+time_to_profit) >= self.max_distance_between_planets:
                        break
                    enemy_max_aid = self.max_aid_at_turn[PLAYER2][planet_to_attack][distance+time_to_profit]
                    if planet_to_attack_future_owner == player.PLAYER2:
                        enemy_max_aid += self.planet_timeline[planet_to_attack][distance+time_to_profit-1][1]
                    my_max_aid = self.max_aid_at_turn[PLAYER1][planet_to_attack][distance+time_to_profit] - (cost_to_conquer + 1) if planet_to_attack_future_owner == NOBODY else 0
                    ships_to_send = cost_to_conquer + max(enemy_max_aid - my_max_aid, 0) + 1

                    # calculate if we can get enough ships from my planets to planet_to_attack within 'distance' turns
                    ships_avail_to_attack = self.get_available_ships_within_distance(planet_to_attack, PLAYER1, distance)
                    if ships_avail_to_attack >= ships_to_send:
                        if self.planet_timeline[planet_to_attack][distance-1][0] in player.ENEMIES and self.planet_timeline[planet_to_attack][distance-2][0] == player.NOBODY:
                            continue

                        attack_score = self.get_attack_score(planet_to_attack, planet_to_attack_future_owner, distance)
                        log.info("Attack score of %s at dist %s is: %s - %s ships, cost %s" % (planet_to_attack, distance, attack_score, ships_to_send, cost_to_conquer))
                        if planet_to_attack_future_owner in player.ENEMIES or (attack_score-cost_to_conquer) >= 140:
                            if attack_score > best_planet_to_attack_score:
                                best_planet_to_attack_score = attack_score
                                best_planet_to_attack = planet_to_attack
                                best_planet_to_attack_distance = distance
                                best_planet_to_attack_ships_to_send = ships_to_send
                        break


            if best_planet_to_attack is None:
                return

            log.info("Best planet to attack: %s at dist %s with score %s" % (best_planet_to_attack, best_planet_to_attack_distance, best_planet_to_attack_score))

            ships_left_to_send = best_planet_to_attack_ships_to_send
            for source_planet in sorted(self.my_planets, key=lambda p : p.distance(best_planet_to_attack) + p.id/1000000.0):
                distance = source_planet.distance(best_planet_to_attack)
                ships_avail = self.ships_available[source_planet]
                if len(source_planet.attacking_fleets) == 0  and len(self.get_scheduled_fleets_from(source_planet)) == 0:
                    ships_avail += (best_planet_to_attack_distance-distance) * source_planet.growth_rate
                if self.ships_needed[source_planet] > 0:
                    ships_avail = 0
                if source_planet.id != best_planet_to_attack.id and ships_avail > 0:
                    ships_to_send = min(ships_left_to_send, ships_avail)
                    if distance == best_planet_to_attack_distance:
                        source_planet.send_fleet(best_planet_to_attack, ships_to_send)
                    if distance < best_planet_to_attack_distance:
                        future_turn = self.current_turn + (best_planet_to_attack_distance - distance)
                        future_move = Move(source_planet, best_planet_to_attack, future_turn, ships_to_send)
                        log.info("Scheduled move: %s" % future_move)
                        if not self.scheduled_moves_at_turn.has_key(future_turn):
                            self.scheduled_moves_at_turn[future_turn] = []
                        self.scheduled_moves_at_turn[future_turn].append(future_move)
                    ships_left_to_send -= ships_to_send
                    self.ships_available[source_planet] -= ships_to_send
                    if ships_left_to_send == 0:
                        break

    def doPostOffense(self):
        log.info("Post-Offense phase")
        if len(self.enemy_planets) == 0:
            return

        planets_to_send_to = copy(self.my_planets)
        neutral_candidate = self.closest_to_enemy_neutral_under_my_attack()
        if neutral_candidate is not None:
           planets_to_send_to = planets_to_send_to | neutral_candidate

        # cache closest and com enemy planet distances
        closest_enemy_planet_distance_map = {}
        com_enemy_planet_distance_map = {}
        for planet in planets_to_send_to:
            closest_enemy_planet_distance_map[planet] = self.closest_enemy_planet_distance(planet)
            com_enemy_planet_distance_map[planet] = self.enemy_com.distance(planet)

        my_nearest_to_enemy_planets = sorted(planets_to_send_to, key=lambda p : p.distance(self.enemy_com) +  p.id/1000000.0)

        for source_planet in self.my_planets:
            if self.ships_available[source_planet] > 0:
                #log.info("Post-Offense for %s" % source_planet)
                for dest_planet in my_nearest_to_enemy_planets:
                    distance = source_planet.distance(dest_planet)
                    if distance > 0 and distance < com_enemy_planet_distance_map[source_planet]:
                        if com_enemy_planet_distance_map[dest_planet] < com_enemy_planet_distance_map[source_planet] and \
                          closest_enemy_planet_distance_map[dest_planet] <= closest_enemy_planet_distance_map[source_planet]:
                            source_planet.send_fleet(dest_planet, self.ships_available[source_planet])
                            self.ships_available[source_planet] = 0
                            break


    def do_turn(self):
        self.all_planets = self.universe.all_planets
        self.my_planets = self.universe.my_planets
        self.enemy_planets = self.universe.enemy_planets
        self.nobodies_planets = self.universe.nobodies_planets
        self.not_my_planets = self.universe.not_my_planets
        self.current_turn = self.universe.game.turn_count

        if len(self.my_planets) == 0:
            return

        self.doPrep()
        self.doScheduled()
        self.doDefense()
        self.doOffense()
        self.doPostOffense()
Esempio n. 4
0
class MyBot(BaseBot):
    def total_fleet_ship_count(self, owner):
        return sum(
            [fleet.ship_count for fleet in self.universe.find_fleets(owner)])

    def closest_enemy_planet_distance(self, p):
        return min((lambda ep: ep.distance(p))(ep)
                   for ep in self.universe.enemy_planets)

    def enemy_ships_reinforcing(self, planet, turn):
        return sum([
            fleet.ship_count for fleet in planet.reinforcement_fleets
            if fleet.owner in player.NOT_ME and fleet.turns_remaining <= turn
        ])

    def doPrep(self):
        log.info("Prep phase")

        self.max_distance_between_planets = 0
        for p1 in self.universe.all_planets:
            for p2 in self.universe.all_planets:
                self.max_distance_between_planets = max(
                    self.max_distance_between_planets, p1.distance(p2))
        #log.info("Max distance: %s" % self.max_distance_between_planets)

        # calculate current high level metrics
        self.my_total_ships_available = 0
        self.my_total_ships = 0
        self.my_total_growth_rate = 0
        self.enemy_total_ships_available = 0
        self.enemy_total_ships = 0
        self.enemy_total_growth_rate = 0
        self.ships_available = {}
        self.ships_needed = {}
        self.planet_timeline = {}
        for planet in self.universe.all_planets:
            if len(planet.attacking_fleets) == 0:
                self.ships_available[planet] = planet.ship_count
                self.ships_needed[planet] = 0
                simulation_distance = self.max_distance_between_planets
                self.planet_timeline[planet] = planet.in_future_timeline(
                    simulation_distance)
            else:
                simulation_distance = self.max_distance_between_planets
                self.planet_timeline[planet] = planet.in_future_timeline(
                    simulation_distance)
                max_needed = 0
                min_available = 1000000
                #log.info("timeline for %s: %s" % (planet, self.planet_timeline[planet]))
                for step in self.planet_timeline[planet]:
                    owner = step[0]
                    ship_count = step[1]
                    if owner != planet.owner:
                        max_needed = max(max_needed, ship_count)
                    else:
                        min_available = min(min_available, ship_count)
                if max_needed > 0:
                    # do we bail if we are going to lose this planet anyway?
                    self.ships_available[planet] = 0
                    self.ships_needed[planet] = max_needed
                else:
                    self.ships_available[planet] = min_available
                    self.ships_needed[planet] = 0

            if (planet.owner == player.ME):
                self.my_total_ships_available += self.ships_available[planet]
                self.my_total_growth_rate += planet.growth_rate
                self.my_total_ships += planet.ship_count
            else:
                self.enemy_total_ships_available += self.ships_available[
                    planet]
                self.enemy_total_growth_rate += planet.growth_rate
                self.enemy_total_ships += planet.ship_count
            #log.info("avail ships for %s: %s" % (planet, self.ships_available[planet]))

        # prevent initial overexpansion
        if self.universe.game.turn_count <= 2:
            for my_planet in self.universe.my_planets:
                for enemy_planet in self.universe.enemy_planets:
                    max_enemy_fleet = self.ships_available[enemy_planet]
                    distance = my_planet.distance(enemy_planet)
                    ships_needed_for_safety = max_enemy_fleet - distance * my_planet.growth_rate
                    if ships_needed_for_safety > (
                            my_planet.ship_count -
                            self.ships_available[my_planet]):
                        deficit = ships_needed_for_safety - (
                            my_planet.ship_count -
                            self.ships_available[my_planet])
                        #log.info("deficit for %s: %s" % (my_planet, deficit))
                        if deficit > self.ships_available[my_planet]:
                            deficit = self.ships_available[my_planet]

                        self.ships_available[my_planet] -= deficit
                        self.my_total_ships_available -= deficit

        self.my_total_ships += self.total_fleet_ship_count(player.ME)
        self.enemy_total_ships += self.total_fleet_ship_count(player.NOT_ME)

        # calculate enemy's center of mass
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.universe.enemy_planets:
            weighted_x += planet.position.x * (self.ships_available[planet] +
                                               planet.growth_rate)
            weighted_y += planet.position.y * (self.ships_available[planet] +
                                               planet.growth_rate)
            div += self.ships_available[planet] + planet.growth_rate
        if div == 0:
            div = 1

        self.enemy_com = Planet(self.universe, 666, weighted_x / div,
                                weighted_y / div, 2, 0, 0)

        # For every planet, and every turn, calculate how many ships the enemy CAN sent to it's aid
        self.max_aid_at_turn = {}
        for planet in self.universe.all_planets:
            self.max_aid_at_turn[planet] = {}
            for turn in range(1, self.max_distance_between_planets + 1):
                max_aid = 0
                for enemy_planet in self.universe.all_planets:
                    if enemy_planet.id != planet.id and planet.distance(
                            enemy_planet) < turn:
                        enemy_planet_time_step = self.planet_timeline[
                            enemy_planet][turn - planet.distance(enemy_planet)]
                        if (enemy_planet_time_step[0] in player.ENEMIES):
                            max_aid += enemy_planet_time_step[1]
                if self.planet_timeline[planet][turn - 1][0] in player.ENEMIES:
                    max_aid += self.planet_timeline[planet][turn - 1][1]
                self.max_aid_at_turn[planet][turn] = max_aid
                #log.info("Max aid for %s at %s: %s" % (planet.id, turn, self.max_aid_at_turn[planet][turn]))
        #log.info("Max aid: %s" % self.max_aid_at_turn)

        log.info("MY STATUS: %s/%s - %s available" %
                 (self.my_total_ships, self.my_total_growth_rate,
                  self.my_total_ships_available))
        log.info("ENEMY STATUS: %s/%s - %s available" %
                 (self.enemy_total_ships, self.enemy_total_growth_rate,
                  self.enemy_total_ships_available))
        #log.info("ENEMY COM: %s, %s" % (self.enemy_com.position.x, self.enemy_com.position.y))

    def doDefenseOffense(self):
        log.info("Offense/Defense phase")

        possible_moves = []
        for my_planet in self.universe.my_planets:
            for planet_to_attack in self.universe.all_planets:
                if planet_to_attack.id == my_planet.id:
                    continue
                attack_distance = my_planet.distance(planet_to_attack)
                planet_to_attack_future = self.planet_timeline[
                    planet_to_attack][attack_distance - 1]
                planet_to_attack_future_owner = planet_to_attack_future[0]
                cost_to_conquer = -1
                time_to_profit = 0
                if planet_to_attack_future_owner == player.NOBODY:
                    cost_to_conquer = planet_to_attack_future[1]
                    if planet_to_attack.growth_rate > 0:
                        time_to_profit = int(
                            ceil((cost_to_conquer + 0.001) /
                                 planet_to_attack.growth_rate))
                    if (time_to_profit + attack_distance
                        ) >= self.max_distance_between_planets:
                        time_to_profit = self.max_distance_between_planets - attack_distance
                    #log.info("Time to profit for %s is %s" % (planet_to_attack, time_to_profit))
                else:
                    if planet_to_attack_future_owner in player.ENEMIES:
                        cost_to_conquer = 0

                max_aid = self.max_aid_at_turn[planet_to_attack][
                    attack_distance + time_to_profit]
                ships_to_send = cost_to_conquer + max_aid + 1
                if planet_to_attack_future_owner != player.ME and ships_to_send > 0 and ships_to_send <= self.ships_available[
                        my_planet]:
                    if self.planet_timeline[planet_to_attack][
                            attack_distance -
                            1][0] in player.ENEMIES and self.planet_timeline[
                                planet_to_attack][attack_distance -
                                                  2][0] == player.NOBODY:
                        continue
                    attack_score = (self.max_distance_between_planets -
                                    attack_distance +
                                    40) * planet_to_attack.growth_rate
                    possible_moves.append((my_planet, planet_to_attack,
                                           ships_to_send, attack_score))
                    #log.info("Attack score of %s from %s is: %s - %s ships" % (planet_to_attack, my_planet, attack_score, ships_to_send))

        # execute the best moves
        planets_attacked = []
        sorted_moves = sorted(possible_moves, key=lambda m: m[3], reverse=True)
        log.info("Best moves: %s" % len(sorted_moves))

        for move in sorted_moves:
            ships_to_send = move[2]
            planet_to_attack = move[1]
            my_planet = move[0]
            if ships_to_send <= self.ships_available[
                    my_planet] and planet_to_attack not in planets_attacked:
                my_planet.send_fleet(planet_to_attack, ships_to_send)
                self.ships_available[my_planet] -= ships_to_send
                planets_attacked.append(planet_to_attack)

    def doPostOffense(self):
        log.info("Post-Offense phase")
        if len(self.universe.enemy_planets) == 0:
            return

        planets_to_send_to = self.universe.my_planets
        # cache closest and com enemy planet distances
        closest_enemy_planet_distance_map = {}
        com_enemy_planet_distance_map = {}
        for planet in planets_to_send_to:
            closest_enemy_planet_distance_map[
                planet] = self.closest_enemy_planet_distance(planet)
            com_enemy_planet_distance_map[planet] = self.enemy_com.distance(
                planet)

        my_nearest_to_enemy_planets = sorted(
            planets_to_send_to,
            key=lambda p: p.distance(self.enemy_com) + p.id / 1000000.0)

        for source_planet in self.universe.my_planets:
            if self.ships_available[source_planet] > 0:
                #log.info("Post-Offense for %s" % source_planet)
                for dest_planet in my_nearest_to_enemy_planets:
                    distance = source_planet.distance(dest_planet)
                    if distance > 0 and closest_enemy_planet_distance_map[
                            dest_planet] <= closest_enemy_planet_distance_map[
                                source_planet]:
                        if com_enemy_planet_distance_map[
                                dest_planet] < com_enemy_planet_distance_map[
                                    source_planet]:
                            source_planet.send_fleet(
                                dest_planet,
                                self.ships_available[source_planet])
                            self.ships_available[source_planet] = 0
                            break

    def do_turn(self):
        if len(self.universe.my_planets) == 0:
            return

        self.doPrep()
        self.doDefenseOffense()
        self.doPostOffense()
class MyBot(BaseBot):
    def __init__(self, universe):
        self.universe = universe
        self.scheduled_moves_at_turn = {}

    def total_fleet_ship_count(self, owner):
        return sum(
            [fleet.ship_count for fleet in self.universe.find_fleets(owner)])

    def get_neutrals_under_player_attack(self, player):
        result = []
        for planet in self.nobodies_planets:
            if sum([
                    1 for fleet in planet.attacking_fleets
                    if fleet.owner == player
            ]) > 0:
                result.append(planet)
        return result

    def get_available_ships_within_distance(self, planet_to_attack, player,
                                            distance):
        result = 0
        for planet in self.universe.find_planets(player):
            if planet.id != planet_to_attack.id and planet.distance(
                    planet_to_attack
            ) <= distance and self.ships_needed[planet] == 0:
                ships_avail = self.ships_available[planet]
                if len(planet.attacking_fleets) == 0 and len(
                        self.get_scheduled_fleets_from(planet)) == 0:
                    ships_avail += (
                        distance -
                        planet.distance(planet_to_attack)) * planet.growth_rate
                result += ships_avail
        return result

    def get_attack_score(self, planet_to_attack, future_owner, distance):
        attack_score = (self.max_distance_between_planets - distance +
                        40) * planet_to_attack.growth_rate
        if future_owner in player.ENEMIES:
            attack_score *= 2
        return attack_score

    def get_scheduled_fleets_to(self, planet):
        result = []
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.target == planet:
                    distance = move.source.distance(move.target)
                    turns_remaining = distance + (
                        move.turn - self.universe.game.turn_count)
                    fleet = Fleet(self.universe, 12345, 1, move.ship_count,
                                  move.source.id, move.target.id, distance,
                                  turns_remaining)
                    result.append(fleet)
        return result

    def get_scheduled_fleets_from(self, planet):
        result = []
        for moves in self.scheduled_moves_at_turn.values():
            for move in moves:
                if move.source == planet:
                    turns_remaining = move.turn - self.universe.game.turn_count
                    fleet = Fleet(self.universe, 12345, 1, move.ship_count,
                                  move.source.id, move.target.id,
                                  turns_remaining, turns_remaining)
                    result.append(fleet)
        return result

    def get_attack_ship_count_first_turn(self, planet_to_attack, my_home,
                                         enemy_home):
        my_dist = my_home.distance(planet_to_attack)
        enemy_dist = enemy_home.distance(planet_to_attack)
        if my_dist < enemy_dist:
            return planet_to_attack.ship_count + 1
        if my_dist == enemy_dist and planet_to_attack.ship_count <= planet_to_attack.growth_rate:
            return planet_to_attack.ship_count + 1
        return 1000000

    def closest_enemy_planet_distance(self, p):
        return min(
            (lambda ep: ep.distance(p))(ep) for ep in self.enemy_planets)

    def my_fleets_attacking(self, planet):
        return sum([
            1 for fleet in planet.attacking_fleets if fleet.owner == player.ME
        ])

    def closest_to_enemy_neutral_under_my_attack(self):
        best_distance = 1000000
        result_planet = None
        for planet in self.nobodies_planets:
            if self.my_fleets_attacking(planet) > 0:
                distance = self.enemy_com.distance(planet)
                if distance < best_distance:
                    best_distance = distance
                    result_planet = planet
        return result_planet

    def doScheduled(self):
        log.info("Scheduled move phase")
        # execute delayed moves first
        if self.scheduled_moves_at_turn.has_key(self.current_turn):
            for move in self.scheduled_moves_at_turn[self.current_turn]:
                if move.ship_count <= move.source.ship_count and move.ship_count > 0 and move.source.owner == PLAYER1 and self.ships_available[
                        move.source] >= move.ship_count:
                    move.source.send_fleet(move.target, move.ship_count)
                    self.ships_available[move.source] -= move.ship_count
                else:
                    log.info("Can't execute move: %s,  ships avail: %s" %
                             (move, self.ships_available[move.source]))

    def doPrep(self):
        log.info("Prep phase")

        self.max_distance_between_planets = 0
        for p1 in self.all_planets:
            for p2 in self.all_planets:
                self.max_distance_between_planets = max(
                    self.max_distance_between_planets, p1.distance(p2))
        #log.info("Max distance: %s" % self.max_distance_between_planets)

        # calculate current high level metrics
        self.total_ships = {PLAYER1: 0, PLAYER2: 0}
        self.total_growth_rate = {PLAYER1: 0, PLAYER2: 0}
        self.ships_available = {}
        self.ships_needed = {}
        self.ships_needed_at_turn = {}
        self.planet_timeline = {}

        for planet in self.all_planets:
            scheduled_fleets_to_planet = self.get_scheduled_fleets_to(planet)
            scheduled_fleets_from_planet = self.get_scheduled_fleets_from(
                planet)
            self.planet_timeline[planet] = planet.in_future_timeline(
                self.max_distance_between_planets, scheduled_fleets_to_planet,
                scheduled_fleets_from_planet)
            need_help = False
            min_available = 1000000
            #log.info("timeline for %s: %s" % (planet, self.planet_timeline[planet]))
            prev_owner = planet.owner
            for step in self.planet_timeline[planet]:
                owner = step[0]
                ship_count = step[1]
                if owner == PLAYER2 and prev_owner == PLAYER1 and not need_help:
                    self.ships_needed[planet] = ship_count
                    self.ships_needed_at_turn[
                        planet] = self.planet_timeline[planet].index(step) + 1
                    need_help = True
                    log.info("Planet %s needs help %s at %s" %
                             (planet, ship_count,
                              self.ships_needed_at_turn[planet]))
                if owner == planet.owner:
                    min_available = min(min_available, ship_count)
                    if min_available < 0:
                        log.info("Negative min_available: %s for %s" %
                                 (min_available, planet))
                        min_available = 0
                prev_owner = owner
            if need_help:
                self.ships_available[planet] = 0
            else:
                self.ships_available[planet] = min(min_available,
                                                   planet.ship_count)
                self.ships_needed[planet] = 0

            if planet.owner != NOBODY:
                self.total_ships[planet.owner] += planet.ship_count
                self.total_growth_rate[planet.owner] += planet.growth_rate

        self.total_ships[PLAYER1] += self.total_fleet_ship_count(PLAYER1)
        self.total_ships[PLAYER2] += self.total_fleet_ship_count(PLAYER2)

        if self.universe.game.turn_count <= 2:
            for my_planet in self.my_planets:
                for enemy_planet in self.enemy_planets:
                    max_enemy_fleet = self.ships_available[enemy_planet]
                    distance = my_planet.distance(enemy_planet)
                    ships_needed_for_safety = max_enemy_fleet - distance * my_planet.growth_rate
                    if ships_needed_for_safety > (
                            my_planet.ship_count -
                            self.ships_available[my_planet]):
                        deficit = ships_needed_for_safety - (
                            my_planet.ship_count -
                            self.ships_available[my_planet])
                        #log.info("deficit for %s: %s" % (my_planet, deficit))
                        if deficit > self.ships_available[my_planet]:
                            deficit = self.ships_available[my_planet]
                        self.ships_available[my_planet] -= deficit

        # calculate enemy's center of mass
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.enemy_planets:
            weighted_x += planet.position.x * (self.ships_available[planet] +
                                               planet.growth_rate)
            weighted_y += planet.position.y * (self.ships_available[planet] +
                                               planet.growth_rate)
            div += self.ships_available[planet] + planet.growth_rate
        if div == 0:
            div = 1

        self.enemy_com = Planet(self.universe, 666, weighted_x / div,
                                weighted_y / div, 2, 0, 0)

        # For every planet, and every turn, calculate how many ships each player can send to it
        self.max_aid_at_turn = {PLAYER1: {}, PLAYER2: {}}
        for player in (PLAYER1 | PLAYER2):
            source_planets = list(self.universe.find_planets(
                player)) + self.get_neutrals_under_player_attack(player)
            for planet in self.all_planets:
                self.max_aid_at_turn[player][planet] = {}
                for turn in range(1, self.max_distance_between_planets + 1):
                    max_aid = 0
                    for source_planet in source_planets:
                        if source_planet.id != planet.id and planet.distance(
                                source_planet) < turn:
                            source_planet_time_step = self.planet_timeline[
                                source_planet][turn -
                                               planet.distance(source_planet)]
                            if (source_planet_time_step[0] == player):
                                max_aid += source_planet_time_step[1]
                        else:
                            if source_planet.id != planet.id and planet.distance(
                                    source_planet) == turn:
                                source_planet_time_step = self.planet_timeline[
                                    source_planet][0]
                                if (source_planet_time_step[0] == player):
                                    max_aid += source_planet.ship_count
                    self.max_aid_at_turn[player][planet][turn] = max_aid
                    #log.info("Max aid by %s for %s at %s: %s" % (player.id, planet.id, turn, self.max_aid_at_turn[player][planet][turn]))

        log.info("MY STATUS: %s/%s" %
                 (self.total_ships[PLAYER1], self.total_growth_rate[PLAYER1]))
        log.info("ENEMY STATUS: %s/%s" %
                 (self.total_ships[PLAYER2], self.total_growth_rate[PLAYER2]))

    def doDefense(self):
        log.info("Defense phase")

        for planet_to_defend in self.all_planets:
            ships_to_send = self.ships_needed[planet_to_defend]
            if ships_to_send <= 0:
                continue
            min_distance = self.max_distance_between_planets
            max_distance = self.ships_needed_at_turn[planet_to_defend]
            for my_planet in self.my_planets:
                distance = my_planet.distance(planet_to_defend)
                min_distance = min(min_distance, distance)
            min_distance = max(min_distance, 1)
            defended = False
            for distance in range(min_distance, max_distance + 1):
                # calculate if we can get enough ships from my planets to planet_to_defend within 'distance' turns
                ships_avail_to_defend = self.get_available_ships_within_distance(
                    planet_to_defend, PLAYER1, distance)
                log.info("Ships avail to defend %s within %s dist: %s" %
                         (planet_to_defend, distance, ships_avail_to_defend))
                if ships_avail_to_defend >= ships_to_send:
                    ships_left_to_send = ships_to_send
                    for source_planet in sorted(
                            self.my_planets,
                            key=lambda p: p.distance(planet_to_defend
                                                     ) + p.id / 1000000.0):
                        current_distance = source_planet.distance(
                            planet_to_defend)
                        ships_avail = self.ships_available[source_planet] + (
                            distance -
                            current_distance) * source_planet.growth_rate
                        if source_planet.id != planet_to_defend.id and ships_avail > 0:
                            log.info(
                                "Ships avail from %s: %s  at dist %s, dist = %s"
                                % (source_planet, ships_avail,
                                   current_distance, distance))
                            ships_to_send = min(ships_left_to_send,
                                                ships_avail)
                            if current_distance == distance:
                                log.info(
                                    "defending avail from %s: %s  at dist %s" %
                                    (source_planet, ships_to_send,
                                     current_distance))
                                source_planet.send_fleet(
                                    planet_to_defend, ships_to_send)
                            if current_distance < distance:
                                future_turn = self.current_turn + (
                                    distance - current_distance)
                                future_move = Move(source_planet,
                                                   planet_to_defend,
                                                   future_turn, ships_to_send)
                                log.info("Scheduled move: %s" % future_move)
                                if not self.scheduled_moves_at_turn.has_key(
                                        future_turn):
                                    self.scheduled_moves_at_turn[
                                        future_turn] = []
                                self.scheduled_moves_at_turn[
                                    future_turn].append(future_move)
                            ships_left_to_send -= ships_to_send
                            self.ships_available[
                                source_planet] -= ships_to_send
                            if ships_left_to_send == 0:
                                defended = True
                                break
                if defended:
                    break

    def doFirstTurnOffense(self):
        candidates = []
        candidate_map = {}
        my_home = list(self.my_planets)[0]
        enemy_home = list(self.enemy_planets)[0]
        home_planet_distance = my_home.distance(enemy_home)
        ships_available = min(my_home.ship_count,
                              my_home.growth_rate * home_planet_distance)

        i = 0
        max_attack_distance = 0
        for p in sorted(self.nobodies_planets,
                        key=lambda p: self.get_attack_ship_count_first_turn(
                            p, my_home, enemy_home) + p.id / 1000000.0):
            if p.distance(my_home) < p.distance(enemy_home) or p.distance(
                    my_home) == p.distance(enemy_home):
                if p.distance(my_home) == p.distance(
                        enemy_home) and p.ship_count > 10:
                    continue
                candidates.append(p)
                candidate_map[i] = p
                max_attack_distance = max(max_attack_distance,
                                          p.distance(my_home))
                i += 1

        weights = []
        profits = []
        for c in candidates:
            attack_score = (self.max_distance_between_planets -
                            c.distance(my_home) + 40) * c.growth_rate
            weight = self.get_attack_ship_count_first_turn(
                c, my_home, enemy_home)
            weights.append(weight)
            profits.append(attack_score)

        best_planets_to_attack = zeroOneKnapsack(profits, weights,
                                                 ships_available)
        #log.info("best planets: %s" % best_planets_to_attack)

        sorted_moves = []
        for i in range(len(best_planets_to_attack[1])):
            if (best_planets_to_attack[1][i] != 0):
                planet_to_attack = candidate_map[i]
                my_home.send_fleet(planet_to_attack,
                                   planet_to_attack.ship_count + 1)

    def doOffense(self):
        log.info("Offense phase")
        if self.current_turn == 1:
            self.doFirstTurnOffense()
            return

        best_planet_to_attack = None
        while True:
            best_planet_to_attack = None
            best_planet_to_attack_score = 0
            best_planet_to_attack_distance = 0
            best_planet_to_attack_ships_to_send = 0
            for planet_to_attack in self.all_planets:
                min_distance = self.max_distance_between_planets
                max_distance = 0
                for my_planet in self.my_planets:
                    distance = my_planet.distance(planet_to_attack)
                    min_distance = min(min_distance, distance)
                    max_distance = max(max_distance, distance)
                for fleet in self.universe.find_fleets(
                        owner=PLAYER2, destination=planet_to_attack):
                    max_distance = max(max_distance, fleet.turns_remaining)
                #log.info("Max distance for %s: %s" % (planet_to_attack, max_distance))
                min_distance = max(min_distance, 1)
                for distance in range(min_distance, max_distance + 1):
                    # calculate how many ships we need to get from my planets to planet_to_attack within 'distance' turns
                    planet_to_attack_future = self.planet_timeline[
                        planet_to_attack][distance - 1]
                    planet_to_attack_future_owner = planet_to_attack_future[0]
                    if planet_to_attack_future_owner == PLAYER1:
                        break
                    cost_to_conquer = 0 if planet_to_attack_future_owner == PLAYER2 else -1
                    time_to_profit = 0
                    if planet_to_attack_future_owner == player.NOBODY:
                        cost_to_conquer = planet_to_attack_future[1]
                        time_to_profit = int(
                            ceil((cost_to_conquer + 0.001) /
                                 planet_to_attack.growth_rate)
                        ) if planet_to_attack.growth_rate > 0 else 1000000
                        if planet_to_attack_future_owner == NOBODY and self.enemy_com.distance(
                                planet_to_attack) < distance:
                            break
                    #log.info("Time to profit for %s is %s" % (planet_to_attack, time_to_profit))

                    if (distance + time_to_profit
                        ) >= self.max_distance_between_planets:
                        break

                    can_hold = True
                    for turn in range(distance, distance + time_to_profit + 1):
                        enemy_max_aid = self.max_aid_at_turn[PLAYER2][
                            planet_to_attack][turn]
                        if planet_to_attack_future_owner == player.PLAYER2:
                            enemy_max_aid += self.planet_timeline[
                                planet_to_attack][turn + time_to_profit - 1][1]
                        my_max_aid = self.max_aid_at_turn[PLAYER1][
                            planet_to_attack][turn] - (cost_to_conquer + 1)
                        if enemy_max_aid > my_max_aid:
                            can_hold = False
                            break
                    if not can_hold:
                        continue

                    enemy_max_aid = self.max_aid_at_turn[PLAYER2][
                        planet_to_attack][distance + time_to_profit]
                    if planet_to_attack_future_owner == player.PLAYER2:
                        enemy_max_aid += self.planet_timeline[
                            planet_to_attack][distance + time_to_profit - 1][1]
                    my_max_aid = self.max_aid_at_turn[PLAYER1][
                        planet_to_attack][distance + time_to_profit] - (
                            cost_to_conquer + 1
                        ) if planet_to_attack_future_owner == NOBODY else 0
                    ships_to_send = cost_to_conquer + max(
                        enemy_max_aid - my_max_aid, 0) + 1

                    # calculate if we can get enough ships from my planets to planet_to_attack within 'distance' turns
                    ships_avail_to_attack = self.get_available_ships_within_distance(
                        planet_to_attack, PLAYER1, distance)
                    if ships_avail_to_attack >= ships_to_send:
                        if self.planet_timeline[planet_to_attack][distance - 1][
                                0] in player.ENEMIES and self.planet_timeline[
                                    planet_to_attack][distance -
                                                      2][0] == player.NOBODY:
                            continue

                        attack_score = self.get_attack_score(
                            planet_to_attack, planet_to_attack_future_owner,
                            distance)
                        log.info(
                            "Attack score of %s at dist %s is: %s - %s ships, cost %s"
                            % (planet_to_attack, distance, attack_score,
                               ships_to_send, cost_to_conquer))
                        if planet_to_attack_future_owner in player.ENEMIES or (
                                attack_score - cost_to_conquer) >= 140:
                            if attack_score > best_planet_to_attack_score:
                                best_planet_to_attack_score = attack_score
                                best_planet_to_attack = planet_to_attack
                                best_planet_to_attack_distance = distance
                                best_planet_to_attack_ships_to_send = ships_to_send
                        break

            if best_planet_to_attack is None:
                return

            log.info("Best planet to attack: %s at dist %s with score %s" %
                     (best_planet_to_attack, best_planet_to_attack_distance,
                      best_planet_to_attack_score))

            ships_left_to_send = best_planet_to_attack_ships_to_send
            for source_planet in sorted(
                    self.my_planets,
                    key=lambda p: p.distance(best_planet_to_attack
                                             ) + p.id / 1000000.0):
                distance = source_planet.distance(best_planet_to_attack)
                ships_avail = self.ships_available[source_planet]
                if len(source_planet.attacking_fleets) == 0 and len(
                        self.get_scheduled_fleets_from(source_planet)) == 0:
                    ships_avail += (best_planet_to_attack_distance -
                                    distance) * source_planet.growth_rate
                if self.ships_needed[source_planet] > 0:
                    ships_avail = 0
                if source_planet.id != best_planet_to_attack.id and ships_avail > 0:
                    ships_to_send = min(ships_left_to_send, ships_avail)
                    if distance == best_planet_to_attack_distance:
                        source_planet.send_fleet(best_planet_to_attack,
                                                 ships_to_send)
                    if distance < best_planet_to_attack_distance:
                        future_turn = self.current_turn + (
                            best_planet_to_attack_distance - distance)
                        future_move = Move(source_planet,
                                           best_planet_to_attack, future_turn,
                                           ships_to_send)
                        log.info("Scheduled move: %s" % future_move)
                        if not self.scheduled_moves_at_turn.has_key(
                                future_turn):
                            self.scheduled_moves_at_turn[future_turn] = []
                        self.scheduled_moves_at_turn[future_turn].append(
                            future_move)
                    ships_left_to_send -= ships_to_send
                    self.ships_available[source_planet] -= ships_to_send
                    if ships_left_to_send == 0:
                        break

    def doPostOffense(self):
        log.info("Post-Offense phase")
        if len(self.enemy_planets) == 0:
            return

        planets_to_send_to = copy(self.my_planets)
        neutral_candidate = self.closest_to_enemy_neutral_under_my_attack()
        if neutral_candidate is not None:
            planets_to_send_to = planets_to_send_to | neutral_candidate

        # cache closest and com enemy planet distances
        closest_enemy_planet_distance_map = {}
        com_enemy_planet_distance_map = {}
        for planet in planets_to_send_to:
            closest_enemy_planet_distance_map[
                planet] = self.closest_enemy_planet_distance(planet)
            com_enemy_planet_distance_map[planet] = self.enemy_com.distance(
                planet)

        my_nearest_to_enemy_planets = sorted(
            planets_to_send_to,
            key=lambda p: p.distance(self.enemy_com) + p.id / 1000000.0)

        for source_planet in self.my_planets:
            if self.ships_available[source_planet] > 0:
                #log.info("Post-Offense for %s" % source_planet)
                for dest_planet in my_nearest_to_enemy_planets:
                    distance = source_planet.distance(dest_planet)
                    if distance > 0 and distance < com_enemy_planet_distance_map[
                            source_planet]:
                        if com_enemy_planet_distance_map[dest_planet] < com_enemy_planet_distance_map[source_planet] and \
                          closest_enemy_planet_distance_map[dest_planet] <= closest_enemy_planet_distance_map[source_planet]:
                            source_planet.send_fleet(
                                dest_planet,
                                self.ships_available[source_planet])
                            self.ships_available[source_planet] = 0
                            break

    def do_turn(self):
        self.all_planets = self.universe.all_planets
        self.my_planets = self.universe.my_planets
        self.enemy_planets = self.universe.enemy_planets
        self.nobodies_planets = self.universe.nobodies_planets
        self.not_my_planets = self.universe.not_my_planets
        self.current_turn = self.universe.game.turn_count

        if len(self.my_planets) == 0:
            return

        self.doPrep()
        self.doScheduled()
        #self.doDefense()
        self.doOffense()
        self.doPostOffense()
Esempio n. 6
0
class MyBot(BaseBot):

    def total_fleet_ship_count(self, owner):
        return sum( [ fleet.ship_count for fleet in self.universe.find_fleets(owner) ] )

    def closest_enemy_planet_distance(self, p):
        return min((lambda ep:ep.distance(p))(ep) for ep in self.universe.enemy_planets)

    def enemy_ships_reinforcing(self, planet, turn):
        return sum( [ fleet.ship_count for fleet in planet.reinforcement_fleets if fleet.owner in player.NOT_ME and fleet.turns_remaining <= turn ] )

    def doPrep(self):
        log.info("Prep phase")

        self.max_distance_between_planets = 0
        for p1 in self.universe.all_planets:
            for p2 in self.universe.all_planets:
                self.max_distance_between_planets = max(self.max_distance_between_planets, p1.distance(p2))
        #log.info("Max distance: %s" % self.max_distance_between_planets)


        # calculate current high level metrics
        self.my_total_ships_available = 0
        self.my_total_ships = 0
        self.my_total_growth_rate = 0
        self.enemy_total_ships_available = 0
        self.enemy_total_ships = 0
        self.enemy_total_growth_rate = 0
        self.ships_available = {}
        self.ships_needed = {}
        self.planet_timeline = {}
        for planet in self.universe.all_planets:
            if len(planet.attacking_fleets) == 0:
                self.ships_available[planet] = planet.ship_count
                self.ships_needed[planet] = 0
                simulation_distance = self.max_distance_between_planets
                self.planet_timeline[planet] = planet.in_future_timeline(simulation_distance)
            else:
                simulation_distance = self.max_distance_between_planets
                self.planet_timeline[planet] = planet.in_future_timeline(simulation_distance)
                max_needed = 0
                min_available = 1000000
                #log.info("timeline for %s: %s" % (planet, self.planet_timeline[planet]))
                for step in self.planet_timeline[planet]:
                    owner = step[0]
                    ship_count = step[1]
                    if owner != planet.owner:
                        max_needed = max(max_needed, ship_count)
                    else:
                        min_available = min(min_available, ship_count)
                if max_needed > 0:
                    # do we bail if we are going to lose this planet anyway?
                    self.ships_available[planet] = 0
                    self.ships_needed[planet] = max_needed
                else:
                    self.ships_available[planet] = min_available
                    self.ships_needed[planet] = 0

            if (planet.owner == player.ME):
                self.my_total_ships_available += self.ships_available[planet]
                self.my_total_growth_rate += planet.growth_rate
                self.my_total_ships += planet.ship_count
            else:
                self.enemy_total_ships_available += self.ships_available[planet]
                self.enemy_total_growth_rate += planet.growth_rate
                self.enemy_total_ships += planet.ship_count
            #log.info("avail ships for %s: %s" % (planet, self.ships_available[planet]))

        # prevent initial overexpansion
        if self.universe.game.turn_count <= 2:
            for my_planet in self.universe.my_planets:
                for enemy_planet in self.universe.enemy_planets:
                    max_enemy_fleet = self.ships_available[enemy_planet]
                    distance = my_planet.distance(enemy_planet)
                    ships_needed_for_safety = max_enemy_fleet-distance*my_planet.growth_rate
                    if ships_needed_for_safety > (my_planet.ship_count - self.ships_available[my_planet]):
                        deficit = ships_needed_for_safety - (my_planet.ship_count - self.ships_available[my_planet])
                        #log.info("deficit for %s: %s" % (my_planet, deficit))
                        if deficit > self.ships_available[my_planet]:
                            deficit = self.ships_available[my_planet]

                        self.ships_available[my_planet] -= deficit
                        self.my_total_ships_available -= deficit
            
        self.my_total_ships += self.total_fleet_ship_count(player.ME)
        self.enemy_total_ships += self.total_fleet_ship_count(player.NOT_ME)

        # calculate enemy's center of mass
        weighted_x = 0
        weighted_y = 0
        div = 0
        for planet in self.universe.enemy_planets:
            weighted_x += planet.position.x * (self.ships_available[planet] + planet.growth_rate)
            weighted_y += planet.position.y * (self.ships_available[planet] + planet.growth_rate)
            div += self.ships_available[planet] + planet.growth_rate
        if div == 0:
            div = 1

        self.enemy_com = Planet(self.universe, 666, weighted_x/div, weighted_y/div, 2, 0, 0)

        # For every planet, and every turn, calculate how many ships the enemy CAN sent to it's aid
        self.max_aid_at_turn = {}
        for planet in self.universe.all_planets:
            self.max_aid_at_turn[planet] = {}
            for turn in range(1, self.max_distance_between_planets+1):
                max_aid = 0
                for enemy_planet in self.universe.all_planets:
                    if enemy_planet.id != planet.id and planet.distance(enemy_planet) < turn:
                        enemy_planet_time_step = self.planet_timeline[enemy_planet][turn - planet.distance(enemy_planet)]
                        if (enemy_planet_time_step[0] in player.ENEMIES):
                            max_aid += enemy_planet_time_step[1]
                if self.planet_timeline[planet][turn-1][0] in player.ENEMIES:
                    max_aid += self.planet_timeline[planet][turn-1][1]
                self.max_aid_at_turn[planet][turn] = max_aid
                #log.info("Max aid for %s at %s: %s" % (planet.id, turn, self.max_aid_at_turn[planet][turn]))
        #log.info("Max aid: %s" % self.max_aid_at_turn)

        log.info("MY STATUS: %s/%s - %s available" % (self.my_total_ships, self.my_total_growth_rate, self.my_total_ships_available))
        log.info("ENEMY STATUS: %s/%s - %s available" % (self.enemy_total_ships, self.enemy_total_growth_rate, self.enemy_total_ships_available))
        #log.info("ENEMY COM: %s, %s" % (self.enemy_com.position.x, self.enemy_com.position.y))

    def doDefenseOffense(self):
        log.info("Offense/Defense phase")

        possible_moves = []
        for my_planet in self.universe.my_planets:
            for planet_to_attack in self.universe.all_planets:
                if planet_to_attack.id == my_planet.id:
                    continue
                attack_distance = my_planet.distance(planet_to_attack)
                planet_to_attack_future = self.planet_timeline[planet_to_attack][attack_distance-1]
                planet_to_attack_future_owner = planet_to_attack_future[0]
                cost_to_conquer = -1
                time_to_profit = 0
                if planet_to_attack_future_owner == player.NOBODY:
                    cost_to_conquer = planet_to_attack_future[1]
                    if planet_to_attack.growth_rate > 0:
                        time_to_profit = int(ceil((cost_to_conquer+0.001)/planet_to_attack.growth_rate))
                    if (time_to_profit+attack_distance) >= self.max_distance_between_planets:
                        time_to_profit = self.max_distance_between_planets - attack_distance
                    #log.info("Time to profit for %s is %s" % (planet_to_attack, time_to_profit))
                else:
                    if planet_to_attack_future_owner in player.ENEMIES:
                        cost_to_conquer = 0

                max_aid = self.max_aid_at_turn[planet_to_attack][attack_distance+time_to_profit]
                ships_to_send = cost_to_conquer + max_aid + 1
                if planet_to_attack_future_owner != player.ME and ships_to_send > 0 and ships_to_send <= self.ships_available[my_planet]:
                    if self.planet_timeline[planet_to_attack][attack_distance-1][0] in player.ENEMIES and self.planet_timeline[planet_to_attack][attack_distance-2][0] == player.NOBODY:
                        continue
                    attack_score = (self.max_distance_between_planets - attack_distance + 40) * planet_to_attack.growth_rate
                    possible_moves.append((my_planet, planet_to_attack, ships_to_send, attack_score))
                    #log.info("Attack score of %s from %s is: %s - %s ships" % (planet_to_attack, my_planet, attack_score, ships_to_send))

        # execute the best moves
        planets_attacked = []
        sorted_moves = sorted(possible_moves, key=lambda m : m[3], reverse=True)
        log.info("Best moves: %s" % len(sorted_moves))

        for move in sorted_moves:
            ships_to_send = move[2]
            planet_to_attack = move[1]
            my_planet = move[0]
            if ships_to_send <= self.ships_available[my_planet] and planet_to_attack not in planets_attacked:
                my_planet.send_fleet(planet_to_attack, ships_to_send)
                self.ships_available[my_planet] -= ships_to_send
                planets_attacked.append(planet_to_attack)

    def doPostOffense(self):
        log.info("Post-Offense phase")
        if len(self.universe.enemy_planets) == 0:
            return

        planets_to_send_to = self.universe.my_planets
        # cache closest and com enemy planet distances
        closest_enemy_planet_distance_map = {}
        com_enemy_planet_distance_map = {}
        for planet in planets_to_send_to:
            closest_enemy_planet_distance_map[planet] = self.closest_enemy_planet_distance(planet)
            com_enemy_planet_distance_map[planet] = self.enemy_com.distance(planet)

        my_nearest_to_enemy_planets = sorted(planets_to_send_to, key=lambda p : p.distance(self.enemy_com) +  p.id/1000000.0)

        for source_planet in self.universe.my_planets:
            if self.ships_available[source_planet] > 0:
                #log.info("Post-Offense for %s" % source_planet)
                for dest_planet in my_nearest_to_enemy_planets:
                    distance = source_planet.distance(dest_planet)
                    if distance > 0 and closest_enemy_planet_distance_map[dest_planet] <= closest_enemy_planet_distance_map[source_planet]:
                        if com_enemy_planet_distance_map[dest_planet] < com_enemy_planet_distance_map[source_planet]:
                            source_planet.send_fleet(dest_planet, self.ships_available[source_planet])
                            self.ships_available[source_planet] = 0
                            break


    def do_turn(self):
        if len(self.universe.my_planets) == 0:
            return

        self.doPrep()
        self.doDefenseOffense()
        self.doPostOffense()