コード例 #1
0
ファイル: test_Table.py プロジェクト: simon-hibbs/T5_worldgen
 def test_add_row(self):
     '''Table: Test add_row()'''
     tbl = Table()
     tbl.add_row(1, 'Entry 1')          # Should just work
     tbl.add_row((2, 3), 'Entry 2')     # Should ust work
     tbl.add_row('A', 'Entry 3')
     with self.assertRaises(ValueError):
         tbl.add_row(dict(), 'Bogus entry')     # ValueError
コード例 #2
0
ファイル: test_Table.py プロジェクト: simon-hibbs/T5_worldgen
    def test_table_in_table(self):
        '''Table: Test subtable'''
        tbl2 = Table()
        tbl2.add_row(1, 'Subentry 1')
        tbl2.dice = 1

        tbl1 = Table()
        tbl1.add_row(1, 'Entry 1')
        tbl1.add_row(2, tbl2.roll)
        self.assertEquals(tbl1.lookup(2), 'Subentry 1')
コード例 #3
0
    def generate_chemistry(self, ranges, roll):
        '''
        Inputs:
        - range in table
        - roll
        Returns:
        - chemistry
        - age modifier
        '''
        if len(ranges) == 3:
            # Build table
            chemistry_table = Table()
            for i in range(0, len(ranges)):
                chemistry_table.add_row(ranges[i], self.chemistry_values[i])

            # Generate
            return chemistry_table.lookup(roll)
コード例 #4
0
ファイル: test_Table.py プロジェクト: simon-hibbs/T5_worldgen
 def test_roll(self):
     '''Table: Test roll()'''
     tbl = Table()
     tbl.add_row((1, 6), 'Entry 1')
     tbl.dice = 1
     self.assertEquals(tbl.roll(), 'Entry 1')
コード例 #5
0
ファイル: test_Table.py プロジェクト: simon-hibbs/T5_worldgen
 def test_lookup(self):
     '''Table: Test lookup()'''
     tbl = Table()
     tbl.add_row(1, 'Entry 1')
     self.assertEquals(tbl.lookup(1), 'Entry 1')
コード例 #6
0
class System(object):
    '''Return a T5 basic system with the specified name and location hex'''

    naval_base_presence = Table()
    naval_base_presence.add_row('A', 6)
    naval_base_presence.add_row('B', 5)

    scout_base_presence = Table()
    scout_base_presence.add_row('A', 4)
    scout_base_presence.add_row('B', 5)
    scout_base_presence.add_row('C', 6)
    scout_base_presence.add_row('D', 7)

    mw_orbit_flux_table = Table()
    mw_orbit_flux_table.add_row(-6, -2)
    mw_orbit_flux_table.add_row((-5, -3), -1)
    mw_orbit_flux_table.add_row((-2, 2), 0)
    mw_orbit_flux_table.add_row((3, 5), 1)
    mw_orbit_flux_table.add_row(6, 2)

    def __init__(self, name='', location_hex='0000'):
        self.hex = location_hex
        self.name = name
        self.zone = ''
        self.stellar = Primary()
        self.mainworld = Planet()
        self.determine_mw_orbit()

        self.bases = self.determine_bases()
        self.pbg = Pbg(self.mainworld)
        self.allegiance = 'Na'
        self.determine_trade_codes()
        self.determine_x()
        self.nobility = self.determine_nobility()
        self.num_worlds = (self.pbg.belts + self.pbg.gasgiants + D6.roll(1) +
                           1)
        self.determine_travel_zone()

    def display(self):
        '''Display'''
        return '{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}'.format(
            self.hex, self.name, self.mainworld.uwp(),
            ' '.join(self.mainworld.trade_codes), str(self.importance_x),
            str(self.economic_x),
            str(self.cultural_x), self.nobility, self.bases, self.zone,
            str(self.pbg), self.num_worlds, self.allegiance,
            self.stellar.display())

    def __str__(self):
        oformat = '{0:4} {1:20} {2:9} {3:18} {4:4} {5:7} {6:6} ' +\
            '{7:7} {8:2} {9:1} {10:3} {11:2} {12:2} {13:14}'
        return oformat.format(self.hex, self.name, self.mainworld.uwp(),
                              ' '.join(self.mainworld.trade_codes),
                              str(self.importance_x), str(self.economic_x),
                              str(self.cultural_x.display()),
                              self.nobility, self.bases, self.zone,
                              str(self.pbg), self.num_worlds, self.allegiance,
                              self.stellar.display())

    def determine_nobility(self):
        '''Determine noble representation'''
        nobility = 'B'  # Every world gets a knight
        # Baronet
        if ('Pa' in self.mainworld.trade_codes
                or 'Pr' in self.mainworld.trade_codes):
            nobility += 'c'
        # Baron
        if ('Ag' in self.mainworld.trade_codes
                or 'Ri' in self.mainworld.trade_codes):
            nobility += 'C'
        if 'Pi' in self.mainworld.trade_codes:
            nobility += 'D'  # Marquis
        if 'Ph' in self.mainworld.trade_codes:
            nobility += 'e'  # Viscount
        # Count
        if ('In' in self.mainworld.trade_codes
                or 'Hi' in self.mainworld.trade_codes):
            nobility += 'E'
        if int(self.importance_x) >= 4:
            nobility += 'f'  # Duke
        return nobility

    def determine_bases(self):
        '''Determine bases'''
        bases = ''
        # Naval base
        target = self.naval_base_presence.lookup(self.mainworld.starport)
        if target is not None:
            if D6.roll(2) <= target:
                bases += 'N'
        # Scout base
        target = self.scout_base_presence.lookup(self.mainworld.starport)
        if target is not None:
            if D6.roll(2) <= target:
                bases += 'S'
        return bases

    def determine_trade_codes(self):
        '''Determine climate trade codes'''
        tcs = TradeCodes(self.mainworld, self)
        self.mainworld.trade_codes = tcs.generate()

    def determine_mw_orbit(self):
        '''Determine mainworld orbit'''
        orbit = self.stellar.habitable_zone +\
            self.mw_orbit_flux_table.lookup(FLUX.flux())
        orbit = max(orbit, 0)
        self.mainworld.orbit = orbit

    def determine_travel_zone(self, starport_x_is_red=True):
        '''Determine travel zone - A or R'''
        self.zone = ''
        if int(self.mainworld.government) + \
                int(self.mainworld.law_level) in [20, 21]:
            self.zone = 'A'
            self.mainworld.trade_codes.append('Da')
        elif int(self.mainworld.government) + \
                int(self.mainworld.law_level) > 22:
            self.zone = 'R'
        if starport_x_is_red:
            if self.mainworld.starport == 'X':
                self.zone = 'R'
                self.mainworld.trade_codes.append('Fo')

    def as_json(self):
        '''Return JSON representation of system'''
        system_dict = {
            'name': self.name,
            'hex': self.hex,
            'stellar': self.stellar.as_json(),
            'mainworld': self.mainworld.as_json(),
            'bases': self.bases,
            'pbg': str(self.pbg),
            'allegiance': self.allegiance,
            'Ix': str(self.importance_x),
            'Ex': str(self.economic_x),
            'Cx': str(self.cultural_x),
            'nobility': self.nobility,
            'worlds': self.num_worlds,
            'zone': self.zone
        }
        return json.dumps(system_dict)

    def json_import(self, jdata):
        '''Import from JSON'''
        system_dict = json.loads(jdata)
        self.name = system_dict['name']
        self.hex = system_dict['hex']
        self.bases = system_dict['bases']
        self.allegiance = system_dict['allegiance']
        self.nobility = system_dict['nobility']
        self.num_worlds = int(system_dict['worlds'])
        self.zone = system_dict['zone']
        self.stellar.json_import(system_dict['stellar'])
        self.mainworld.json_import(system_dict['mainworld'])
        self.pbg.json_import(system_dict['pbg'])
        self.importance_x.json_import(system_dict['Ix'])
        self.economic_x.json_import(system_dict['Ex'])
        self.cultural_x.json_import(system_dict['Cx'])

    def determine_x(self):
        '''Determine Ix Ex Cx values'''
        self.importance_x = ImportanceExtension(self.mainworld, self)
        self.economic_x = EconomicExtension(self.pbg,
                                            int(self.mainworld.population),
                                            int(self.mainworld.tech_level),
                                            self.mainworld.trade_codes,
                                            int(self.importance_x))
        self.cultural_x = CulturalExtension(int(self.mainworld.population),
                                            int(self.importance_x),
                                            int(self.mainworld.tech_level))
コード例 #7
0
class _MappingRegion(object):
    '''Sector/subsector base class'''

    system_presence_table = Table()
    system_presence_table.add_row('Extra galactic', 1)
    system_presence_table.add_row('Rift', 3)
    system_presence_table.add_row('Sparse', 17)
    system_presence_table.add_row('Scattered', 33)
    system_presence_table.add_row('Standard', 50)
    system_presence_table.add_row('Dense', 66)
    system_presence_table.add_row('Cluster', 83)
    system_presence_table.add_row('Core', 91)

    def __init__(self, name, density='Standard'):
        seed()
        self.name = name
        self.size_x = 0
        self.size_y = 0
        self.hexes = {}
        self.density = density

    def display(self):
        '''Display'''
        hexlist = sorted(self.hexes.keys())
        for hex_id in hexlist:
            print(self.hexes[hex_id].display())

    def as_list(self):
        '''Return contents as list'''
        out = []
        hexlist = sorted(self.hexes.keys())
        for hex_id in hexlist:
            out.append(self.hexes[hex_id].display())
        return out

    @staticmethod
    def percentile():
        '''1-100%'''
        return randint(1, 100)

    def process_hex(self, hex_id, ss_id=''):
        '''Add system on probability check'''
        name = 'Name-{}{}'.format(hex_id, ss_id)
        if self.percentile() <= \
                self.system_presence_table.lookup(self.density):
            self.hexes[hex_id] = System(name, hex_id)

    def t5_tab(self):
        '''Output in T5 tab format'''
        out = [
            '\t'.join([
                'Hex', 'Name', 'UWP', 'Remarks', '{Ix}', '(Ex)', '[Cx]',
                'Nobility', 'Bases', 'Zone', 'PBG', 'W', 'Allegiance', 'Stars'
            ])
        ]
        out.extend(self.as_list())
        return out

    def find_nearby_hexes(self, o_hex_id, radius=1):
        '''Find hexes within radius of hex_id'''
        '''
            B
        A       C
            O
        F       D
            E

        Add column of r+1 hexes starting at A, C
        Add column of r+2 hexes starting at A+1
        ... etc ...
        Add column of 2r hexes starting at B (excluding O)

        Hex IDs on A->B (and B->C) depend on co-ordinates of O, size of r
        '''
        nearby_hexes = []
        o_col = int(o_hex_id[:2])
        o_row = int(o_hex_id[2:])
        side_length = radius + 1
        a_col = o_col - radius
        c_col = o_col + radius
        if self._is_even(o_col):
            if self._is_even(radius):
                x_row = o_row - int(radius / 2) + 0.5
            else:
                x_row = o_row - int(radius / 2)
        else:
            if self._is_even(radius):
                x_row = o_row - int(radius / 2)
            else:
                x_row = o_row - int(radius / 2) - 0.5

        LOGGER.debug('O = %s radius = %s', o_hex_id, radius)
        LOGGER.debug('A = %s%s', a_col, x_row)
        col_length = side_length
        for col in range(0, radius):
            l_col = a_col + col
            r_col = c_col - col
            LOGGER.debug('l_col = %s r_col = %s x_row = %s', l_col, r_col,
                         x_row)
            LOGGER.debug('col_length = %s', col_length)
            for idx in range(0, col_length):
                row = x_row + idx
                if l_col > 0 and int(row) > 0:
                    l_hex_id = '{0:02d}{1:02d}'.format(l_col, int(row))
                    LOGGER.debug('Adding %s', l_hex_id)
                    nearby_hexes.append(l_hex_id)
                if r_col > 0 and int(row) > 0:
                    r_hex_id = '{0:02d}{1:02d}'.format(r_col, int(row))
                    LOGGER.debug('Adding %s', r_hex_id)
                    nearby_hexes.append(r_hex_id)
            x_row -= 0.5
            col_length += 1
        for row in range(o_row - radius, o_row + radius + 1):
            if row > 0:
                hex_id = '{0:02d}{1:02d}'.format(o_col, row)
                if hex_id != o_hex_id:
                    LOGGER.debug('Adding %s', hex_id)
                    nearby_hexes.append(hex_id)
        return sorted(nearby_hexes)

    @staticmethod
    def _is_even(number):
        '''Return True for even number'''
        return float(number) / 2 == int(number / 2)

    def find_nearby_systems(self, hex_id, radius):
        '''Find worlds/systems in nearby hexes'''
        nearby_worlds = []
        for hex_id in self.find_nearby_hexes(hex_id, radius):
            if self.is_system(hex_id):
                nearby_worlds.append(self.is_system(hex_id))
        return nearby_worlds

    def is_system(self, hex_id):
        '''Return False if there is a system at hex_id, system otherwise'''
        if hex_id in self.hexes.keys():
            return self.hexes[hex_id]
        else:
            return False

    def find_owning_system(self, hex_id):
        '''Return hex_id for most important system within 6 hexes'''
        nearby_systems = self.find_nearby_systems(hex_id, 6)
        most_important_system_ix = []
        importance = -10
        for system in nearby_systems:
            if int(system.importance_x) == importance:
                most_important_system_ix.append(system)
            elif int(system.importance_x) > importance:
                most_important_system_ix = [system]
                importance = int(system.importance_x)
        if len(most_important_system_ix) > 1:
            # Need to resolve tie - use Population
            population = -1
            most_important_system_pop = []
            for system in most_important_system_ix:
                if int(system.mainworld.population) == population:
                    most_important_system_pop.append(system)
                elif int(system.mainworld.population) > population:
                    most_important_system_pop = [system]
                    population = int(system.mainworld.population)
            if len(most_important_system_pop) > 1:
                # Another tie - resolve wth TL
                tech_level = -1
                most_important_system_tl = []
                for system in most_important_system_pop:
                    if int(system.mainworld.tech_level) == tech_level:
                        most_important_system_tl.append(system)
                    elif int(system.mainworld.tech_level) > tech_level:
                        most_important_system_tl = [system]
                        tech_level = int(system.mainworld.tech_level)
                if len(most_important_system_tl) > 1:
                    # Tie - pick one at random
                    return (most_important_system_tl[randint(
                        0,
                        len(most_important_system_tl) - 1)].hex)
                else:
                    return most_important_system_tl[0].hex
            else:
                return most_important_system_pop[0].hex
        else:
            return most_important_system_ix[0].hex

    def trade_code_owning_system(self):
        '''Trade codes extra pass - O:'''
        for hex_id in self.hexes.keys():
            owned = False
            trade_codes = self.hexes[hex_id].mainworld.trade_codes
            for i, code in enumerate(trade_codes):
                if code.startswith('O:'):
                    LOGGER.debug('Found owned system %s', str(hex_id))
                    owner = self.find_owning_system(hex_id)
                    trade_codes[i] = 'O:{}'.format(owner)
                    owned = True
            if owned:
                self.hexes[hex_id].mainworld.trade_codes = trade_codes
コード例 #8
0
class Planet(object):
    '''Planet object'''
    starport_table = Table()
    starport_table.add_row((2, 4), 'A')
    starport_table.add_row((5, 6), 'B')
    starport_table.add_row((7, 8), 'C')
    starport_table.add_row((9), 'D')
    starport_table.add_row((10, 11), 'E')
    starport_table.add_row(12, 'X')
    starport_table.dice = 2

    spaceport_table = Table()
    spaceport_table.add_row((0), 'Y')
    spaceport_table.add_row((1, 2), 'H')
    spaceport_table.add_row(3, 'G')
    spaceport_table.add_row((4, 6), 'F')

    mw_type_flux_table = Table()
    mw_type_flux_table.add_row((-5, -4), 'Far Satellite')
    mw_type_flux_table.add_row(-3, 'Close Satellite')
    mw_type_flux_table.add_row((-2, 5), 'Planet')

    parent_type_flux_table = Table()
    parent_type_flux_table.add_row((-5, 0), 'Gas Giant')
    parent_type_flux_table.add_row((1, 5), 'BigWorld')

    def __init__(self, system=None):
        self.starport = '?'
        self.size = uwp.Size()
        self.atmosphere = uwp.Atmosphere()
        self.hydrographics = uwp.Hydrographics()
        self.biosphere = uwp.Biosphere()
        self.population = uwp.Population()
        self.government = uwp.Government()
        self.law_level = uwp.LawLevel()
        self.tech_level = uwp.TechLevel()

        self.trade_codes = []
        self.travel_code = ''
        self.bases = ''
        self.is_mainworld = True
        self.orbit = ''
        self.system = system
        self.mainworld_type = None
        self.parent_type = None
        self.orbit_around_parent = None

        self.determine_starport()
        self.determine_size()
        self.determine_atmosphere()
        self.determine_hydrographics()
        self.determine_population()
        self.determine_government()
        self.determine_law()
        self.determine_tech()
        # self.determine_trade_codes()
        self.determine_mainworld_type()

    def __str__(self):
        return self.uwp()

    def uwp(self):
        '''UPP'''
        return '{}{}{}{}{}{}{}-{}'.format(
            self.starport,
            str(self.size),
            str(self.atmosphere),
            str(self.hydrographics),
            str(self.population),
            str(self.government),
            str(self.law_level),
            str(self.tech_level),
        )

    def display_trade_codes(self):
        '''Display method - UPP + other stuff'''
        return '{}'.format(
            ' '.join(self.trade_codes),
        )

    def determine_starport(self):
        '''Set starport'''
        self.starport = self.starport_table.roll()

    def determine_size(self):
        '''Set size'''
        roll = D6.roll(2, -2)
        if roll >= 10:
            roll = D6.roll(1, 9)
        self.size = uwp.Size(roll)

    def determine_atmosphere(self):
        '''Set atmosphere'''
        roll = FLUX.flux() + int(self.size)
        if roll < 0 or int(self.size) == 0:
            roll = 0
        if roll > 15:
            roll = 15
        self.atmosphere = uwp.Atmosphere(roll)

    def determine_hydrographics(self):
        '''Set hydrographics'''
        mods = 0
        if int(self.atmosphere) < 2 or int(self.atmosphere) > 9:
            mods -= 4
        roll = FLUX.flux() + int(self.atmosphere) + mods
        if int(self.size) < 2:
            roll = 0
        roll = max(0, roll)
        roll = min(10, roll)
        self.hydrographics = uwp.Hydrographics(roll)

    def determine_population(self):
        '''Set population'''
        roll = D6.roll(2, -2)
        if roll == 10:
            roll = D6.roll(2, 3)
        self.population = uwp.Population(roll)

    def determine_government(self):
        '''Set government'''
        roll = FLUX.flux() + int(self.population)
        roll = min(roll, 15)
        roll = max(roll, 0)
        self.government = uwp.Government(roll)

    def determine_law(self):
        '''Set law level'''
        roll = FLUX.flux() + int(self.government)
        roll = min(roll, 18)
        roll = max(roll, 0)
        self.law_level = uwp.LawLevel(roll)

    def determine_tech(self):
        '''Set tech level'''
        mod = 0
        mod += self._mod_starport()
        mod += self._mod_physical()
        mod += self._mod_population()
        LOGGER.debug('determine_tech(): mod = %s', mod)
        roll = D6.roll(1, mod, floor=0)
        LOGGER.debug('TL result = %s', roll)
        self.tech_level = uwp.TechLevel(roll)

    def _mod_starport(self):
        '''TL mods for starport'''
        mod = 0
        if self.starport == 'A':
            mod += 6
        elif self.starport == 'B':
            mod += 4
        elif self.starport == 'C':
            mod += 2
        elif self.starport == 'X':
            mod -= 4
        elif self.starport == 'F':
            mod += 1
        LOGGER.debug('Starport = %s TL DM = %s', str(self.starport), mod)
        return mod

    def _mod_physical(self):
        '''TL mods for physical profile values'''
        mod = 0
        # Size
        if str(self.size) in '01':
            mod += 2
        elif str(self.size) in '234':
            mod += 1
        # Atmosphere
        if str(self.atmosphere) in '0123ABCDEF':
            mod += 1
        # Hydrographics
        if str(self.hydrographics) == '9':
            mod += 1
        elif str(self.hydrographics) == 'A':
            mod += 2
        LOGGER.debug('Physical TL DM = %s', mod)
        return mod

    def _mod_population(self):
        ''' TL mods for population-related profile values'''
        mod = 0
        # Population
        if str(self.population) in '12345':
            mod += 1
        elif str(self.population) == '9':
            mod += 2
        elif str(self.population) in 'ABCDEF':
            mod += 4
        # Government
        if str(self.government) in '05':
            mod += 1
        elif str(self.government) == 'D':
            mod -= 2
        LOGGER.debug('Population TL DM = %s', mod)
        return mod

    def determine_trade_codes(self):
        '''Set trade codes'''
        if self.system is not None:
            tcs = TradeCodes(self, self.system)
        else:
            tcs = TradeCodes(self)
        self.trade_codes = tcs.generate()

    def as_json(self):
        '''Return JSON representation'''
        planet = {
            'uwp': self.uwp(),
            'trade_codes': self.trade_codes,
            'travel_code': self.travel_code,
            'bases': self.bases,
            'is_mainworld': self.is_mainworld,
            'orbit': self.orbit,
            'mainworld_type': self.mainworld_type,
            'parent_type': self.parent_type,
            'orbit_around_parent': self.orbit_around_parent
        }
        return json.dumps(planet)

    def json_import(self, jdata):
        '''Import from JSON'''
        planet = json.loads(jdata)
        self.trade_codes = planet['trade_codes']
        self.travel_code = planet['travel_code']
        self.bases = planet['bases']
        self.is_mainworld = planet['is_mainworld']
        self.orbit = planet['orbit']
        self._load_uwp(planet['uwp'])
        self.mainworld_type = planet['mainworld_type']
        self.parent_type = planet['parent_type']
        self.orbit_around_parent = planet['orbit_around_parent']

    def _load_uwp(self, uwp_data):
        '''Set planetary data from UWP'''
        try:
            self.starport = str(uwp_data[0])
            self.size = uwp.Size(str(uwp_data[1]))
            self.atmosphere = uwp.Atmosphere(str(uwp_data[2]))
            self.hydrographics = uwp.Hydrographics(str(uwp_data[3]))
            self.population = uwp.Population(str(uwp_data[4]))
            self.government = uwp.Government(str(uwp_data[5]))
            self.law_level = uwp.LawLevel(str(uwp_data[6]))
            self.tech_level = uwp.TechLevel(str(uwp_data[8]))
        except (IndexError, ValueError):
            raise ValueError('Invalid UWP {}'.format(uwp_data))

    def determine_mainworld_type(self):
        '''Determine if satellite (and type) or planet'''
        close_orbits = ['Ay', 'Bee', 'Cee', 'Dee', 'Ee', 'Eff',
                        'Gee', 'Aitch', 'Eye', 'Jay', 'Kay', 'Ell', 'Em']
        far_orbits = ['En', 'Oh', 'Pee', 'Que', 'Arr', 'Ess',
                      'Tee', 'Yu', 'Vee', 'Dub', 'Ex', 'Wye', 'Zee']
        LOGGER.debug('UWP = %s', self.uwp())
        if int(self.size) == 0:
            # Asteroid belt => planet
            LOGGER.debug('Planet is belt => mainworld_type = Planet')
            self.mainworld_type = 'Planet'
        else:
            flux = FLUX.flux()
            LOGGER.debug('Flux roll is %s', flux)
            self.mainworld_type = self.mw_type_flux_table.lookup(flux)
            LOGGER.debug('Mainworld type is %s', self.mainworld_type)
        if self.mainworld_type == 'Planet':
            self.parent_world = None
            self.orbit_around_parent = None
        elif self.mainworld_type == 'Close Satellite':
            self.trade_codes.append('Lk')
            self.parent_type = self.parent_type_flux_table.lookup(FLUX.flux())
            roll = FLUX.flux()
            if self.parent_type == 'Gas Giant':
                roll -= 2
            roll = max(roll, -6)
            roll = min(roll, 6)
            self.orbit_around_parent = close_orbits[roll + 6]
        elif self.mainworld_type == 'Far Satellite':
            self.trade_codes.append('Sa')
            self.parent_type = self.parent_type_flux_table.lookup(FLUX.flux())
            roll = FLUX.flux()
            if self.parent_type == 'Gas Giant':
                roll -= 2
            roll = max(roll, -6)
            roll = min(roll, 6)
            self.orbit_around_parent = far_orbits[roll + 6]
コード例 #9
0
ファイル: star.py プロジェクト: simon-hibbs/T5_worldgen
class _Star(object):
    '''Star base class'''
    # Spectral type
    spectral_type_table = Table()
    spectral_type_table.add_row(-6, 'OB')
    spectral_type_table.add_row((-5, -4), 'A')
    spectral_type_table.add_row((-3, -2), 'F')
    spectral_type_table.add_row((-1, 0), 'G')
    spectral_type_table.add_row((1, 2), 'K')
    spectral_type_table.add_row((3, 5), 'M')
    spectral_type_table.add_row((6, 8), 'BD')

    size_o_table = Table()
    size_o_table.add_row((-6, -5), 'Ia')
    size_o_table.add_row(-4, 'Ib')
    size_o_table.add_row(-3, 'II')
    size_o_table.add_row((-2, 0), 'III')
    size_o_table.add_row((1, 3), 'V')
    size_o_table.add_row(4, 'IV')
    size_o_table.add_row(5, 'D')
    size_o_table.add_row((6, 8), 'IV')

    # Size
    size_b_table = Table()
    size_b_table.add_row((-6, -5), 'Ia')
    size_b_table.add_row(-4, 'Ib')
    size_b_table.add_row(-3, 'II')
    size_b_table.add_row((-2, 1), 'III')
    size_b_table.add_row((2, 3), 'V')
    size_b_table.add_row(4, 'IV')
    size_b_table.add_row(5, 'D')
    size_b_table.add_row((6, 8), 'IV')

    size_a_table = Table()
    size_a_table.add_row((-6, -5), 'Ia')
    size_a_table.add_row(-4, 'Ib')
    size_a_table.add_row(-3, 'II')
    size_a_table.add_row(-2, 'III')
    size_a_table.add_row(-1, 'IV')
    size_a_table.add_row((0, 4), 'V')
    size_a_table.add_row(5, 'D')
    size_a_table.add_row((6, 8), 'V')

    size_f_table = Table()
    size_f_table.add_row((-6, -5), 'II')
    size_f_table.add_row(-4, 'III')
    size_f_table.add_row(-3, 'IV')
    size_f_table.add_row((-2, 3), 'V')
    size_f_table.add_row(4, 'VI')
    size_f_table.add_row(5, 'D')
    size_f_table.add_row((6, 8), 'VI')

    size_g_table = Table()
    size_g_table.add_row((-6, -5), 'II')
    size_g_table.add_row(-4, 'III')
    size_g_table.add_row(-3, 'IV')
    size_g_table.add_row((-2, 3), 'V')
    size_g_table.add_row(4, 'VI')
    size_g_table.add_row(5, 'D')
    size_g_table.add_row((6, 8), 'VI')

    size_k_table = Table()
    size_k_table.add_row((-6, -5), 'II')
    size_k_table.add_row(-4, 'III')
    size_k_table.add_row(-3, 'IV')
    size_k_table.add_row((-2, 3), 'V')
    size_k_table.add_row(4, 'VI')
    size_k_table.add_row(5, 'D')
    size_k_table.add_row((6, 8), 'VI')

    size_m_table = Table()
    size_m_table.add_row((-6, -3), 'II')
    size_m_table.add_row(-2, 'III')
    size_m_table.add_row((-1, 3), 'V')
    size_m_table.add_row(4, 'VI')
    size_m_table.add_row(5, 'D')
    size_m_table.add_row((6, 8), 'VI')

    # Habitable zone
    hz_orbit_o_table = Table()
    hz_orbit_o_table.add_row('Ia', 15)
    hz_orbit_o_table.add_row('Ib', 15)
    hz_orbit_o_table.add_row('II', 14)
    hz_orbit_o_table.add_row('III', 13)
    hz_orbit_o_table.add_row('IV', 12)
    hz_orbit_o_table.add_row('V', 11)
    hz_orbit_o_table.add_row('D', 1)

    hz_orbit_b_table = Table()
    hz_orbit_b_table.add_row('Ia', 13)
    hz_orbit_b_table.add_row('Ib', 13)
    hz_orbit_b_table.add_row('II', 12)
    hz_orbit_b_table.add_row('III', 11)
    hz_orbit_b_table.add_row('IV', 10)
    hz_orbit_b_table.add_row('V', 9)
    hz_orbit_b_table.add_row('D', 0)

    hz_orbit_a_table = Table()
    hz_orbit_a_table.add_row('Ia', 12)
    hz_orbit_a_table.add_row('Ib', 11)
    hz_orbit_a_table.add_row('II', 9)
    hz_orbit_a_table.add_row('III', 7)
    hz_orbit_a_table.add_row('IV', 7)
    hz_orbit_a_table.add_row('V', 7)
    hz_orbit_a_table.add_row('D', 0)

    hz_orbit_f_table = Table()
    hz_orbit_f_table.add_row('Ia', 11)
    hz_orbit_f_table.add_row('Ib', 10)
    hz_orbit_f_table.add_row('II', 9)
    hz_orbit_f_table.add_row('III', 6)
    hz_orbit_f_table.add_row('IV', 6)
    hz_orbit_f_table.add_row('V', 5)
    hz_orbit_f_table.add_row('VI', 3)
    hz_orbit_f_table.add_row('D', 0)

    hz_orbit_g_table = Table()
    hz_orbit_g_table.add_row('Ia', 12)
    hz_orbit_g_table.add_row('Ib', 10)
    hz_orbit_g_table.add_row('II', 9)
    hz_orbit_g_table.add_row('III', 7)
    hz_orbit_g_table.add_row('IV', 5)
    hz_orbit_g_table.add_row('V', 3)
    hz_orbit_g_table.add_row('VI', 2)
    hz_orbit_g_table.add_row('D', 0)

    hz_orbit_k_table = Table()
    hz_orbit_k_table.add_row('Ia', 12)
    hz_orbit_k_table.add_row('Ib', 10)
    hz_orbit_k_table.add_row('II', 9)
    hz_orbit_k_table.add_row('III', 8)
    hz_orbit_k_table.add_row('IV', 5)
    hz_orbit_k_table.add_row('V', 2)
    hz_orbit_k_table.add_row('VI', 1)
    hz_orbit_k_table.add_row('D', 0)

    hz_orbit_m_table = Table()
    hz_orbit_m_table.add_row('Ia', 12)
    hz_orbit_m_table.add_row('Ib', 11)
    hz_orbit_m_table.add_row('II', 10)
    hz_orbit_m_table.add_row('III', 0)
    hz_orbit_m_table.add_row('V', 0)
    hz_orbit_m_table.add_row('VI', 0)
    hz_orbit_m_table.add_row('D', 0)

    def __init__(self):
        self.spectral_type = ''
        self.decimal = 0
        self.size = ''
        self.companion = None
        self.primary_rolls = {}
        self.habitable_zone = ''

    def __str__(self):
        return self.code()

    def code(self):
        '''Return spec type, decimal, size for this star only'''
        if self.spectral_type == 'BD':
            return 'BD'
        elif self.size == 'D':
            return 'D'
        else:
            return '{}{} {}'.format(
                self.spectral_type, self.decimal, self.size)

    def display(self):
        '''Combine spectral type, decimal, size'''
        resp = [str(self)]
        if self.companion is not None:
            resp.append(str(self.companion))
        return ' '.join(resp)

    def set_decimal(self):
        '''Set spectral decimal'''
        if self.spectral_type == 'F' and self.size == 'VI':
            self.decimal = D5.roll(1, -1)
        else:
            self.decimal = D10.roll(1, -1)

    def has_companion(self):
        '''Companion star?'''
        if FLUX.flux() >= 3:
            LOGGER.debug('Companion exists')
            self.companion = Secondary(self.primary_rolls)

    def json_import(self, jdata):
        '''Import from JSON'''
        LOGGER.setLevel(logging.ERROR)
        star_dict = json.loads(jdata)
        self.decimal = star_dict['decimal']
        self.habitable_zone = star_dict['habitable_zone']
        self.spectral_type = star_dict['spectral_type']
        self.size = star_dict['size']
        if star_dict['companion'] is not None:
            self.companion = Secondary({'Spectral type': 3, 'Size': 3})
            self.companion.json_import(star_dict['companion'])
        else:
            self.companion = None

    def set_hz(self):
        '''Set habitable zone orbit'''
        if self.spectral_type == 'O':
            self.habitable_zone = self.hz_orbit_o_table.lookup(self.size)
        elif self.spectral_type == 'B':
            self.habitable_zone = self.hz_orbit_b_table.lookup(self.size)
        elif self.spectral_type == 'A':
            self.habitable_zone = self.hz_orbit_a_table.lookup(self.size)
        elif self.spectral_type == 'F':
            self.habitable_zone = self.hz_orbit_f_table.lookup(self.size)
        elif self.spectral_type == 'G':
            self.habitable_zone = self.hz_orbit_g_table.lookup(self.size)
        elif self.spectral_type == 'K':
            self.habitable_zone = self.hz_orbit_k_table.lookup(self.size)
        elif self.spectral_type == 'M':
            self.habitable_zone = self.hz_orbit_m_table.lookup(self.size)