Source code for blackjack21.players

# MIT License
#
# Copyright (c) 2022 Rahul Nanwani
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import Union, List

from .deck import Card
from .exceptions import PlayFailure

__all__ = (
    "PlayerBase",
    "Player",
    "Hand",
    "Result",
)
Hand = List[Card]
Result = Union[int, None]


[docs]class PlayerBase: """Base/Split player class :param name: str :param bet: int :param table: Table class object """ __slots__ = ( "__name", "__bet", "__hand", "__bust", "__stand", "__table", ) def __init__(self, name: str, bet: int, table): self.__name = name self.__bet = bet self.__hand = [] self.__bust = False self.__stand = False self.__table = table # dunder methods def __repr__(self): return self.__name def __str__(self): return self.__name # properties @property def name(self) -> str: """Player name""" return self.__name @property def bet(self) -> int: """Player's bet amount""" return self.__bet @property def hand(self) -> Hand: """List of Card class objects in the player's hand""" return self.__hand @property def bust(self) -> bool: """Player's bust status""" return self.__bust @property def stand(self) -> bool: """Player's stand status""" return self.__stand @property def total(self) -> int: """Player's hand total""" values = [card.value for card in self.hand] aces = values.count(11) total = sum(values) while aces > 0 and total > 21: total -= 10; aces -= 1 return total @property def result(self) -> Result: """Player result Negative implies the player loses, positive implies the player wins, and 0 implies a draw. The key below shows the reasons for the result: :key -2: Player busts :key -1: Player has less than the dealer :key 0: Player has the same total as the dealer and both are not bust :key 1: Player has 21 (aka. blackjack) :key 2: Player has greater than the dealer :key 3: The dealer is bust :key None: Dealer has not finished playing yet """ if not any((self.__table.dealer.bust, self.__table.dealer.stand,)): return None elif self.bust: return -2 # Player is bust elif self.total < self.__table.dealer.total and not self.__table.dealer.bust: return -1 # Player has less than the dealer elif self.total == 21: return 1 # Player has 21 (aka. blackjack) elif self.total > self.__table.dealer.total: return 2 # Player has greater than the dealer elif self.total < self.__table.dealer.total and self.__table.dealer.bust: return 3 # The dealer is bust else: return 0 # Player has the same total as the dealer and both are not bust # methods
[docs] def play_hit(self) -> Card: """Deals another card to the player if the player is not busted or has played stand :return: Card class object """ if not (self.stand or self.bust): card = self.__table.deck.draw_card() self.__hand.append(card) if self.total > 21: self.__bust = True if self.total == 21: self.__stand = True return card
[docs] def play_stand(self) -> bool: """Stop further dealing any cards to the player before being busted. :return: bool """ if not self.bust: self.__stand = True return True return False
[docs]class Player(PlayerBase): """Player class (Inherited from PlayerBase class) :param name: str :param bet: int :param table: Table class object """ __slots__ = ( "__bet", "__split", "__table", ) def __init__(self, name: str, bet: int, table): super().__init__(name, bet, table) self.__bet = bet self.__split = None self.__table = table # properties @property def bet(self) -> int: return self.__bet @property def split(self) -> Union[PlayerBase, None]: """PlayerBase class object if split is played else none""" return self.__split @property def can_double_down(self) -> bool: """bool if the player is eligible to play double down""" return True if len(self.hand) == 2 and not self.split else False @property def can_split(self) -> bool: """bool if the player is eligible to play split""" if len(self.hand) == 2: ranks = [card.rank for card in self.hand] return ranks[0] == ranks[1] # methods
[docs] def play_double_down(self) -> Card: """Double down can be played only on the first turn by doubling the bet amount and will hit only once :return: Card class object :raises PlayFailure: if player is not eligible to play this """ if self.can_double_down: self.__bet *= 2 card = self.play_hit() self.play_stand() return card else: raise PlayFailure(self.name, "double down")
[docs] def play_split(self) -> PlayerBase: """Split can be played only on the first turn by splitting the hand if both the cards have the same ranks. The bet will remain the same on both the hands. If the player bets 100 initially, so after playing split the player will have placed 100 on first hand, and 100 more on the second hand. :return: Player class object :raises PlayFailure: if player is not eligible to play this """ if self.can_split: self.__split = PlayerBase(self.name, self.bet, self.__table) self.__split.play_hit() return self.__split else: raise PlayFailure(self.name, "split")