Search code examples
pythonlistobjectplaying-cardscmp

Python: 'object in list' checks and '__cmp__' overflow


this is my first time at stack overflow so I'm sorry if the format doesn't fit quite right with the site. I just recently started learning programming, almost 2 weeks have passed since. I'm learning python from http://openbookproject.net/thinkcs/python/english3e/index.html and everything had been quite nice until now, where I just got stuck for hours. I googled a lot but couldn't find a proper solution to my problem so here I am.

I'm trying to get the OldMaidGame() run without problems as explained on CH17. http://openbookproject.net/thinkcs/python/english3e/ch17.html - Most of the code also comes from the previous chapter.

What I've found out is I can't get the Deck.remove, Hand.remove_matches, or any other kind of remove function to work. After some debugging I found out that the problem occurs when the program checks if the given card is present in the deck/hand/etc. It can't ever make a match. Then after some looking back on the chapter, (in ch16), I found out that 'if card in deck/hand/etc: remove(card)' etc looks up the .cmp() of the object to determine if the card actually exists in the deck/hand/etc. This is my version of the cmp after doing the additions for 'ace's on the given code from the e-book.

def __cmp__(self, other):
    """ Compares cards, returns 1 if greater, -1 if lesser, 0 if equal """
    # check the suits
    if self.suit > other.suit: return 1
    if self.suit < other.suit: return -1
    # suits are the same... check ranks
    # check for aces first.
    if self.rank == 1 and other.rank == 1: return 0
    if self.rank == 1 and other.rank != 1: return 1
    if self.rank != 1 and other.rank == 1: return -1
    # check for non-aces.
    if self.rank > other.rank: return 1
    if self.rank < other.rank: return -1
    # ranks are the same... it's a tie
    return 0

The cmp itself seems fine afaik, ofc I could use some tips on how to make it better (like with ace checks). So I have no idea why the card in deck/hand checks always return false. This was the given remove function:

class Deck:
    ...
    def remove(self, card):
        if card in self.cards:
            self.cards.remove(card)
            return True
        else:
            return False

Desperately trying to get it to work, I came up with this:

class Deck:
    ...
    def remove(self, card):
        """ Removes the card from the deck, returns true if successful """
        for lol in self.cards:
            if lol.__cmp__(card) == 0:
                self.cards.remove(lol)
                return True
        return False

Seemed to work fine, until I moved on to the other non-working remove functions:

class OldMaidHand(Hand):
    def remove_matches(self):
        count = 0
        original_cards = self.cards[:]
        for card in original_cards:
            match = Card(3 - card.suit, card.rank)
            if match in self.cards:
                self.cards.remove(card)
                self.cards.remove(match)
                print("Hand {0}: {1} matches {2}".format(self.name, card, match))
                count = count + 1
        return count

I again made some adjustments:

class OldMaidHand(Hand):
    def remove_matches(self):
        count = 0
        original_cards = self.cards[:]
        for card in original_cards:
            match = Card(3 - card.suit, card.rank)
            for lol in self.cards:
                if lol.__cmp__(match) == 0:
                    self.cards.remove(card)
                    self.cards.remove(match)
                    print("Hand {0}: {1} matches {2}".format(self.name, card, match))
                    count = count + 1
        return count

The removing worked fine for the card, but it would give an error (x not in list) when I tried to remove match. Another our or so, I might've been able to make that work too, but since it already feels like I'm on the wrong road since I can't fix the original 'card in deck/hand/etc' etc, I came here looking for some answers/tips.

Thanks for reading and I greatly appreciate any help you can give :)

--------------------- EDIT 1 *>

This is my current code: http://pastebin.com/g77Y4Tjr

--------------------- EDIT 2 *>

I've tried every single cmp advised here, and I still can't get it to find a card with 'in'.

>>> a = Card(0, 5)
>>> b = Card(0, 1)
>>> c = Card(3, 1)
>>> hand = Hand('Baris')
>>> hand.add(a)
>>> hand.add(b)
>>> hand.add(c)
>>> d = Card(3, 1)
>>> print(hand)
Hand Baris contains
5 of Clubs
 Ace of Clubs
  Ace of Spades
>>> d in hand.cards
False
>>> 

I've also tried the card.py @DSM has used successfully, and I get errors there too, like at the sort function it says it cant compare the two card objects.
So I was wondering, maybe it is a problem with Python 3.2, or maybe the syntax has changed somewhere?


Solution

  • "So I was wondering, maybe it is a problem with Python 3.2, or maybe the syntax has changed somewhere?"

    Oh, you're running Python 3.2? This'll never work in Python 3: python 3 doesn't use __cmp__!

    See the data model (look for __eq__). Also read the what's new in Python 3 for some other things it's way too easy to miss.

    Sorry, this is on us Python programmers here; we should have caught this far earlier. Most of probably looked at all the code, realized without even thinking about it that the source was obviously python 2 code, and assumed that's what we were working with. The cmp function doesn't even exist in Python 3.2, but the reason that it doesn't blow up with a NameError is because __cmp__ is never called.

    If I run the code in Python 3.2, I reproduce your problem exactly:

    >>> c = Card(0,2)
    >>> str(c)
    '2 of Clubs'
    >>> c in [c]
    True
    >>> c in Deck().cards
    False
    

    In Python 3, you either implement all the rich cmps or __eq__ and one of them and use a total_ordering decorator.

    from functools import total_ordering
    
    @total_ordering
    class Card(object):
        """Represents a standard playing card."""
        suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
        rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
                  "8", "9", "10", "Jack", "Queen", "King"]
        def __init__(self, suit=0, rank=2):
            self.suit = suit
            self.rank = rank
        def __str__(self):
            return '%s of %s' % (Card.rank_names[self.rank],
                                 Card.suit_names[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 __eq__(self, other):
            t1 = self.suit, self.rank
            t2 = other.suit, other.rank
            return t1 == t2
    
    
    >>> c = Card(2,3)
    >>> c
    3 of Hearts
    >>> c in Deck().cards
    True