I am trying to make a text based object oriented card game. Two players draw a card each from a deck of cards, and the player with the strongest card wins. I have four class
Here is a sketch of an approach. You can easily combine this with your own approach, the biggest change being for the Card
class. Here, I've used namedtuple
to make a Card
class, but your current class can simply wrap a tuple
value:
import enum
from functools import total_ordering
from collections import namedtuple
@total_ordering
class OrderedEnum(enum.Enum):
def __lt__(self, other):
if isinstance(other, type(self)):
return self.value < other.value
return NotImplemented
Rank = OrderedEnum('Rank', ['one', 'two', 'three', 'four', 'five', 'six',
'seven', 'eight', 'nine', 'jack', 'queen','king', 'ace'])
Suit = OrderedEnum('Suit', ['clubs', 'diamonds', 'hearts', 'spades'])
Card = namedtuple('Card', ['rank', 'suit'])
c1 = Card(Rank.four, Suit.clubs)
c2 = Card(Rank.four, Suit.spades)
c3 = Card(Rank.ace, Suit.diamonds)
Now, in action:
>>> c1
Card(rank=<Rank.four: 4>, suit=<Suit.clubs: 1>)
>>> c2
Card(rank=<Rank.four: 4>, suit=<Suit.spades: 4>)
>>> c1 < c2
True
>>> c1 > c3
False
Tuple sorting is lexicographic! Nice!
>>> hand = [c2, c1, c3]
>>> hand
[Card(rank=<Rank.four: 4>, suit=<Suit.spades: 4>), Card(rank=<Rank.four: 4>, suit=<Suit.clubs: 1>), Card(rank=<Rank.ace: 13>, suit=<Suit.diamonds: 2>)]
>>> sorted(hand)
[Card(rank=<Rank.four: 4>, suit=<Suit.clubs: 1>), Card(rank=<Rank.four: 4>, suit=<Suit.spades: 4>), Card(rank=<Rank.ace: 13>, suit=<Suit.diamonds: 2>)]
>>>
Note, I've used the total_ordering
decorator, which is simply a shortcut, and indeed, I think it might be better to simply do the whole class by hand. Here's a recipe.
EDIT
So, to elaborate, here is how I would implement your Card
and Deck
classes. Notice how much more readable your code becomes when you use the enum
and namedtuple
.
import enum
from functools import total_ordering
from collections import namedtuple
from random import shuffle
@total_ordering
class OrderedEnum(enum.Enum):
def __lt__(self, other):
if isinstance(other, type(self)):
return self.value < other.value
return NotImplemented
Rank = OrderedEnum('Rank', ['one', 'two', 'three', 'four', 'five', 'six',
'seven', 'eight', 'nine', 'jack', 'queen','king', 'ace'])
Suit = OrderedEnum('Suit', ['clubs', 'diamonds', 'hearts', 'spades'])
CardValue = namedtuple('CardValue', ['rank', 'suit'])
@total_ordering
class Card(object):
def __init__(self, rank, suit):
self.value = CardValue(rank, suit)
def __repr__(self):
return "Card({:s}, {:s})".format(self.value.rank, self.value.suit)
def __lt__(self, other):
if isinstance(other, __class__):
return self.value < other.value
return NotImplemented
def __eq__(self, other):
if isinstance(other, __class__):
return self.value == other.value
return NotImplemented
class Deck(object):
def __init__(self):
self.cards = []
for rank in Rank:
for suit in Suit:
self.cards.append(Card(rank, suit))
shuffle(self.cards)
Now, in action:
>>> deck = Deck()
>>> c1 = deck.cards.pop()
>>> c2 = deck.cards.pop()
>>> c1
Card(Rank.queen, Suit.hearts)
>>> c2
Card(Rank.king, Suit.clubs)
>>> c1 == c2
False
>>> c1 > c2
False
>>> c1 < c2
True
>>> c1.value
CardValue(rank=<Rank.queen: 11>, suit=<Suit.hearts: 3>)
>>> c2.value
CardValue(rank=<Rank.king: 12>, suit=<Suit.clubs: 1>)
Also, notice that __repr__
should try to represent the object, if you want a pretty message, use __str__
. See this question
An enumeration (https://docs.python.org/3.5/library/enum.html) would be appropriate. For rich comparisons (and ordering) you should also consider implementing some or all of the __eq__
, __ne__
, __lt__
, __le__
, __gt__
, __ge__
methods (from https://docs.python.org/3/reference/datamodel.html) on the Card
class.
You could implement operators for your Card class
__gt__(), __lt__(),
etc ...
than you can use a number of standard library functions like max() to determine the higher value card or deck and could even use sort() to simply sort a 'hand' for example a list [Card, Card, ...].
I would recommend you store the value of each card as an int, so you can compare them, and not to use strings such as "King" or "Ace". You can do this and change repr() to print a human readable version using those strings.
The Card class could look like this:
class Card(object):
suits = ["Clubs", "Diamonds", "Hearts", "Spades"] #list of suits
values = [None, "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"] #list of card values
def __init__(self, suit= 0, value= 2):
"""
Initializes card
:param suit: Suit of card int value 0-3
:param value: Value of card int value 0-13
"""
self.suit = suit
self.value = value
def __str__(self):
"""
Returns a readable format of the card
"""
return "%s of %s" %(Card.values[self.value],
Card.suits[self.suit])
Notice how the value of the card is stored as an int all the time.
In the Game class you could have a function that compares two cards, I'm not sure how you want to do this but it could look something like this:
def compare(card1, card2):
"""
Compares the value of two cards and returns the greater of the two
:param card1: A card object
:param card2: A second card object
:return: The higher value card, if tie returns 0
"""
if card1.value > card2.value:
return card1
elif card2.value == card1.value:
return 0
else:
return card2