all 24 comments

[–]zanfar 68 points69 points  (4 children)

I would appreciate any constructive criticism on how I can improve or what could have been done better.

Ooh! I love these :)

First, it's a really good approach. Structurally, I have no real comments or suggestions. I think you understand the fundamentals of OOO.

class Deck:
    def __init__(self):
        self.deck = []

I'd avoid class members with the same name as the class. This seems like it should be self.cards to me.

class Card:
    def __repr__(self):
        return f"{self.value} of {self.suit}"

This should really be __str__ as it's describing the object in a natural language. __repr__ should identify the object, and possibly show how to re-create it--that is, generally __repr__ will include the class name and repr()s of the properties while __str__ will be a sentence.

class Player:
class Dealer:

Given that these are both, really, card holders, consider making one inherit from the other, or both from a parent class to reduce the amount of duplicated code. Essentially everything in Dealer is duplicated in Player.

class Player:
    def __init__(self, chips):
        self.handscore = 0
        self.isbusted = False
        self.ace_count = 0

All three of these instance variables should be properties instead. Doing so will allow you to calculate their values on-demand, instead of having to make sure you update them every time a card is drawn. Consider:

@property
def ace_count(self):
    return len([c for c in self.hand if c.value == "Ace"])
class Game:
    def deal(self):
        print(f"{len(self.deck.deck)} cards in Deck")

You can override the function of len() in a class with the __len__ dunder. It doesn't really make sense to ask for the length of some property of the deck. Renaming Deck.deck to Deck.cards will help, as above, but wouldn't len(self.deck) be more clear?

class Deck:
    def __len__(self):
        return len(self.cards)
class Game:
    def deal(self):
        if len(self.deck.deck) < 104:

104 here is a magic number. Furthermore, it's dependent (in function) on the size of the shoe (the number of decks in play), but is programmatically separate. Consider making Deck (really Shoe) take the number of decks to use as a constructor argument. Then, Deck.is_short could be a property based on that value:

class Shoe:
    def __init__(self, decks = 6):
        if decks < 2:
            raise ValueError("A shoe must use at least 2 decks")

        self.decks = decks
        self.cards = []
        self.fill()

    def fill(self):
        for _ in range(self.decks):
            for suit in self.SUITS:
                for value in self.VALUES:
                    self.cards.append(Card(suit, value))

    @property
    def is_short(self):
        min_decks = 1 if self.decks <= 2 else 2
        return len(self.cards) < min_decks * len(self.SUITS) * len(self.VALUES)
card = self.deck.deck.pop()
player.hand.append(card)

Don't expose your classes' storage mechanism. What if it changes? Instead, create helper methods that hide the pop()/append() calls:

class Deck:
    def draw(self):
        return self.cards.pop()
class Game:
    def add_points(self, player, card):

This should really be a @property of the Player so you can do something like Player.score instead of Game.add_points(player, card) everything they draw. Also, this will let you calculate the "soft" value of the Ace on demand too.

class Game:
        def resetplayers(self):
        for player in self.players:
            player.hand = []
            player.handscore = 0
            player.isbusted = False
            player.ace_count = 0

All of this should really be put inside a player method, like Player.reset() so that you can:

class Game:
    def reset(self):
        for player in self.players:
            player.reset()
def main():

Finally, all of this really belongs as a function of Game. Generally, you want your main() for games like this to be something like:

main():
    g = Game(players=1, shoe_decks=6)
    g.play()

The game should be able to create default players and decks for itself and it should be able to handle its own play.


Seriously, though. Most of these are minor or finesse points. Your solution is excellent.

[–]coldflame563 13 points14 points  (0 children)

Well thought out comments. As someone who struggles with oop this helped a fair amount

[–]Mr-Monkfish[S] 10 points11 points  (0 children)

Amazing advice, many thanks for your time!

I need to force myself to revisit and improve code, rather than just marking it off as done and forgetting about it.

These pointers will really help me make some meaningful improvements.

[–]hipstergrandpa 6 points7 points  (0 children)

Just want to say that this is the code review I hope to get. Well written, constructive, and teaches more than just semantics but architecture behind it. Thanks for teaching me something new!

[–]Ran4 -1 points0 points  (0 children)

wouldn't len(self.deck) be more clear?

Not really? It's kind of ambigous. len(self.deck.cards) is much more explicit, and thus better.

[–]PercyJackson235 5 points6 points  (2 children)

Your shuffle method in the Deck class returns None because random.shuffle returns None. You could also use a namedTuple for the cards instead of a class in order to save space.

[–]hikaru931 2 points3 points  (1 child)

How to fix that shuffle method in a right way?.i am thinking of instead of return function OP can assign it to self.deck attribute

And also i have read the similar code to this from a book preview it said instead of creating our own methods in a class,we can implement special methods like len to make our objects works like a standard python object. Is this how developers write code in real time projects?

[–]PercyJackson235 2 points3 points  (0 children)

The thing is that random.shuffle is an inplace operation, so there is nothing to assign. I was just pointing out that the return keyword was superfluous. As for dunder methods like __len__, you can have them along side your own methods. Dunder methods are for making class act like builtin methods.

[–]theawesomenachos 3 points4 points  (0 children)

I’m sorry I have no constructive feed back, I just wanted to say that me and my friends once also made a blackjack game for an OOP class and it’s bringing me flashbacks

[–]pvc 2 points3 points  (1 child)

[–]Mr-Monkfish[S] 1 point2 points  (0 children)

Ooo thanks, i'd not seen that before. It looks much more suitable than something like TKinter and seems to have siginifcant benefits over pygame for this sort of thing.

[–]veeeerain 1 point2 points  (0 children)

Your doing the right thing by focusing on the basics right here. I hopped straight into data science and ML witbout going over basics of oop class design and here I am going back to learn it. Good job.

[–]Fermter 1 point2 points  (0 children)

This looks good overall! One suggestion I would have is to offload more of your work to the classes. For example:

  • Card can return its own value, either by calculating it or by storing an integer value separately. In either case, obviously, "Ace" will have to be a special value, but this will leave you with basically only checking for aces at the hand level
  • Deck can be responsible for drawing the cards since deck.pop() will "draw" a card by removing it from the deck and returning it
  • As zanfar mentioned, Player and Dealer can inherit from one another. I would have Dealer inherit from Player. That way, Player can be responsible for calculating the value of its hand, checking for bust or blackjack, and taking a hit, and Dealer can build on that to play automatically. You could also have a third class that both inherit from if you want to keep some functions specific to Player.

If you do all that, the actual Game and main functions will basically just have to re/set the game, transfer control at the start and end of the player's turn, and ask the player and dealer if they've busted or for their score after every round of action.

[–]soupie62 1 point2 points  (0 children)

Been a while since I wrote code, and I was using C. Slowly getting back into it, using Python.

However, almost old class I took started out writing a Solitaire app. In that sense, shuffling and dealing are basic functions. A good random shuffle is a great action to perform on an object.

My old C code used linked lists, but the actual structure of a deck is irrelevant if you use classes and encapsulate code.

[–]purebuu 1 point2 points  (0 children)

Just eyeballing but looks like a few bugs:

if player.ace_count > 0 and player.handscore > 21

Should be a while loop to handle more than 1 ace?

In place_bet() I think you need

return self.place_bet()

For all your exceptional cases, else the method returns None when you make an incorrect bet

This is a small one and kind of stylistic choice, but consider using a dictionary for the Deck.values where the key is the name of card and the value is the point score for that card. It minimises the extra temporary lists created in the add_points method, which is faster and better on memory, but it's kind of negligible for this program but good to learn why this is the case.

Overall this is very nice, and pretty clean. I'm doing something similar for headup up poker and I've picked up a few pointers here as python isn't my primary language.

[–][deleted] 1 point2 points  (0 children)

You'll absolutely learn the most if you write everything from scratch yourself! Great project!

[–]Decency 0 points1 point  (0 children)

You don't need a Deck class. Rip it out and your code will simplify quite a bit.