In [96]:
# Write a Deck method called deal_hands that takes two parameters, the number
# of hands and the number of cards per hand, and that creates new Hand
# objects, deals the appropriate number of cards per hand, and returns a 
# list of Hand objects.
# Inheritance is a useful feature. Some programs that would be repetitive
# without inheritance can be written more elegantly with it. Inheritance can
# facilitate code reuse, since you can customize the behavior of parent
# classes without having to modify them. In some cases, the inheritance 
# structure reflects the natural structure of the problem, which makes the
# program easier to understand.
# On the other hand, inheritance can make programs difficult to read. When
# a method is invoked, it is sometimes not clear where to find its definition.
# The relevant code may be scattered among several modules. Also, many of 
# the things that can be done using inheritance can be done as well or 
# better without it.

# Current Status: Complete

from random import shuffle

class Card(object):
    """Represents a standard playing card."""

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

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

    def __str__(self):
        return '%s of %s' % (Card.rank_names[self.rank],
                             Card.suit_names[self.suit])
    
    def __repr__(self):
        return 'Card <{}, {}>'.format(self.rank_names[self.rank], self.suit_names[self.suit])
    
    # I'm using the following 'rich comparison' methods in place of __cmp__ 
    # https://docs.python.org/2/reference/datamodel.html#object.__lt__
    
    def __lt__(self, other):
        return self.rank < other.rank
    
    def __gt__(self, other):
        return self.rank > other.rank
    
    def __eq__(self, other):
        return self.rank == other.rank



class Deck(object):
    def __init__(self):
        self.cards = [Card(suit, rank) for suit in range(4) for rank in range(1, 14)]
        
    def __str__(self):
        return '\n'.join([str(card) for card in self.cards])
    
    def __repr__(self):
        return "Deck <{}>".format(self.cards)
    
    def __len__(self):
        return len(self.cards)
    
    def shuffle(self):
        shuffle(self.cards)
        return "deck has been shuffled."
    
    def sort(self):
        self.cards.sort()
        return "deck has been sorted."
    
    def deal_hands(self, num_of_hands, cards_per_hand):
        if (num_of_hands * cards_per_hand) > len(self):
            msg = '\n{} hands with {} cards each is {} cards\
            \nTotal cards left in deck: {}'.format(num_of_hands, cards_per_hand, num_of_hands * cards_per_hand, len(self))
            raise ValueError(msg)
        else:
            hands = []
            for h in range(num_of_hands):
                hand = Hand()
                for c in range(cards_per_hand):
                    hand.cards.append(self.cards.pop())
                hands.append(hand)
            return hands
            
                    
    
class Hand(object):
    def __init__(self):
        self.cards = []
        
    def __repr__(self):
        return "Hand <{}>".format(self.cards)

In [97]:
hand = Hand()

In [98]:
deck = Deck()
deck.shuffle()


Out[98]:
'deck has been shuffled.'

In [99]:
deck.deal_hands(3, 3)


Out[99]:
[Hand <[Card <5, Hearts>, Card <10, Diamonds>, Card <10, Clubs>]>,
 Hand <[Card <4, Diamonds>, Card <Jack, Diamonds>, Card <Jack, Hearts>]>,
 Hand <[Card <5, Spades>, Card <9, Clubs>, Card <5, Diamonds>]>]

In [100]:
deck.deal_hands(3, 3)


Out[100]:
[Hand <[Card <Ace, Clubs>, Card <7, Clubs>, Card <2, Diamonds>]>,
 Hand <[Card <8, Clubs>, Card <4, Hearts>, Card <Jack, Spades>]>,
 Hand <[Card <Ace, Diamonds>, Card <Jack, Clubs>, Card <3, Hearts>]>]

In [101]:
deck.deal_hands(3, 3)


Out[101]:
[Hand <[Card <Ace, Spades>, Card <King, Diamonds>, Card <6, Hearts>]>,
 Hand <[Card <9, Spades>, Card <7, Diamonds>, Card <8, Spades>]>,
 Hand <[Card <3, Clubs>, Card <6, Spades>, Card <10, Hearts>]>]

In [102]:
deck.deal_hands(5, 5)


Out[102]:
[Hand <[Card <2, Spades>, Card <King, Clubs>, Card <Queen, Spades>, Card <3, Diamonds>, Card <4, Spades>]>,
 Hand <[Card <Queen, Hearts>, Card <6, Diamonds>, Card <Queen, Clubs>, Card <8, Diamonds>, Card <Ace, Hearts>]>,
 Hand <[Card <6, Clubs>, Card <Queen, Diamonds>, Card <7, Hearts>, Card <7, Spades>, Card <4, Clubs>]>,
 Hand <[Card <9, Diamonds>, Card <King, Spades>, Card <8, Hearts>, Card <5, Clubs>, Card <3, Spades>]>,
 Hand <[Card <9, Hearts>, Card <10, Spades>, Card <2, Clubs>, Card <King, Hearts>, Card <2, Hearts>]>]

In [103]:
deck.deal_hands(1, 1)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-103-d3fde1e72d35> in <module>()
----> 1 deck.deal_hands(1, 1)

<ipython-input-96-58f59268dddb> in deal_hands(self, num_of_hands, cards_per_hand)
     75         if (num_of_hands * cards_per_hand) > len(self):
     76             msg = '\n{} hands with {} cards each is {} cards            \nTotal cards left in deck: {}'.format(num_of_hands, cards_per_hand, num_of_hands * cards_per_hand, len(self))
---> 77             raise ValueError(msg)
     78         else:
     79             hands = []

ValueError: 
1 hands with 1 cards each is 1 cards            
Total cards left in deck: 0

In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]: