Question

What I am trying to do is make a deck of cards, then move a specific card from the deck to a player's hand. I am having no trouble moving creating the deck and adding a card to a player's hand, but whenever I try to remove the card from the deck it tells me the card isn't in the deck to begin with, which does not make sense.

Here is the relevant code

class Card(object):

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"] 
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]  #so zero returns nothing, an Ace is 1 and so forth

    def __init__(self, suit, rank): #initial method, just defines suit and rank so you can find it later
        self.suit = suit
        self.rank = rank

    def getRank(self): #returns the rank of the card as an integer
        return self.rank

    def getSuit(self): #returns suit as an integer
        return self.suit

    def __str__(self):
        return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])

class Deck(object):  

    def __init__(self): #this is just creating a deck of cards, in standard order with the suits arranged alphabetically
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card=Card(suit, rank)
                self.cards.append(card) #what you should be ending up here is 52 objects from the "Card" class

    def shuffle(self):
        shuffle(self.cards)

def main():
                selfHand=[]
                s,r=eval(input("Input your first downcard's suit and rank, separated by a comma" ))
                card=Card(s,r)
                selfHand.append(card)
                deck.cards.remove(card)

again, everything is working OK (I've omitted the irrelevant code, if anything looks a bit off--that is why there are big indents in the main function) but the last line prompts the error "ValueError: list.remove(x): x not in list"

To be clear, the deck should be a list of cards. I am trying to remove a specific card from the deck, that's it. Thought it would be simple but has eaten up a whole afternoon (to be fair, I am very new to python and coding in general)

I have tried countless different approaches at this point, with similar results.

Thanks for the help

Was it helpful?

Solution 2

The problem is that when you create card = Card(s,r), the Card(s,r) object isn't actually in the deck (hence the ValueError). I know it seems like it should be, but here's why it isn't:

Every time you create you create a new Card, the location of that card in memory will be different (i.e. they are different objects). You can see this like so:

card1 = Card(0, 2)
card2 = Card(0, 2)
assert not card1 is card2 # i.e. id(card1) != id(card2)

So, from Python's point of view, your newly created card isn't actually in the deck. To solve this, you need to at least override the __eq__ and __ne__ methods to have Python use what you think of as "equality" when comparing your Card objects:

def __eq__(self, other):
    if isinstance(other, Card):
        return self.suit == other.getSuit() and self.rank == other.getRank()
    return NotImplemented

def __ne__(self, other):
    return not self.__eq__(other)

But also, as mentioned in Python documentation, "The only required property is that objects which compare equal have the same hash value". This is to make sure your Card class would work as expected as items in hashable collections. So, it's a good idea to override the __hash__ method; perhaps, like so:

def __hash__(self):
    return hash((self.suit, self.rank))

OTHER TIPS

The new card that you have created in main is not the same object as any of the cards in the array inside deck, even though it has the same suit and rank as one of those cards.

>>> c = Card(0,3)
>>> d = Card(0,3)
>>> c
<__main__.Card object at 0x0000000002025978>
>>> d
<__main__.Card object at 0x0000000002025908>
>>> c == d
False

You would need to override the equals and not equals methods in your Card class so it knows how to compare two Card instances.

def __eq__(self, other):
    if isinstance(other, Card):
        return self.suit == other.getSuit() and self.rank == other.getRank()
    return NotImplemented
def __ne__(self, other):
    if isinstance(other, Card):
        return self.suit != other.getSuit() or self.rank != other.getRank()
    return NotImplemented

Then you'll get your desired output of:

>>> c = Card(0,3)
>>> d = Card(0,3)
>>> e = Card(1,4)
>>> c == d
True
>>> c != d
False
>>> c == e
False
>>> c != e
True
>>> deck = Deck()
>>> len(deck.cards)
52
>>> deck.cards.remove(c)
>>> len(deck.cards)
51

There is no deck object. Assuming, you also fix that, you will still have a problem because the deck does not have the same card object that you are trying to remove from the deck. They are two different objects even though they have the same parameters. To address this, I would add a method to deck that will search through the list of cards for a given suit and number and remove that specifically to also avoid breaking abstraction barriers. Also, this is more of a technical note but you can make your init method a little simpler (possibly more efficient) by replacing what you have with a list comprehension:

def __init__(self):
     self.cards = [ Card(suit, rank) for rank in range(1 14) for suit in range(4) ]

Hope that helps.

I'm doing a bit of code review to help you get started with python, I stripped out your comments (don't say what, say why, by the way), and added my own.

import random # don't forget the module for shuffle

class Card(object):

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"] 
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]  # Start with your first one, you could then iterate over this list instead of from 1 up to but not including 14.

    def __init__(self, suit, rank): 
        self.suit = suit
        self.rank = rank

    def getRank(self): # why not use Python's builtin property for getters?
        return self.rank

    def getSuit(self): 
        return self.suit

    def __str__(self):
        return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])

class Deck(object):  

    def __init__(self): 
        self.cards = []
        for suit in range(4): # 0-3, again, Pythonic to iterate over the list, 
            for rank in range(1, 14): # 1-13, why start with 1 here, and 0 with suit?
                card=Card(suit, rank)
                self.cards.append(card) 

    def shuffle(self):
        random.shuffle(self.cards) # call shuffle from the random module, 

def main():
    selfHand=[]
    s,r=input("Input your first downcard's suit and rank, separated by a comma" )
    card=Card(s,r)# here you're creating a new card, why not find it in the deck?
    selfHand.append(card) 
    deck.cards.remove(card) # and this deletes the old card, you'll need to be careful to put it back if you do this again.

So I might do this instead:

import random # don't forget the module for shuffle

class Card(object):

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"] 
    rank_names = ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit, rank): 
        self._suit = Card.suit_names.index(suit)
        self._rank = Card.rank_names.index(rank)

    @property # Python's built-in property, treat rank as an attribute!
    def rank(self):
        return Card.rank_names[self._rank]

    @property
    def suit(self): 
        return Card.suit_names[self._suit]

    def __repr__(self): # this will reproduce a card, and will echo on your REPL
        return 'Card(%s, %s)' % (self.rank, self.suit) # see how we get attrs now?

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


class Deck(object):  

    def __init__(self): 
        self.cards = []
        for suit in Card.suit_names:
            for rank in Card.rank_names:
                card=Card(suit, rank)
                self.cards.append(card) 

    def shuffle(self):
        random.shuffle(self.cards) # avoid dumping in your namespace where you might overwrite, call shuffle from the random module

def main():
    deck = Deck()
    selfHand=[]
    response=raw_input("Input suit and rank, as in 'Ace of Clubs' or '10 of Hearts'" )
    # and now don't create and delete more cards so the accounting is easier.
    deck_str = [str(c) for c in deck.cards]
    selfHand.append(deck.cards.pop(deck_str.index(response)))

    print selfHand
    print 'response not in deck_str:', response not in deck_str

And when I enter Ace of Clubs:

[Card(Ace, Clubs)]
response not in deck.cards: True
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top