A good way to make classes for more complex playing card types than those found in a standard deck?
Asked Answered
C

4

9

I am extremely new to object-oriented programming, and am trying to begin learning in python by making a simple card game (as seems to be traditional!). I have done the following example which works fine, and teaches me about making multiple instances of the PlayingCard() class to create an instance of the Deck() class:

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()

I want to make something now with more complex cards, not just a standard 52 deck (which has nicely incrementing values). The deck I have in mind is the [Monopoly card game][1]:

enter image description here

There are 3 fundamental types of cards - ACTION cards, PROPERTY cards, and MONEY cards. The action cards perform different actions, the property cards belong to different colour sets, and the money cards can have different values. Additionally, the property cards can be "wildcards", and can be used as part of one of two sets. Finally, every card also has an equivalent money value (indicated in the top corner of each card). In the rent action cards, the card can only apply to the colour property indicated on the card.

My question is just generally how to handle a situation like this, and what would be a nice way to include these different cards in a class-based python program? Should I keep my single PlayingCard() class, and just have many inputs, such as PlayingCard(type="PROPERTY", value="3M"). Or would it be better to create seperate classes such as ActionPlayingCard(), PropertyPlayingCard(), etc ? Or is there a better way? As I say, I am at the beginning of my learning here, and how to organise these types of situations in terms of the higher level design.

Many thanks.

Chaffer answered 18/2, 2020 at 21:0 Comment(2)
If you find that the different types of cards share some features, you might use inheritance, or even an Abstract class. You can read about and use the Factory Pattern so you can pass the type of the card and the appropriate class will be usedNarration
@Narration Thank you for pointing that out - I have read a little about the factory pattern you mentioned. As I understand it, this is most useful when you don't know beforehand which classes of objects you will need to create (perhaps only knowing at runtime). However, since in this case I know what the whole deck should look like (how many of each type of card, what they each do, etc), is the factory pattern applicable here?Chaffer
G
3

When you are approaching a problem with OOP, you usually want to model behavior and properties in a reusable way, i.e., you should think of abstractions and organize your class hierarchy based on that.

I would write something like the following:

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)


Due to Python being a dynamically typed language, OOP is a little harder to justify in my opinion, since we can just rely on duck typing and dynamic binding, the way you organize your hierarchy is less important.

If I were to model this problem in C# for example, I would without a doubt use the hierarchy showed above, because I could rely on polymorphism to represent different types and guide the flow of my logic based on what type of card is being analyzed.

A couple of final remarks:

  1. Python has very powerful builtin types, but most of the time using new custom types that build on them makes your life easier.
  2. You don't have to inherit from object since types in Python 3 (which is the only one maintained as of today) inherit from object by default.

But, at the end of the day, there isn't a perfect answer, the best way would be to try both of the approaches and see what you're more comfortable with.

Geodesy answered 1/3, 2020 at 2:45 Comment(0)
B
7

These are what we call "design decisions". Often the "correct" way is a matter of opinion. As a beginner, I think it would be instructive to try both implementations to see how they work. There will be trade offs no matter which one you pick. You have to decide which of those trade offs are most important. Making these kinds of decisions will be informed as you gain more experience.

Briannebriano answered 18/2, 2020 at 21:5 Comment(1)
Yes, thanks, I am doing exactly as you say - just looking for guidance from people who are experienced enough to instinctively know a good approach, and hopefully understand why.Chaffer
G
3

When you are approaching a problem with OOP, you usually want to model behavior and properties in a reusable way, i.e., you should think of abstractions and organize your class hierarchy based on that.

I would write something like the following:

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)


Due to Python being a dynamically typed language, OOP is a little harder to justify in my opinion, since we can just rely on duck typing and dynamic binding, the way you organize your hierarchy is less important.

If I were to model this problem in C# for example, I would without a doubt use the hierarchy showed above, because I could rely on polymorphism to represent different types and guide the flow of my logic based on what type of card is being analyzed.

A couple of final remarks:

  1. Python has very powerful builtin types, but most of the time using new custom types that build on them makes your life easier.
  2. You don't have to inherit from object since types in Python 3 (which is the only one maintained as of today) inherit from object by default.

But, at the end of the day, there isn't a perfect answer, the best way would be to try both of the approaches and see what you're more comfortable with.

Geodesy answered 1/3, 2020 at 2:45 Comment(0)
S
2

You could use inheritance. This is where you create a main class then have sub-classes which still contain functions and values from the mother class however can also have extra values and functions for that specific class.

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

Now the iPhone class has the same functions as the Apple class and its own function. If you wish to learn more about inheritance I recommend doing some research.

Selfdenial answered 25/2, 2020 at 22:33 Comment(1)
@Chaffer As each card has a value, you are able to trade them and can play them, you could create functions/procedures in a class to handle these events and then have extra attributes and functions for certain cards in a sub class.Selfdenial
S
-1

For monopoly, i would design the game landings point of view. Not cards. Cards simply represent the landings for real world.

Suffrage answered 27/2, 2020 at 23:18 Comment(1)
Please elaborate.Chaffer

© 2022 - 2024 — McMap. All rights reserved.