def get_seen_in_direction(pos: Tuple[int, int], direction: Tuple[int, int]) -> int: """get the first seen seat in the direction""" i, j = tuple_add_tuple(pos, direction) while 0 <= i < len(data) and 0 <= j < len(data[i]): if data[i][j] >= 0: break else: i, j = tuple_add_tuple((i, j), direction) return data[i][j] if 0 <= i < len(data) and 0 <= j < len( data[i]) else None
def follow_directions(dirs: List[List[str]]) -> Tuple[int, int]: """Follow a given set of directions""" pos: Tuple[int, int] = (0, 0) for dir in dirs: if dir[0] == "forward": pos = tuple_add_tuple(pos, (int(dir[1]), 0)) elif dir[0] == "down": pos = tuple_add_tuple(pos, (0, int(dir[1]))) elif dir[0] == "up": pos = tuple_add_tuple(pos, (0, -int(dir[1]))) else: raise Exception("Unknown direction {}".format(dir[0])) return pos
def follow_direction(dirs: str) -> Tuple[int, int]: """follow a string of given instructions and change the tile at the end""" pos = (0, 0) i = 0 while i < len(dirs): # change position if dirs[i] == "e" or dirs[i] == "w": pos = tuple_add_tuple(pos, directions[dirs[i]]) i += 1 else: pos = tuple_add_tuple(pos, directions[dirs[i:i + 2]]) i += 2 return pos
def get_neighbor_in_direction( d, pos: Tuple[int, int, int, int], direction: Tuple[int, int, int, int]) -> bool: """get the neighboring element in the direction""" i, j, k, l = tuple_add_tuple(pos, direction) return d[i][j][k][l] if 0 <= i < len(d) and 0 <= j < len( d[i]) and 0 <= k < len(d[i][j]) and 0 <= l < len( d[i][j][k]) else None
def get_adjacent_tile_colors(colored: Dict[Tuple[int, int], bool], tile: Tuple[int, int]) -> Dict[bool, int]: """get the colors of all adjacent tiles""" adjacent = {False: 0, True: 0} for curr_dir in directions.values(): new = tuple_add_tuple(curr_dir, tile) if new in colored: adjacent[colored[new]] += 1 else: adjacent[False] += 1 return adjacent
def get_basin_size(height_map: Map, x: int, y: int) -> int: """find the size of a basin with minimum at (x,y) [9 does not count]""" neighbors = [(1, 0), (-1, 0), (0, 1), (0, -1)] basin_points: List[Tuple[int, int]] = [(x, y)] new_added = True while new_added: new_added = False for point in basin_points: # if current point has neighbors != 9 add them if get_lowest_adjacent(height_map, point[0], point[1], exclude=basin_points) < 9: # add all points that have not been analyzed yet for neighbor in neighbors: neighbor_coord = tuple_add_tuple(point, neighbor) # point hasn't been analyzed yet and is in bounds and is no maximum if neighbor_coord not in basin_points and \ 0 <= neighbor_coord[0] < len(height_map[0]) and \ 0 <= neighbor_coord[1] < len(height_map) and \ height_map[neighbor_coord[1]][neighbor_coord[0]] < 9: basin_points.append(neighbor_coord) new_added = True return len(basin_points)
def flash_octopus(position: pos, curr_map: List[List[int]], already_flashed: List[pos]) -> int: """flash an octopus at position, call recursive if necessary""" nof_flashes = 0 # octopus can flash once every time-step if position not in already_flashed: # append to flashed and flash already_flashed.append(position) nof_flashes += 1 # add +1 to all 8 adjacent octopus for adj in adjacent: neighbor = tuple_add_tuple(position, adj) # if neighbor on board increase if 0 <= neighbor[0] < 10 and 0 <= neighbor[1] < 10: curr_map[neighbor[1]][neighbor[0]] += 1 # if this changed neighbor above threshold, call recursive if curr_map[neighbor[1]][neighbor[0]] > 9: sub_flashes = flash_octopus(neighbor, curr_map, already_flashed) nof_flashes += sub_flashes return nof_flashes
def tile_colors_after_n_days(colored: Dict[Tuple[int, int], bool], n: int = 100) -> Dict[Tuple[int, int], bool]: """get the number of active tiles after n days""" for _ in range(n): # work on duplicate of data new_colored = deepcopy(colored) # add neighboring tiles for tile in colored.keys(): for curr_dir in directions.values(): new = tuple_add_tuple(curr_dir, tile) if not new in new_colored: new_colored[new] = False # for every tile check adjacent tiles and calculate changes for tile in new_colored.keys(): adjacent = get_adjacent_tile_colors(colored, tile) if new_colored[tile] and (0 == adjacent[True] or adjacent[True] > 2): new_colored[tile] = False elif not new_colored[tile] and adjacent[True] == 2: new_colored[tile] = True colored = new_colored return colored
def get_neighbor_in_direction(pos: Tuple[int, int], direction: Tuple[int, int]) -> int: """get the neighboring element in the direction""" i, j = tuple_add_tuple(pos, direction) return data[i][j] if 0 <= i < len(data) and 0 <= j < len( data[i]) else None
def probe_step(pos: Position, vel: Velocity) -> (Position, Velocity): """given all the params, calculate the next step""" new_pos = tuple_add_tuple(pos, vel) new_vel = (vel[0] + (-1 if vel[0] > 0 else 0 if vel[0] == 0 else 1), vel[1] - 1) return new_pos, new_vel
def move_wp(self, dist: Tuple[int, int]): """move the waypoint in a given direction""" self.wp = tuple_add_tuple(self.wp, dist)
def move_ship(self, dist: Tuple[int, int]): """move ship in the given direction""" self.pos = tuple_add_tuple(self.pos, dist)
def assemble_puzzle(tiles: Dict[int, List[str]]) -> List[str]: """get the final puzzle image""" # get a dict of all the tile sides tile_sides = get_puzzle_sides(tiles) # find matching tiles for every tile matching_ids = get_puzzle_matching(tile_sides) # start from one tile and add all matching ids to the current stack curr_key, stack = list(matching_ids.items())[0] tile_height = len(tiles[curr_key]) stack = list([(curr_key, *item)[:4] for item in stack]) stack: List[Tuple[int, int, int, int]] # init first tile position to (0,0) (-> numbers may be negative if its the wrong corner!) positions: Dict[int, Tuple[int, int]] positions = {curr_key: (0, 0)} while len(stack) > 0: # take one new item from stack old_tile_id, new_tile_id, old_side_pos, new_side_pos = stack[0] new_tile = tiles[new_tile_id] old_pos = positions[old_tile_id] # rotate the tile according to reference tile if old_side_pos < 0: raise Exception("old id is negative") if (old_side_pos + new_side_pos) % 2 != 0: # rotation by 90° or 270° diff = (abs(old_side_pos) - abs(new_side_pos)) % 4 if diff == -1 or diff == 3: # 90° new_tile = rotate_tile("r90", new_tile) else: # 270° new_tile = rotate_tile("r270", new_tile) # Special cases -> change direction to keep orientation of sides consistent # case positive if old_side_pos == 0 and new_side_pos == 1: new_tile = rotate_tile("h", new_tile) elif old_side_pos == 1 and new_side_pos == 0: new_tile = rotate_tile("v", new_tile) elif old_side_pos == 2 and new_side_pos == 3: new_tile = rotate_tile("h", new_tile) elif old_side_pos == 3 and new_side_pos == 2: new_tile = rotate_tile("v", new_tile) # case negative elif old_side_pos == 0 and new_side_pos == -3: new_tile = rotate_tile("h", new_tile) elif old_side_pos == 1 and new_side_pos == -2: new_tile = rotate_tile("v", new_tile) elif old_side_pos == 2 and new_side_pos == -1: new_tile = rotate_tile("h", new_tile) elif old_side_pos == 3 and new_side_pos == -4: new_tile = rotate_tile("v", new_tile) elif abs(new_side_pos ) % 2 != 0: # abs = 1, 3 -> match left and right sides if old_side_pos == abs(new_side_pos): # rotate left to right new_tile = rotate_tile("h", new_tile) if new_side_pos < 0: # mirror left to right to invert top and bottom sides new_tile = rotate_tile("v", new_tile) else: # abs = 0, 2, 4 -> match top and bottom sides if old_side_pos == abs(new_side_pos) % 4: # rotate top to bottom new_tile = rotate_tile("v", new_tile) if new_side_pos < 0: # mirror top to bottom to invert left and right sides new_tile = rotate_tile("h", new_tile) # save tile and tile position tiles[new_tile_id] = deepcopy(new_tile) diff_pos = (1 if abs(old_side_pos) == 1 else -1 if abs(old_side_pos) == 3 else 0, -1 if abs(old_side_pos) % 4 == 0 else 1 if abs(old_side_pos) == 2 else 0) # int id: pos (x,y) positions[new_tile_id] = tuple_add_tuple(old_pos, diff_pos) # get new tile sides and change orientation of neighbors new_tile_sides = get_tile_sides(new_tile) tile_sides[new_tile_id] = new_tile_sides matching = find_matching_tiles(tile_sides, new_tile_sides, new_tile_id) # modify current stack: remove all items that refer to curr item stack = [ stack_item for stack_item in stack if stack_item[1] != new_tile_id ] # modify future stack: add new edges that are not on stack for item in matching: if not (item[0]) in positions.keys( ): # skip tiles that have been added yet stack.append((new_tile_id, *item)[:4]) # get top left item (min vals) min_x = min(pos[0] for pos in positions.values()) min_y = min(pos[1] for pos in positions.values()) max_x = max(pos[0] for pos in positions.values()) max_y = max(pos[1] for pos in positions.values()) # put every puzzle piece in its place arranged_pieces: List[list] = [[ None for __ in range(abs(max_x - min_x + 1)) ] for _ in range(abs(max_y - min_y + 1))] for pos_id, pos in positions.items(): tile_content = tiles[ pos_id] # don't know why, but I need to invert all the pieces top to bottom once tile_content_cut = tile_content[1:-1] tile_content_cut = [line[1:-1] for line in tile_content_cut] arranged_pieces[pos[1] - min_y][pos[0] - min_x] = tile_content # arranged_pieces[pos[1] - min_y][pos[0] - min_x] = tile_content_cut # puzzle is assembled -> remove edges from every tile and assemble it to one big list of strings height_wo_borders = tile_height - 2 assembled = [ "" for _ in range(abs(max_y - min_y + 1) * height_wo_borders) ] # init to correct length for y in range(min_y, max_y + 1): for x in range(min_x, max_x + 1): pos = (x, y) tile_id = get_key_from_val(positions, pos) tile_content = tiles[ tile_id] # don't know why, but I need to invert all the pieces top to bottom once tile_content_cut = tile_content[1:-1] tile_content_cut = [line[1:-1] for line in tile_content_cut] for i in range(len(tile_content_cut)): y_offset = y - min_y assembled[i + height_wo_borders * y_offset] += tile_content_cut[i] return assembled