File: starnames.py

package info (click to toggle)
freeorion 0.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 194,940 kB
  • sloc: cpp: 186,508; python: 40,969; ansic: 1,164; xml: 719; makefile: 32; sh: 7
file content (273 lines) | stat: -rw-r--r-- 11,668 bytes parent folder | download
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())