Go Fish, Part III: Play a Full Game
This function plays one turn of the game Go Fish. It uses the drawCard() and checkCard() functions (the corrected versions) defined in the previous two parts.
One turn is completed as follows: A rank is randomly selected from the ones that are in the player's hand, and the other hand is interrogated to see if it has any cards of that rank. If it does, they are transferred over. This continues with another card until no card is transferred, at which point the player has to "go fish" and draw a new card from the deck.
Note: The code does not check if the card drawn was the same rank as the last card asked for. (In traditional Go Fish, this gives the player another turn.) That is not the bug to look for!
To play a complete game, the code continues until both player's hands are empty.
To recap the definitions from the previous example:
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 is itself a tuple with two elements: the rank and the suit. So, a single entry in the deck might be the tuple ("K", "spades"); that is, the king of spades. A hand is a dictionary. In each element of the dictionary, the key is a card rank and the value is a list that contains 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" will have 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.
Keep in mind the definitions of the functions from the two previous parts:
def drawCard(name, deck, hand):
""" Draw a new card from the deck and add it to
hand. If the hand now holds the rank in all four
suits, then remove them from the hand.
name: A string with the name of playerHand, used
only for display purposes.
deck: A deck as described above.
hand: A hand dictionary as described above.
Returns: None.
"""
def checkCard ( handName, playerHand,
cardRank, opponentHand ):
""" Check if opponentHand contains any cards of the
specified rank, if it does, transfer them to
playerHand.
handName: A string with the name of playerHand
playerHand: A hand dictionary, as described above.
cardRank: A string with the name of a
card rank ("2" through "10", "J", "Q",
"K", or "A")
opponentHand: A hand dictionary, described above.
Returns: 1 if a card is transferred, 0 otherwise.
"""
Source Code
1. import random
2.
3. def doTurn ( handName, deck, playerHand, opponentHand ):
4.
5. """ Play one turn of "Go Fish". A rank in playerHand
6. is chosen, and if any cards of that rank exist in
7. opponentHand, they are transferred. This continues
8. until no cards are transferred, at which point a
9. new card is drawn from the deck into playerHand.
10.
11. handName: A string with the name of playerHand.
12. deck: The current deck, a list of two-element
13. tuples of the form [ rank, suit ].
14. playerHand: A hand dictionary.
15. opponentHand: A hand dictionary.
16.
17. Returns: None.
18. """
19.
20. """ Loop unless the playerHand is empty. Normally this
21. loop exits via the break statement, when
22. checkCard() returns false meaning a card was not
23. transferred.
24. """
25.
26. while len(playerHand):
27.
28. """ Pick a random index within the current hand...
29. """
30. index = int (len(playerHand) * random.random())
31.
32. """ ...and use the rank of the card at that index
33. as the one to ask for.
34. """
35. rankToCheck = playerHand.keys()[index]
36. found = checkCard( handName, opponentHand,
37. rankToCheck, playerHand);
38. if found == 0:
39. break
40.
41. # no transfer, so "go fish"
42. drawCard( handName, deck, playerHand )
43.
44. ranks = [ "2", "3", "4", "5", "6", "7", "8",
45. "9", "10", "J", "Q", "K", "A" ]
46. suits = [ "spades", "hearts", "diamonds", "clubs" ]
47.
48. def playGoFish():
49.
50. deck = []
51. hand1 = {}
52. hand2 = {}
53.
54. for i in range(52):
55. deck.append( (ranks[i % 13], suits[i % 4]) )
56.
57. for i in range(7):
58. drawCard("HAND1", deck, hand1)
59. drawCard("HAND2", deck, hand2)
60.
61. while 1:
62.
63. doTurn ("HAND1", deck, hand1, hand2);
64. doTurn ("HAND2", deck, hand2, hand1);
65.
66. if len(hand1) == 0 and len(hand2) == 0:
67. break
Suggestions
It is a good idea to move from the bottom up: verify that the doTurn() function is correct before moving on to test the playGoFish() function. Design a good set of parameters to test doTurn() with. Is the test on line 66 correct? Will the game always end? Will it end at the correct time? Is the initialization of the deck on lines 54 and 55 correct? Look at the four parameters to doTurn(). Which ones are modified and which ones are only used?
Hints
Walk through the doTurn() function with the following parameters: The deck has only one card, to remove any randomness in which card will be picked. (These are artificial in the fact that, in a real game, the situation would not occur, but they are reasonable to test the function.)
Player asks opponent for a rank that results in player getting all four cards of that rank:
handname == "HAND1"
deck == ( ( "3", "hearts" ) )
playerHand = { "7" : [ "clubs", "spades" ] }
opponentHand = { "7" : [ "hearts", "diamonds" ] }
Player asks opponent for a rank that the opponent does not have:
handname == "HAND1"
deck == { ( "5", "spades" ) }
playerHand == { "10" : [ "diamonds" ],
"K" : [ "spades" ] }
opponentHand == { "Q" : [ "clubs" ] }
Consider the following situation near the end of the game. The variables are as follows:
hand1 == { }
hand2 == { "4" : [ "diamonds, clubs" ] }
deck == [ ( "4", "clubs" ) , ( "4", "spades" ) ]
The program is just before line 61, about to iterate the while loop. Will the program terminate properly?
Explanation of the Bug
The call to checkCard() on lines 36-37 has two parameters reversed. It currently reads as follows:
found = checkCard( handName, opponentHand,
rankToCheck, playerHand);
However, it should read:
found = checkCard( handName, playerHand,
rankToCheck, opponentHand);
Because the two hands are reversed in the function, cards get transferred in the wrong direction. Because the rank to ask for (calculated on lines 30 and 35) is chosen from the correct deck, but checkCard() is then called with the hands reversed, it's also possible that the card asked for won't be in the receiving player's deck. As it happens, as written (see previous program), checkCard() does handle this case correctly, even though it is unexpected given the rules of Go Fish.
Even in a language that had predeclared function prototypes and strong type checking (which Python does not), this kind of B.variable error could easily occur because playerHand and opponentHand are of the same type.
|