Я думаю, что использование random.sample
- это хороший способ выбора случайных вопросов из основного списка вопросов. Вот что я придумал:
Различные типы символов превратились в enum
(перечисление). Одним из больших преимуществ этого подхода является то, что он не позволяет вам «раскусить» вещи. Вместо жесткого кодирования строковых литералов в вашем коде в любое время, когда вам нужно напечатать или выполнить сравнение с типом символа, вы можете вместо этого использовать экземпляр enum.
У вас есть collections.Counter
(что в нашем случае, на самом деле это всего лишь словарь, в котором ключом является экземпляр перечисления Character
, а связанным значением является количество очков или подсчет из теста). Вам не нужно использовать collections.Counter
, простой словарь Python подойдет также. В любом случае предпочтительнее создавать множество отдельных переменных счетчика.
queries
- это список из Query
объектов (Query
- это то, что я решил назвать вопросом). Это в основном ваш основной список всех возможных вопросов, любой из которых может появиться в викторине позже. Один объект Query
состоит из строки prompt
, которая представляет собой фактический вопрос, который нужно задать. Он также содержит список Choice
объектов с именем choices
. Они представляют все возможные допустимые варианты выбора / ответы / ответы, которые пользователь может сделать в ответ на этот запрос.
A Choice
объект состоит из некоторого text
, то есть текста, который должен отображаться,а также переменную character_type
, которая будет экземпляром перечисления Character
. Если пользователь выбирает этот выбор, этот экземпляр enum будет увеличивать связанный счетчик в объекте counter
.
И Query
, и Choice
- просто collections.namedtuple
с, потому что я не чувствовалнапример, создание классов для них.
После определения списка queries
начинается актуальный тест. Мы выбираем некоторое количество случайных запросов из нашего queries
списка, используя random.sample
, а затем делаем следующее для каждого запроса:
- Распечатывает подсказку текущего запроса.
- Показать вседоступные опции для этого запроса
- Получить пользовательский ввод (введите цикл, который завершается, только если пользователь вводит допустимый ввод).
- Увеличивает счетчик в зависимости от выбора пользователя.
- Запустите цикл заново и делайте все это снова для следующего запроса, пока не останется больше случайно выбранных запросов.
Наконец, мы печатаем результат теста. Я понимаю, что у вас есть гораздо больше типов персонажей и даже других атрибутов, таких как сила и ловкость, которые я даже не принял во внимание. Надеюсь, это полезно и не подавляюще.
def main():
from enum import Enum, auto
from collections import Counter, namedtuple
from random import sample
class Character(Enum):
Barbarian = auto()
Bard = auto()
Cleric = auto()
counter = Counter()
Choice = namedtuple("Choice", ["text", "character_type"])
Query = namedtuple("Query", ["prompt", "choices"])
queries = [
Query("Battleaxe or Lute?", [
Choice("Battleaxe (grunt)", Character.Barbarian),
Choice("The Lute! Oh how I love singing!", Character.Bard)]),
Query("Do you prefer healing people or singing to them?", [
Choice("I prefer to heal people", Character.Cleric),
Choice("I LOVE SINGING", Character.Bard)]),
Query("Do you fight against Man or the Undead?", [
Choice("Me fight Man (grunt)", Character.Barbarian),
Choice("I prefer to fight the undead", Character.Cleric)]),
Query("Sing and dance, heal people, or wage war?", [
Choice("I'LL SING!!", Character.Bard),
Choice("I'd prefer to heal people", Character.Cleric),
Choice("Me wage war (ugh)", Character.Barbarian)])
]
number_of_random_queries = 3
for current_query in sample(queries, k=number_of_random_queries):
print(current_query.prompt)
for choice_index, choice in enumerate(current_query.choices, start=1):
print(f" {choice_index}.) {choice.text}")
# Get user input, stay in this loop until they enter something valid.
while True:
user_input = input("Enter your choice: ")
try:
user_choice_index = int(user_input)-1
assert user_choice_index >= 0
user_choice = current_query.choices[user_choice_index]
except (ValueError, AssertionError, KeyError):
# Either the input was not a number, or the number was an invalid index.
continue
else:
break
# Increment the counter / tally for the associated character type.
counter.update({user_choice.character_type})
print("-" * 20)
# Print the final result.
character_type = counter.most_common(n=1)[0][0]
print(f"You have the heart of a {character_type.name}.")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Вывод:
Sing and dance, heal people, or wage war?
1.) I'LL SING!!
2.) I'd prefer to heal people
3.) Me wage war (ugh)
Enter your choice: 1
--------------------
Do you prefer healing people or singing to them?
1.) I prefer to heal people
2.) I LOVE SINGING
Enter your choice: 2
--------------------
Do you fight against Man or the Undead?
1.) Me fight Man (grunt)
2.) I prefer to fight the undead
Enter your choice: 2
--------------------
You have the heart of a Bard.
РЕДАКТИРОВАТЬ - Просто подумал, что я должен отредактировать этот пост, объяснить несколько вещей и сделать свой оригинальный ответменее сложный.
Забудьте все эти вещи def main
, collections.Counter
и enum.auto
прямо сейчас.
Начиная с этого, вы можете испытать желание написать что-то вроде этого (как у вас):
barbarian_count = 0
bard_count = 0
cleric_count = 0
# ...
if barbarian_count > bard_count and barbarian_count > cleric_count:
print("You're a barbarian!")
elif bard_count > barbarian_count and bard_count > cleric_count:
print("You're a bard!")
elif cleric_count > barbarian_count and cleric_count > bard_count:
print("You're a cleric!")
else:
# ...
Что неплохо - просто не очень легко поддерживать и расширять - возможно, вы уже это обнаружили. Например, этот код работает только для трех уникальных типов символов (варвар, бард и клерик). Что если я хочу добавить новый тип символа в проект? Мне придется изменить свой код следующим образом:
barbarian_count = 0
bard_count = 0
cleric_count = 0
druid_count = 0
# ...
if barbarian_count > bard_count and barbarian_count > cleric_count and barbarian_count > druid_count:
print("You're a barbarian!")
elif bard_count > barbarian_count and bard_count > cleric_count and bard_count > druid_count:
print("You're a bard!")
elif cleric_count > barbarian_count and cleric_count > bard_count and cleric_count > druid_count:
print("You're a cleric!")
elif druid_count > barbarian_count and druid_count > bard_count and druid_count > cleric_count:
print("You're a druid!")
else:
# ...
Мне пришлось добавить новую переменную-счетчик, расширить условия для всех моих существующих операторов if и даже добавить совершенно новый оператор ifкоторый так же долго, как другие. Вы можете представить, как все может быстро выйти из-под контроля, если вы попытаетесь добавить больше типов символов, используя этот подход. У вас может возникнуть соблазн скопировать-вставить оператор if, потому что вы не хотите вводить все вручную, но, по моему опыту, это приводит к ошибкам в вашем коде (поскольку вы можете забыть изменить имя переменной в одном из условий,например).
Вот почему я предлагаю использовать enum для определения возможных типов символов. Просто краткое введение в перечисления:
from enum import Enum
class CharacterType(Enum):
Barbarian = 0
Bard = 1
Cleric = 2
for character_type in CharacterType:
print(character_type)
Вывод:
CharacterType.Barbarian
CharacterType.Bard
CharacterType.Cleric
>>>
Вы можете видеть, что я изменил название enum с Character
на CharacterType
, что, я думаю, более уместно. Этот фрагмент предназначен только для демонстрации того, что вы можете перебирать все возможные состояния перечисления (в нашем случае уникальные типы символов) в простом цикле for. Между прочим, это не строки, которые печатаются в цикле for - character_type
является экземпляром перечисления CharacterType
.
Посмотрите на перечисление CharacterType
. Я устанавливаю Barbarian = 0
, Bard = 1
и Cleric = 2
. Эти значения не имеют ничего общего с подсчетом или подсчетом. Это просто уникальные значения - считайте их уникальными идентификаторами. Они даже не должны быть целыми числами - они могут быть чем угодно, например строками:
from enum import Enum
class CharacterType(Enum):
Barbarian = "Hello"
Bard = "World"
Cleric = "WUIAHkjahdkjsh893y724uhj"
for character_type in CharacterType:
print(character_type)
Если я запускаю этот код, я получаю точно такой же вывод. Связанное значение с любым одним CharacterType просто должно быть уникальным объектом - вот почему обычно просто нумеровать их, начиная с 0 или 1 (или использовать enum.auto
, который выбирает для нас уникальные идентификаторы).
Теперь я хотел бы иметь способ введения каких-то счетчиков или переменных счетчиков - по одной для каждого из моих типов символов. Вместо того, чтобы создавать отдельные переменные, мы можем создать простой старый словарь Python, где ключ - это тип символа, а соответствующее значение - это счет или число для этого конкретного типа символа.
from enum import Enum
class CharacterType(Enum):
Barbarian = 0
Bard = 1
Cleric = 2
counter = {}
for character_type in CharacterType:
counter.update({character_type: 0})
for character_type, count in counter.items():
print(f"The current count for {character_type.name} is {count}")
Вывод:
The current count for Barbarian is 0
The current count for Bard is 0
The current count for Cleric is 0
В нашем перечислении CharacterType
мы создаем пустой словарь с именем counter
. Затем мы имеем тот же цикл for, что и раньше, где мы перебираем все типы символов в нашем перечислении. Однако вместо печати типов символов мы просто добавляем новые пары ключ-значение в наш словарь. По сути, мы просто инициализируем наш словарь так, чтобы счетчик / подсчет для каждого отдельного символьного типа начинался с нуля. Второй цикл for предназначен для того, чтобы мы убедились, что в словаре есть три пары ключ-значение - по одной для каждого типа символов и что их соответствующие значения (счетчики) равны нулю.
Если вам интереснопридерживаясь моего первоначального подхода к построению вопросов и ответов, это та часть, где вы будете создавать именованные кортежи. Не забудьте from collections import namedtuple
вверху вашего кода:
Query = namedtuple("Query", ["text", "choices"])
Choice = namedtuple("Choice", ["text", "character_types"])
queries = [
Query(
text="Battleaxe or Lute?",
choices=[
Choice(
text="Battleaxe (grunt)",
character_types=[
(CharacterType.Barbarian, 2),
(CharacterType.Cleric, 1)
]
), # First choice ends here
Choice(
text="The Lute! Oh how I love singing!",
character_types=[
(CharacterType.Bard, 2)
]
) # Second choice ends here
] # List of choices ends here
), # First Query ends here
Query(
text="Do you prefer healing people or singing to them?",
choices=[
Choice(
text="I prefer to heal people",
character_types=[
(CharacterType.Cleric, 1)
]
), # First choice ends here
Choice(
text="I LOVE SINGING",
character_types=[
(CharacterType.Bard, 2)
]
) # Second choice ends here
] # List of choices ends here
) # Second Query ends here
] # List of all queries ends here
Я изменил несколько вещей. Запрос имеет строку text
(вместо строки prompt
, я просто изменил имя) и список Choice
объектов с именем choices
.
Объект Choice
также имеетtext
строка. Однако свойство, которое раньше называлось character_type
, теперь равно character_types
- это означает, что это должен быть список вещей, а не отдельная вещь. Я сделал это изменение, чтобы поддержать изменение количества символов разных типов, выбрав определенные варианты.
Затем есть список queries
, который представляет собой список Query
объектов. На этот раз есть только два объекта Query вместо четырех, потому что печатание заняло бы много времени - это просто для демонстрации. Я также добавил отступ, где я думаю, что это может быть полезно, просто чтобы увидеть, где один объект начинается, а другой заканчивается. Я знаю, что может показаться, что мы не особо экономим много печатания с помощью этого подхода, но подход, основанный на данных, как этот, имеет тенденцию окупаться - выполнение немного большего набора текста до того, как произойдут действительно интересные вещи, может сэкономить нам много головной боли ивведите позже.
Посмотрите на первое Choice
первого Query
, в частности, свойство character_types
:
character_types=[
(CharacterType.Barbarian, 2),
(CharacterType.Cleric, 1)
]
Это отличается от того, что мы делалидо. character_types
теперь список кортежей. Есть два кортежа. Первый элемент каждого кортежа является типом символа, на который влияют, а второй элемент является целым числом, которое является значением, на которое увеличивается счетчик для этого типа символа. В этом случае выбор этого варианта увеличит количество варваров на 2, а число клериков на 1. Таким образом мы можем поддерживать увеличение нескольких счетчиков на разные значения.
Так как вы упомянули, что в конце хотите получить пул из 50 потенциальных запросов, я бы подумал о том, чтобы поместить все ваши вопросы в отдельный текстовый файл (что-то вроде формата, совместимого с JSON, может быть особенно симпатичным), просто чтобы вы ненаписать этот огромный список прямо в вашем исходном коде. Ваш код будет просто читать файл во время выполнения и загружать вопросы.
Вот остальная часть кода. Опять же, некоторые вещи изменились - количество случайных запросов установлено на один, потому что в моем пуле возможных запросов только два. Я добавил комментарии, где я думаю, что они могут быть полезны, и я использовал операторы if вместо исключений для проверки ввода. Обновление счетчика также изменилось, поскольку мы больше не используем объект collections.Counter
, а также потому, что мы хотели бы поддерживать обновление нескольких счетчиков типов символов одновременно. Последний шаг определения того, какой счетчик был самым высоким, также изменился. Кроме того, не забудьте выполнить from random import sample
.
number_of_random_queries = 1
for current_query in sample(queries, k=number_of_random_queries):
# Print the current query's text
print(current_query.text)
# Print all the available options for the current query
for choice_index, choice in enumerate(current_query.choices, start=1):
print(f" {choice_index}.) {choice.text}")
# Get user input
while True:
user_input = input("Enter your choice: ")
if not user_input.isdigit():
# User input is not a number, ask them for input again
continue
user_choice_index = int(user_input)-1
if not (0 <= user_choice_index <= len(current_query.choices)-1):
# User input was a number, but it was an invalid index, ask them for input again
continue
user_choice = current_query.choices[user_choice_index]
# Terminate the input loop
break
# We now have the user's selection (Choice object). Now update the appropriate counter(s)
for character_type, increment_value in user_choice.character_types:
old_counter_value = counter[character_type]
new_counter_value = old_counter_value + increment_value
counter.update({character_type: new_counter_value})
print("-" * 20)
# The quiz for-loop is now over
# Determine which counter has the highest value
character_type, value = max(counter.items(), key=lambda pair: pair[1])
print(f"You have the heart of a {character_type.name} with {value} point(s)!")
Подводя итог: даже если кажется, что мы не сэкономили много времени на наборе текста, мы на самом деле имеем. Теперь вы можете добавлять столько уникальных типов символов в перечисление CharacterType
, и вам не нужно будет вносить какие-либо другие изменения в остальную часть вашего кода - за возможным исключением списка запросов, поскольку вы можете захотеть добавитьбольше вопросов о новых типах символов.
ONE MORE EDIT - Я пытался избежать слишком плотного текста - извините за это. У вас есть словарь с одним счетчиком, ключи которого являются перечислениями, а связанные значения - это словари, которые выполняют фактический подсчет.