def __init__(self, state, actions, bounties, spawns, protection_memory): self.num_ships = len(actions.ships) # if there are no ships, there is nothing to do if self.num_ships == 0: return # read relevant spawning information self.spawns_wanted = spawns.ships_wanted self.spawns_possible = spawns.ships_possible likely_spawns = spawns.spawn_pos[0:self.spawns_possible] self.protected = np.setdiff1d(protection_memory, likely_spawns) # set up candidate moves for each ship and compute # distances on an appropriately weighted graph self.geometry(state, actions) # the optimal assignment will assign only one ship to each site # but we want more than one ship to go back to each yard so # we add duplicates of the yards to the rewards to make this possible duplicates = np.tile(state.my_yard_pos, self.num_ships - 1) ind_to_site = np.append(duplicates, state.sites) # calculate the value of going to a site for each ship cost_matrix = np.vstack( [self.rewards(ship, state, bounties) for ship in actions.ships]) # find the optimal assignment of ships to destinations # the optimal assignment assigns ship_inds[i] to site_inds[i] ship_inds, site_inds = assignment(cost_matrix, maximize=True) # go through the solution of the optimal assignment problem and # order the moves by preference self.destinations = {} self.values = {} for ship_ind, site_ind in zip(ship_inds, site_inds): # store destination and value of the ship ship = actions.ships[ship_ind] self.destinations[ship] = ind_to_site[site_ind] self.values[ship] = cost_matrix[ship_ind, site_ind] # sort moves by how much they decrease the distance # to the assigned destination dest_dists = self.move_dists[ship][:, self.destinations[ship]] self.moves[ship] = self.moves[ship][dest_dists.argsort()] return
def move(state, actions, targets): # if there are no ships pending, there is nothing to do if len(actions.ships) == 0: return # calculate the value per step of going to a specific site cost_matrix, threat_matrix, threat_scores = matrices( state, actions, targets) # regularize infinities - replace them by very negative finite values # that can never be compensated by a good matching. so any matching # with an effective infinity is worse than any matching without one finite = np.isfinite(cost_matrix) infinite = ~finite eff_inf = 1 + 2 * len(actions.ships) * np.max(np.abs(cost_matrix[finite])) cost_matrix[infinite] = -eff_inf # find the optimal assignment of ships to sites ship_inds, sites = assignment(cost_matrix, maximize=True) # go through the solution - if the assigned site is legal and safe # we move onto it, otherwise we add the ship to list of ships # for which decisions are made independently threatened = [] depositing = [] for ship_ind, site in zip(ship_inds, sites): ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # if the ship was assigned to an unsafe site, decide on a move later # unless the site is a protected yard if infinite[ship_ind, site] or threat_matrix[ship_ind, site]: threatened.append(ship_ind) continue # if the ship is depositing after the interest spike and # there is traffic at the yard, move freely spike = (state.total_steps - state.step) < STEPS_SPIKE spike = spike and (state.my_yard_pos.size > 0) if spike: yard_ind = np.argmin(state.dist[state.my_yard_pos, pos], axis=0) yard = state.my_yard_pos[yard_ind] close = state.dist[yard, pos] <= 3 traffic = np.sum(state.dist[state.my_ship_pos, yard] <= 2) >= 6 if traffic and close: depositing.append(ship_ind) continue decision = state.pos_to_move(pos, site) actions.decided[ship] = decision state.update(ship, decision) # decide on actions for ships that were assigned to unsafe sites for ship_ind in threatened: ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # restrict to sites with fewest threats legal = np.flatnonzero(state.dist[pos, :] <= 1) scores = threat_scores[ship_ind, legal] candidates = legal[scores == scores.min()] # further restrict to sites with least opponent collisions scores = state.moved_this_turn[candidates] candidates = candidates[scores == scores.min()] # of these, choose the site with the highest ranking ranking = targets.moves[ship] ind = np.in1d(ranking, candidates).argmax() site = ranking[ind] decision = state.pos_to_move(pos, site) # if we don't have any safe squares to go to, and more cargo # than the cost to convert, convert and keep the difference if threat_matrix[ship_ind, site] and (hal >= state.convert_cost): decision = "CONVERT" actions.decided[ship] = decision state.update(ship, decision) for ship_ind in depositing: ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # only check for threats, not self-collisions legal = np.flatnonzero(state.dist[pos, :] <= 1) scores = threat_scores[ship_ind, legal] candidates = legal[scores == scores.min()] ranking = targets.moves[ship] ind = np.in1d(ranking, candidates).argmax() site = ranking[ind] decision = state.pos_to_move(pos, site) actions.decided[ship] = decision state.update(ship, decision) actions.ships.clear() return
def __init__(self, state, actions, bounties, spawns): self.num_ships = len(actions.ships) # если кораблей нет, делать нечего if self.num_ships == 0: return # защищаем те верфи, которые работают, и в этом ходу у них не будет спауна likely_spawns = spawns.spawn_pos[0:spawns.ships_possible] yards = np.setdiff1d(working_yards(state), likely_spawns) # расстояние от ближайшего корабля противника до каждой верфи inds = np.ix_(state.opp_ship_pos, yards) opp_ship_dist = np.amin(state.dist[inds], axis=0, initial=state.map_size) # расстояние ближайшего дружественного корабля к каждой верфи inds = np.ix_(state.my_ship_pos, yards) my_ship_dist = np.amin(state.dist[inds], axis=0, initial=state.map_size) # если корабли противника начинают приближаться к верфи по сравнению # к своим, возвращаемся, чтобы защитить их inds = opp_ship_dist <= (2 + my_ship_dist) self.protected = yards[inds] self.protection_radius = opp_ship_dist[inds] # настраиваем возможные ходы для каждого корабля и вычисляем # расстояния на правильно взвешенном графике self.geometry(state, actions) # оптимальное назначение присвоит каждому месту только один корабль # но мы хотим, чтобы на каждую верфь вернулось более одного корабля, поэтому # мы добавляем дубликаты верфей к наградам, чтобы это стало возможным duplicates = np.tile(state.my_yard_pos, self.num_ships - 1) ind_to_site = np.append(duplicates, state.sites) # рассчитываем стоимость посещения места для каждого корабля cost_matrix = np.vstack( [self.rewards(ship, state, bounties) for ship in actions.ships]) # найти оптимальное назначение кораблей по направлениям # оптимальное назначение присваивает ship_inds [i] site_inds [i] ship_inds, site_inds = assignment(cost_matrix, maximize=True) # проходим решение задачи оптимального назначения и # упорядочиваем ходы по предпочтениям self.destinations = {} self.values = {} for ship_ind, site_ind in zip(ship_inds, site_inds): # сохранить пункт назначения и стоимость корабля ship = actions.ships[ship_ind] self.destinations[ship] = ind_to_site[site_ind] self.values[ship] = cost_matrix[ship_ind, site_ind] # sort перемещается по тому, насколько он уменьшает расстояние # в назначенный пункт назначения dest_dists = self.move_dists[ship][:, self.destinations[ship]] self.moves[ship] = self.moves[ship][dest_dists.argsort()] return
def move(state, actions, targets, protection_memory): # if there are no ships pending, there is nothing to do if len(actions.ships) == 0: return # calculate the value per step of going to a specific site cost_matrix, threats, weak_threats = matrices(state, actions, targets) # regularize infinities - replace them by very negative finite values # that can never be compensated by a good matching. so any matching # with an effective infinity is worse than any matching without one finite = np.isfinite(cost_matrix) infinite = ~finite eff_inf = 1 + 2 * len(actions.ships) * np.max(np.abs(cost_matrix[finite])) cost_matrix[infinite] = -eff_inf # find the optimal assignment of ships to sites ship_inds, sites = assignment(cost_matrix, maximize=True) # go through the solution - if the assigned site is legal and safe # we move onto it, otherwise we add the ship to list of ships # for which decisions are made independently threatened = [] depositing = [] for ship_ind, site in zip(ship_inds, sites): ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # if the ship was assigned to an unsafe site, decide on a move later # unless the site is a protected yard if infinite[ship_ind, site] or threats[ship_ind, site]: if site not in protection_memory: threatened.append(ship_ind) continue # if the ship is depositing after the interest spike and # there is traffic at the yard, move freely spike = (state.total_steps - state.step) < STEPS_SPIKE spike = spike and (state.my_yard_pos.size > 0) if spike: yard_ind = np.argmin(state.dist[state.my_yard_pos, pos], axis=0) yard = state.my_yard_pos[yard_ind] close = state.dist[yard, pos] <= 3 traffic = np.sum(state.dist[state.my_ship_pos, yard] <= 4) >= 10 if traffic and close: depositing.append(ship_ind) continue decision = state.pos_to_move(pos, site) actions.decided[ship] = decision state.update(ship, decision) # decide on actions for ships that were assigned to unsafe sites for ship_ind in threatened: ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] illegal = (state.dist[pos, :] > 1) self_col = state.moved_this_turn weak_opp_col = weak_threats[ship_ind, :] # ideally, don't collide with opponents or our own ships opp_col_flag = False exclude = illegal | self_col | weak_opp_col # if this is impossible, don't collide with opponents if np.sum(~exclude) == 0: exclude = illegal | weak_opp_col # if this is impossible, don't collide with our own ships if np.sum(~exclude) == 0: opp_col_flag = True exclude = illegal | self_col # otherwise just make a move if np.sum(~exclude) == 0: exclude = illegal # in most of these situations, staying put is a bad move # we could get lucky escaping other ships if np.sum(~exclude) >= 2: exclude[pos] = True # get the highest-ranked moved that is not excluded candidates = state.sites[~exclude] ranking = targets.moves[ship] ind = np.in1d(ranking, candidates).argmax() site = ranking[ind] decision = state.pos_to_move(pos, site) # if we don't have any safe squares to go to, and more cargo # than it cost to convert, convert and keep the difference if opp_col_flag and (hal >= state.convert_cost): decision = "CONVERT" actions.decided[ship] = decision state.update(ship, decision) for ship_ind in depositing: ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] illegal = (state.dist[pos, :] > 1) weak_opp_col = weak_threats[ship_ind, :] # ideally don't collide with opponent ships exclude = illegal | weak_opp_col # otherwise just make a move if np.sum(~exclude) == 0: exclude = illegal # get the highest-ranked moved that is not excluded candidates = state.sites[~exclude] ranking = targets.moves[ship] ind = np.in1d(ranking, candidates).argmax() site = ranking[ind] decision = state.pos_to_move(pos, site) actions.decided[ship] = decision state.update(ship, decision) actions.ships.clear() return
def __init__(self, state, actions, bounties, spawns): self.num_ships = len(actions.ships) # if there are no ships, there is nothing to do if self.num_ships == 0: return # protect those yards that are working and won't have a spawn this turn likely_spawns = spawns.spawn_pos[0:spawns.ships_possible] yards = np.setdiff1d(working_yards(state), likely_spawns) # distance of closest opponent ship to each yard inds = np.ix_(state.opp_ship_pos, yards) opp_ship_dist = np.amin(state.dist[inds], axis=0, initial=state.map_size) # distance of closest friendly ship to each yard inds = np.ix_(state.my_ship_pos, yards) my_ship_dist = np.amin(state.dist[inds], axis=0, initial=state.map_size) # if opponent ships start getting too close to a yard compared # to our own, start heading back to protect them inds = opp_ship_dist <= (2 + my_ship_dist) self.protected = yards[inds] self.protection_radius = opp_ship_dist[inds] # set up candidate moves for each ship and compute # distances on an appropriately weighted graph self.geometry(state, actions) # the optimal assignment will assign only one ship to each site # but we want more than one ship to go back to each yard so # we add duplicates of the yards to the rewards to make this possible duplicates = np.tile(state.my_yard_pos, self.num_ships - 1) ind_to_site = np.append(duplicates, state.sites) # calculate the value of going to a site for each ship cost_matrix = np.vstack( [self.rewards(ship, state, bounties) for ship in actions.ships]) # find the optimal assignment of ships to destinations # the optimal assignment assigns ship_inds[i] to site_inds[i] ship_inds, site_inds = assignment(cost_matrix, maximize=True) # go through the solution of the optimal assignment problem and # order the moves by preference self.destinations = {} self.values = {} for ship_ind, site_ind in zip(ship_inds, site_inds): # store destination and value of the ship ship = actions.ships[ship_ind] self.destinations[ship] = ind_to_site[site_ind] self.values[ship] = cost_matrix[ship_ind, site_ind] # sort moves by how much they decrease the distance # to the assigned destination dest_dists = self.move_dists[ship][:, self.destinations[ship]] self.moves[ship] = self.moves[ship][dest_dists.argsort()] return
def move(state, actions, targets): # если нет ожидающих кораблей, то делать нечего if len(actions.ships) == 0: return # рассчитываем стоимость за каждый шаг, идя к определенному месту cost_matrix, threat_matrix, threat_scores = matrices( state, actions, targets) # упорядочим бесконечности - заменим их очень отрицательными конечными значениями # чтобы никогда не могло быть компенсировано хорошим соответствием. так что любое соответствие # с эффективной бесконечностью хуже, чем любое сопоставление без нее finite = np.isfinite(cost_matrix) infinite = ~finite eff_inf = 1 + 2 * len(actions.ships) * np.max(np.abs(cost_matrix[finite])) cost_matrix[infinite] = -eff_inf # находим оптимальное присваивание кораблей к местам ship_inds, sites = assignment(cost_matrix, maximize=True) # найти решение - если назначенное место законно и безопасно # переходим на него, иначе добавляем корабль в список кораблей # по которым решения принимаются независимо threatened = [] depositing = [] for ship_ind, site in zip(ship_inds, sites): ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # если корабль был назначен на небезопасный объект, решаем переехать позже # если место не является охраняемой верфью if infinite[ship_ind, site] or threat_matrix[ship_ind, site]: threatened.append(ship_ind) continue # если корабль делает депозит после всплеска процентов и # в верфи движение, передвигаемся свободно spike = (state.total_steps - state.step) < STEPS_SPIKE spike = spike and (state.my_yard_pos.size > 0) if spike: yard_ind = np.argmin(state.dist[state.my_yard_pos, pos], axis=0) yard = state.my_yard_pos[yard_ind] close = state.dist[yard, pos] <= 3 traffic = np.sum(state.dist[state.my_ship_pos, yard] <= 2) >= 6 if traffic and close: depositing.append(ship_ind) continue decision = state.pos_to_move(pos, site) actions.decided[ship] = decision state.update(ship, decision) # принимаем решение о действиях для кораблей, которые были отнесены к небезопасным местам for ship_ind in threatened: ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # ограничиваться местами с наименьшим количеством угроз legal = np.flatnonzero(state.dist[pos, :] <= 1) scores = threat_scores[ship_ind, legal] candidates = legal[scores == scores.min()] # далее ограничиваем места с наименьшим количеством столкновений с противниками scores = state.moved_this_turn[candidates] candidates = candidates[scores == scores.min()] # из них выбираем места с наивысшим рейтингом ranking = targets.moves[ship] ind = np.in1d(ranking, candidates).argmax() site = ranking[ind] decision = state.pos_to_move(pos, site) # если у нас нет безопасных площадок, куда можно было бы пойти, и еще груза # чем затраты на преобразование, преобразование и сохранение разницы if threat_matrix[ship_ind, site] and (hal >= state.convert_cost): decision = "CONVERT" actions.decided[ship] = decision state.update(ship, decision) for ship_ind in depositing: ship = actions.ships[ship_ind] pos, hal = state.my_ships[ship] # проверяем только на угрозы, а не на столкновения legal = np.flatnonzero(state.dist[pos, :] <= 1) scores = threat_scores[ship_ind, legal] candidates = legal[scores == scores.min()] ranking = targets.moves[ship] ind = np.in1d(ranking, candidates).argmax() site = ranking[ind] decision = state.pos_to_move(pos, site) actions.decided[ship] = decision state.update(ship, decision) actions.ships.clear() return