# Efficient implementation of comparison operators for cards in a poker simulation

Just as a learning exercise, I am implementing a poker game simulator. I am purposefully not searching for ready made tutorials showing how to do this, relying mostly on the python docs - that way I hope to learn in a more memorable, albeit slower, way. I'm looking for advice on how to code the comparison between cards, so that e.g. Five beats Four.

My current implementation works, but I feel it is a little hacky and very inefficient in one particular detail and there must be a better way. In the Card class init, I am setting an attribute order which is just a list of the members of the CardValue enum, in the order they are declared.

While I could just use enum.auto(), or manually set the values as integers and hence simply use those for comparisons (still with eq etc), that would lose me the ability to make use of the enum values to form the human readable card value names.

Is there a better way to go about this than the below code? I feel like the comparison operators should be in the enum itself, but so far have not worked out how to get that to work with my meaningful values for the members of the enum.

from enum import Enum
from functools import total_ordering

class CardValue(Enum):
TWO = "2"
THREE = "3"
...
...
KING = "King"
ACE = "Ace"

@total_ordering
class Card:
def __init__(self, value, suit):
self.value = value
self.suit = suit
self.order = [en for en in CardValue] ### This seems wrong and very inefficient!

def __eq__(self, other):
if self.__class__ is other.__class__:
return self.order.index(self.value) == self.order.index(other.value)

def __lt__(self, other):
if self.__class__ is other.__class__:
return self.order.index(self.value) < self.order.index(other.value)

• type(self) is type(other) =) Commented Sep 28, 2020 at 11:13
• Actually, the comparison of cards varies across different card games. Therefore it might be more logical to separate cards from their ordering in different classes.
– GZ0
Commented Sep 29, 2020 at 3:54

You can define an __init__ method for an Enum. It gets passed the value of each enum member. Use it to define an order attribute for each enum member. For example, the Enum member Ace = "Ace" calls __init__ with value = "Ace", and CardValue.ACE.order will be set to '__23456789TJQKA'.index(value[0]), which is 14.

Also, define __eq__ and __lt__ for comparing CardValues.

I changed the Enum to CardRank, because I found it clearer.

@total_ordering
class CardRank(Enum):
TWO = "2"
THREE = "3"
KING = "King"
ACE = "Ace"

def __init__(self, value):
# the '__' are so "2" has an order of 2 and so on.
self.order = '__23456789TJQKA'.index(value[0])

def __eq__(self, other):
if self.__class__ is other.__class__:
return self.order == other.order
raise NotImplemented

def __lt__(self, other):
if self.__class__ is other.__class__:
return self.order < other.order
raise NotImplemented

@total_ordering
class Card:
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit

def __eq__(self, other):
return self.rank == other.rank

def __lt__(self, other):
return self.rank < other.rank


Try to choose data types/structures/values that are going to make programming the core of your application easier. If needed, provide methods to translate internal values for the user. In this case, using an IntEnum would let you compare CardRanks without needing extra code (__lt__, total_ordering, etc). Just add a __str__() method to get the text for the rank.

class CardRank(IntEnum):
Two = 2
Three = 3
Four = 4
Five = 5
Six = 6
Seven = 7
Eight = 8
Nine = 9
Ten = 10
Jack = 11
Queen = 12
King = 13
Ace = 14

def __str__(self):
if self.value <= 10:
return str(self.value)
else:
return ['Jack', 'Queen', 'King', 'Ace'][self.value - 11]


Test it:

>>> print(CardRank.King, str(CardRank.Two), CardRank.Four < CardRank.Ten)
King 2 True

• ['Jack', 'Queen', 'King', 'Ace'][self.value - 11] can be replaced with self.name.
– GZ0
Commented Sep 28, 2020 at 22:54
• Using IntEnum also means your enum members can be compared to completely different integer-based enums, which is almost always a bad idea. Commented Sep 29, 2020 at 21:16

There are a couple choices, but the best is to record the value's index on Card creation:

self.order = value._member_names_.index(value.name)


Then your comparison can use self.order directly:

return self.order < other.order


_member_names_ is a supported attribute and won't be going away.

• Nice answer. To expand on the answer; if, for some reason, value is mutable then you'd want to make value a property. Putting the assignment to order in the setter instead of the __init__. Commented Sep 28, 2020 at 13:43
• Nope, sorry -- I forgot my answer was not in the Enum, and thought you were speaking about the Enum. You're first comment is perfectly correct; deleting my erroneous comment. Commented Sep 30, 2020 at 0:46

In the end I have decided to use IntEnum. Also, a little trick to create it I found on other posts (fine for now, I can refactor as need arises if I need something more complex later).

from enum import IntEnum
cards = ['LowAce', 'Two', 'King', 'Ace']
CardRank = IntEnum('CardRank', cards)


Check:

_ = [print(card.name, card.value) for card in CardRank]
CardRank.Two < CardRank.King


Result:

LowAce 1
Two 2
King 3
Ace 4
True