def add_center_duchy(size_list, allowable_chunks, a_dist, b_dist, ranking): '''Given a list of necessary sizes (size_list), and a list of list of hexes (allowable_chunks), attempt to create a center duchy, where counties are adjacent to both a and b (has a hex with 1 a_dist and 1 b_dist). Ranking is a dictionary of all (base) elements in allowable_chunks. Returns False if it doesn't find a solution in time.''' size = sum(size_list) while len(allowable_chunks) > 0: chunk = allowable_chunks.pop(0) if len(chunk) >= size: poss_centers = sort_hexlist([ el for el in chunk if all([nel in chunk for nel in el.neighbors()]) ], ranking) a_adj = sort_hexlist([el for el in chunk if a_dist[el] == 1], ranking) b_adj = sort_hexlist([el for el in chunk if b_dist[el] == 1], ranking) for center in poss_centers: duchy = Tile(hex_list=[], tile_list=[ make_capital_county(size_list[0], origin=center, coastal=False) ], rgb=d_col()) c_nbrs = [ el for el in duchy.tile_list[0].neighbors() if el in chunk ] drhl = duchy.real_hex_list() a_county = add_center_county( size_list[1], c_nbrs, a_adj, [el for el in chunk if el not in drhl]) if a_county: duchy.add_tile(a_county) else: continue drhl = duchy.real_hex_list() c_nbrs = [ el for el in duchy.tile_list[0].neighbors() if el in chunk and el not in drhl ] b_county = add_center_county(size_list[2], duchy.tile_list[0].neighbors(), b_adj, chunk) if b_county: duchy.add_tile(b_county) else: continue for _ in range(20): try: duchy.add_bordering_tile(size_list[3], rgb=c_col(), only=chunk, ranking=ranking) break except: continue return duchy return False
def duchy_from_snake(snake, size_list): duchy = Tile(rgb=d_col(), hex_list=[]) ind = 0 for c_size in size_list: duchy.add_tile(Tile(rgb=c_col(), hex_list=snake[ind:ind + c_size])) ind += c_size #The capital is assumed to come first, but it should be in the middle. if len(size_list) > 2: duchy.tile_list.insert(0, duchy.tile_list.pop(1)) return duchy
def make_world(cont_size_list=[3, 3, 3], island_size_list=[1, 1, 1], angles=[2, 4, 0]): '''Create three continents, with number of continental kingdoms determined by cont_size_list, and arrange them around an inner sea. angles determines where the continents go; 2,4,0 is northwest, northeast, south; 3,5,1 is north, southeast, southwest.''' assert len(cont_size_list) == 3 assert len(cont_size_list) == len(angles) assert len(cont_size_list) == len(island_size_list) world = Tile(hex_list=[]) #Continents for cont_idx, cont_size in enumerate(cont_size_list): cont = new_continent_gen(num_kingdoms=cont_size) bounding_hex = BoundingHex(cont) cont.origin, cont.rotation = bounding_hex.best_corner(angles[cont_idx]) world.add_tile(cont) #Inner sea if False: bounding_hex = BoundingHex(world) inner_sea = set() to_search = set(Cube()) while len(to_search) > 0: curr = to_search.pop() inner_sea.add(curr) to_search.update([ el for el in curr.neighbors() if el in bounding_hex and bounding_hex.dist(el) >= 2 and el not in inner_sea ]) if len(inner_sea) > 20: #We have enough to make a kingdom here water_height = {el: bounding_hex.dist(el) for el in inner_sea} inner_kingdom = make_island_kingdom(water_height) if inner_kingdom: world.add_tile(inner_kingdom) inner_sea -= set(inner_kingdom.inclusive_neighbors()) #We should just drop some sicily-esque islands. #Outer islands for cont_idx, island_size in enumerate(island_size_list): for _ in range(island_size): land = world.real_hex_list() land_dist = calculate_distances(world, land, 3)[0] cont_dist = calculate_distances(world.tile_list[cont_idx], land, 6)[0] for k, v in cont_dist.items(): if k in land_dist: cont_dist[k] = min(v, land_dist[k]) new_island = make_island_kingdom(cont_dist) if new_island: world.tile_list[cont_idx].add_tile(new_island) else: print("Failed to add an island!", cont_idx) return world
def inner_continent_gen(center, kingdoms, cen_nbrs, k_r_bnds, port_locs): continent = Tile(hex_list=[]) continent.add_tile(center) for k_idx in range(3): if not move_kingdom_into_place(continent, kingdoms[k_idx], cen_nbrs): return True, 'move into place' if not (check_water_access(continent.real_hex_list(), continent.real_water_list())): return True, 'water access 0' assigned = continent.real_total_list() c_dist = calculate_distances(center, assigned, 10)[0] k_dists = calculate_distances(kingdoms[:3], assigned, sum(BORDER_SIZE_LIST)) for a_idx, b_idx in combinations(range(3), r=2): allowable = [el for el in k_dists[a_idx] if el in k_dists[b_idx]] ranking = { el: c_dist[el] if el in c_dist else el.mag() for el in allowable } new_duchies = divide_into_duchies(BORDER_SIZE_LIST, 2, get_chunks(allowable), k_dists[a_idx], k_dists[b_idx], ranking) if new_duchies: for duchy in new_duchies: continent.add_tile(duchy) else: return True, 'border duchies 3' if not (check_water_access(continent.real_hex_list(), continent.real_water_list())): return True, 'water access bd 3' print('Added 3 kingdoms!') if len(kingdoms) > 3: if not inner_add_triangle(continent, kingdoms, 3): return True, 'add fourth kingdom' print('Added 4 kingdoms!') if len(kingdoms) > 4: if not inner_add_triangle(continent, kingdoms, 4): return True, 'add fifth kingdom' print('Added 5 kingdoms!') return False, continent #(continent, kingdoms)
def make_kingdom( origin=Cube(0, 0, 0), size_list=KINGDOM_SIZE_LIST, rgb_tuple=None, coastal=True, ): """rgb_tuple is complicated. For each level, the left element is the rgb of the title for that tile, and the right element is a list of rgb_tuples for the tiles the next element below (or a list of rgb tuples for baronies).""" rgb_tuple = rgb_tuple or random_rgb_tuple(size_list) kingdom = Tile(origin=origin, tile_list=[ make_capital_duchy(size_list=size_list[0], coastal=coastal, rgb_tuple=rgb_tuple[1][0]) ], hex_list=[], rgb=rgb_tuple[0]) d_idx = 1 while d_idx < len(size_list): duchy_size_list = size_list[d_idx] krhl = kingdom.relative_hex_list() krwl = kingdom.relative_water_list() new_county = Tile.new_tile(duchy_size_list[0], rgb=rgb_tuple[1][d_idx][1][0]) if new_county.move_into_place([kingdom.relative_neighbors()], krhl, krwl): new_duchy = Tile(origin=Cube(0, 0, 0), tile_list=[new_county], hex_list=[], rgb=rgb_tuple[1][d_idx][0]) for c_idx, county_size_list in enumerate(duchy_size_list[1:]): new_duchy.add_new_tile(county_size_list, cant=krhl + krwl, rgb=rgb_tuple[1][d_idx][1][c_idx]) if check_water_access(krhl + new_duchy.relative_hex_list(), krwl): kingdom.add_tile(new_duchy) d_idx += 1 return kingdom
def divide_into_duchies(size_list, num_duchies, allowable_chunks, a_dist, b_dist, ranking): '''Given a list of necessary sizes (size_list), and a list of list of hexes (allowable_chunks), attempt to create num_duchies duchies with size hexes each, where each is adjacent to both a and b (has a hex with 1 a_dist and 1 b_dist). Ranking is a dictionary of all (base) elements in allowable_chunks. Returns False if it doesn't find a solution in time.''' assert num_duchies > 0 size = sum(size_list) possible_tiles = [] while len(allowable_chunks) > 0: tile_split = [[]] * len(size_list) chunk = allowable_chunks.pop(0) if len(chunk) < size: continue # We can't make one, so don't bother trying. sorted_chunk = [ pair[1] for pair in sorted([(ranking.get(el, el.mag()) + a_dist[el] + b_dist[el], el) for el in chunk]) ] if len(get_chunks(sorted_chunk[:size])) == 1: possible_tiles, allowable_chunks = salvage_remainder( possible_tiles, duchy_from_snake(sorted_chunk[:size], size_list), chunk, allowable_chunks, size) else: sorted_chunk = [ pair[1] for pair in sorted([(ranking.get(el, 999), el) for el in chunk]) ] a_adj = [el for el in sorted_chunk if a_dist[el] == 1] b_adj = [el for el in sorted_chunk if b_dist[el] == 1] if len(a_adj) == 0 or len(b_adj) == 0: continue # We're not going to get adjacency to both. closest_a = a_adj[0] snake = [closest_a] disconnected = True while disconnected: closer_nbrs = [ el for el in snake[-1].neighbors() if el in chunk and b_dist.get(el, 999) < b_dist[snake[-1]] ] if len(closer_nbrs) == 0: break sorted_nbrs = [ pair[1] for pair in sorted([(ranking.get(el, 999), el) for el in closer_nbrs]) ] snake.append(sorted_nbrs[0]) if b_dist[snake[-1]] == 1: disconnected = False if not disconnected and len( snake ) >= size: # I'm not sure why I thought this case was possible. overage = len(snake) - size if a_dist[snake[overage]] == 1: snake = snake[overage:] possible_tiles, allowable_chunks = salvage_remainder( possible_tiles, duchy_from_snake(snake, size_list), chunk, allowable_chunks, size) elif not disconnected: # We have a valid snake, but too few. underage = size - len(snake) extendable = True while underage > 0 and extendable: start_nbrs = [ el for el in snake[0].neighbors() if el in chunk and ranking.get(el, 999) <= ranking.get( snake[0]) and el not in snake ] if len(start_nbrs) > 0: snake.insert(0, random.choice(start_nbrs)) underage -= 1 if underage > 0: end_nbrs = [ el for el in snake[-1].neighbors() if el in chunk and ranking.get(el, 999) <= ranking.get(snake[-1]) and el not in snake ] if len(end_nbrs) > 0: snake.append(random.choice(end_nbrs)) underage -= 1 if len(start_nbrs) == 0 and len(end_nbrs) == 0: # Now we have to grow in the middle. extendable = False if underage == 0: possible_tiles, allowable_chunks = salvage_remainder( possible_tiles, duchy_from_snake(snake, size_list), chunk, allowable_chunks, size) else: duchy = Tile(rgb=d_col(), hex_list=[]) for c_size in size_list: duchy.add_tile(Tile(rgb=c_col(), hex_list=[])) assigned = [] ind = 0 while len(snake) > 0 and underage > 0 and ind < len( size_list): el_nbrs = [ el for el in snake[0].neighbors() if el in chunk and el not in snake and el not in assigned ] assigned.append(snake.pop(0)) duchy.tile_list[ind].hex_list.append(assigned[-1]) if len(duchy.tile_list[ind].hex_list ) == size_list[ind]: ind += 1 if ind == len(size_list): break if len(el_nbrs) > 0: num_to_take = min( underage, size_list[ind] - len(duchy.tile_list[ind].hex_list)) added_now = random.sample( el_nbrs, min(num_to_take, len(el_nbrs))) assigned.extend(added_now) duchy.tile_list[ind].hex_list.extend(added_now) if len(duchy.tile_list[ind].hex_list ) == size_list[ind]: ind += 1 # Note that we could keep going here, and check the neighbors of these neighbors, but I'm going to skip this for now. if underage == 0: if len(size_list) > 2: duchy.tile_list.insert(0, duchy.tile_list.pop(1)) possible_tiles, allowable_chunks = salvage_remainder( possible_tiles, duchy, chunk, allowable_chunks, size) if len(possible_tiles) == num_duchies: return possible_tiles return False
def make_island_kingdom(water_height, origin=None, size_list=[6, 4, 4, 3], banned=[], weighted=True, min_mag=6, min_capital_coast=3, min_coast=2, max_tries=1000, strait_prob=0.5, center_bias=0.5, coast_bias=0.125): '''Given a dictionary from cubes to distance from shore, return a tile with duchies whose size are from duchy_size_list, and which are connected either directly or by straits (with probability strait_prob), and doesn't have any hexes in banned. The probability that a hex is selected as the origin is proportional to np.exp(-el.mag() * center_bias) * np.exp(water_height[el] * coast_bias), so high values of center_bias will make it closer to the center and high values of coast_bias will make it further from the shore. Tries max_tries times and returns False if it fails.''' assert min_capital_coast >= 3 assert min_coast >= 2 for _ in range(max_tries): island = Tile(hex_list=[], tile_list=[make_capital_duchy(d_size=size_list[0])]) if origin: island.tile_list[0].origin = origin else: opts = [ k for k, v in water_height.items() if v >= min_capital_coast and k.mag() >= min_mag ] #center-coast-water-land means center has to be at least 3. probs = [ np.exp(-el.mag() * center_bias) * np.exp(water_height[el] * coast_bias) for el in opts ] probs /= sum(probs) island.tile_list[0].origin = np.random.choice(opts, p=probs) allowable = [ k for k, v in water_height.items() if v >= min_coast and k not in banned ] if any([el not in allowable for el in island.real_hex_list()]): break for size in size_list[1:]: allocated = island.real_total_list() land_nbrs = island.neighbors() allowable = [el for el in allowable if el not in allocated] if np.random.rand() <= strait_prob: opts = [ el for el in island.strait_neighbors() if el in allowable ] new_origin = random.choice(opts) water_hexes = set() new_origin_nbrs = new_origin.neighbors() land = island.real_hex_list() for strait_neighbor in new_origin.strait_neighbors(): if strait_neighbor in land: water_hexes.update([ el for el in strait_neighbor.neighbors() if el in new_origin_nbrs ]) new_tile = Tile(hex_list=[new_origin], water_list=list(water_hexes)) while len(new_tile.hex_list) < size: new_neighbors = new_tile.relative_neighbors(weighted) new_neighbors = [ x for x in new_neighbors if x not in land_nbrs and x in allowable ] if len(new_neighbors) > 0: new_tile.add_hex(random.choice(new_neighbors)) else: break if len(new_tile.hex_list) == size: island.add_tile(new_tile) else: opts = [el for el in island.neighbors() if el in allowable] new_origin = random.choice(opts) new_tile = Tile(hex_list=[new_origin]) while len(new_tile.hex_list) < size: new_neighbors = new_tile.relative_neighbors(weighted) new_neighbors = [ x for x in new_neighbors if x not in allocated and x in allowable ] if len(new_neighbors) > 0: new_tile.add_hex(random.choice(new_neighbors)) else: break if len(new_tile.hex_list) == size: island.add_tile(new_tile) if len(island.real_hex_list()) == sum( size_list) and check_water_access( island.real_hex_list(), island.real_water_list(), max([el.mag() for el in island.real_hex_list()])): return island return False