2
votes

So I'm trying to create a Deck of cards in Python. I am at the point where I'm trying to create the method to sort the deck of cards back into order.

2 of clubs, 3 of clubs, ...Ace of clubs

2 of Diamonds, 3 of Diamonds, ...Ace of Diamonds

2 of Hearts, 3 of Hearts, ...Ace of Hearts

2 of Spades, 3 of Spades, ...Ace of Spades

I have part of it already implemented but the sorting part is only KIND OF working. It successfully sorts them in the suit, but the rank part is kind of messed up. 10 comes before 2 and the Picture Cards are incorrect. My guess is that my __lt__ and __eq__ functions aren't processing the ranks correctly (esp the Picture Cards)

CODE:

import random
# deck = ["2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "10C", "JC", "QC", "KC", "AC", "2D", "3D", "4D", "5D", "6D",
#        "7D", "8D", "9D", "10D", "JD", "QD", "KD", "AD", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "10H", "JH",
#        "QH", "KH", "AH", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "10S", "JS", "QS", "KS", "AS"]
from functools import total_ordering

graveyard = []


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

    def __str__(self):
        return '%s of %s' % (self.rank,
                             self.suit)

    def __repr__(self): return str(self)

    def __lt__(self, other):
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return t1 < t2

    def __gt__(self, other):
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return t1 > t2

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




class Deck(object):
    def __init__(self):
        """
        Idea for this found here.
        https://stackguides.com/questions/8511745/sorting-a-hand-of-cards-accoring-to-rank-and-suit-in-python

        I could use this to ALWAYS have a shuffled deck at the beginning or to just start with a 'clean' deck as above...
        :return:
        """
        self.rank = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
        self.suit = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
        self.deck = [Card(r, s) for r in self.rank for s in self.suit]
        random.shuffle(self.deck)

    def __getitem__(self, item):
        return self.deck[item]

    def deal(self):
        """
        Return a card from the deck.
        :return:
        """
        topCard = self.deck.pop(0)
        graveyard.append(topCard)
        print(topCard)

    def shuffle(self):
        """
        Shuffle the deck
        :return:
        """
        self.deck.extend(graveyard)
        random.shuffle(self.deck)
        self.fan()

    def fan(self):
        """
        Print out the deck
        :return:
        """
        for card in self.deck:
            print(card)

    def order(self):
        return self.deck.sort()

    def printGraveyard(self):
        for dead in graveyard:
            print(dead)


d = Deck()

d.order()

d.fan()

OUTPUT:

10 of Clubs
2 of Clubs
3 of Clubs
4 of Clubs
5 of Clubs
6 of Clubs
7 of Clubs
8 of Clubs
9 of Clubs
Ace of Clubs
Jack of Clubs
King of Clubs
Queen of Clubs
10 of Diamonds
2 of Diamonds
3 of Diamonds
4 of Diamonds
5 of Diamonds
6 of Diamonds
7 of Diamonds
8 of Diamonds
9 of Diamonds
Ace of Diamonds
Jack of Diamonds
King of Diamonds
Queen of Diamonds
10 of Hearts
2 of Hearts
3 of Hearts
4 of Hearts
5 of Hearts
6 of Hearts
7 of Hearts
8 of Hearts
9 of Hearts
Ace of Hearts
Jack of Hearts
King of Hearts
Queen of Hearts
10 of Spades
2 of Spades
3 of Spades
4 of Spades
5 of Spades
6 of Spades
7 of Spades
8 of Spades
9 of Spades
Ace of Spades
Jack of Spades
King of Spades
Queen of Spades
2
Don't use random.shuffle to shuffle a deck. Use a dedicated card-deck-shuffling like Fischer-Yates. Note the docs: Note that for even rather small len(x), the total number of permutations of x is larger than the period of most random number generators; this implies that most permutations of a long sequence can never be generated. Fischer-Yates in python - Daenyth
As for 10-cards showing up before 2-cards, you're sorting by string. if they were int type, you'd have 2 showing up first. - economy
The problem is that the rank is a string and therefore you're ordering correctly in alphabetical order. - SimoV8
Your card values are being sorted alphabetically. 10 precedes 2 because 1 precedes 2. A-J-K-Q is also alphabetical order. - phoog
you may want to represent ace, king, queen, jack as int as well (14,13,12,11 respectively), for convenience. - economy

2 Answers

2
votes

You're correct, it's the comparision of the rank that compares the string, that is "10"<"2" and "Ace"<"Queen" etc.

What you could do is to for example write a suitable string to numeric rank function.

def num_rank(rank):
    if rank[0] == "A":
         return 14
    if rank[0] == "J":
         return 11
    if rank[0] == "Q":
         return 12
    if rank[0] == "K":
         return 13
    return int(rank)

then you use this to rewrite the comparison:

def __lt__(self, other):
    t1 = self.suit, num_rank(self.rank)
    t2 = other.suit, num_rank(other.rank)
    return t1 < t2
0
votes

I didn't test it, but I think you could create a class for the rank, like:

class CardRank():
    def __init__(self, name, value):
        self.name = name # e.g. Ace
        self.value = value # e.g. 12

in Deck:

def __init__(self):
    self.rank = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
    self.suit = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
    self.deck = [Card(CardRank(r, self.rank.index(r)), s) for r in self.rank for s in self.suit]

So in class Card you could do something like:

 def __gt__(self, other):
     return self.rank.value > other.rank.value