@DrMorro

Как реализовать колоду карт через классы на python?

Я только начал знакомиться с классами и хочу решить такую задачу: Создать колоду карт, для колоды создать несколько карт с разными атрибутами - все через классы. У колоды должны быть функции: вывести свое содержимое сортируя по группам, по хп, атаке и так далее. Потом можно будет создать еще одну колоду с другими картами и еще что-нибудь придумать. У меня пока только так получилось. Мне кажется, что это неоптимальный вариант, поскольку в том направлении, в котором я начал есть и еще будет много повторения кода и т. п. Я хотел бы понять как устроены классы и как их лучше реализовывать. Мне говорили, что подклассы опциональных и их всегда можно реализовать как-то с наследованием - только я не понимаю как такое сделать наследованием. Поэтому будут ли у вас какие-то советы как лучше такой концепт реализовать?

class Deck():
    num_of_cards = 0
    group_name = { "Intelegence" : [], "Strength" : [], "Agility" : [] }
    
    def __init__(self, deck_name = "Колода", date = None):
        self.deck_name = deck_name
        self.date = date
        
    def print_by_group(self):
        for key in self.group_name:
            print(f"Группа {key}:")
            for name in self.group_name[key]:
                print(f"  {name}") 
                
    class Card():
        
        def __init__(self, name, phys_damage, hp):
            self.name = name
            self.phys_damage = phys_damage
            self.hp = hp
            
            Deck.num_of_cards += 1
    
    class CardInt(Card):
        group = "Intelegence"
        def __init__(self, name, phys_damage, hp, mage_damage):
            super().__init__(name, phys_damage, hp)
            self.mage_damage = mage_damage
            Deck.group_name[self.group].append(self.name) 
            
    class CardStr(Card):
        group = "Strength"
        def __init__(self, name, phys_damage, hp, add_hp):
            super().__init__(name, phys_damage, hp)
            
            self.hp = hp + add_hp
            Deck.group_name[self.group].append(self.name)
            
    class CardAgl(Card):
        group = "Agility"
        def __init__(self, name, phys_damage, hp, armor):
            super().__init__(name, phys_damage, hp)
            
            self.armor = armor
            Deck.group_name[self.group].append(self.name)

my_deck = Deck("Моя колода", "24.05.2022")

# name, phys_damage, hp

techies = my_deck.CardInt("Течис", 32, 600, 100)
earth_spirit = my_deck.CardInt("Земеля", 40, 600, 60)
pudge = my_deck.CardStr("Рудге", 80, 600, 150)
antimage = my_deck.CardAgl("Крип", 60, 600, 10)

my_deck.print_by_group()
  • Вопрос задан
  • 980 просмотров
Решения вопроса 1
trapwalker
@trapwalker Куратор тега Python
Программист, энтузиаст
Это у вас вышел совсем плохой варант.
Классы ради классов. Вообще в первую очередь нужно решать задачу, а не придумывать как бы применить тут наследование или полморфизм.
В вашем случае лишне сложности ни к чему. Они только добавляют проблем, но ни одной не решают.

Сделайте карту универсальной, сделайте ее на основе typing.NnamedTuple.
Пусть у каждой карты будет весь набор атрбутов, но только один какой-то будет ненулевым. Не нужно тут наследования, ведь у вас не используется полиморфизм.

В вашей реализации не получится сделать две колоды или больше. Колода у вас - это синглтон получается. Такое себе решение.
На сколько я понмаю предметную область, в колоде важна обычно не столько группировка карт, сколько их порядок. Так и храните карты в инкапсулированном списке.
Порядок карт не атрибут карт, он должен храниться в колоде.
Вполне можно держать в инстансе колоды в качестве индекса для быстрой группировки специальный словарь, но это сомнительная оптимизация на таком этапе. Сколько будет в вашей колоде? Является ли вообще бутылочным гордышком проблема группировки? Почему нельзя ее решать на даном этапе просто понятной фильтрацией?
В общем старайтесь применять новые изученные фичи аккуратно и тоько там, где это на самом деле решает проблемы.
Интересное упражнение - придумывать пример, где изученная концепция дейтсивтельно к месту. Нследование - это не про ваш вот этот пример с картами. По крайней мере на текущем уровне постановки задачи

Вот пример:
import datetime
import itertools
from typing import NamedTuple


class Card(NamedTuple):
    name: str = None
    phys_damage: int = 0
    hp: int = 0
    mage_damage: int = 0
    armor: int = 0
    add_hp: int = 0

    def __repr__(self):
        """repr у нас будет показывать только аргументы, которые отличаются от умолчательных"""
        defaults = type(self)._field_defaults
        params = ', '.join(
            f'{k}={v!r}' 
            for k, v in self._asdict().items() 
            if k in defaults and v != defaults[k]
        )
        return f'{type(self).__name__}({params})'

    def __str__(self):
        return f'<{self.name}#{id(self)}>'
        
    @property
    def total_hp(self):
        return self.hp + self.add_hp

    @property
    def group(self):
        return max(
            (self.mage_damage, 'Intelegence'),
            (self.add_hp, 'Strength'),
            (self.armor, 'Agility'),
        )[1]
    

class Deck():
    def __init__(self, deck_name="Колода", date=None, cards=None):
        self.deck_name = deck_name
        self.date = date or datetime.now()
        self.cards = cards or []

    def __iter__(self):
        return iter(self.cards)
    
    def by_group(self):
        return itertools.groupby(self, lambda card: card.group)

my_deck = Deck("Моя колода", "24.05.2022", [
    Card("Течис", phys_damage=32, hp=600, mage_damage=100),
    Card("Земеля", phys_damage=40, hp=600, mage_damage=60),
    Card("Рудге", phys_damage=80, hp=600, add_hp=150),
    Card("Крип", phys_damage=60, hp=600, armor=10),
])


for group, cards in my_deck.by_group():
    print(f'# {group}:')
    for card in cards:
        print(f'\t{card!r}')
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
mayton2019
@mayton2019
Bigdata Engineer
Для проверки свойств карты я-бы использовал представление кода карты как битовое число. Биткарта.
К примеру - карты делятся на красные и черные масти. Это один бит. Далее цвета делятся на трефы, пики
и т.п. Еще один бит. Тоесть двумя битами от 00 до 11 мы полностью кодируем масть. И ранг карты в игре
(он может менятся в зависимости от вида игры) можно закодировать еще четырьмя битами. Итого шесть бит.
Плюс еще отдельно остается джокер как карта универсал. Для нее можно ввести один бит масти как признак
того что масть игнорируется например. Итого 7 бит. Такой код карты удобно сортировать например или представлять различные проверки условий.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы