from rnd import * import math import os, sys def color(str, col, bright=0, reset=1): if os.name != "posix": "color not supported, am too lazy" return str ansi = {'R': '31', 'X': '30', 'G': '32', 'Y': '33', 'B': '34', 'M': '35', 'C': '36', 'W': '37'} b = '1' if bright else '0' a = '\x1b[%s;%sm' % (b, ansi[col.upper()]) r = '' if reset: r = '\x1b[0;37m' return '%s%s%s' % (a, str, r) class Star(object): def __init__(self, lumin, mass, spect, radius, x, y, z): self.lumin, self.mass, self.spect, self.radius, self.x, self.y, self.z = \ lumin, mass, spect, radius, x, y, z def dist(self): return dist(self.x, self.y, self.z) / 149589000.0 # return value in Astronomical Units def dists(self): return [x / 149589000.0 for x in (self.x, self.y, self.z)] # return value in Astronomical Units def info(self): return "%s-class star at %.3f (%.3f, %.3f, %.3f), mass %.3f, luminosity %.3f, radius %.3f" % \ tuple([self.spect, self.dist()] + self.dists() + [self.mass, self.lumin, self.radius]) class Planet(object): def __init__(self, x, y, z, mass, radius, surfarea, areainsol, gas_giant, density, atmo, albedo, min_temp, max_temp, avg_temp): self.x, self.y, self.z, self.mass, self.radius, self.surfarea = x, y, z, mass, radius, surfarea self.areainsol, self.gas_giant, self.density, self.atmo = areainsol, gas_giant, density, atmo self.albedo, self.min_temp, self.max_temp, self.avg_temp = albedo, min_temp, max_temp, avg_temp def dist(self): return dist(self.x, self.y, self.z) / 149589000.0 # return value in Astronomical Units def dists(self): return [x / 149589000.0 for x in (self.x, self.y, self.z)] # return value in Astronomical Units def info(self): return "%s%s planet at %.3f (%.3f, %.3f, %.3f) mass %.3e kg, radius %.1f km, density %.3f, atmo %.3e, avg temp %.1f range (%.1f : %.1f), albedo %s" % \ tuple([self.habitability(), self.type(), self.dist()] + self.dists() + [self.mass, self.radius, self.density, self.atmo, self.avg_temp, self.min_temp, self.max_temp, self.albedo]) def type(self): if self.gas_giant: return "gas-giant" elif self.avg_temp < 60.0: return "ice" elif self.max_temp >= 700.0: return "lava" else: return "rocky" def habitability(self): if not self.habitable(): return color("Uninhabitable ", 'x', 1) if self.max_temp < 273.0: return color("FREEZING ", 'c', 1) elif self.max_temp < 290.0 and self.avg_temp > 273.0: return color("COOL ", 'g', 0) elif self.min_temp > 290.0 and self.avg_temp > 325.0: return color("SCORCHING ", 'y', 1) elif self.min_temp > 290.0: return color("WARM ", 'g', 0) elif self.avg_temp < 273.0: return color("COLD ", 'b', 1) elif self.avg_temp > 300.0: return color("HOT ", 'r', 1) elif (self.max_temp - self.min_temp) > 150.0: return color("EXTREME ", 'm', 1) elif (self.max_temp - self.min_temp) > 60.0: return color("EARTHLIKE ", 'g', 0) elif (self.max_temp - self.min_temp) > 30.0: return color("COMFORTABLE ", 'g', 1) else: return color("GAIA ", 'g', 1) def habitable(self): if not self.gas_giant: if self.atmo > 1e-6 and (245.0 < self.min_temp < 325.0 or 245.0 < self.max_temp < 325.0): return True return False class Asteroid_Belt(Planet): def __init__(self, radius, mass): self.radius, self.mass = radius, mass self.num_smashed = 2 def dist(self): return self.radius def info(self): return "-- Asteroid belt at %.3f, mass %.3e --" % (self.radius, self.mass) def habitable(self): return False systemsize = 1e8 stars_randcount = (1.0, 0.5) planets_randcount = (8.0, 4.0) star_spectra = ['O', 'B', 'A', 'F', 'G', 'K', 'M'] star_lumin = [(500000.0, 30000.0), (30000.0, 25.0), (25.0, 5.0), (5.0, 1.5), (1.5, 0.6), (0.6, 0.008), (0.08, 0.0008)] star_masses = [(40.0, 16.0), (16.0, 2.1), (2.1, 1.4), (1.4, 1.04), (1.04, 0.8), (0.8, 0.45), (0.45, 0.1)] star_radii = [(22.0, 8.0), (7.0, 1.8), (1.8, 1.4), (1.4, 1.15), (1.15, 0.96), (0.96, 0.7), (0.7, 0.25)] star_prob = [0.0, 0.05, 0.35, 0.6, 0.8, 0.97, 1.0] gen_counter = 0 starsys_counter = 0 def dist(x, y, z): return math.sqrt(x**2 + y**2 + z**2) def gen_system(force_habitable=False): global gen_counter single = True system_habitable = False while single or (force_habitable and not system_habitable): gen_counter += 1 single = False num_stars = round(gaussian(*stars_randcount)) if num_stars < 1.0: num_stars = 1.0 num_stars = int(num_stars) num_planets = round(gaussian(*planets_randcount)) if num_planets < 0.0: num_planets = 0.0 num_planets = int(num_planets) stars = [] x = y = z = 0.0 while len(stars) < num_stars: r = random() for i, e in enumerate(star_prob): if r <= e: si = i break r = random() slumin = (star_lumin[si][1] * 0.8) + (star_masses[si][0] * 1.2 * r) r2 = r + (random() * 0.3) - 0.15 smass = (star_masses[si][1] * 0.8) + (star_masses[si][0] * 1.2 * r) r2 = r + (random() * 0.3) - 0.15 sradius = (star_radii[si][1] * 0.8) + (star_radii[si][0] * 1.2 * r) stars += [Star(slumin, smass, star_spectra[si], sradius, x, y, z)] # first star is always at 0.0, 0.0, 0.0, next star gets random coords x = gaussian(0.0, systemsize) y = gaussian(0.0, systemsize) if len(stars) > 1: z = gaussian(0.0, systemsize / 10e3) planets = [] sys_lumin = sum([x.lumin for x in stars]) sys_size = math.sqrt(sys_lumin / 2.0) while len(planets) < num_planets: if random() >= 0.5: loc = 2e7 loczone = 20.0 else: loc = 2e7 loczone = 1.0 x = gaussian(loc * randsign(), systemsize * sys_size * loczone) y = gaussian(loc * randsign(), systemsize * sys_size * loczone) z = gaussian(0, systemsize * sys_size * loczone/ 10e0) reject = False for star in stars: d = dist(x-star.x,y-star.y,z-star.z) deathzone = star.radius + (star.radius * 0.25 * math.sqrt(star.lumin)) if d < deathzone: reject = True if reject: continue r = random() rocky = False gas_giant = False if r < 0.6: # rock planet rocky = True massmin = 1.0e19 massmax = 8.8e25 mass = 0.0 while mass < massmin or mass > massmax: massexp = gaussian(24.5, 1.25) mass = 10.0 ** massexp density = 0.0 while density < 0.75: density = gaussian(4.5, 1.0) mag = -1.0 while mag < 0.0: mag = gaussian(density, 3.0) else: # gas giant gas_giant = True massmin = 1e25 massmax = 4e28 mass = 0.0 while mass < massmin or mass > massmax: massexp = gaussian(26.5, 1.0) mass = 10.0 ** massexp density = 0.0 while density < 0.3: density = gaussian(1.5, 0.75) mag = -1.0 while mag < 2.0: mag = gaussian(7.0, 3.0) volume = mass / density / 1e12 radius = pow(volume * 0.75 / math.pi, 1.0/3.0) circsurfarea = math.pi * radius**2 surfarea = circsurfarea * 4.0 insol = 0.0 for star in stars: d = dist(x-star.x,y-star.y,z-star.z) spower = 3.8e26 * star.lumin distsphere = 4.0 * math.pi * d**2 insol += (circsurfarea / distsphere) * spower areainsol = insol / surfarea / 1e6 if areainsol > 75000: continue atmo = None atmo_count = 0 while atmo == None or atmo > density: atmo_count += 1 if gas_giant: atmo_exp = randrange(-5.0, 12.0) else: atmo_exp = randrange(-9.0, 2.0) atmo = 1.3 ** atmo_exp if atmo_count > 50 and atmo > density: print "ATMO FAIL for gasgiant:%s, wanted less than %f, kept getting values like %f due to exponent %f" % (gas_giant, density, atmo, atmo_exp) atmo = 1.3 ** (atmo_exp - 10.0) break albedo = -1.0 while albedo <= 0.01 or albedo >= 0.99: albedo = gaussian(0.35, 0.25) emissivity = 1.0 - albedo #emissivity = randrange(0.00001, 1.0) emiss_diff = (((10.0 - pow(atmo, 1.0/2.0)) / 10.0) ** 100.0) / 2.0 emissivity_min = emissivity - (emissivity * emiss_diff) emissivity_max = emissivity + (emissivity * emiss_diff) avg_temp = pow(((1.0 - albedo) * areainsol) / (4.0 * emissivity * 5.67e-8), 1.0/4.0) min_temp = pow(((1.0 - albedo) * areainsol) / (4.0 * emissivity_max * 5.67e-8), 1.0/4.0) max_temp = pow(((1.0 - albedo) * areainsol) / (4.0 * emissivity_min * 5.67e-8), 1.0/4.0) planets.append(Planet(x, y, z, mass, radius, surfarea, areainsol, gas_giant, density, atmo, albedo, min_temp, max_temp, avg_temp)) planets.sort(cmp=lambda a, b: cmp(dist(a.x, a.y, a.z), dist(b.x, b.y, b.z))) dels = [] inserts = [] lpl = None for i in range(0, len(planets)-1): pl = planets[i] npl = planets[i+1] if (npl.dist() - pl.dist()) < math.sqrt((npl.mass + pl.mass) / 1e22) * 0.001: "BOOM. asteroids" if i in dels: # ooh, three or more planets smashing together! exciting! # this one has already been deleted by the previous one, so we only need to add the next delete # also update the old asteroid belt to include this planet dels += [i+1] belt = inserts[-1][1] belt.mass += npl.mass belt.radius = ((belt.radius * float(belt.num_smashed)) + npl.dist()) / (belt.num_smashed + 1) belt.num_smashed += 1 else: inserts.append((i - len(dels), Asteroid_Belt((npl.dist() + pl.dist()) / 2.0, npl.mass + pl.mass))) dels += [i, i+1] dels.reverse() for dd in dels: del planets[dd] inserts.reverse() for ii in inserts: planets.insert(ii[0], ii[1]) system_habitable = any([x.habitable() for x in planets]) return (stars, planets) def print_system(syst, show_hab=True): stars, planets = syst print "\nStars:\n" for star in stars: print star.info() print "\nPlanets:\n" hab = False for i, pl in enumerate(planets): if pl.habitable(): hab = True print "#%i: %s" % (i+1, pl.info()) if show_hab: print "" if hab: print "System is habitable!" else: print "System UNINHABITABLE!" def main(): if len(sys.argv) > 1: runs = int(sys.argv[1]) else: runs = 1 fl = False systems = [] dead_systems = [] for i in range(runs): syst = gen_system(fl) fl = True if any([x.habitable() for x in syst[1]]): systems.append(syst) else: dead_systems.append(syst) print color("\n ***************** INHABITABLE SYSTEMS (%d) *****************\n", 'm', 1) % (len(systems),) for syst in systems: print_system(syst, False) ansi_cancel = "" if os.name == "posix": ansi_cancel = "\x1b[0;37m" print color("\n ***************** UNINHABITABLE SYSTEMS (%d) *****************%s\n", 'm', 1) % (len(dead_systems), ansi_cancel) for syst in dead_systems: print_system(syst, False) if __name__ == '__main__': main()