You are tasked with creating a single-player version of the card game 21. In your version, the single player will play against the computer, who will always be the dealer. Your game will be played using a normal deck of 52 cards. A deck of 52 cards has four suits: club, diamond, heart, and spade. Each suite has 13 ranks: Ace, 2 through 10, Jack, Queen, and King.
card game 21 Using Python
In your program, you will initially read in each card as a string with exactly 2 characters in it. The first character represents the rank of the card (A,2,3,4,5,6,7,8,9,T,J,Q,K) and the second character represents the suit (C,D,H,S). So for example, the ten of hearts would be 'TH'.
Rules for this simplified version of 21:
In 21, each card is assigned a value based on rank alone. If the rank is numeric, the value is its rank. (e.g. two of spades have a value of 2, ten of hearts has a value of 10). If is it a Jack, Queen, or King, its value is 10. In our version of the game, the Ace always has a value of 1.
At the beginning of a round, the dealer deals a card (face up) to the player, and a card (face up) to the dealer. The dealer then deals a second card (face up) to the player, and a second card (face DOWN) to the dealer. The value of each hand is determined by summing the value of the face-up cards in the hand together. For example, if the player has a Three of Hearts and a King of Spades, the value of the hand is 3+10 = 13.
Although we do not know the value of the dealer’s hand at the beginning of the round (because one of the dealer’s cards is face down), we do know that in our version of the game neither the dealer nor the player can possibly have a hand that sums to 21 after the first four cards have been dealt. So the player is asked if s/he wants another card or not, in an effort to get closer to 21 (without going over also known as going “bust”). The goal is to get closer to 21 than the dealer, without going bust. The player can continue to ask for one additional card at a time (by saying “Hit”) until s/he decides to stop, or until s/he goes bust. The player indicates that s/he wants to stop receiving any more cards by saying “Stay.” All of the player’s cards are dealt face-up, so the current value of the player’s hand is always known. If the player goes bust, the round ends and the dealer wins (regardless of what cards the dealer has – in fact, in this case, the dealer does not even show the card that is still facing down).
When the player decides to stay (and has not gone bust), the dealer turns over the hidden second card and is dealt additional cards based on automatic rules. As long as the dealer has a summed value of 16 or less, the dealer is dealt another card (always face-up from now on). If the dealer goes bust by hitting and staying according to these rules, the round ends and the player wins.
When the dealer has to stay according to the automatic rules, the summed values from the player and dealer are compared. That is if the player is closer to 21 (without going over), the player wins the round. If the dealer is closer to 21 (without going over), the dealer wins the round. If the player and dealer have the same best-summed value, the round ends in a tie.
Task 1: Card class
Create a python file called playingCards.py. Inside this file, create and test a Card class according to the description below. Note that the following methods form the public interface for this class – you must complete them all as specified, and you cannot add additional methods to the public interface. However, you can include additional private helper methods that are only called within this class, if you wish.
Card(code, faceUp) – creates a card, where the code should be a 2 character string representing the rank and suit of the card as described above, and faceUp should be a Boolean value indicating whether the card is facing up (True) or down (False). After asserting that the input parameters are valid, use this information to initialize three private attributes: rank, suit, and faceUp. Note that when checking for a valid code, the capitalization of the characters does not matter. So for example, a code of 'jd' is valid and represents the Jack of Diamonds. But your private attributes rank and suit should always be capitalized. Nothing is returned.
getRank() –returns the single string character representing the rank of the Card instance.
getSuit() – returns the single string character representing the suit of the Card instance.
getValue() – returns the integer value of the Card instance, according to its rank (and as described above).
isFaceUp() – returns the Boolean value indicating whether the Card instance is facing up (True) or down (False).
turnOver() –updates the Card instance so the if it was facing up, it will now be facing down. Similarly, if it was facing down, it will now be facing up. Nothing is returned.
str () –returns the string representation of the Card instance. The format of this string should be '[(single space)(rank)(suit)(single space)]' if it is facing up (e.g. '[ JD ]' ). Any card that is facing down should have xx instead of revealing the rank and suit: '[ xx ]'.
Test your Card class thoroughly before moving on. You may wish to use assertions to verify expected behavior like in Lab 7 (Linked Lists), but you do not have to. Call your tests under if name == " main ": in playingCards.py for the marker to see.
Task 2: EmptyDeckException class
Copy the following EmptyDeckException class into playingCards.py:
class EmptyDeckException(Exception): def init (self):
self.args = ("Cannot deal card from an empty deck.", )
Task 3: Deck class
Create and test a Deck class in playingCards.py, according to the description below. Note that the following methods form the public interface for this class – you must complete them all as specified, and you cannot add additional methods to the public interface. However, you can include additional private helper methods that are only called within this class, if you wish.
Deck() – creates a deck that is capable of holding 52 cards, and populates that deck based on information read in from a text file. Things to keep in mind while completing this init method:
- Notice that this deck acts very much like a queue, where we deal with the front card and add new cards to the rear of the deck. Therefore, you should create a single private attribute that is the most time-efficient queue with a maximum capacity that we covered in the lectures. You should import the queues.py file provided with Lab 6 to accomplish this. (You do not need to submit the queues.py file since your marker will also have access to that file.)
- This method should also prompt the user to provide the name of an input text file. If any problem occurs when opening the file, an error message should be displayed and the user should be re-prompted to provide the name of another input text file until the file can be opened successfully. (See invalid1_output.txt) The input text file should contain information about the cards that will be added to the deck (face down), in the same order as indicated in the file. (i.e. The first line of the text file corresponds to the first card that should be added face down to the rear of the deck.) Each valid line of the text file will contain a code that represents the rank and suit of a card. (See the sample shuffledDeck.txt provided.) You cannot assume that all lines in the file will be valid, and you cannot assume that there will be information for exactly 52 unique cards in the file. If there is invalid data in the file or not 52 unique cards, you should raise an Exception with the argument "Cannot populate deck: invalid data in xx" (where xx is replaced with the filename), ensure the deck is empty, and close the file before leaving this method.
deal() – modifies the deck by removing the card from the front of the deck, and returns that front card face up. This method should have an O(1) time efficiency. Raise a custom EmptyDeckException if the deck is empty and a card cannot be dealt.
repopulate(cardList) –displays a message that the deck is being repopulated and modifies the deck by adding the cards from cardList to the deck in a random (shuffled) order. Nothing is returned.
str() –returns the string representation of the Deck instance. You can decide how this string should be formatted, but make sure to show all cards in the deck (show them face up, not face down), and make it clear which card is at the front of the deck.
Test your Deck class thoroughly before moving on. Call your tests under if name == " main ": in playingCards.py for the marker to see.
Task 4: Player class
Create a python file called simple21.py. Inside this file, create and test a Player class according to the description below. Note that the following methods form the public interface for this class – you must complete them all as specified, and you cannot add additional methods to the public interface. However, you can include additional private helper methods that are only called within this class, if you wish.
Player() – creates a player with an empty hand of cards. Initialize two private attributes: hand (an empty list which will eventually be used to store the cards in the player’s hand) and value (the integer sum of all of the values of the cards in the player’s hand). Nothing is returned.
addToHand(card) – adds the card to the player’s hand after asserting that it is a Card instance. Both the hand and value attributes should be updated. Nothing is returned.
clearHand() – removes any cards in the player’s hand and resets the value to 0. This method returns a list of the cards that were removed from the player’s hand.
getHandValue() – returns the current value (an integer) of the player’s hand.
revealAllCards() –turns over any cards that are face down in a player’s hand so that all cards are face up. Nothing is returned.
str() –returns the string representation of the Player instance. Specifically, the string includes information about what cards are in the hand, and if all cards are facing up, it also includes information about the value of the hand. For example, if a player has a Nine of Spades and Ace of Diamonds (both face up) in their hand, the string representation would be hand:[ 9S ][ AD ], value = 10. However, if the Ace was face down, the string representation would be hand:[ 9S ][ xx ].
Test your Player class thoroughly before moving on. Call your tests under if name == " main ": in simple21.py for the marker to see.
Task 5: Table class
Create a Table class in simple21.py, according to the description below. Note that the following methods form the public interface for this class – you must complete them all as specified, and you cannot add additional methods to the public interface. However, you can include additional private helper methods that are only called within this class, if you wish.
Table() – creates a table with two Player objects: a player and a dealer. A deck of cards is also created, along with a discard pile. The discard pile is initially empty and is where the cards from the table will be cleared too. Any exceptions that may be raised by creating an invalid deck are propagated (i.e. not dealt with in this init method). Nothing is returned.
dealHands() –deals the first four cards from the front of the deck to the player and dealer. Specifically, this method deals the first card to the player (face up), the second card to the dealer (face up), the third card to the player (face up), and the fourth card to the dealer (face DOWN). If the deck runs out of cards at any point, the deck should be repopulated using the cards from the discard pile.
Nothing is returned
playerHit() – deals a card (from the front of the deck) to the player, face up. If the deck runs out of cards, the deck should be repopulated using the cards from the discard pile. Returns whether the player has gone bust with the new card (True) or not (False). (Remember that a player goes “bust” if the value of their hand exceeds 21.)
dealerHit() –turns the dealer’s second card over and displays the value of the current hand. As long as the value of the dealer’s hand is 16 or less, this method continues to deal cards (from the front of the deck) to the dealer, face up. If the deck runs out of cards at any point, the deck should be repopulated using the cards from the discard pile. Displays a message every time the dealer must hit ("Dealer must take a card…"), along with the updated hand and value. Also displays a message if the dealer goes bust ("Dealer went bust.") or if the dealer gets 21 ("Dealer has a natural 21!"). Returns whether the dealer has gone bust (True) or not (False).
playerNatural() – returns True if the value of the player’s hand is exactly 21, False otherwise.
whoWon() – compares the player’s and dealer’s hands to determine who wins. Displays either
"Player wins", "Dealer wins" , or "Tie! No one wins". Nothing is returned.
clearTable() – removes all cards from the player’s and dealer’s hands, and adds those cards to the discard pile (the order does not matter). Nothing is returned.
str () – returns the string representation of the Table instance. Specifically, the string contains information about the player’s hand and the dealer’s hand. (See shuffledDeck_sampleOutput.txt.)
Be sure to test this Table class thoroughly before moving on. However, you do not need to include these tests for the marker to see.
Task 6: main
Create a python file called assignment2.py. In this file, try to create an instance of your Table class. If the deck on the table is created from a file that contains invalid data, the game should end with an explanatory message (see invalidDeck1_output.txt).
Otherwise, deal the first four cards to the player and dealer to start round 1. Display the table, and then ask the player if they would like to hit or stay. Any entry starting with an "H" or "h" will be considered to mean that the player wants to hit; anything else indicates that they wish to stay. Deal the player a new card, display the updated table, and ask if they would like to hit or stay. Continue on in this way as long as the player keeps indicating that they wish to hit, or until the player goes bust. Display an appropriate message if the player goes bust, or if they have exactly 21, and end the round. (See shuffledDeck_sampleOutput.txt.)
If the player stays (and has not gone bust), reveal the dealer’s cards and deal cards to the dealer according to the automatic rules of the game. At the end of the round, display who won the game (if anyone). Then clear the table, and ask the user if they would like to play another round. The only valid entries are ones that start with "Y" or "y" for yes, or "N" or "n" for no. Your program should re-prompt the user until a valid entry is entered. If the user chooses to play another round, the round number is updated (see shuffledDeck_sampleOutput.txt) and another round is played using the existing deck (i.e. do NOT create a new deck for each round). This continues until the user decides not to play another round, at which point the game ends with a goodbye message.
from collections import deque
import random
class Card:
validRanks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K']
validSuits = ['S', 'D', 'C', 'H']
def __init__(self, code, faceUp):
if len(code) != 2:
raise ValueError(code)
upperRank = code[0].upper()
if upperRank not in self.validRanks:
raise ValueError(code[0])
upperSuit = code[1].upper()
if upperSuit not in self.validSuits:
raise ValueError(code[1])
self.rank = upperRank
self.suit = upperSuit
self.faceUp = faceUp
def getRank(self):
return self.rank
def getSuit(self):
return self.suit
def getValue(self):
if self.rank == 'A':
return 1
if self.rank == 'T' or self.rank == 'J' or self.rank == 'Q' or self.rank == 'K':
return 10
return int(self.rank)
def isFaceUp(self):
return self.faceUp
def turnOver(self):
self.faceUp = not self.faceUp
def __str__(self):
if self.faceUp:
return '[ ' + self.rank + self.suit + ' ]'
return '[ xx ]'
def __eq__(self, other):
return self.rank == other.rank and self.suit == other.suit
class EmptyDeckException(Exception):
def __init__(self):
self.args = ("Cannot deal card from an empty deck.", )
class Deck:
def __init__(self):
self.deck = deque([], 52)
f = None
filename = None
while True:
filename = input('Please, enter input file name: ')
try:
f = open(filename)
break
except OSError:
print('Cannot read from ', filename)
try:
for line in f.readlines():
code = line.strip('\t\n ')
card = Card(code, False)
if card in self.deck:
raise ValueError(code)
self.deck.append(card)
if len(self.deck) != 52:
raise Exception()
except Exception as e:
raise Exception('Cannot populate deck: invalid data in ' + filename)
def deal(self):
if len(self.deck) == 0:
raise EmptyDeckException()
card = self.deck.popleft()
card.turnOver()
return card
def repopuplate(self, cardList):
print('Repopulating deck with cards...')
random.shuffle(cardList)
for card in cardList:
self.deck.append(card)
def __str__(self):
s = '['
for card in self.deck:
if card.isFaceUp():
s += str(card)
else:
card.turnOver()
s += str(card)
card.turnOver()
s += ']'
return s
if __name__ == '__main__':
code = 'QWER'
try:
Card(code, False)
except ValueError:
print('Invalid code', code)
code = 'WS'
try:
Card(code, False)
except ValueError:
print('Invalid code', code)
code = 'TI'
try:
Card(code, False)
except ValueError:
print('Invalid code', code)
code = '2D'
c = Card(code, False)
print(c)
print('Code:', code, ' Rank:', c.getRank(), ' Suit:', c.getSuit(), ' Value:', c.getValue(), ' FaceUp:', c.isFaceUp())
c.turnOver()
print(c)
print('Code:', code, ' Rank:', c.getRank(), ' Suit:', c.getSuit(), ' Value:', c.getValue(), ' FaceUp:', c.isFaceUp())
code = 'QH'
c = Card(code, True)
print('Code:', code, ' Rank:', c.getRank(), ' Suit:', c.getSuit(), ' Value:', c.getValue(), ' FaceUp:', c.isFaceUp())
c.turnOver()
print(c)
print('Code:', code, ' Rank:', c.getRank(), ' Suit:', c.getSuit(), ' Value:', c.getValue(), ' FaceUp:', c.isFaceUp())
deck = Deck()
print(deck)
from playingCards import Card, Deck
class Player:
def __init__(self):
self.hand = []
self.value = 0
def addToHand(self, card):
self.hand.append(card)
self.value += card.getValue()
def clearHand(self):
old = self.hand
self.hand = []
self.value = 0
return old
def getHandValue(self):
return self.value
def revealAllCards(self):
for card in self.hand:
if not card.isFaceUp():
card.turnOver()
def __str__(self):
allFaceUp = True
s = 'hand:'
for card in self.hand:
if not card.isFaceUp():
allFaceUp = False
s += str(card)
if allFaceUp:
s += ', value = ' + str(self.value)
return s
class Table:
def __init__(self):
self.player = Player()
self.dealer = Player()
self.deck = Deck()
self.discard = []
def dealHands(self):
print('Dealing cards to player and dealer...')
for i in range(4):
card = None
try:
card = self.deck.deal()
except:
self.deck.repopuplate(self.discard)
self.discard = []
card = self.deck.deal()
if i % 2 == 0:
self.player.addToHand(card)
elif i % 4 == 1:
self.dealer.addToHand(card)
else:
card.turnOver()
self.dealer.addToHand(card)
def playerHit(self):
card = None
try:
card = self.deck.deal()
except:
self.deck.repopuplate(self.discard)
self.discard = []
card = self.deck.deal()
self.player.addToHand(card)
return self.player.getHandValue() > 21
def dealerHit(self):
self.dealer.revealAllCards()
print(self)
while self.dealer.getHandValue() < 17:
print('Dealer must take a card...')
card = None
try:
card = self.deck.deal()
except:
self.deck.repopuplate(self.discard)
self.discard = []
card = self.deck.deal()
self.dealer.addToHand(card)
print(self)
if self.dealer.getHandValue() > 21:
print('Dealer went bust.', end=' ')
elif self.dealer.getHandValue() == 21:
print('Dealer has a natural 21!', end=' ')
def playerNatural(self):
return self.player.getHandValue() == 21
def whoWon(self):
if self.player.getHandValue() > 21:
if self.dealer.getHandValue() > 21:
print('Tie! No one wins', end=' ')
else:
print('Dealer wins', end=' ')
return
if self.dealer.getHandValue() > 21:
print('Player wins', end=' ')
return
if self.player.getHandValue() > self.dealer.getHandValue():
print('Player wins', end=' ')
elif self.player.getHandValue() < self.dealer.getHandValue():
print('Dealer wins', end=' ')
else:
print('Tie! No one wins', end=' ')
def clearTable(self):
for card in self.player.clearHand():
self.discard.append(card)
for card in self.dealer.clearHand():
self.discard.append(card)
def __str__(self):
s = 'Player\'s ' + str(self.player) + '\n'
s += 'Dealer\'s ' + str(self.dealer)
return s
if __name__ == '__main__':
p = Player()
p.addToHand(Card('9S', True))
p.addToHand(Card('AD', False))
print(p)
p.clearHand()
print(p)
p.addToHand(Card('9S', True))
p.addToHand(Card('AD', False))
print(p)
p.revealAllCards()
print(p)
from simple21 import Table
if __name__ == '__main__':
print('=======================')
print('Welcome to CARD GAME 21')
print('=======================')
try:
round = 1
t = Table()
print()
isOver = False
while not isOver:
t.dealHands()
bust = False
print(t)
while True:
choice = input('Would you like to HIT (H/h) or STAY (S/s)? >> ')
if choice[0].lower() == 'h':
bust = t.playerHit()
else:
break
if bust:
break
print(t)
if bust:
print(t)
print('Player went bust. Dealer won round ' + str(round) + '!' )
else:
if t.playerNatural():
print('Player wins round', round, 'with a natural 21!')
else:
t.dealerHit()
t.whoWon()
print('round ' + str(round) + '!')
print()
while True:
choice = input('Would you like to play another round (Y/N)? >> ')
if choice[0].lower() == 'y':
break
if choice[0].lower() == 'n':
isOver = True
break
print('Invalid Entry.', end = ' ')
print()
t.clearTable()
round += 1
except Exception as e:
print(e)
print()
print('Thank you for playing. Goodbye...')
Related Samples
Discover our free Python assignment samples, tailored to help you with your projects and deepen your understanding of Python programming. Access a variety of examples now to boost your learning and improve your skills.
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python
Python