Статистика-классы для текстовых приключенческих


Я учусь Python при создании текстовых приключенческих.
Я поставила создание стат и классы хранения данных в отдельные файлы (модули?), и общие функции (ValueError обработки и т. п.) в другой так же.
Это хорошая практика?

import common_functions

# Maximum number of points. Adjust for balance later
statsMax = 26
stopMessage = "No. Stop. Try again."

# Class to store the stats of a character - both Players and NPCs


class statsStore():

    def __init__(self):
        self.S = 0      # Strength
        self.P = 0      # Perception
        self.E = 0      # Endurance
        self.C = 0      # Charisma
        self.It = 0     # Intelligence
        self.A = 0      # Agility
        self.L = 0      # Luck

# Class to ask players to customise their player stats, and display them.


class stats(statsStore):

    def __init__(self):
        super().__init__()
        # Purely used for counting in the statsList function
        self.x = 0

    # Stats Functions

    def statsFunc(self, stat, Text):
        global stopMessage
        global statsMax
        if self.x == 0:
            Limit = statsMax - 10
        else:
            Limit = statsMax - 20 - self.x
        print("You have " + str(statsMax) + " points left.")
        while True:
            print(Text)
            stat = common_functions.errorHandle_int(stat, ">>>")

            if stat <= 10 and stat >= 1:
                if (statsMax - stat) < Limit:
                    print(stopMessage)
                    continue
                else:
                    statsMax = statsMax - stat
                return stat
                break
            else:
                print(stopMessage)

    # What's Fallout?
    def statsList(self):
        global statsMax
        self.statsDict = {"Strength": self.S,
                          "Perception": self.P,
                          "Endurance": self.E,
                          "Charisma": self.C,
                          "Intelligence": self.It,
                          "Agility": self. A,
                          "Luck": self.L}

        self.attributes = [self.S, self.P, self.E,
                           self.C, self.It, self.A, self.L]

        for statsTip, stat in self.statsDict.items():
            self.statsDict[statsTip] = self.statsFunc(stat, statsTip)
            self.attributes[self.x] = self.statsDict[statsTip]

            # I need this block of code to make changes to the individual stats.
            # Otherwise, printing self.S would output 0.
            self.S = self.attributes[0]
            self.P = self.attributes[1]
            self.E = self.attributes[2]
            self.C = self.attributes[3]
            self.It = self.attributes[4]
            self.A = self.attributes[5]
            self.L = self.attributes[6]
            self.x += 1

            # print(self.statsDict)
            # Printing a list looks neater
            print(self.attributes)


# Stats Debug
Player1 = stats()
Player1.statsList()
print(Player1.S)
print(Player1.attributes)

Блок кода, где я сидел self.S = self.attribute[0] (и другие статы так же) кажется неэффективным, но это не меняет эти переменные в противном случае, даже если она изменяется в Словарь/Список.
Есть ли более рациональный способ реализации этого?

Вот общие функции модуля также:

import sys
stopMessage = "No. Stop. Try again."


def checkIn_list(List, Check):
    global stopMessage
    while True:
        Check = "".join(
            [letter for letter in input(">>>") if str.isalpha(letter)]).lower()
        if Check == "exit":
            sys.exit()
        elif Check not in List:
            print(List)
            print(stopMessage)
            continue
        else:
            return Check
            break


def errorHandle_int(value, message):
    global stopMessage
    while True:
        value = input(message)
        if value.title() == "Stop":
            sys.exit()
        try:
            value = int(value)
            return value
            break
        except ValueError:
            print(stopMessage)


# test_list = ("yes", "no", "stop")
# test_input = 0
# checkIn_list(test_list, test_input)

Комментарий общий код тоже оценили!



Комментарии
2 ответа

Перво-наперво: ваш код читает рядом кода на Python. Вы должны прочитать в PEP8 , чтобы правильно отформатировать его.

Во-вторых, вы должны стремиться избегать global как сложно, как вы можете, как это легко сделать код ошибок, трудно рассуждать, и трудно проверить. В вашем случае, из двух глобальных переменных вы используете, stopMessage доступно только для чтения, так как постоянно где применение global является ненужным. Второй, statsMax сложнее. Для простого использования, это, кажется, работает, но если вы были, чтобы создать второго игрока, statsMax будет 0 и это было бы невозможно. Вместо этого, вы должны сделать это параметр какой-то: с __init__ метод stats класс, кажется, хорошей отправной точкой:

class stats(statsStore):
def __init__(self, max_points=26):
...
self.max_points = max_points

Существует также фрагментации исходного кода в двух файлах. Хотя это хорошо, чтобы рассмотреть в какой-то момент уметь группировать связанные functionnalities под пространство имен, вы слишком мало functionnalities для того, чтобы быть полезным. Это даже может привести к дублированию stopMessage. Держите его просто с одним файлом.

Дизайн stats класс тоже очень хрупкие: на statsFunc метод не предназначен для вызова извне statsList и statsList сама уже называться по stats объект считается инициализированной. Эту роль выполняют простую функцию, может classmethod на statsStore сам класс. Кроме того, statsStore только там должен быть набор значений: лучше использовать namedtuple здесь.

Еще одна вещь, которая интригует меня, как вы используете некоторые параметры. Например stat в statsFunc или Check в checkIn_List. Это как вы хотите объявить переменную перед использованием, но вы никогда ничего не делаете со значением переданного в качестве параметра. Вам это не нужно. Просто присвойте значение нового имени, когда вам нужна новая переменная, и не пропускают ненужные параметры.

Наконец, вместо сдачи тестов в конце файла, вы должны обернуть их в соответствии с if __name__ == '__main__' тест, чтобы они не выполняться, когда вы import ваш файл (в оболочку Python, чтобы проверить его, или в другом модуле).


Предлагаемые улучшения:

import sys
from collections import namedtuple

Statistics = namedtuple('Statistics', 'strength, perception, endurance, charisma, intelligence, agility, luck')

def ask_for_value_in_list(prompt_message, *authorized_values):
while True:
value = input(prompt_message)
word = ''.join(filter(str.isalpha, value)).lower()
if word in authorized_values:
return word
if word == 'exit':
raise ValueError('asked to exit')
print('Only one of the following values is allowed:', authorized_values, file=sys.stderr)

def ask_for_integer(prompt_message):
while True:
value = input(prompt_message)
try:
return int(value)
except ValueError:
if value.lower() == 'stop':
raise
print('An integer is required!', file=sys.stderr)

def ask_for_stats(stat_name, max_points):
message = '[{} points left] {} >>> '.format(max_points, stat_name)
while True:
value = ask_for_integer(message)
if 1 <= value <= min(10, max_points):
return value
print('The provided value is out of bounds, try again.')

def build_statistics(max_points=26):
statistics = dict.fromkeys(Statistics._fields)
for statistic in statistics:
if not max_points:
raise ValueError('character exhausted statistics points')
value = ask_for_stats(statistic, max_points)
max_points -= value
statistics[statistic] = value
return Statistics(**statistics)

def main():
try:
stats = build_statistics()
except ValueError:
print('Could not build statistics, aborting.', file=sys.stderr)
else:
print('Statistics collected:', stats)

if __name__ == '__main__':
main()

3
ответ дан 15 марта 2018 в 02:03 Источник Поделиться

У меня есть несколько проблем с вашим кодом. Во-первых, объекты всегда должны быть действительными. Когда вы отделяете инициализации и конфигурации, создать шов, в который ошибки будет уверенно ползать.

Далее, имена материи. Ваш именования, и орфография, несколько странно. Я бы предположил, что вы измените свое stats и statsStore классы Character.

Я не вижу никаких причин, почему двое должны быть отдельные классы. Вместо этого, я думаю, вам нужен один класс С некоторые вспомогательные функции. (Создание класса, чтобы "сделать что-то" - это Java-изм. Питон очень не Ява.)

Ваши атрибуты должны быть прописаны. Помните, что написание кода-это гораздо больше об общении с другим парнем техническое обслуживание по сравнению с компилятором.

К примеру, я вижу пару. Во-первых, вы можете создать "класс персонажа" механизм, с определенного минимального и/или максимального ограничения атрибута. Во-вторых, у вас может быть "монстр" механизм, где интересно задать некоторые атрибуты, но остальное может быть объявлен дефолт. Так давайте попробуем эти две вещи.

Во-первых, "указать некоторые атрибуты плюс по умолчанию" подход:

monster = Character(strength=15, agility=14, default=12)

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

class Character:   # formerly, statsStore

def __init__(self, *, default=10, strength=None, perception=None,
endurance=None, charisma=None, intelligence=None,
agility=None, luck=None):
"""Initialize a character. Set attributes to "default" unless
they are given explicitly.

"""
self.strength = default if strength is None else strength
self.perception = default if perception is None else perception
self.endurance = default if endurance is None else endurance
self.charisma = default if charisma is None else charisma
self.intelligence = default if intelligence is None else intelligence
self.agility = default if agility is None else agility
self.luck = default if luck is None else luck

Теперь, что насчет персонажа класса, с минимальным/максимальным атрибутами?

Ну, есть несколько способов решения этой. Во-первых, как нам надо работать? Я предвижу, что там есть "установить атрибутов процесса", где пользователь может указать атрибут и значение. Это будет цикл, пока они не удовлетворены:

ATTR_NAMES = ('strength', 'perception', 'endurance', 'charisma', 'intelligence', 'agility', 'luck')

def set_attributes(char, points):
"""Interact with the user. Set attributes, up to maximum points.

"""

done = False
while not done:
print("Stats: ", end='')
for name in ATTR_NAMES:
print("{}={}".format(name[:3].upper(), getattr(char, name)),
end=' ')
print("\n\nYou have {} points remaining to increase attributes"
.format(points))

# FIXME: Maybe validate entire character here, to decide if
# 'done' is allowed.

if points == 0:
cmd = input("Enter 'done' or 'NAME=VALUE' to set an attribute: ")

if 'done'.startswith(cmd.lower()):
done = True
continue

else:
cmd = input("Enter 'NAME=VALUE' to set an attribute: ")

(attr, value) = cmd.split('=')
attr = attr.strip()
value = value.strip()

for name in ATTR_NAMES:
if name.startswith(attr.lower()):
attr = name
break
else:
print("'{}' is not a valid attribute name.".format(attr))
print("Valid attribute names are: ", ', '.join(ATTR_NAMES))
continue

try:
value = int(value)
except:
print("Please provide an integer value. '{}' is not valid".format(value))
continue

cur_value = getattr(char, attr)
point_cost = value - cur_value
if point_cost > points:
print("You don't have enough points for that!")
continue

# FIXME: Maybe check attr/value pair here.

setattr(char, attr, value)
points -= point_cost

player = Character()
set_attributes(player, points=26)

Что насчет максимальных/минимальных значений? Я оставлю это до вас. Вы могли бы реализовать .check_attributes() метода на различных классах (Fighter.check_attributes(), Mage.check_attributes()и т. д.) или вы могли бы пройти в словаре мин/макс кортежи, или передать в функцию проверки и вызвать его каждый раз. Я отметила несколько пятен вообще где вы могли бы сделать проверку.

Насчет разных рас? В некоторых играх есть бонусы к статам на основе расы. Вы можете сделать это несколькими способами. Самым очевидным было бы просто увеличить/уменьшить "базовые" атрибуты, прежде чем процесс распределения начинается. Вместо того, чтобы прийти с 10,10,10 персонаж придет в 10,12,9 или что-то. Другой выбор будет применять их в качестве бонуса после завершения процесса создания, но мне не нравится это, потому что это ставит пользователя в положение того, чтобы запоминать, бонусы и делать математику в своей голове.

А как насчет увеличения затрат? Следует изменять ул.=12 по ул.=15 только стоимость трех точек, или же она должна начать стоить больше, как только вы идете мимо 14? Это не так сложно, это просто требует таблицу подстановки. Вместо вычисления point_cost как value - cur_valueвы бы вычислить сумму список атрибутов расходы, от cur_value + 1 для value + 1:

point_cost = sum(attr_points[value+1:cur_value+1])

(Или, спускаясь, несколько иной срез с шагом -1.) Вам придется инициализировать attr_points список все дополнительные расходы были.

1
ответ дан 16 марта 2018 в 11:03 Источник Поделиться