Home>

See Qiita article I tried to write it.
The basic functionality was easily made into code, but I'm wondering how to design it to expand the functionality.

# code summary
class Card
class Deck
class Player:
  def deal ()
class Dealer (Player):
  def deal ()
def blackjack ():
  # initialize deck, player, dealer
  # player.deal ()
  # dealer.deal ()
  # showdown
def confirm ()
def main ():
  while True:
    blackjack ()
    confirm ()


Assuming a one-to-one player vs dealer,
initial deal->player action->dealer action->showdowncode>initial dealandshowdownare inblackjack (), and the other two are in each classdeal ()is implemented.
Also,confirm ()outside the class is called to confirm with the player whether to draw another card withplayer.deal ().

Considering the expansion of rules, the addition of players, and the use of each class for other playing card games, features specific to blackjack can be accessed fromPlayer,DealerI think it should be divided.
I'm worried about whether I should describe all processing in the function ofblackjack (), or prepare a newBlackjackclass. Is it better to divide the processing in detail?

I have heard that processing within functions should fit within 30-40 lines.
If processing is complex, I would appreciate any advice on how to choose a function, class, or other form of implementation.

# Full code
# blackjack.py
from random import shuffle

class Card:
    suits = ["H", "D", "S", "C"]
    numbers = [i for i in range (1, 14)]
    cards_converter = {** {i: i for i in range (2, 11)},

 ** {1: "A", 11: "J", 12: "Q", 13: "K"}}
    def __init __ (self, suit, number):
        self.suit = suit
        self.number = number
    def __repr __ (self):
        return f "{self.suit}-{self.cards_converter [self.number]}"

class Deck (list):def __init __ (self):
        super () .__ init __ (Card (suit, num) for suit in Card.suits for num in Card.numbers)
        shuffle (self)

class Player:
    def __init __ (self, deck: "Deck"):
        self.deck = deck
        self.hands = []
        self._initial_deal ()
    def __repr __ (self):
        return f "{self .__ class __.__ name__}: {self.score} pts/{self.hands}"
    def _initial_deal (self)->None:
        for _ in range (2):
            self.draw ()
        print (self)
    def draw (self)->int:
        card = self.deck.pop ()
        self.hands.append (card)
        return self.score
    def deal (self)->bool:
        while confirm ("Keep drawing a card?"):
            score = self.draw ()
            print (self)
            if score == 21:
                print ("<You win!>\ n")
                return False
            elif score>21:
                print ("<You lose ...>\ n")
                return False
        print_line ()
        return True
    @property
    def score (self)->int:
        # count royal cards as 10
        score = sum (min (card.number, 10) for card in self.hands)
        # exception of ace
        if score<= 11 and 1 in {card.number for card in self.hands}:
            score + = 10
        return score

class Dealer (Player):
    def _initial_deal (self)->None:
        self.draw ()
        print (self)self.draw () # hide the second hand
    def deal (self)->bool:
        while self.score<17:
            self.draw ()
        if self.score>21:
            print (self)
            print ("<You win!>\ n") # dealer loses, and player wins
            return False
        print_line ()
        return True

def blackjack ()->None:
    # initialize/initial deal
    deck = Deck ()
    player = Player (deck)
    if player.score == 21:
        print ("<You win!>\ n")
        return
    dealer = Dealer (deck)
    # showdown
    if player.deal () and dealer.deal ():
        print (player, dealer, sep = "\ n")
        if player.score == dealer.score:
            print ("<Draw>\ n")
        elif player.score>dealer.score:
            print ("<You win!>\ n")
        else:
            print ("<You lose ...>\ n")
    print_line ()

def confirm (message: str)->bool:
    return input (f "{message} [y/n]"). lower () [0] == "y"

def print_line (length: int = 55)->None:
    print ("-" * length)

def main ():
    blackjack ()
    while confirm ("Play again?"):
        print_line ()
        blackjack ()

if __name__ == "__main__":
    main ()
  • Answer # 1

    It ’s a masterpiece.
    However, while being well-developed, there are places where it feels a little difficult to read.

    A mix of game progression logic and player behavior
    ・ Particularly concerned about the dispersion ofwin/loss determination

    In terms of the above points, solzard has already noticed in a different way.

      

    I think that Blackjack specific functions should be separated from Player and Dealer, considering the expansion of rules, the addition of players, and the use of each class for other card games.

    Think about separation of players

    Rethinkplayer rolein separating players from the game itself.
    What is the common behavior of players in a card game?

    I thought as follows.

    Draw cards from a given deck according to certain judgments

    Put/Discard your hand according to certain judgment

    Report your score according to specific judgment

    If you can separate "specific judgment", you can use the player class for other games.

    class Player:
        ...
        def draw (self, deck, draw_strategy):
            if draw_strategy (self.hand):
                card = deck.pop ()
                self.hands.append (card)
                return True # Enum may be better
            return False

    This is the so-called Strategy pattern.
    You can ask for the user's judgment by calling it as follows:

    def interactive_strategy (hand):
        print (hand)
        return input ('subtract? [y/n]') [0] == 'y'
    user.draw (deck, interactive_strategy)

    You can make a computer like this.

    def random_strategy (hand):
        return random.random ()<0.5
    def score_based_strategy (hand):
        score = calculate score
        return score<17

    However, since you will be peeking into the user's hand from an external function,
    If you are worried about it, you can create a dedicated blackjack player class.

    def BlackJackPlayer (Player):
        def draw (self, deck):
            super (). draw (self, deck, self.xxx_strategy)
        @staticmethod
        def xxx_strategy (hand):
            ...
    Think about separation of winning and losing decisions

    Score calculation is highly game dependent, so it should be separated from the player class.
    As before, you should receive a function object for calculation from the outside.

    class Player:
        ...
        def report_score (self, scorer):
            return scorer (self.hand)

    The player only declares the score, and the caller is responsible for the decision.
    If it is troublesome to do the score calculation method every time, it will be passed together at the time of instantiation.

    Caller

    Looks like this:

    def scorer (hand):
        ...
    def player_strategy (hand):
        ...
    def dealer_strategy (hand):
        ...
    ...
    #
    #
    user = Player ()
    dealer = Player ()
    deck = Deck ()
    player_to_strategy = {
        user: user_strategy, dealer: dealer_strategy
    }#
    #
    for _ in range (2):
        user.draw (deck, lambda _: True)
    dealer.draw (deck, lambda _: True)
    Reveal player and dealer cards
    dealer.draw (deck, lambda _: True)
    #
    #
    players = [user, dealer] # play order
    while True:
        for player in players:
            did_draw = player.draw (
                deck, player_to_strategy [player]
            )
            score = player.report_score (scorer)
            if 22

    I'm writing with my imagination, so I think it needs to be adjusted a little more.
    For example, we do not consider betting implementation.


      

    I'm worried about whether all the processing should be described in the function of blackjack () or whether it is better to prepare a new Blackjack class and divide the processing finely in it. is.

    I personally think that functions are enough.
    I don't feel the need to inherit or create multiple instances.

    However, if you want to implement another game, you should separate the modules.

Trends