Создания планеты из системы РПГ путешественник в Python


Я пытаюсь научить себя использовать объектно-ориентированный код, и я подумал, что это интересный способ узнать будет написать класс планет, основанный на системе правил из Мангуста путешественника.

При создании объекта ПЛАНЕТЕ, это алгоритмически генерирует планеты. Я определил метод String и метод JSON для возвращения данных планеты. В two_dice функция() имитирует сумма двух кубиков.

from models.common import two_dice
from random import randint

# Translate int values to string equivalent
numMap = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: 'A', 11: 'B', 12: 'C',
          13: 'D', 14: 'E', 15: 'F', '-': '-'}


class Planet(object):
    def __init__(self, name, **kwargs):
        """
        creates a planet object with algorithmically defined attributes
        Attributes can be passed as keywords and creation process will account for them
        """
        self.name = name
        # Size is 2D - 2
        self.size = self._check_kwargs("size", 10, 0, kwargs)
        if self.size is None:
            self.size = two_dice()-2
        # Atmosphere is Size + 2D - 7. If size = 0, atmosphere = 0
        self.atmosphere = self._check_kwargs('atmosphere', 15, 0, kwargs)
        if self.atmosphere is None:
            self.atmosphere = self.size+two_dice()-7
            if self.size == 0:
                self.atmosphere = 0
            self.atmosphere = max(0, self.atmosphere)
        # Temperature is 2D. Affected by atmosphere type
        self.temperature = self._check_kwargs('temperature', 12, 0, kwargs)
        if self.temperature is None:
            self.temperature = two_dice()
            if self.atmosphere in [2, 3]:
                self.temperature -= 2
            elif self.atmosphere in [4, 5, 14]:
                self.temperature -= 2
            elif self.atmosphere in [8, 9]:
                self.temperature += 1
            elif self.atmosphere in [10, 13, 15]:
                self.temperature += 2
            elif self.atmosphere in [11, 12]:
                self.temperature += 6
            self.temperature = max(0, self.temperature)
            self.temperature = min(12, self.temperature)
        # Hydrographics is atmosphere + 2D - 7. Affected by size, temperature and atmosphere
        self.hydrographics = self._check_kwargs('hydrographics', 10, 0, kwargs)
        if self.hydrographics is None:
            if self.size <= 1:
                self.hydrographics = 0
            else:
                self.hydrographics = self.atmosphere + two_dice() - 7
                if self.atmosphere in [0, 1, 10, 11, 12]:
                    self.hydrographics -= 4
                if self.atmosphere != 13:
                    if self.temperature in [10, 11]:
                        self.hydrographics -= 2
                    elif self.temperature >= 12:
                        self.hydrographics -= 6
            self.hydrographics = max(0, self.hydrographics)
            self.hydrographics = min(10, self.hydrographics)
        # Population is 2D - 2.
        self.population = self._check_kwargs('population', 12, 0, kwargs)
        if self.population is None:
            self.population = two_dice() - 2
        # Government is population + 2D - 7.
        self.government = self._check_kwargs('government', 15, 0, kwargs)
        if self.government is None:
            self.government = self.population + two_dice() - 7
            if self.population == 0:
                self.government = 0
            self.government = max(0, self.government)
            self.government = min(15, self.government)
        # Culture is determined by rolling two dice, and concatenating the result.
        self.culture = randint(1, 6) + randint(1, 6) * 10
        if self.population == 0:
            self.culture = 0
        # Law level is government + 2D - 7
        self.law_level = self._check_kwargs('law_level', 9, 0, kwargs)
        if self.law_level is None:
            self.law_level = self.government + two_dice() - 7
            if self.population == 0:
                self.law_level = 0
            self.law_level = max(0, self.law_level)
            self.law_level = min(9, self.law_level)
        # Starport level is 2D, affected by population
        self.starport = self._check_kwargs('starport', 12, 0, kwargs)
        if self.starport is None:
            self.starport = two_dice()
            if self.population >= 10:
                self.starport += 2
            elif self.population >= 8:
                self.starport += 1
            elif self.population <= 2:
                self.starport -= 2
            elif self.population <= 4:
                self.starport -= 1
            self.starport = min(12, self.starport)
            self.starport = max(0, self.starport)
        # tech level is 1D, affected by lots of modifiers
        self.tech_level = self._check_kwargs('tech_level', 15, 0, kwargs)
        if self.tech_level is None:
            self.tech_level = randint(1, 6)
            if self.atmosphere <= 3 or self.atmosphere >= 10:
                self.tech_level += 1
            if self.size in [0, 1]:
                self.tech_level += 2
            elif self.size in [2, 3, 4]:
                self.tech_level += 1
            if self.hydrographics in [0, 9]:
                self.tech_level += 1
            elif self.hydrographics == 10:
                self.tech_level += 2
            if self.population in [1, 2, 3, 4, 5, 8]:
                self.tech_level += 1
            elif self.population == 9:
                self.tech_level += 2
            elif self.population == 10:
                self.tech_level += 4
            if self.government in [0, 5]:
                self.tech_level += 1
            elif self.government in [13, 14]:
                self.tech_level -= 2
            elif self.government == 7:
                self.tech_level += 2
            if self.starport >= 11:
                self.tech_level += 6
            elif self.starport >= 9:
                self.tech_level += 4
            elif self.starport >= 7:
                self.tech_level += 2
            elif self.starport <= 2:
                self.tech_level -= 4
            if self.population == 0:
                self.tech_level = 0
            self.tech_level = max(0, self.tech_level)
            self.tech_level = min(15, self.tech_level)

    @staticmethod
    def _check_kwargs(keyword, maxvalue, minvalue, kwargs):
        """
        Checks the given keyword exists, and is between given max & min values
        Returns keyword value if exists, otherwise None
        """
        if keyword in kwargs:
            if maxvalue >= kwargs[keyword] >= minvalue:
                return kwargs[keyword]
            else:
                raise ValueError("{} must be between {} and {}".format(keyword, maxvalue, minvalue))
        else:
            return None

    def json(self):
        return {'name': self.name,
                'size': self.size,
                'atmosphere': self.atmosphere,
                'temperature': self.temperature,
                'hydrographics': self.hydrographics,
                'population': self.population,
                'government': self.government,
                'culture': self.culture,
                'law_level': self.law_level,
                'starport': self.starport,
                'tech_level': self.tech_level}

    def __str__(self):
        attributes_list = [self.size, self.atmosphere, self.hydrographics, self.population, self.government,
                           self.law_level, '-', self.tech_level]
        if self.starport <= 2:
            starport = 'X'
        elif self.starport <= 4:
            starport = 'E'
        elif self.starport <= 6:
            starport = 'D'
        elif self.starport <= 8:
            starport = 'C'
        elif self.starport <= 10:
            starport = 'B'
        else:
            starport = 'A'
        return self.name + ' ' + starport + ''.join(list(map(lambda X: numMap[X],attributes_list)))

Это моя первая попытка написания класса. Хотя он сейчас одинок, я хотел бы для того, чтобы быть в состоянии соответствовать внутри большого проекта. Есть ли какие способы я могу улучшить это, так что лучше следуйте рекомендациям, и удаляет повторяющийся код?



190
5
задан 28 января 2018 в 02:01 Источник Поделиться
Комментарии
1 ответ


  1. Там, кажется, не быть способ, чтобы пройти culture в качестве ключевого слова аргумент. Это выглядит как недосмотр.

  2. Нет проверьте недопустимые аргументы ключевого слова. Это означает, что если мы делаем ошибку, мы не узнаем об этом, но результат будет неправильный:

    >>> planet = Planet('Hrod', atmopshere=10)
    >>> planet.atmosphere
    7

    Лучше было бы поднять исключение такой:

    TypeError: 'atmopshere' is an invalid keyword argument for this function

  3. Функция two_dice кажется, странно. Путешественник использует большое 2д6 роллы, но в коде я вижу выражения типа randint(1, 6) и two_dice() - 7. Я думаю, что нечто подобное было бы полезно:

    import re

    def roll(dice):
    """Roll dice and return their sum. The argument must be a string
    describing the roll, for example '2d6+3' to roll two 6-sided dice
    and add three.

    """
    match = re.match(r'([1-9]\d*)d([1-9]\d*)([+-]\d+)?$', dice)
    if not match:
    raise ValueError(f"Expected dice but got {dice!r}")
    n, sides, bonus = match.groups()
    sides = int(sides)
    return sum(randint(1, sides) for _ in range(int(n))) + int(bonus or 0)

    Затем вы можете написать roll('1d6') или roll('2d6-7') что может быть понятнее читателю и легче проверить против правил.


  4. Температура регулируется тем же значением (-2) для двух различных наборов значений для атмосферы:

    if self.atmosphere in [2, 3]:
    self.temperature -= 2
    elif self.atmosphere in [4, 5, 14]:
    self.temperature -= 2

    Это последняя строка опечатка self.temperature -= 1?


  5. numMap карты числами от 0 до 15 в их шестнадцатеричное аналог продукции, а также дефис к себе. Если бы не дефис, вы могли бы написать format(n, 'X') и не нужно numMap. Глядя на использование numMap похоже, что точка дефис-реализовать особый случай в __str__ метод. Но как говорится в Дзен питона,


    Особые случаи не настолько особые, чтобы нарушать правила.

    Так что я бы порекомендовал сделать __str__ немного сложнее, для того, чтобы elimate специальный чехол, который позволит вам использовать format(..., 'X'), что в свою очередь позволит вам избавиться от numMap.


  6. Там много повторений. Для каждого атрибута, у нас есть имя, мы имеем максимальное значение, и мы, возможно, потребуется включить его шестнадцатиричное число в строке-код. Мы можем поставить эти данные в таблицу, как это:

    _ATTRIBUTES = [
    # Name, maximum, code
    ('size', 10, True),
    ('atmosphere', 15, True),
    ('temperature', 12, False),
    ('hydrographics', 10, True),
    ('population', 12, True),
    ('government', 15, True),
    ('law_level', 9, True),
    ('starport', 12, False),
    ('tech_level', 15, False),
    ('culture', 66, False),
    ]

    И мы могли бы поставить правила для генерации каждого атрибута в свои собственные методы, вроде этого:

    def _size_default(self):
    return roll('2d6-2')

    def _atmosphere_default(self):
    if self.size == 0:
    return 0
    else:
    return self.size + roll('2d6-7')

    _TEMPERATURE_ADJUST = [0, 0, -2, -2, -1, -1, 0, 0, 1, 1, 1, 6, 6, 1, -1, 1]

    def _temperature_default(self):
    return roll('2d6') + self._TEMPERATURE_ADJUST[self.atmosphere]

    def _hydrographics_default(self):
    if self.size <= 1:
    return 0
    hydrographics = self.atmosphere + roll('2d6-7')
    if self.atmosphere in (0, 1, 10, 11, 12):
    hydrographics -= 4
    if self.atmosphere != 13:
    if self.temperature in (10, 11):
    hydrographics -= 2
    elif self.temperature >= 12:
    hydrographics -= 6
    return hydrographics

    def _population_default(self):
    return roll('2d6-2')

    def _government_default(self):
    if self.population == 0:
    return 0
    else:
    return self.population + roll('2d6-7')

    def _culture_default(self):
    if self.population == 0:
    return 0
    else:
    return roll('1d6') * 10 + roll('1d6')

    def _law_level_default(self):
    if self.population == 0:
    return 0
    else:
    return self.government + roll('2d6-7')

    _STARPORT_ADJUST = [-2, -2, -2, -1, -1, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2]

    def _starport_default(self):
    return roll('2d6') + self._STARPORT_ADJUST[self.population]

    _TECH_LEVEL_ADJUST = [
    # attribute, {value:adjustment}
    ('size', {0:2, 1:2, 2:1, 3:1, 4:1}),
    ('atmosphere', {0:1, 1:1, 2:1, 3:1, 10:1, 11:1, 12:1, 13:1,
    14:1, 15:1}),
    ('hydrographics', {0:1, 9:1, 10:2}),
    ('population', {1:1, 2:1, 3:1, 4:1, 5:1, 8:1, 9:2, 10:4}),
    ('government', {0:1, 5:1, 7:2, 13:-2, 14:-2}),
    ('starport', {0:-4, 1:-4, 2:-4, 7:2, 8:2, 9:4, 10:4, 11:6, 12:6,
    13:6, 14:6, 15:6}),
    ]

    def _tech_level_default(self):
    if self.population == 0:
    return 0
    tech_level = roll('1d6')
    for attribute, adjustment in self._TECH_LEVEL_ADJUST:
    tech_level += adjustment.get(getattr(self, attribute), 0)
    return tech_level

    Теперь мы можем обрабатывать аргументы сайта систематически, такой, точки крепления §1 и §2, и во избежание повторения:

    for attribute, maximum, _ in self._ATTRIBUTES:
    if attribute in kwargs:
    value = kwargs.pop(attribute)
    if not (0 <= value <= maximum):
    raise ValueError(f"{attribute!r} must be between 0 and "
    "{maximum} inclusive")
    else:
    value = getattr(self, '_' + attribute + '_default')()
    value = max(0, min(value, maximum))
    setattr(self, attribute, value)
    if kwargs:
    kwarg = next(iter(kwargs))
    raise TypeError(f"{kwarg!r} is an invalid keyword argument for "
    "this function")

    После этого нам не нужно _check_kwargs больше, и json и __str__ методы:

    def json(self):
    result = dict(name=self.name)
    for attribute, _, _ in self._ATTRIBUTES:
    result[attribute] = getattr(self, attribute)
    return result

    _STARPORT_CODE = 'XXXEEDDCCBBAAAAA'

    def __str__(self):
    return "{} {}{}-{}".format(
    self.name,
    self._STARPORT_CODE[self.starport],
    ''.join(format(getattr(self, attribute), 'X')
    for attribute, _, code in self._ATTRIBUTES if code),
    format(self.tech_level, 'X'))


5
ответ дан 29 января 2018 в 09:01 Источник Поделиться