Star Systems

The first thing I'm going to work on is the layout of the star systems. Everything else should follow from this, so it seems like a good place to start.

In EV all of the action takes place in systems. Systems are like rooms in an adventure game. They contain ships and planets and link to other systems via hyperlinks. In EV, there were around 100 star systems. However, according to the EV bible, there's room for 1000 star systems. Let's do 500 star systems for now. Each system can link to 16 other systems, but probably we don't want the links to be that dense.

Armed with this information, let's write some simple code to spit out a map. We'll start by just putting them randomly in a field:

import random
def cartSystems(n=500):
    r = random.Random(1)
    for i in range(n):
        x = r.random()*1000
        y = r.random()*1000
        yield (x,y)

Fine. A perfectly acceptable start.

We need links between these systems, so let's just write a first pass at that, too. We want a connected graph, so I'll just put in links starting with the shortest ones until we get one.

from math import sqrt
def distance(a,b):
    """Cartesian Distance"""
    (ax,ay),(bx,by) = (a,b)
    dx,dy = (ax-bx,ay-by)
    return sqrt(dx**2 + dy**2)

import networkx as nx
def nearbyLinks(S):
    G = nx.Graph()
    G.add_nodes_from(range(len(S)))

    links = [(a,b,distance(S[a],S[b]))
        for a in range(len(S))
        for b in range(len(S))
        if b > a]
    links.sort(key=lambda x: x[2])

    while not nx.is_connected(G):
        a,b,d = links.pop(0)
        G.add_edge(a,b)
    return G

That's way too dense. Let's try only putting edges in if they don't overlap an existing edge:

from shapely.geometry import LineString
def nearbyLinks(S,noOverlap=True):

    Geo = LineString()

    G = nx.Graph()
    G.add_nodes_from(range(len(S)))

    links = [(a,b,distance(S[a],S[b]))
        for a in range(len(S))
        for b in range(len(S))
        if b > a]
    links.sort(key=lambda x: x[2])

    while not nx.is_connected(G):
        a,b,d = links.pop(0)

        line = LineString([S[a],S[b]])
        if noOverlap and line.crosses(Geo): continue

        G.add_edge(a,b)
        Geo = Geo.union(line)
    return G

Let's try running it through neato from graphviz:

The standard EV universe is roughly circular. Let's try a circular layout of systems:

from math import cos,sin,pi
def polarSystems(n=500):
    r = random.Random(1)
    for i in range(n):
        a = r.random()*pi*2
        d = r.random()*100
        x = cos(a)*d
        y = sin(a)*d
        yield (x,y)

Nice. Let's crank it up to 1000 systems and see what we get.

I'm looking for lots of local features to give a sense of position when you're only looking at part of it, and lots of interesting structure around the rim for players to explore and find secrets. It would also be good if there were distinct neighborhoods of multiple systems, with only a few ways between the clusters.

For a sense of perspective, here's a map with 100 systems, roughly the number in the original EV universe. It's important to realize that bigger isn't always better. There's a lot of pressure on the later stages to produce enough variety and distinctiveness to fill this space.

One final run. Let's try combining the nice chunkiness of the first layout with the circular overall shape of the second:

def circleSystems(n=500):
    r = random.Random(1)
    for i in range(n):
        while True:
            x = r.random()*1000
            y = r.random()*1000
            d = sqrt(x*x + y*y)
            if d < 1000: break
        yield (x,y)

~adodge, 28 October 2014