from common.inputChecking.boolInput import boolInput from os import get_terminal_size, system from math import ceil import random class Card: def __init__(self, cardId, color): """ cardId goes from 1 to 1 where 1 is A and 13 is K """ self.id = cardId self.color = color cardNums = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] cardSyms = { 'spade': '♠', 'heart': '♥', 'diamond': '♦', 'club': '♣' } self.num = cardNums[cardId-1] self.sym = cardSyms[color] WIDTH = 11 def __str__(self): return f"""┌─────────┐ │{self.num}{' ' if self.id != 10 else ''} │ │ │ │ │ │ {self.sym} │ │ │ │ │ │ {' ' if self.id != 10 else ''}{self.num}│ └─────────┘""" def value(self, aceValue): cardValues = { 'A': aceValue, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 10 } return cardValues[self.num] class CardHandler: def __init__(self, cards, printSep=5, aceValue = 1): self.cards = cards self.printSeparator = printSep self.aceValue = aceValue def addCard(self, card): self.cards.append(card) def generateNewCard(self): cardTypes = range(1,14) cardColors = ['spade', 'heart', 'diamond', 'club'] self.addCard(Card(random.choice(cardTypes), random.choice(cardColors))) def _concatenateCards(self, cards): """ Concatenate several card objects into an list of sublists where each sublist contains the strings for the cards to be printed for a specific horizontal line. """ cardStringLists = [(str(card)).split('\n') for card in cards] cardHeight = len(cardStringLists[0]) linesToPrint = [[] for line in range(cardHeight)] for cardStringList in cardStringLists: [linesToPrint[line].append(cardStringList[line]) for line in range(cardHeight)] return linesToPrint def printCards(self, cardsPerLine): """ Print cards per line, horizontally aligned """ splitCards =[[] for _ in range(ceil(len(self.cards)/cardsPerLine))] for i, card in enumerate(self.cards): splitCards[i // cardsPerLine].append(card) SplitCardStrings = [self._concatenateCards(cardList) for cardList in splitCards] printCardList = lambda cardList: print('\n'.join([ (' ' * self.printSeparator).join(line) for line in cardList ])) [ printCardList(SplitCardString) for SplitCardString in SplitCardStrings ] def safePrintCards(self): """ Print the amount of cards that there is room for depending on the terminal width """ cardWidth = Card.WIDTH extendedCardWidth = (cardWidth + self.printSeparator) terminalWidth = get_terminal_size().columns isRoomForExtraCard = terminalWidth % extendedCardWidth >= cardWidth cardsPerLine = terminalWidth // extendedCardWidth + (1 if isRoomForExtraCard else 0) self.printCards(cardsPerLine) @property def cardSum(self): return sum([card.value(aceValue=self.aceValue) for card in self.cards]) @property def containsAce(self): return any([True for card in self.cards if card.id == 1]) @property def containsBlackJack(self): return any([True for card in self.cards if card.num == 'A']) \ and any([True for card in self.cards if card.value(self.aceValue) == 10]) def emptyCard(): result = Card(1,'spade') result.num = '?' result.sym = '?' return result class Blackjack: def __init__(self): self.handler = CardHandler([]) self.emptyHandler = CardHandler([emptyCard(), emptyCard()]) self.dealerHandler = CardHandler([]) self.gameResults = [0,0] self.reset() def generateNewCards(self): self.dealerHandler.cards = [] self.handler.cards = [] for _ in range(2): self.dealerHandler.generateNewCard() self.handler.generateNewCard() self.emptyHandler.cards[0] = self.dealerHandler.cards[0] def determineAceValue(self): if self.handler.cardSum < 11 and self.handler.containsAce: # 11 + 1 = 12, 11 + 11 = 22 self.handler.aceValue = self.dealerHandler.aceValue = 11 else: self.handler.aceValue = self.dealerHandler.aceValue = 1 def reset(self): self.generateNewCards() self.determineAceValue() def youWin(self): self.gameResults[0] += 1 print(""" __ __ /\ \ /\ \ __ \ `\`\\\\/'/ ___ __ __ __ __ __/\_\ ___ `\ `\ /' / __`\/\ \/\ \ /\ \/\ \/\ \/\ \ /' _ `\ `\ \ \/\ \L\ \ \ \_\ \\ \\ \ \_/ \_/ \ \ \/\ \/\ \ \ \_\ \____/\ \____/ \ \___x___/'\ \_\ \_\ \_\\ \/_/\/___/ \/___/ \/__//__/ \/_/\/_/\/_/ """) def youLose(self): self.gameResults[1] += 1 print(""" __ __ ___ /\ \ /\ \ /\_ \ \ `\`\\\\/'/ ___ __ __ \//\ \ ___ ____ __ `\ `\ /' / __`\/\ \/\ \ \ \ \ / __`\ /',__\ /'__`\ `\ \ \/\ \L\ \ \ \_\ \ \_\ \_/\ \L\ \/\__, `\/\ __/ \ \_\ \____/\ \____/ /\____\ \____/\/\____/\ \____\\ \/_/\/___/ \/___/ \/____/\/___/ \/___/ \/____/ """) def gameOver(self, gameWon): system('clear') print('\nDEALERS CARDS\n') self.dealerHandler.safePrintCards() print('\nYOUR CARDS\n') self.handler.safePrintCards() print() print('Ace value is', self.handler.aceValue) if gameWon: self.youWin() else: self.youLose() print() print(f'Wins: {self.gameResults[0]} Losses: {self.gameResults[1]}') if boolInput( 'Do you want to play again? [y/n]: ', yesNoLetters=['y','n'], error='' ): self.reset() else: exit(0) def checkIfLost(self): winningConditions = [ self.handler.containsBlackJack and not self.dealerHandler.containsBlackJack, self.dealerHandler.cardSum > 21 ] losingConditions = [ self.handler.cardSum > 21 ] if any(losingConditions): self.gameOver(gameWon=False) return True elif any(winningConditions): self.gameOver(gameWon=True) return True return False def update(self): system('clear') print('\nDEALERS CARDS\n') self.emptyHandler.safePrintCards() print('\nYOUR CARDS\n') self.handler.safePrintCards() print() print('Ace value is', self.handler.aceValue) print() if not self.checkIfLost(): if not boolInput('Continue? [y/n]: ', yesNoLetters=('y','n')): gameWon = self.dealerHandler.cardSum < self.handler.cardSum self.gameOver(gameWon=gameWon) return self.handler.generateNewCard() def loop(self): while True: self.update() if __name__ == "__main__": game = Blackjack() game.loop()