Хороший способ создать классы для более сложных типов игральных карт, чем те, что есть в стандартной колоде?

Я новичок в объектно-ориентированном программировании и пытаюсь начать изучение python с создания простой карточной игры (что кажется традиционным!). Я сделал следующий пример, который отлично работает и учит меня создавать несколько экземпляров класса PlayingCard() для создания экземпляра класса Deck():

class PlayingCard(object):
    def __init__(self, suit, val):
        self.suit = suit
        self.value = val

    def print_card(self):
        print("{} of {}".format(self.value, self.suit))

class Deck(object):
    def __init__(self):
        self.playingcards = []
        self.build()

    def build(self):
        for s in ["Spades", "Clubs", "Diamonds", "Hearts"]:
            for v in range(1,14):
                self.playingcards.append(PlayingCard(s,v))

deck = Deck()



Теперь я хочу сделать что-нибудь с более сложными картами, а не только со стандартной колодой из 52 карт (в которой хорошо увеличиваются значения). Колода, которую я имею в виду, — это карточная игра «Монополия»:

введите здесь описание изображения

Существует 3 основных типа карт — карты ДЕЙСТВИЯ, карты СОБСТВЕННОСТИ и карты ДЕНЬГИ. Карты действий выполняют разные действия, карты свойств относятся к разным наборам цветов, а карты денег могут иметь разное значение. Кроме того, карты свойств могут быть «подстановочными знаками» и могут использоваться как часть одного из двух наборов. Наконец, каждая карта также имеет эквивалентную денежную стоимость (указана в верхнем углу каждой карты). В картах действий по аренде карта может применяться только к свойству цвета, указанному на карте.

Мой вопрос заключается в том, как справиться с такой ситуацией, и что было бы хорошим способом включить эти разные карты в программу Python на основе классов? Должен ли я сохранить свой единственный класс PlayingCard() и просто иметь много входных данных, таких как PlayingCard(type="PROPERTY", value="3M"). Или было бы лучше создать отдельные классы, такие как ActionPlayingCard(), PropertyPlayingCard() и т. д.? Или есть лучший способ? Как я уже сказал, я нахожусь в начале своего обучения здесь, и как организовать такие типы ситуаций с точки зрения дизайна более высокого уровня.

Большое спасибо.


person teeeeee    schedule 18.02.2020    source источник
comment
Если вы обнаружите, что различные типы карточек имеют некоторые общие черты, вы можете использовать наследование или даже абстрактный класс. Вы можете прочитать и использовать Factory Pattern, чтобы вы могли передать тип карты, и будет использоваться соответствующий класс.   -  person Tomerikoo    schedule 19.02.2020
comment
@Tomerikoo Спасибо, что указали на это - я немного прочитал о фабричном шаблоне, который вы упомянули. Насколько я понимаю, это наиболее полезно, когда вы заранее не знаете, какие классы объектов вам нужно будет создать (возможно, зная только во время выполнения). Однако, поскольку в этом случае я знаю, как должна выглядеть вся колода (сколько карт каждого типа, что каждая из них делает и т. д.), применим ли здесь шаблон фабрики?   -  person teeeeee    schedule 19.02.2020


Ответы (4)


Когда вы подходите к проблеме с ООП, вы обычно хотите моделировать поведение и свойства повторно используемым способом, т. е. вы должны думать об абстракциях и организовывать иерархию классов на их основе.

Я бы написал что-то вроде следующего:

class Card:
    def __init__(self, money_value=0):
        self.money_value = money_value

class ActionCard(Card):
    def __init__(self, action, money_value=0):
        super().__init__(money_value=money_value)

        self.action = action

class RentActionCard(ActionCard):
    def __init__(self, action, color, money_value=0):
        super().__init__(action, money_value=money_value)

        self.color = color

    def apply(self, property_card):
        if property_card.color != self.color:
            # Don't apply
        # Apply

class PropertyCard(Card):
    def __init__(self, color, money_value=0):
        super().__init__(money_value=money_value)

        self.color = color

class WildcardPropertyCard(PropertyCard):
    def __init__(self, color, money_value=0):
        super().__init__(color, money_value=money_value)

class MoneyCard(Card):
    def __init__(self, money_value=0):
        super().__init__(money_value=money_value)


Поскольку Python является языком с динамической типизацией, ООП, на мой взгляд, немного сложнее обосновать, поскольку мы можем просто полагаться на утиную типизацию и динамическая привязка, способ организации иерархии менее важен.

Если бы мне нужно было смоделировать эту проблему, например, на C#, я бы, без сомнения, использовал иерархию, показанную выше, потому что я мог полагаться на полиморфизм для представления различных типов и направления поток моей логики, основанный на том, какой тип карты анализируется.

Пара заключительных замечаний:

  1. Python имеет очень мощные встроенные типы, но в большинстве случаев использование новых пользовательских типов, основанных на них, упрощает вашу жизнь.
  2. Вам не нужно наследовать от object, так как типы в Python 3 (единственный поддерживаемый на сегодняшний день) наследуются от object по умолчанию.

Но, в конце концов, идеального ответа не существует, лучше всего было бы попробовать оба подхода и посмотреть, что вам больше нравится.

person alexfertel    schedule 01.03.2020

Это то, что мы называем «дизайнерскими решениями». Часто «правильный» путь зависит от мнения. Как новичок, я думаю, было бы поучительно попробовать обе реализации, чтобы увидеть, как они работают. Независимо от того, какой из них вы выберете, будут компромиссы. Вы должны решить, какие из этих компромиссов являются наиболее важными. Принятие такого рода решений будет осознанным по мере того, как вы приобретете больше опыта.

person Code-Apprentice    schedule 18.02.2020
comment
Да, спасибо, я делаю именно так, как вы говорите - просто ищу руководства от людей, которые достаточно опытны, чтобы инстинктивно знать хороший подход и, надеюсь, понимают, почему. - person teeeeee; 19.02.2020

Вы можете использовать наследование. Здесь вы создаете основной класс, а затем имеете подклассы, которые по-прежнему содержат функции и значения из материнского класса, но также могут иметь дополнительные значения и функции для этого конкретного класса.

class Apple:
    def __init__(self, yearMade):
        pass

    def ring(self):
        print('ring ring')

class iPhone(Apple):
    def __init__(self, number)
        number = number

    def func():
        pass

Теперь класс iPhone имеет те же функции, что и класс Apple, и собственную функцию. Если вы хотите узнать больше о наследовании, я рекомендую провести небольшое исследование.

person MoriartyPy    schedule 25.02.2020
comment
@teeeeeee Поскольку каждая карта имеет значение, вы можете обменивать их и играть ими, вы можете создавать функции/процедуры в классе для обработки этих событий, а затем иметь дополнительные атрибуты и функции для определенных карт в подклассе. - person MoriartyPy; 26.02.2020

Для монополии я бы разработал точку зрения на игровые приземления. Не карты. Карты просто представляют приземления для реального мира.

person Ali Berat Çetin    schedule 27.02.2020
comment
Пожалуйста, дополните. - person teeeeee; 28.02.2020