def place_food_fixed(self, board: BoardState): # Place 1 food within exactly 2 moves of each snake for s in board.snakes: snake_head = s.get_head() possible_food_locations = [ Position(x=snake_head.x - 1, y=snake_head.y - 1), Position(x=snake_head.x - 1, y=snake_head.y + 1), Position(x=snake_head.x + 1, y=snake_head.y - 1), Position(x=snake_head.x + 1, y=snake_head.y + 1), ] available_food_locations = [] for p in possible_food_locations: if not board.is_occupied_by_food(p): available_food_locations.append(p) if len(available_food_locations) <= 0: raise ValueError('not enough space to place food') food_position = np.random.choice(available_food_locations) food = Food(position=food_position) board.add_food(food) # Finally, always place 1 food in center of board for dramatic purposes center_position = Position(x=int((board.width - 1) / 2), y=int((board.height - 1) / 2)) if board.is_occupied(center_position): raise ValueError('not enough space to place food') center_food = Food(position=center_position) board.add_food(center_food)
def test_parse_snake(self): data = { "id": "snake-508e96ac-94ad-11ea-bb37", "name": "My Snake", "health": 54, "body": [ {"x": 0, "y": 5}, {"x": 1, "y": 5}, {"x": 1, "y": 4} ], "latency": "111", "head": {"x": 0, "y": 0}, "length": 3, "shout": "why are we shouting??", "squad": "A" } snake = Importer.parse_snake(data) self.assertEqual(snake.snake_id, "snake-508e96ac-94ad-11ea-bb37") self.assertEqual(snake.snake_name, "My Snake") self.assertEqual(snake.health, 54) self.assertEqual(len(snake.body), 3) self.assertEqual(snake.body[0], Position(x=0, y=5)) self.assertEqual(snake.body[1], Position(x=1, y=5)) self.assertEqual(snake.body[2], Position(x=1, y=4)) self.assertEqual(snake.get_length(), 3) self.assertEqual(snake.latency, "111") self.assertEqual(snake.shout, "why are we shouting??") self.assertEqual(snake.squad, "A")
def neighbor_positions(pos: Position) -> List[Position]: return [ Position(x=pos.x - 1, y=pos.y), Position(x=pos.x, y=pos.y - 1), Position(x=pos.x, y=pos.y + 1), Position(x=pos.x + 1, y=pos.y), ]
def _action_flood_fill(self, flood_queue: List, step: int, visited: List, action_plan: np.ndarray, enemy: bool): x_size, y_size = (self.board.width, self.board.height) new_queue = [] for (x, y) in flood_queue: if (x, y) in visited: continue if self.board.is_out_of_bounds(Position(x, y)): continue if enemy: if self.valid_board[x][y] + step < 0: continue if self.valid_board[x][y] == 0 or step <= abs( self.valid_board[x][y]): self.valid_board[x][y] = step if step < 4: action_plan[x][y] = Params_ValidActions.AREA_VALUE * (4 - step) if not enemy: if step < self.valid_board[x][y] < 20 or self.valid_board[x][ y] == 0: self.valid_board[x][y] = -step # eigenenes Schwanzende berücksichtigen if 20 < self.valid_board[ x, y] < 40 and self.valid_board[x, y] % 20 <= step: self.valid_board[x][y] = -step # Schwanzanfang berücksichtigen if step == 1: for snake in self.board.snakes: tail = snake.get_tail() if snake.health != 100 and (x, y) == ( tail.x, tail.y) and self.valid_board[x, y] != 1: self.valid_board[x][y] = -step visited.append((x, y)) # add next steps to queue if x > 0 and (x - 1, y) not in visited: new_queue.append((x - 1, y)) if x < (x_size - 1) and (x + 1, y) not in visited: new_queue.append((x + 1, y)) if y > 0 and (x, y - 1) not in visited: new_queue.append((x, y - 1)) if y < (y_size - 1) and (x, y + 1) not in visited: new_queue.append((x, y + 1)) return new_queue, visited, action_plan
def flood_kill(enemy: Snake, my_snake: Snake, kill_board: np.ndarray, board: BoardState, grid_map: GridMap): # TODO: Parameter snake_dead_in_rounds um mehr valide actions zu erhalten # - breite des PFades auf 1 begrenzen my_head = my_snake.get_head() enemy_head = enemy.get_head() kill_actions = [] search = False for part in enemy.body: kill_board[part.x][part.y] -= 1000 kill_board[my_head.x][my_head.y] -= 1000 x, y = np.unravel_index(kill_board.argmax(), kill_board.shape) print(kill_board) print(Position(x, y)) for (pos_x, pos_y) in get_valid_neigbours(x, y, kill_board): if 0 > kill_board[pos_x][pos_y] > -800: x, y = pos_x, pos_y search = True break enemy_dist = Distance.manhattan_dist(enemy_head, Position(x, y)) my_dist = Distance.manhattan_dist(my_head, Position(x, y)) if enemy_dist > my_dist and search: cost, path = AStar.a_star_search(my_head, Position(x, y), board, grid_map) count = 0 for pos, dir in path: if kill_board[pos.x][pos.y] >= 0: return [] kill_actions.append(dir) count += 1 return kill_actions
def test_parse_request(self): with open(os.path.dirname(__file__) + '/data/request_1.json') as json_file: data = json.load(json_file) print(data) parsed = Importer.parse_request(data) print(parsed) game_info, turn, board, you = parsed self.assertEqual(game_info.id, "game-00fe20da-94ad-11ea-bb37") self.assertEqual(game_info.ruleset['name'], "standard") self.assertEqual(game_info.ruleset['version'], "v.1.2.3") self.assertEqual(game_info.timeout, 500) self.assertEqual(turn, 14) self.assertEqual(you.snake_id, "snake-508e96ac-94ad-11ea-bb37") self.assertEqual(you.snake_name, "My Snake") self.assertEqual(you.health, 54) self.assertEqual(you.body[1], Position(x=1, y=0)) self.assertEqual(you.get_length(), 3) self.assertEqual(you.latency, "111") self.assertEqual(you.shout, "why are we shouting??") self.assertEqual(you.squad, "") self.assertEqual(board.height, 11) self.assertEqual(board.width, 11) self.assertEqual(len(board.snakes), 2) self.assertEqual(board.snakes[0].snake_id, "snake-508e96ac-94ad-11ea-bb37") self.assertEqual(board.snakes[1].snake_id, "snake-b67f4906-94ae-11ea-bb37") self.assertEqual(len(board.food), 3) self.assertEqual(board.food[0], Position(x=5, y=5)) self.assertEqual(board.food[1], Position(x=9, y=0)) self.assertEqual(board.food[2], Position(x=2, y=6)) self.assertEqual(len(board.hazards), 1) self.assertEqual(board.hazards[0], Position(x=3, y=2))
def test_clone(self): snake_a = Snake(snake_id="snake-a", body=[Position(x=3, y=3)]) snake_b = Snake(snake_id="snake-a", body=[Position(x=9, y=9)]) food_a = Food(x=1, y=2) snakes = [snake_a, snake_b] food = [food_a] board = BoardState(width=15, height=15, snakes=snakes, food=food) board_clone = board.clone() self.assertNotEqual(id(board), id(board_clone)) self.assertNotEqual(id(board.snakes[0]), id(board_clone.snakes[0])) self.assertNotEqual(id(board.food[0]), id(board_clone.food[0])) board_export = board.to_json() board_clone_export = board_clone.to_json() self.assertEqual(board_export, board_clone_export)
def test_to_json(self): snake_a = Snake(snake_id="snake-a", body=[Position(x=3, y=3)]) snake_b = Snake(snake_id="snake-a", body=[Position(x=9, y=9)]) food_a = Food(x=1, y=2) hazard_a = Hazard(x=4, y=1) snakes = [snake_a, snake_b] food = [food_a] hazards = [hazard_a] board = BoardState(width=15, height=20, snakes=snakes, food=food, hazards=hazards) json_data = board.to_json() self.assertEqual(json_data['width'], 15) self.assertEqual(json_data['height'], 20) self.assertListEqual(json_data['food'], [{'x': 1, 'y': 2}]) self.assertListEqual(json_data['hazards'], [{'x': 4, 'y': 1}])
def get_unoccupied_points(self, board: BoardState) -> List[Position]: occupied: GridMap[bool] = GridMap(width=board.width, height=board.height) for f in board.food: occupied.set_value_at_position(f, True) for snake in board.snakes: for p in snake.body: occupied.set_value_at_position(p, True) unoccupied_points = [] for y in range(board.height): for x in range(board.width): if not occupied.get_value_at(x=x, y=y): unoccupied_points.append(Position(x=x, y=y)) return unoccupied_points
def next_step(snake: Snake, board: BoardState, grid_map: GridMap) -> Direction: head = snake.get_head() #tail = snake.get_tail() middle = Position(board.height // 2, board.width // 2) valid = ValidActions.get_valid_actions(board, snake.possible_actions(), [snake], snake, grid_map, False) #for direction in Direction: # if not board.is_out_of_bounds(head.advanced(direction)): # if not (head.advanced(direction) in snake.get_body()): # valid.append(direction) #if snake.get_length() > 10: # for action in valid: # _, path = AStar.a_star_search(head.advanced(action), tail, board, grid_map) # print(len(path)) # end, pat = path[len(path)-1] # if not end == tail: # valid.remove(action) if middle in snake.get_body(): need, next_direction = SoloSurvival.need_food(snake, board, grid_map, valid) if need: if next_direction in valid: return next_direction else: #print("not valid") return np.random.choice(valid) else: next_direction = SoloSurvival.tail_gate(snake, board, grid_map) if next_direction in valid: return next_direction else: return np.random.choice(valid) else: if SoloSurvival.food_around_head(head, board): _, path = AStar.a_star_search(head, middle, board, grid_map) _, next_direction = path[0] else: _, path = AStar.a_star_search(head, middle, board, grid_map) _, next_direction = path[0] if next_direction in valid: return next_direction else: return np.random.choice(valid)
def place_snakes_fixed(self, board: BoardState): width = board.width starting_positions = [ Position(1, 1), Position(width - 2, width - 2), Position(1, width - 2), Position(width - 2, 1), Position(int((width - 1) / 2), 1), Position(width - 2, int((width - 1) / 2)), Position(int((width - 1) / 2), width - 2), Position(1, int((width - 1) / 2)) ] num_snakes = len(board.snakes) if num_snakes > len(starting_positions): raise ValueError('too many snakes for fixed start positions') # starting_positions = [starting_positions[i] for i in range(num_snakes)] random.shuffle(starting_positions) for i in range(num_snakes): board.snakes[i].set_initial_position(starting_positions[i], n=self.snake_start_size)
def food_around_head(head: Position, board: BoardState) -> bool: for direction in Direction: if not board.is_occupied(head.advanced(direction)): return False return True
def get_fill_stats(board: BoardState, next_position: Position, my_id: str, new_pos: bool) -> Tuple[Dict, List[Position]]: # TODO: Food in der Gewichtung des boards einbeziehen flood_queue = [] fill_stats = {} fill_board = np.full((board.width, board.height), 10) visited = [] no_food = False if Position(next_position.x, next_position.y) in board.food else True snake_length = [snake.get_length() for snake in board.snakes] snake_ids = [snake.snake_id for snake in board.snakes] order = np.argsort(snake_length) copy_snakes = board.snakes.copy() snakes = [copy_snakes[i] for i in order][::-1] snake_ids = [snake_ids[i] for i in order][::-1] snake_marker = -99 for snake in snakes: if snake.snake_id == my_id: flood_queue.append([(next_position.x, next_position.y)]) if new_pos: snake.body.insert(0, next_position) if no_food: del snake.body[-1] snake_marker = -50 else: flood_queue.append([(snake.get_head().x, snake.get_head().y)]) for pos in snake.body: fill_board[pos.x][pos.y] = snake_marker if pos is not snake.get_head(): visited.append((pos.x, pos.y)) snake_marker += 1 fill_stats[snake.snake_id] = 0 # iterativ Bewegungsbereich erschließen my_index = 0 count = 1 while any(flood_queue): snake_index = 0 # Tail iterativ freigeben idx = 0 for snakey in snakes: if snakey.get_length() > count: fill_board[snakey.body[-count].x][ snakey.body[-count].y] = 10 if (snakey.body[-count].x, snakey.body[-count].y) in visited: visited.remove( (snakey.body[-count].x, snakey.body[-count].y)) # flood_queue[idx].append((snakey.body[-(count + 1)].x, snakey.body[-(count + 1)].y)) idx += 1 # größte Schlange zuerst for snake_id in snake_ids: filled_fields_count = FloodFill.calcuate_step( fill_board, flood_queue, snake_index, visited) fill_stats[snake_id] += filled_fields_count if snake_id == my_id: my_index = snake_index snake_index += 1 count += 1 reachable_food = FloodFill.flood_food(fill_board, board.food, my_index) return fill_stats, reachable_food
def avoid_enemy(my_snake: Snake, board: BoardState, grid_map: GridMap, valid_actions: List[Direction], action_plan: ActionPlan, direction_depth: Dict) -> Direction: if not valid_actions: possible_actions = my_snake.possible_actions() valid_actions = ValidActions.get_valid_actions(board, possible_actions, board.snakes, my_snake, grid_map, avoid_food=False) num_snakes = 4 - len(board.snakes) flood_dist = 6 if len(board.snakes) > 2 else 99 my_head = my_snake.get_head() enemy_heads = [ snake.get_head() for snake in board.snakes if snake.snake_id != my_snake.snake_id ] middle = Position(int(board.height / 2), int(board.width / 2)) corners = [ Position(0, 0), Position(0, board.width), Position(board.height, 0), Position(board.height, board.width) ] escape_cost_dict = action_plan.escape_lane(my_head, valid_actions) # TODO: Unterscheidung der Params in Late game und early game abhängig von anzahl der Schlangen p_head = Params_Anxious.ALPHA_DISTANCE_ENEMY_HEAD[num_snakes] * (-1) p_corner = Params_Anxious.BETA_DISTANCE_CORNERS[num_snakes] p_mid = Params_Anxious.THETA_DISTANCE_MID[num_snakes] * (-1) p_border = Params_Anxious.EPSILON_NO_BORDER[num_snakes] p_food = (Params_Anxious.GAMMA_DISTANCE_FOOD[num_snakes] * 20 / my_snake.health) p_flood_min = Params_Anxious.OMEGA_FLOOD_FILL_MIN[num_snakes] * (-1) p_flood_max = Params_Anxious.OMEGA_FLOOD_FILL_MAX[num_snakes] p_flood_dead = Params_Anxious.OMEGA_FLOOD_DEAD[num_snakes] p_corridor = Params_Anxious.RHO_ESCAPE_CORRIDOR[num_snakes] * (-1) p_length = Params_Anxious.TAU_PATH_LENGTH[num_snakes] total_cost = np.array([], dtype=np.float64) direction_cost = np.zeros(10) for action in valid_actions: next_position = my_head.advanced(action) # calculate flood fill flood_fill_value, relevant_food = FloodFill.get_fill_stats( board, next_position, my_snake.snake_id, new_pos=True) enemy_flood = sum([ flood_fill_value[snake.snake_id] for snake in board.snakes if snake.snake_id != my_snake.snake_id and Distance.manhattan_dist(snake.get_head(), my_head) < flood_dist ]) # calculate all costs for heuristics direction_cost[0] = direction_depth[action] * p_length direction_cost[1] = escape_cost_dict[action] * p_corridor direction_cost[2] = ActionPlan.punish_border_fields( next_position, my_head, grid_map.width, grid_map.height) * p_border direction_cost[3] = sum([ Distance.manhattan_dist(next_position, enemy_head) for enemy_head in enemy_heads ]) * p_head direction_cost[4] = sum([ Distance.manhattan_dist(next_position, corner) for corner in corners if Distance.manhattan_dist(next_position, corner) < 9 ]) * p_corner direction_cost[5] = Distance.manhattan_dist(next_position, middle) * p_mid direction_cost[6] = flood_fill_value[ my_snake.snake_id] * p_flood_max direction_cost[7] = enemy_flood * p_flood_min direction_cost[8] = len(relevant_food) * p_food # extra points for killing enemy snake if num_snakes == 1: enemy_id = [ snake.snake_id for snake in board.snakes if snake.snake_id != my_snake.snake_id ][0] if flood_fill_value[enemy_id] < 20: flood_kill_value = (20 - flood_fill_value[enemy_id]) * 1000 direction_cost[9] = flood_kill_value * p_flood_dead total_cost = np.append(total_cost, direction_cost.sum()) direction_cost = np.zeros(10) if valid_actions: best_action = valid_actions[int(np.argmax(total_cost))] print(best_action) else: best_action = None return best_action
def parse_position(json): x = json['x'] y = json['y'] return Position(x=x, y=y)
def expand(self, next_position: Position, direction: Direction) -> int: step_history = [] dead_ends = {} searching = True value = -1 dead = False longest_way = 0 food_count = 0 # get first field of Direction and check if valid if self.valid_board[next_position.x][next_position.y] != value: return 0 step_history.append((next_position.x, next_position.y)) x_coord, y_coord = next_position.x, next_position.y while searching: positions = get_valid_neigbours(x_coord, y_coord, self.valid_board) for x, y in positions: # check if next value is valid and no dead end if self.valid_board[x][y] == value - 1 and ( x, y) not in dead_ends.keys(): if self.grid_map.grid_cache[x][y] == Occupant.Food: food_count += 1 # TODO: Logik dass schlangen ende berücksichtigt wird if Position(x, y) == self.my_snake.get_tail() and food_count: longest_way -= 1 dead = False step_history.append((x, y)) x_coord, y_coord = x, y value -= 1 break # mark dead ends else: dead = True # break if a valid endnode was found if self.valid_board[x][ y] == 0: # or self.valid_board[x][y] == -Params_ValidActions.DEPTH searching = False dead = False break # check if dead end and no more possible nodes to explore if dead and not step_history: searching = False # update range for each direction if longest_way >= value: longest_way = value self.food_on_path_count[direction] = food_count # check if dead end but still valid nodes to explore if dead and step_history: dead_ends[(x_coord, y_coord)] = value (x_pos, y_pos) = step_history.pop(-1) if self.grid_map.grid_cache[x_pos][y_pos] == Occupant.Food: food_count -= 1 if step_history: x_coord, y_coord = step_history[-1] value += 1 return longest_way
if y + 1 < square.shape[1]: neighbour_fields.append((x, y + 1)) if y - 1 >= 0: neighbour_fields.append((x, y - 1)) return neighbour_fields def count_border_fields(my_head: Position, board: np.ndarray) -> int: count = 0 width, height = board.shape valid_neigbours = get_valid_neigbours(my_head.x, my_head.y, board) for (x, y) in valid_neigbours: if x == 0 or y == 0 or x == width - 1 or y == height - 1: count = 1 return count """ self.board.snakes[0].body = [Position(2,3),Position(2,4),Position(2,5),Position(2,6),Position(2,7),Position(2,8), Position(2,9),Position(3,9),Position(4,9),Position(5,9),Position(6,9)] self.board.snakes[2].body = [Position(0,6),Position(0,7),Position(0,8),Position(0,9),Position(0,10),Position(1,10), Position(2,10),Position(3,10),Position(4,10),Position(5,10)Position(6,10)Position(7,10)] self.board.snakes[1].body = [Position(0,0),Position(0,1),Position(0,2),Position(0,3)] self.board.snakes[1].body = [Position(1,3),Position(1,4),Position(1,5),Position(1,6)] self.board.snakes[1].body = [Position(1,3),Position(1,4),Position(1,5),Position(0,5)] self.board.snakes[1].body = [Position(1,3),Position(1,4),Position(1,5),Position(0,5),Position(0,6), Position(0,7),Position(0,8)] """