More Practice with Python

After completing Automate The Boring Stuff, and while reading Learning Python 3 The Hard Way, I decided to tackle a few simple projects on my own. Below is a selection of the more involved projects that I completed.

Blackjack

Though admittedly I don’t know the rules of blackjack that well so there is likely so weird things . This program was exciting for me because it was the first experience with creating a program using object oriented design(OOP). I had previously made a blackjack game program using basic procedural techinques so. I really do think that the OOP design really. Especially it helped prevent repeated code for the player and dealer actions. Though as always looking back there are some structures that I would like to change. For instance, could inherit a method for firstDraw(), which defines the initial hand cards, from a parent abstract class that contains information used by both the player and the dealer

#! python3
#blackJacks_oop.py -

import random, logging,time,sys

#Sets up logging

logging.basicConfig(level=logging.DEBUG, format='\t%(asctime)s.%(msecs)03d: %(message)s', datefmt='%H:%M:%S')
logging.disable(logging.DEBUG) # uncomment to block debug logging.debug messages
#logging.disable(logging.INFO) # uncomment to block debug logging.info messages and below

'------------------------------------------------------------------------------'

class cardDeck(object):

    # Holds data about the deck of cards
    cardDeck_list = [['1', 4],['2', 4],['3', 4],['4', 4],['5', 4],['6', 4],['7', 4],
    ['8', 4],['9', 4],['10', 4], ['j', 4], ['q', 4], ['k', 4],['a', 4]]
    cardDeck_len = len(cardDeck_list) # 14


    def cardDrawing(self):

        cardDeck_list = self.cardDeck_list
        cardDeck_len = self.cardDeck_len

        while True:
            randDraw = random.randint(0, cardDeck_len - 1) # generates random position number within range of desk cars
            drawCard = cardDeck_list[randDraw][0] # gets card from deck

            # Check that card is in deck
            if cardDeck_list[randDraw][1] > 0:
                break

        # Remove assign cards from deck
        cardDeck_list[randDraw][1] = cardDeck_list[randDraw][1] - 1

        # calculate the total of those card
        if drawCard == 'j' or drawCard == 'q' or drawCard == 'k':
            cardValue = 10
        elif drawCard == 'a':
            cardValue = 11
        else:
            cardValue = int(drawCard)

        return drawCard, cardValue

    def resetDeck(self): # resets the

        for i in self.cardDeck_list:
            i[1] = 4
        logging.debug(f'Reset cardDeck_list: {self.cardDeck_list}')



class Player(object):

    playerCards = [] # a list to hold player's card
    playerTotal = 0 # holds the pplayer's card total
    logging.debug(f'playerTotal: {playerTotal}')
    player_bustState = False # sets to true if player total goes over 21
    player_blackJacksState = False # sets to true if player total equals 21

    def __init__(self):
        self.Deck = cardDeck()

    def P_firstDraw(self): # Start hand: draws two cards from deck and assigns data to associated variables

        for i in range(0,2):
            p_DrawCard, p_CardValue = self.Deck.cardDrawing()
            self.playerTotal += int(p_CardValue) # calculates player's total card value
            self.playerCards.append(p_DrawCard) # asssigns random card draw to player card list
        logging.debug(f'Player\'s Cards: {self.playerCards} Card Value: {self.playerTotal}')
        print(f'Player\'s Cards: {self.playerCards} Card Value: {self.playerTotal}')


    def P_HitHold(self): # after player and dealer draw starting hand the player decides if he would like to hold or hit taking another card from the deck and adding it to his hand

        while True:
            '------------------------------------------------------------------'
            # Checks if Player card value is over 21
            if self.playerTotal > 21:
                logging.debug('Player BUST')
                self.player_bustState = True
                break

            # Checks if player got blackjacks
            if self.playerTotal == 21:
                logging.debug('Player BLACKJACK')
                self.player_blackJacksState = True
                break

            '------------------------------------------------------------------'
            #ask player if they would like to hit or holds

            hitHold_input = input("\nWould you like to hit or hold? ")
            print()

            while hitHold_input != 'hit' and hitHold_input != 'hold':
                print("ERROR: must enter either 'hit' or hold:", end = ' ')
                hitHold_input = input()

            if hitHold_input == 'hit':
                logging.debug('Player Hit')

                #draws and adds card to player's hand and total
                p_DrawCard, p_CardValue = self.Deck.cardDrawing()
                self.playerTotal += int(p_CardValue)
                self.playerCards.append(p_DrawCard)

                logging.debug(f'Player Cards: {self.playerCards}')
                logging.debug(f'Player Total: {self.playerTotal}')
                logging.debug(f'Updated Card list: {self.Deck.cardDeck_list}')

                print(f"You drew: {p_DrawCard}")
                print(f"You currently hold {','.join(self.playerCards)} for a card total of {self.playerTotal}")

            else:
                logging.debug('Player Hold')
                break
            '------------------------------------------------------------------'
        logging.debug(f"""self.player_bustState: {self.player_bustState}
                self.player_blackJacksState: {self.player_blackJacksState}""" )

        return self.player_bustState, self.player_blackJacksState,self.playerTotal

    def P_resetHand(self): # sets the playerCards to an empty list and the playerTotal to 0
        self.playerCards.clear()
        self.playerTotal = 0
        self.player_bustState = False # sets to true if player total goes over 21
        self.player_blackJacksState = False
        logging.debug(f'Reset Player Hand: {self.playerCards} Total: {self.playerTotal}')



class Dealer(object):

    dealerCards = [] # a list to hold the dealer's cards
    dealerTotal = 0 # holds the dealer's card total
    dealer_bustState = False # sets to true if DEALER total goes over 21
    dealer_blackJacksState = False # sets to true if DEALER total equals 21

    def __init__(self):
        self.Deck = cardDeck()

    def D_firstDraw(self): # Start hand: draws two cards from deck and assigns data to associated variables

        for i in range(0,2):
            d_DrawCard, d_CardValue = self.Deck.cardDrawing()
            self.dealerTotal += int(d_CardValue) # calculates Dealer's total card value
            self.dealerCards.append(d_DrawCard) # asssigns random card draw to Dealer card list
            dealer_faceCard = self.dealerCards[0]
        logging.debug(f'Dealer\'s Cards: {self.dealerCards} Card Value: {self.dealerTotal}')
        print(f"Dealer's face card: {dealer_faceCard}")


    def D_finalDraw(self,playerBust, playerBlackjacks, playerTotal): # After the player has decided to hold dealer draws cards until dealerTotal is greater than the playerTotal or the dealer bust/gets blacksjacks

        self.playerBust = playerBust
        self.playerBlackjacks = playerBlackjacks
        self.playerTotal = playerTotal

        print(f'Dealer shows: {self.dealerCards} Total Card Value: {self.dealerTotal}')
        logging.debug('D_finalDraw function called')

        # If P.playerTotal is greater than or equal self.dealerTotal dealer must draw unit he bust or wins
        logging.debug(f'playerBust: {playerBust}  playerBlackjacks: {playerBlackjacks}' )
        if playerBust == False and playerBlackjacks == False:
            logging.debug(f'dealerTotal: {self.dealerTotal} playerTotal: {playerTotal}')
            while self.dealerTotal < 21 and self.dealerTotal <= playerTotal:
                logging.debug("drawing card")
                d_DrawCard, d_CardValue = self.Deck.cardDrawing()
                self.dealerTotal += int(d_CardValue)
                self.dealerCards.append(d_DrawCard)
                print(f"Dealer drew {d_DrawCard}: Dealer now has {self.dealerTotal}")

            if self.dealerTotal > 21:
                self.dealer_bustState = True
            elif self.dealerTotal == 21:
                self.dealer_blackJacksState = True

        logging.debug(f"""dealer_bustState: {self.dealer_bustState}
                dealer_blackJacksState: {self.dealer_blackJacksState}
                dealerTotal: {self.dealerTotal}""")

        return self.dealer_bustState, self.dealer_blackJacksState, self.dealerTotal

    def D_resetHand(self): # sets the dealerCards to an empty list and the dealerTotal to 0
        self.dealerCards.clear()
        self.dealerTotal = 0
        self.dealer_bustState = False
        self.dealer_blackJacksState = False
        logging.debug(f'Reset Dealer Hand: {self.dealerCards} Total: {self.dealerTotal}')


class GameEngine(object):
    def __init__(self):
        print('\nThis is a game of black jack. \n')

        self.Deck = cardDeck()
        self.P = Player()
        self.D = Dealer()

    def Play(self): # Main function for controlling the high level logic of the game
        self.P.P_firstDraw()
        self.D.D_firstDraw()

        p_bustState, p_blackJacksState, p_playerTotal = self.P.P_HitHold()
        d_bustState, d_blackJacksState, d_dealerTotal = self.D.D_finalDraw(p_bustState, p_blackJacksState, p_playerTotal)

        self.endstate(p_bustState, p_blackJacksState, p_playerTotal, d_bustState, d_blackJacksState, d_dealerTotal)
        self.playAgain()
        
    def endstate(self, p_bust, p_blackjack, p_total, d_bust, d_blackjack, d_total): # takes data from self.Play() and decides the out come of the game
        self.p_bust = p_bust
        self.p_blackjack = p_blackjack
        self.p_total = p_total

        self.d_bust = d_bust
        self.d_blackjack = d_blackjack
        self.d_total = d_total

        winState = False
        logging.debug('END STATE')

        # Checks the final winstate of the game
        if d_bust == True:
            winState = True
        elif d_blackjack == True:
            winState = False
        elif d_total > p_total:
            winState = False

        elif p_bust == True:
            winState = False
        elif p_blackjack == True:
            winState = True
        elif p_total > d_total:
            winState = True
        else:
            pass

        # prints out win or lose statements
        if winState == True:
            print('\nWIN: You win')
        else:
            print('\nLOSE: You lose')

    def playAgain(self): # ask the player if they would like to play again if 'y' resets variables to their start state(player/dealer hand empty and cardDeck has all the cards)
        logging.debug('playAgain')

        playAgain_input = input("\nWould you like to play again: ")
        print()

        # Error check that user input is valid
        while playAgain_input != 'y' and  playAgain_input != 'n':
            print('ERROR; must enter either \'y\' or \'n\'.')
            playAgain_input = input()

        if playAgain_input == 'y': # reset game
            self.Deck.resetDeck()
            self.P.P_resetHand()
            self.D.D_resetHand()
            self.Play()
        else: # exit game
            print('DONE: Good bye')
            quit()

# Main Call
if __name__ == '__main__':
    G = GameEngine()
    G.Play()