def score_planet(pw: planet_wars.PlanetWars, p: planet_wars.Planet): """ Function to give a planet a score based on many factors. :param pw: `PlanetWars` object :param p: `Planet` object :return: `float` score of planet """ raw_score = get_raw_score(p) structural_score = 1 - (pythag(MY_PLANETS_CENTER, (p.x(), p.y())) / pw.map_size) surrounding_score = 0 for planet in filter(lambda _p: _p != p, pw.planets()): temp = ( 1 - (pw.distance(p.planet_id(), planet.planet_id()) / pw.map_size))**5 surrounding_score += get_raw_score(planet) * temp surrounding_score /= pw.total_growth latency_score = p.latency / pw.map_size center_score = 1 - (pw.distance(p.planet_id(), 0) / pw.map_size) score = 0 score += raw_score score += STRUCTURAL_FACTOR * structural_score score += SURROUNDING_FACTOR * surrounding_score score += LATENCY_FACTOR * latency_score score += CENTER_FACTOR * center_score return score
def furthest_meaningful_planet(pw: planet_wars.PlanetWars, planet: planet_wars.Planet, owner: int): planets = tuple(filter(lambda p: p.owner() == owner, pw.planets())) fleets = tuple(filter(lambda f: f.owner() == owner, pw.fleets())) furthest_distance = 0 for other_planet in planets: furthest_distance = max( furthest_distance, pw.distance(other_planet.planet_id(), planet.planet_id())) for fleet in fleets: furthest_distance = max( furthest_distance, fleet.turns_remaining() + pw.distance(fleet.destination_planet(), planet.planet_id())) return furthest_distance
def turn_to_take(pw: planet_wars.PlanetWars, my_planet: planet_wars.Planet, neutral_planet: planet_wars.Planet): """ Finds the minimum turns to take `neutral_planet` with `my_planet`. :param pw: `PlanetWars` object :param my_planet: `Planet` object :param neutral_planet: `Planet` object :return: `int` turns to take the planet """ distance = pw.distance(my_planet.planet_id(), neutral_planet.planet_id()) if my_planet.num_ships() > neutral_planet.num_ships(): return distance else: lacking_ships = neutral_planet.num_ships() - my_planet.num_ships() + 1 for t in range(pw.map_size): lacking_ships -= my_planet.my_arriving_ships[ t] + my_planet.growth_rate() if lacking_ships <= 0: break else: return 999999 return distance + t
def expand(pw: planet_wars.PlanetWars, expand_limit: int = 99, possible_planets=None, reckless: bool = False): """ Expand to neutral planets with all ships. Designed to come after `defend_possible()` because this doesn't account for possible attacks from the opponent. :param pw: `PlanetWars` object :param expand_limit: `int` the maximum number of planets to expand to. :param possible_planets: `list` of `Planet` objects, the planets to consider expanding to. None -> all :param reckless: `bool` whether to care about the defensibility of the planet :return: None """ expand_limit = min(expand_limit, len(pw.neutral_planets())) if possible_planets is None: possible_planets = filter(lambda p: p not in pw.my_future_neutrals, pw.neutral_planets()) possible_planets = filter(lambda p: p not in pw.enemy_future_neutrals, possible_planets) sorted_planets = sorted( possible_planets, key=lambda p: (score_planet(pw, p) - get_raw_score(p) + return_ships(pw, p) / (pw.map_size / 2)) / (p.num_ships() + 1), reverse=True) for _ in range(expand_limit): for attack_planet in sorted_planets[:expand_limit]: if not (attack_planet.latency > 0 and attack_planet.num_ships() < attack_planet.growth_rate()) and \ not reckless and not defensible(pw, attack_planet): continue # if not reckless and not defensible(pw, attack_planet): # continue quickest_planet = min( pw.my_planets(), key=lambda p: turn_to_take(pw, p, attack_planet)) closest_distance = pw.map_size for enemy_planet in pw.enemy_planets(): closest_distance = min( closest_distance, pw.distance(enemy_planet.planet_id(), attack_planet.planet_id())) for enemy_planet in pw.enemy_future_neutrals: closest_distance = min( closest_distance, pw.distance(enemy_planet.planet_id(), attack_planet.planet_id()) + pw.enemy_future_neutrals[enemy_planet][0]) if turn_to_take(pw, quickest_planet, attack_planet) > closest_distance: continue if quickest_planet.num_ships() > attack_planet.num_ships(): pw.issue_order(quickest_planet.planet_id(), attack_planet.planet_id(), attack_planet.num_ships() + 1) quickest_planet.remove_ships(attack_planet.num_ships() + 1) pw.my_future_neutrals[attack_planet] = (pw.distance( quickest_planet.planet_id(), attack_planet.planet_id()), 1) for planet in pw.planets(): planet.my_maximum_ships[pw.distance(quickest_planet.planet_id(), planet.planet_id()) - 1] -= \ attack_planet.num_ships() planet.my_maximum_ships[ pw.distance(quickest_planet.planet_id(), attack_planet.planet_id()) + pw.distance(attack_planet.planet_id(), planet.planet_id())] += 1 for t in range( pw.distance(quickest_planet.planet_id(), attack_planet.planet_id()) + pw.distance(attack_planet.planet_id(), planet.planet_id()), 2 * pw.map_size): planet.my_maximum_ships[ t] += attack_planet.growth_rate() expand_limit -= 1 sorted_planets.remove(attack_planet) break else: quickest_planet.num_ships(0) return else: break
class Engine(Debuggable): def __init__(self, mapp, enemy_cmd, my_bot_class, timeout=1000, max_turns=200): super(Engine, self).__init__() self.mapp = mapp self.timeout = timeout self.max_turns = max_turns self.enemy_cmd = enemy_cmd self.pw = PlanetWars() try: self.my_bot = my_bot_class() except: self.my_bot = None self.debug_name = "engine" self.playback = "" @property def turn(self): return self.pw.turn def plus_turn(self): self.pw.turn += 1 def load_map_data(self): f = open(self.mapp) data = "\n".join([line for line in f]) f.close() return data def load_init_state(self): '''load map data and make initial game state''' self.pw = PlanetWars() self.pw.load_data(self.load_map_data()) self.playback = ":".join(["%.10f,%.10f,%d,%d,%d" % (p.x, p.y, p.owner, p.num_ships, p.growth_rate) for p in self.pw.planets]) + "|" def _departure(self, enemy_fleets, my_fleets): for i, fleets in enumerate((my_fleets, enemy_fleets,)): for src, dest, num_ships in fleets: dist = self.pw.distance(src,dest) self.pw.fleets.append(Fleet(i+1,num_ships,src,dest,dist,dist)) #TODO make check for ships availability self.pw.planets[src].num_ships -= num_ships def _advancement(self): for fl in self.pw.fleets: fl.turns_remaining -= 1 for pl in self.pw.planets: if pl.owner > 0: pl.num_ships += pl.growth_rate def _get_participants(self, pl): participants = {pl.owner:pl.num_ships} updated_fleets = [] for fl in self.pw.fleets: if fl.dest == pl.id and fl.turns_remaining <= 0: if not fl.owner in participants: participants[fl.owner] = fl.num_ships else: participants[fl.owner] += fl.num_ships else: updated_fleets.append(fl) self.pw.fleets = updated_fleets return participants def _get_winner_second(self, participants): winner = Fleet(0, 0) second = Fleet(0, 0) for k, v in participants.items(): if v > second.num_ships: if v > winner.num_ships: second = winner winner = Fleet(k, v) else: second = Fleet(k, v) return winner, second def _process_arrival(self, pl, winner, second): if winner.num_ships > second.num_ships: pl.num_ships = winner.num_ships - second.num_ships pl.owner = winner.owner else: pl.num_ships = 0 def _arrival(self): for pl in self.pw.planets: participants = self._get_participants(pl) winner, second = self._get_winner_second(participants) self._process_arrival(pl, winner, second) def game_state_update(self, enemy_fleets, my_fleets): self._departure(enemy_fleets, my_fleets) self._advancement() self._arrival() @property def winner(self): return "Old" if self.pw.winner == 2 else "New" if self.pw.winner == 1 else "Draw" def set_enemy_standard_io(self): process = subprocess.Popen(self.enemy_cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) self.stdin, self.stdout = process.stdin, process.stdout def run(self): self.load_init_state() self.set_enemy_standard_io() self.my_bot.via_standard_io = False try: while True: self.make_turn() except EndOfTheGame: return def communicate_enemy_bot(self): self.stdin.write(self.pw.repr_for_enemy() + "go\n") self.stdin.flush() enemy_orders = [] while True: line = self.stdout.readline().replace("\n", "") self.debug("> %s" % line) self.print_it("> %s" % line) if line.startswith("go"): break enemy_orders.append(map(int, line.split(" "))) return enemy_orders def communicate_my_bot(self): self.my_bot.load_data(repr(self.pw)) self.my_bot.do_turn() self.print_it("\n".join(["< %d %d %d" % order for order in self.my_bot.real_orders])) def make_turn(self): self.plus_turn() self.print_it("Turn #%d" % self.turn) self.pw.is_game_over(self.max_turns) enemy_orders = self.communicate_enemy_bot() self.communicate_my_bot() self.game_state_update(enemy_orders, self.my_bot.real_orders) self.print_play_back() def print_play_back(self): if self.debug_enabled: planets = ["%d.%d" % (p.owner, p.num_ships) for p in self.pw.planets] fleets = ["%d.%d.%d.%d.%d.%d" % (f.owner, f.num_ships, f.src, f.dest, f.total_trip_length, f.turns_remaining) for f in self.pw.fleets] self.playback += ",".join(planets + fleets) + ":"