file: tools/map_generator_v2.py

#!/usr/bin/python

import math
import random

# minimum and maximum total number of planets in map
minPlanets = 15
maxPlanets = 30
# maximum number of planets specifically generated to be equidistant from both
# players, by chance planet generated in the standard symmetric way could still
# end up equidistant as well
# also does not include the planet exactly in the center of the map
maxCentral = 5
# minimum and maximum number of ships on neutral planets
minShips = 1
maxShips = 100
# minimum and maximum growth for planets
# except for the center planet which is always 0 minimum growth
minGrowth = 1
maxGrowth = 5
# minimum distance between planets
minDistance = 2
# minimum distance between the players starting planets
minStartingDistance = 4
# maximum radius from center of map a planet can be
maxRadius = 15
# minimum difference between true distance and rounded distance between planets
# this is to try and avoid rounding errors causing different distances to be
# calculated on different platforms and languages
epsilon = 0.002


def make_planet(x, y, owner, num_ships, growth_rate):
    return {"x": x, "y": y, "owner": owner, "num_ships": num_ships,
            "growth_rate": growth_rate}


def print_planet(p):
    out = ["P", p["x"], p["y"], p["owner"], p["num_ships"], p["growth_rate"]]
    return " ".join(str(i) for i in out)


def translate_planets(planets):
    for p in planets:
        p["x"] += maxRadius
        p["y"] += maxRadius


def generate_coordinates(p, r, theta):
    if theta < 0:
        theta += 360
    if theta >= 360:
        theta -= 360
    p["x"] = r * math.cos(math.radians(theta))
    p["y"] = r * math.sin(math.radians(theta))


def rand_num(minimum, maximum):
    return (random.random() * (maximum - minimum)) + minimum


def rand_radius(min_r, max_r):
    val = min_r - 1
    while val < min_r:
        val = math.sqrt(random.random()) * max_r
    return val


def distance(p1, p2):
    return math.ceil(actual_distance(p1, p2))


def actual_distance(p1, p2):
    dx = p1["x"] - p2["x"]
    dy = p1["y"] - p2["y"]
    return math.sqrt(dx * dx + dy * dy)


def not_valid(p1, p2, planets):
    a_distance = actual_distance(p1, p2)
    if distance(p1, p2) < minDistance or abs(a_distance - round(a_distance)) < epsilon:
        return True
    for p in planets:
        a_distance1 = actual_distance(p, p1)
        a_distance2 = actual_distance(p, p2)
        if (distance(p, p1) < minDistance
                or distance(p, p2) < minDistance
                or abs(a_distance1 - round(a_distance1)) < epsilon
                or abs(a_distance2 - round(a_distance2)) < epsilon):
            return True
    return False


def not_valids(p1, planets):
    for p in planets:
        a_distance = actual_distance(p, p1)
        if distance(p, p1) < minDistance or abs(a_distance - round(a_distance)) < epsilon:
            return True
    return False


def generate_map():
    # works out information about the map
    planets_to_generate = random.randint(minPlanets, maxPlanets)
    if random.randint(0, 1):
        symmetry_type = 1  # radial symmetry
        # can only generate an odd number of planets in this symmetry
        while planets_to_generate % 2 == 0:
            if planets_to_generate == maxPlanets:
                planets_to_generate = minPlanets
            else:
                planets_to_generate += 1
    else:
        symmetry_type = -1  # linear symmetry

    planets = [make_planet(0, 0, 0, random.randint(minShips, maxShips),
                           random.randint(0, maxGrowth))]

    # adds the centre planet
    planets_to_generate -= 1

    # picks out the home planets
    r = rand_radius(minDistance, maxRadius)
    theta1 = rand_num(0, 360)
    if symmetry_type == 1 and theta1 < 180:
        theta2 = theta1 + 180
    elif symmetry_type == 1:
        theta2 = theta1 - 180
    else:
        theta2 = rand_num(0, 360)

    p1 = make_planet(0, 0, 1, 100, 5)
    p2 = make_planet(0, 0, 2, 100, 5)
    generate_coordinates(p1, r, theta1)
    generate_coordinates(p2, r, theta2)

    while not_valid(p1, p2, planets) or distance(p1, p2) < minStartingDistance:
        r = rand_radius(minDistance, maxRadius)
        theta1 = rand_num(0, 360)
        if symmetry_type == 1 and theta1 < 180:
            theta2 = theta1 + 180
        elif symmetry_type == 1:
            theta2 = theta1 - 180
        else:
            theta2 = rand_num(0, 360)

        generate_coordinates(p1, r, theta1)
        generate_coordinates(p2, r, theta2)
    planets.append(p1)
    planets.append(p2)
    planets_to_generate -= 2

    # makes the center neutral planets
    if symmetry_type == 1:
        no_center_neutrals = 2 * random.randint(0, maxCentral // 2)
        theta_a = (theta1 + theta2) / 2
        theta_b = theta_a + 180
        for i in range(no_center_neutrals // 2):
            r = rand_radius(minDistance, maxRadius)
            num_ships = random.randint(minShips, maxShips)
            growth_rate = random.randint(minGrowth, maxGrowth)
            p1 = make_planet(0, 0, 0, num_ships, growth_rate)
            p2 = make_planet(0, 0, 0, num_ships, growth_rate)
            generate_coordinates(p1, r, theta_a)
            generate_coordinates(p2, r, theta_b)
            while not_valid(p1, p2, planets):
                r = rand_radius(minDistance, maxRadius)
                generate_coordinates(p1, r, theta_a)
                generate_coordinates(p2, r, theta_b)
            planets.append(p1)
            planets.append(p2)
            planets_to_generate -= 2
    else:
        # must have an even number of planets left to generate after this
        min_central = planets_to_generate % 2
        no_center_neutrals = random.randrange(min_central, maxCentral + 1, 2)
        theta = (theta1 + theta2) / 2
        if random.randint(0, 1) == 1:
            theta += 180
        for i in range(no_center_neutrals):
            r = rand_radius(0, maxRadius)
            num_ships = random.randint(minShips, maxShips)
            growth_rate = random.randint(minGrowth, maxGrowth)
            p = make_planet(0, 0, 0, num_ships, growth_rate)
            generate_coordinates(p, r, theta)
            while not_valids(p, planets):
                r = rand_radius(0, maxRadius)
                generate_coordinates(p, r, theta)
            planets.append(p)
            planets_to_generate -= 1

    # picks out the rest of the neutral planets
    assert planets_to_generate % 2 == 0, "Error: odd number of planets left to add"
    for i in range(planets_to_generate // 2):
        r = rand_radius(minDistance, maxRadius)
        theta = rand_num(0, 360)
        if i == 0:
            planet_max = min(100, 5 * distance(planets[1], planets[2]) - 1)
            num_ships = random.randint(minShips, planet_max)
        else:
            num_ships = random.randint(minShips, maxShips)
        growth_rate = random.randint(minGrowth, maxGrowth)
        p1 = make_planet(0, 0, 0, num_ships, growth_rate)
        p2 = make_planet(0, 0, 0, num_ships, growth_rate)
        generate_coordinates(p1, r, theta1 + theta)
        generate_coordinates(p2, r, theta2 + symmetry_type * theta)

        while not_valid(p1, p2, planets):
            r = rand_radius(minDistance, maxRadius)
            theta = rand_num(0, 360)
            generate_coordinates(p1, r, theta1 + theta)
            generate_coordinates(p2, r, theta2 + symmetry_type * theta)
        planets.append(p1)
        planets.append(p2)

    translate_planets(planets)

    return "\n".join(map(print_planet, planets))


def save_map(f="generated.txt"):
    file_object = open(f, "w+")
    file_object.write(generate_map())
    file_object.close()


if __name__ == "__main__":
    print(generate_map())