Previous Section Table of Contents Next Section

Go Fish, Part I: Draw a Card from a Deck

This function draws a card from a deck and puts it into a hand. It is meant to be a part of the game Go Fish, so if the resulting hand has all four suits for a given card rank, those four cards are removed from the hand. The next two programs build on this one to produce a full version of the game.

Cards are identified by their rank and suit: the rank is one of the elements of the list ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"] and the suit is one of the elements of the list ["spades", "hearts", "diamonds", "clubs"].

A deck is a list that initially contains 52 elements. Each element of the list is a tuple with two elements: the rank and the suit. So, a single entry in the deck might be the tuple ("K", "spades"), which is the king of spades.

A hand is a dictionary. In each element of the dictionary, the key is a rank and the value is a list that contains the names of the suits that the hand holds for that rank. For example, if a hand has the 3 of spades and the 3 of hearts, and no other 3s, the key "3" has the value ["spades", "hearts"]. A key should not have an empty list associated with it; if no cards of a given rank are held, no value exists for that key.

Source Code


 1.      import random

 2.

 3.      def getCard(deck):

 4.

 5.          """ Randomly remove a single card from the deck and

 6.              return it. Assumes the deck is not empty.

 7.

 8.              deck: A deck as described above.

 9.

10.              Returns: a single card, which is a tuple with

11.              two elements, the rank and the suit.

12.         """

13.

14.         index = int (len(deck) * random.random())

15.         newCard = deck[index]

16.         del deck[index]

17.         return newCard

18.

19.

20.      def drawCard(name, deck, hand):

21.

22.          """ Draw a new card from the deck and add it to

23.              hand. If the hand now holds the rank in all four

24.              suits, then remove them from the hand.

25.

26.              name: A string with the name of playerHand, used

27.                    only for display purposes.

28.              deck: A deck as described above.

29.              hand: A hand dictionary as described above.

30.

31.              Returns: None.

32.          """

33.

34.          if len(deck) > 0:     # guard against an empty deck

35.

36.              newCard = getCard(deck)

37.              cardRank = newCard[0]

38.              cardSuit = newCard[1]

39.

40.              if cardRank in hand:

41.                  # append this suit to the list

42.                  hand[cardRank].append(cardSuit)

43.                  if len(hand) == 4:

44.                      print name, "lay down", cardRank + "s"

45.                      del hand[cardRank]

46.

47.              else:

48.                  # first of this suit, create a list

49.                  # with one element

50.                  hand[cardRank] = [ cardSuit ]


Suggestions

  1. The return type from the function getCard() should be a tuple. Check that the return statement on line 17 actually returns a tuple (assuming the arguments to getCard() are of the proper type).

  2. What is the most complicated data structure used in the program? Probably the hand dictionary. Walk through the code and look at each location that it is used, and each location it is modified, to ensure that hand is used correctly and remains consistent.

  3. A random number is generated on line 14. What constitutes a good set of values to select as results of this random number when you walk through the program?

  4. What set of inputs to drawCard() ensures coverage of all the code, in particular that the if on line 40 is tested both when it is true and when it is false?

Hints

Walk through the drawCard()function with the following parameters. (In all cases, deck has only one card, the 3 of hearts, for simplicity; in this situation, the randomly selected card is always the same one.) The examples show the hand dictionary using the standard Python dictionary syntax, which is { key1 : value1, key2 : value2 }. In this case, the values are themselves lists:

  1. Card from deck doesn't match existing rank in hand:

    
    deck == [ ( "3", "hearts" ) ]
    
    hand == { "2" : [ "hearts", "spades" ] }
    
    

  2. Card from deck matches existing rank in hand:

    
    deck == [ ( "3", "hearts" ) ]
    
    hand == { "2" : [ "hearts", "spades" ],
    
              "3" : [ "diamonds" ] }

  3. Card from deck is the fourth card of a rank, so that rank should be laid down:

    
    deck == [ ( "3", "hearts" ) ]
    
    hand == { "2" : [ "hearts", "spades" ],
    
              "3" : [ "diamonds", "clubs", "spades" ] }
    
    

Explanation of the Bug

The code on line 43 checks if the hand now has all four ranks of a given suit:


if len(hand) == 4:


This code line checks whether the length of the hand dictionary itself is 4, which is true if the cards in the hand happen to represent four unique ranks (for example, some number of 5s, some number of 8s, some number of Js, and some number of As). This is a B.expression error: The code should be checking if the particular rank has all four suits represented:


if len(hand[cardRank]) == 4:


Because Python uses the same operator, len, to test the length of a dictionary and the length of a list, and allows a list to be an element of a dictionary, it won't report an error on a mistake such as this.

The effect is that the code incorrectly detects when a draw results in the player holding all four ranks of a given suit. It misses the case shown in the third hint, where the player now has all four 3s, and yet decides that cards should be laid when the hand happens to have four unique ranks in it (which will come across to a user as apparently random behavior).

    Previous Section Table of Contents Next Section