1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
|
import freeorion as fo
import random
import names
import options
# for starname modifiers
stargroup_words = []
stargroup_chars = []
stargroup_modifiers = []
greek_letters = [
"Alpha",
"Beta",
"Gamma",
"Delta",
"Epsilon",
"Zeta",
"Eta",
"Theta",
"Iota",
"Kappa",
"Lambda",
"Mu",
"Nu",
"Xi",
"Omicron",
"Pi",
"Rho",
"Sigma",
"Tau",
"Upsilon",
"Phi",
"Chi",
"Psi",
"Omega",
]
def random_star_name():
"""
Returns a random star system name.
"""
return "".join(random.sample(names.consonants + names.vowels, 3)).upper() + "-" + str(random.randint(1000, 9999))
# used in clustering
def recalc_centers(ctrs, points, assignments):
tallies = [[[], []] for _ in ctrs]
for index_p, index_ctr in enumerate(assignments):
tallies[index_ctr][0].append(points[index_p][0])
tallies[index_ctr][1].append(points[index_p][1])
for index_ctr, tally in enumerate(tallies):
num_here = len(tally[0])
if num_here == 0:
pass # leave ctr unchanged if no points assigned to it last round
else:
ctrs[index_ctr] = [float(sum(tally[0])) / num_here, float(sum(tally[1])) / num_here]
# used in clustering
def assign_clusters(points, ctrs):
assignments = []
for point in points:
best_dist_sqr = 1e20
best_ctr = 0
for index_ctr, ctr in enumerate(ctrs):
this_dist_sqr = (ctr[0] - point[0]) ** 2 + (ctr[1] - point[1]) ** 2 # TODO use math.hypot, max
if this_dist_sqr < best_dist_sqr:
best_dist_sqr = this_dist_sqr
best_ctr = index_ctr
assignments.append(best_ctr)
return assignments
def cluster_stars(positions, num_star_groups):
"""
Returns a list, same size as positions argument, containing indices from 0 to num_star_groups.
"""
if num_star_groups > len(positions):
return [[pos] for pos in positions]
centers = [[pos[0], pos[1]] for pos in random.sample(positions, num_star_groups)]
all_coords = [(pos[0], pos[1]) for pos in positions]
clusters = [[], []]
clusters[0] = assign_clusters(all_coords, centers) # assign clusters based on init centers
old_c = 0
num_convergence_loops = 1 # if full convergence is deemed important then use a higher humber here
for loop in range(num_convergence_loops): # main loop to try getting some convergence of center assignments
recalc_centers(centers, all_coords, clusters[old_c]) # get new centers
clusters[1 - old_c] = assign_clusters(all_coords, centers) # assign clusters based on new centers
if clusters[0] == clusters[1]:
break # stop iterating if no change in cluster assignments
old_c = 1 - old_c
else:
if loop > 0: # if here at loop 0, then didn't try for convergence
print("falling through system clustering iteration loop without convergence")
return clusters[1 - old_c]
def check_deep_space(group_list, star_type_assignments, planet_assignments):
deep_space = []
not_deep = []
for systemxy in group_list:
if star_type_assignments.get(systemxy, fo.starType.noStar) != fo.starType.noStar:
not_deep.append(systemxy)
else:
if len(planet_assignments.get(systemxy, [])) > 0:
not_deep.append(systemxy)
else:
deep_space.append(systemxy)
return not_deep, deep_space
def name_group(group_list, group_name, star_type_assignments, planet_assignments):
group_size = len(group_list)
if group_size == 1:
return [(group_list[0], group_name)]
modifiers = list(stargroup_modifiers) # copy the list so we can safely add to it if needed
not_deep, deep_space = check_deep_space(group_list, star_type_assignments, planet_assignments)
these_systems = not_deep + deep_space # so that unnamed deep space will get the later star group modifiers
while len(modifiers) < group_size: # emergency fallback
trial_mod = (
random.choice(stargroup_modifiers)
+ " "
+ "".join(random.sample(names.consonants + names.vowels, 3)).upper()
)
if trial_mod not in modifiers:
modifiers.append(trial_mod)
if options.POSTFIX_STARGROUP_MODIFIERS:
name_list = [group_name + " " + modifier for modifier in modifiers]
else:
name_list = [modifier + " " + group_name for modifier in modifiers]
return zip(these_systems, name_list)
def name_star_systems(system_list): # noqa: C901
# choose star types and planet sizes, before choosing names, so naming can have special handling of Deep Space
star_type_assignments = {}
planet_assignments = {}
position_list = []
for system in system_list:
star_type = fo.sys_get_star_type(system)
systemxy = fo.get_pos(system)
star_type_assignments[systemxy] = star_type
planet_assignments[systemxy] = fo.sys_get_planets(system)
position_list.append(systemxy)
# will name name a portion of stars on a group basis, where the stars of each group share the same base star name,
# suffixed by different (default greek) letters or characters (options at top of file)
star_name_map = {}
star_names = names.get_name_list("STAR_NAMES")
group_names = names.get_name_list("STAR_GROUP_NAMES")
potential_group_names = []
individual_names = []
stargroup_words[:] = names.get_name_list("STAR_GROUP_WORDS")
stargroup_chars[:] = names.get_name_list("STAR_GROUP_CHARS")
stargroup_modifiers[:] = [stargroup_words, stargroup_chars][options.STAR_GROUPS_USE_CHARS]
for starname in star_names:
if len(starname) > 6: # if starname is long, don't allow it for groups
individual_names.append(starname)
continue
# any names that already have a greek letter in them can only be used for individual stars, not groups
for namepart in starname.split():
if namepart in greek_letters:
individual_names.append(starname)
break
else:
potential_group_names.append(starname)
if not potential_group_names:
potential_group_names.append("XYZZY")
# ensure at least a portion of galaxy gets individual starnames
num_systems = len(system_list)
choice = num_systems >= options.NAMING_LARGE_GALAXY_SIZE
target_indiv_ratio = [options.TARGET_INDIV_RATIO_SMALL, options.TARGET_INDIV_RATIO_LARGE][choice]
# TODO improve the following calc to be more likely to hit target_indiv_ratio if more or less than
# 50% potential_group_names used for groups
num_individual_stars = int(
max(
min(num_systems * target_indiv_ratio, len(individual_names) + int(0.5 * len(potential_group_names))),
num_systems - 0.8 * len(stargroup_modifiers) * (len(group_names) + int(0.5 * len(potential_group_names))),
)
)
star_group_size = 1 + int(
(num_systems - num_individual_stars) / (max(1, len(group_names) + int(0.5 * len(potential_group_names))))
)
# make group size a bit bigger than min necessary, at least a trio
star_group_size = max(3, star_group_size)
num_star_groups = 1 + int(num_systems / star_group_size) # initial value
# first cluster all systems, then remove some to be individually named (otherwise groups can have too many
# individually named systems in their middle). First remove any that are too small (only 1 or 2 systems).
# The clusters with the most systems are generally the most closely spaced, and though they might make good
# logical candidates for groups, their names are then prone to overlapping on the galaxy map, so after removing
# small groups, remove the groups with the most systems.
random.shuffle(position_list) # just to be sure it is randomized
init_cluster_assgts = cluster_stars(position_list, num_star_groups)
star_groups = {}
for index_pos, index_group in enumerate(init_cluster_assgts):
systemxy = position_list[index_pos]
star_groups.setdefault(index_group, []).append(systemxy)
indiv_systems = []
# remove groups with only one non-deep-system
for groupindex, group_list in list(star_groups.items()):
max_can_transfer = len(potential_group_names) - len(star_groups) + len(individual_names) - len(indiv_systems)
if max_can_transfer <= 0:
break
elif max_can_transfer <= len(group_list):
continue
not_deep, deep_space = check_deep_space(group_list, star_type_assignments, planet_assignments)
if len(not_deep) > 1:
continue
for systemxy in star_groups[groupindex]:
indiv_systems.append(systemxy)
del star_groups[groupindex]
# remove tiny groups
group_sizes = [(len(group), index) for index, group in star_groups.items()]
group_sizes.sort()
while len(indiv_systems) < num_individual_stars and len(group_sizes) > 0:
groupsize, groupindex = group_sizes.pop()
max_can_transfer = len(potential_group_names) - len(star_groups) + len(individual_names) - len(indiv_systems)
if (max_can_transfer <= 0) or (groupsize > 2):
break
if max_can_transfer <= groupsize:
continue
for systemxy in star_groups[groupindex]:
indiv_systems.append(systemxy)
del star_groups[groupindex]
# remove largest (likely most compact) groups
while len(indiv_systems) < num_individual_stars and len(group_sizes) > 0:
groupsize, groupindex = group_sizes.pop(-1)
max_can_transfer = len(potential_group_names) - len(star_groups) + len(individual_names) - len(indiv_systems)
if max_can_transfer <= 0:
break
if max_can_transfer <= groupsize:
continue
for systemxy in star_groups[groupindex]:
indiv_systems.append(systemxy)
del star_groups[groupindex]
num_star_groups = len(star_groups)
num_individual_stars = len(indiv_systems)
random.shuffle(potential_group_names)
random.shuffle(individual_names)
random.shuffle(group_names)
num_for_indiv = min(
max(len(potential_group_names) // 2, num_individual_stars + 1 - len(individual_names)),
len(potential_group_names),
)
individual_names.extend(potential_group_names[:num_for_indiv])
group_names.extend(potential_group_names[num_for_indiv:])
# print "sampling for %d indiv names from list of %d total indiv names" % (
# num_individual_stars, len(individual_names))
indiv_name_sample = random.sample(individual_names, num_individual_stars)
# indiv_name_assignments = zip([(pos.x, pos.y) for pos in position_list[:num_individual_stars]], indiv_name_sample)
star_name_map.update(zip(indiv_systems, indiv_name_sample))
# print "sampling for %d group names from list of %d total group names"%(num_star_groups, len(group_names))
if len(group_names) < num_star_groups:
group_names.extend([names.random_name(6) for _ in range(num_star_groups - len(group_names))])
group_name_sample = random.sample(group_names, num_star_groups)
for index_group, group_list in enumerate(sorted(star_groups.values())):
star_name_map.update(
name_group(group_list, group_name_sample[index_group], star_type_assignments, planet_assignments)
)
# assign names from star_name_map to star systems
for system in system_list:
fo.set_name(system, star_name_map.get(fo.get_pos(system), "") or random_star_name())
|