10
\$\begingroup\$

I've been programming for 2ish months and I was programming Blackjack on a school computer because I was bored. If you guys could check to see if there are any problems with it that would be great!

from turtle import *
import random

updater = Turtle()
updater.ht()
updater.speed(0)
updater.up()
updater.goto(30, 0)
speed(0)
playerMoney = 1000
ht()

class Card:

  def __init__(self, number = None, suit = None):
    if suit == None:
      suit = random.choice(['D', "S", 'C', 'H'])
      number = random.choice(['A',2,3,4,5,6,7,8,9,10,'J','Q', 'K'])
      if number in ['K', 'Q', 'J']:
        number = [number, 10]
      else:
        number = [number, number]
    self.suit = suit
    self.number = number

  def getSuit(self):
    return self.suit

  def getNumber(self):
    return self.number[1]

  def writing(self,x,y):
    up()
    goto(x,y)
    down()
    write (self.number[0])
    up()
    right(90)
    forward(25)
    left(90)
    write(self.suit)
    left(90)
    forward(50)
    right(90)

  def ace(self):
    if 'A' in  self.number:
      x = int(input('Would you like your ace to equal 11 or 1?'))
      if x == 11:
        self.number[1] = 11
      elif x ==1:
        self.number[1] = 1
      else:
        ace(self)

def setup(x, y):
  up()
  goto(x, y)
  down()
  for i in range(2):
    for i in range(2):
      forward(50)
      right(90)
      forward(100)
      right(90)
    forward(50)

def drawcard(x, y):
  up()
  goto(x-25, y+30)
  down()
  for i in range(2):
    forward(50)
    right(90)
    forward(100)
    right(90)

def PcardUpdate(x, sum, playerMoney):
  updater.clear()
  updater.goto(30, 20)
  updater.write('Player Card Sum: ' + str(x))
  updater.goto(30, 0)
  updater.write('Dealer Card Sum:' + str(sum))
  updater.goto(30, -20)
  updater.write('Player Money: ' + str(playerMoney))

def bust(x, playerMoney):
  if x > 21:
    print 'BUST'
    return 0

def HitorNah(bob):
  x = -25 + (bob* 50)
  choice = input('Hit or stand?')
  while choice.lower() not in ['hit', 'stand']:
    choice = input('Hit or stand')
  if choice.lower() == 'hit':
    print 'hit it is'
    bob += 1
    XtraCard = Card()
    XtraCard.writing(x,-55)
    drawcard(x, -55)
    XtraCard.ace()
    return [XtraCard.getNumber(), bob]

  elif choice.lower() == 'stand':
    print 'ok'
    return [0, 0]

  else:
    print "I didn't understand"
    HitorNah(bob)

def DHit(bob, playerhand, sum):
  x = -25 + (bob* 50)
  bob += 1
  XtraCard = Card()
  XtraCard.writing(x-55, 100)
  drawcard(x, 120)
  if XtraCard.getNumber() == 'A':
    if sum + 11 > 21:
      return [1, bob]
    elif sum + 11 == 21:
      return [11, bob]
    else: 
      return [11, bob]
  return [XtraCard.getNumber(), bob]

def Dchecker(sum, x):
  if sum > 21:
    return 'dealer bust'
  elif sum > x and sum < 21:
    return 'dealer win'
  elif sum == x:
    return 'push'
  elif sum == 21:
    return 'dealer win'

def howmuch(player1): #how much the player wants to bet
  bet = int(input('How much would you like to bet? (whole number)'))
  while bet > player1:
    bet = int(input('You cant bet more than you have'))
  while bet < 0:
    bet = int(input('How much would you like to bet? (whole number positive)'))
  while bet == 0:
    print 'you cant bet nothing...'
    bet = int(input('How much would you like to bet? (whole number positive)'))
  return bet

def QuitorNah():
  choice = input('Would you like to quit?')
  while choice.lower() not in ['yes', 'no']:
    print "Sorry I didn't understand."
    choice = input('Would you like to quit')
  if choice.lower() == 'yes':
    print 'Ok, bye'
    return 'bye'
  elif choice.lower() == 'no':
    print 'Ok'
    return

def round(playerMoney):
  PcardUpdate(0, 0, playerMoney)
  playerMoney = playerMoney
  bet = howmuch(playerMoney)
  PcardUpdate(0, 0, playerMoney)
  playerMoney -= bet
  counter = [0, 1]
  joe = [0, 1]
  setup(-100, 150)
  setup(-100, -25)

  #dealer cards
  Dcard1 = Card()
  Dcard1.writing(-75, 100)

  #player cards
  Pcard1 = Card()
  Pcard2 = Card()

  #writes the card down
  Pcard1.writing(-75, -55)
  Pcard2.writing(-25, -55)

  #checks to see if Pcard is ace
  Pcard1.ace()
  Pcard2.ace()
  x = Pcard1.getNumber() + Pcard2.getNumber()
  if x == 21:
    print 'Blackjack!'
    playerMoney = playerMoney + 2*bet
    return playerMoney
  PcardUpdate(x, Dcard1.getNumber(), playerMoney)
  while counter != [0,0]:
    counter = HitorNah(counter[1])
    x += counter[0]
    PcardUpdate(x, Dcard1.getNumber(), playerMoney)
    z = bust(x, playerMoney)
    if z == 0:
      return playerMoney

  sum = Dcard1.getNumber()
  if sum == 'A':
    sum = 11
  result = 'bob'

  while result not in ['dealer bust', 'dealer win', 'push']:
    joe = DHit(joe[1], x, sum)
    sum += joe[0]
    PcardUpdate(x, sum, playerMoney)
    result = Dchecker(sum, x)

  if result == 'dealer bust':
    print 'Dealer bust!'
    playerMoney += 2*bet
    return playerMoney

  elif result == 'dealer win':
    print 'Dealer win!'
    return playerMoney

  elif result == 'push':
    print 'Push!'
    playerMoney += bet
    return playerMoney

while True:
  playerMoney = round(playerMoney)
  if playerMoney == 0:
    print 'You ran out of money :('
    break
  lol = QuitorNah()
  clear()
  if lol == 'bye':
    break
\$\endgroup\$
2
  • \$\begingroup\$ I just tried to run your program, but I can't seem to get it to work. Running on Python 3 results in a SyntaxError, but on Python 2 after the 'Hit or stand?' prompt, a NameError is raised. Did you mean to use raw_input? \$\endgroup\$
    – Daniel
    Commented Apr 10, 2018 at 10:17
  • 1
    \$\begingroup\$ sorry for a late response, the canvas I coded it on (trinket.io), supported a hybrid version of python that mixed elements from Python 2 and Python 3. So when it's standard to use raw_input for the Hit or stand prompt, i just used input. \$\endgroup\$
    – Jerry Zhou
    Commented Apr 13, 2018 at 23:12

1 Answer 1

2
\$\begingroup\$

There's good news and bad news.

For beginner code, this is fairly well written. The graphics are intuitive and the controls straightforward.

However, the site you've used (trinket.io) allows a kind of Python I haven't seen before. Your code will fail both in Python 2 and in 3. Before we do anything else, let's make sure it works on at least one of those versions so you're no longer restricted to an odd flavour from an odd site.

Note: If anyone happens to know more about this flavour of Python, please leave a comment.

This will be a review in 3 parts:

  1. Make it work in 'normal' Python.
  2. Iron out the odd things and inconsistencies in the code.
  3. Remarks from a user-experience point of view.

Don't be discouraged by the length of the review. Most of the points mentioned in section 2 and 3 are only there because they'll hurt when you start writing larger programs.

At the end of the review a partially fixed piece of code is included to illustrate some of the points. I'll leave it up to you to find the differences and implement the rest of the points mentioned.


Make it work

The code looks more like Python 3 than 2, so let's stick with that one for now. The main reason your code won't work in Python 3, is because of your print statements. In 2, print is an exotic statement. In 3, print() is a proper function. So let's make them all function calls.

That's all!


Odd things and inconsistencies

Repeat after me:

Consistent code is readable code.
Readable code is maintainable code.
Maintainable code is good code.

Most of the things I'll mention are written down as guidelines in the Python Enhancement Proposals (PEP). Read more about those here.

Main issues (in order of appearance, not of importance):

  1. Bugs
    1. Input
    2. Aces
    3. Random cards
  2. Imports
    1. Order
    2. Blanket imports
  3. Whitespace
    1. Indentation
    2. Blank lines
    3. Operators
    4. Comments
  4. Comparison
  5. Strings
  6. Naming
    1. Illegal names
    2. Functions and variables
  7. Miscellaneous

Bug

Input

A bug in both the original and the Python 3 version is apparent when you want to bet more money than available:

ValueError: invalid literal for int() with base 10: '' on line 142 in main.py

Or when the input isn't an integer at all (but, for example, a float or string):

ValueError: invalid literal for int() with base 10: '150.1' on line 140 in main.py

Your program will crash at invalid input. 2 possible ways out: validate input before trying to put a square block through a round hole, or keep trying till a round block is used.

Aces

When an Ace is drawn, you check whether the player wants the ace to count as 11 or 1 point. I'm not sure what version of Blackjack you're used to, but around here the value of the Ace can change at will and doesn't have to be set at draw. For example, if I initially draw an Ace and 7, I'll consider the hand to be 18 and 8 at the same time. Should I decide to draw another card and it's a 3, the Ace is 11 and the hand 21. If it's any higher than a 3, the Ace turns into a 1 to avoid going bust. Your program doesn't account for this.

Random cards

At one point in the game both the dealer and I had the Ace of Diamonds. Your random selector should account for that. Now I know for certain the dealer is cheating.

Part of the game is to know what cards are in play so you can calculate your chances. If the dealer has a King and you have 2 Kings, what are the odds of the dealer having another King? This gets even better when playing with multiple players at the same table. The more cards are on the table, the less you have to guess about the cards remaining.

I don't expect a beginner program to track the cards over multiple rounds (just assume the cards will be shuffled after every round and it doesn't matter anymore), but at the moment your program allows to have 5 Aces at the same time on the field.

If you're truly interested in Blackjack, I recommend reading "Bringing down the house" by Ben Mezrich (2002). Or watch the movie adaptation "21" (2008). The book is much better.


Imports

Order

The order of imports:

  1. Standard library imports
  2. Third party imports
  3. Local application imports

Does it matter when you only have 2 import lines? No. Does it keep things easier when you have 20 of them? Definitely.

from turtle import *
import random

Turns into:

import random
from turtle import *

Blanket imports

Do you know what you're importing when you use from turtle import *? The entire turtle. You don't need the entire turtle. This is what you need:

from turtle import Turtle, ht, speed, write, clear, up, down, left, right, goto, forward

And yes, that's quite a mouthful. And longer than 80 characters. So, for small programs which only import from 1 library this way, there's no problem. Import 6 different libraries like that and you're importing a lot of stuff you don't need. What would happen if 2 or 3 libraries have a clear function? By writing out which ones you need, you immediately realize the problem should such a thing happen.

Of-course, the obvious solution is simply importing like this:

import random
import turtle

Yes, this will lead to somewhat longer calls. updater = Turtle() turns into updater = turtle.Turtle() and ht() turns into turtle.ht(). But the moment you get multiple from lib import * statements in your code, you got a problem.


Whitespace

Indentation

In Python, indentation is important. Forget to indent a line of code the proper amount and your code will do something different altogether (if it still does anything at all). To notice indentation problems, it helps if the indentation is enough to be easily noticeable but not so much that it will make the lines unnecessarily long. 4 spaces is good. If you prefer using tabs during programming, set your editor tabs-to-spaces with a tab length of 4. If you're not sure what editor to use, give Atom a go. Runs on everything.

Blank lines

The amount of blank lines between pieces of code help to see in just a glance where you are in the program. While not as important as indentation (blank lines, or vertical whitespace, won't make or break your program), every bit helps. Remember what I said about consistency?

For example, between imports and the rest of your code there'll always be 2 blank lines. Between methods? A single blank line. Between global functions? 2 lines. Between class declaration and the first method? Zero lines.

Operators

All operators get spaces around them, except around the = when assigning default values. After a comma there's always a space, but never more than one. So this:

number = random.choice(['A',2,3,4,5,6,7,8,9,10,'J','Q', 'K'])

Should've been:

number = random.choice(
    ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']
)

Comments

After the # to start a comment, leave a space before writing down the actual comment. Leave (at least) 2 spaces before the #.

some_var = 'foo'  # like this

Comparison

"Comparisons to singletons like None should always be done with 'is' or 'is not', never the equality operators."

As explained here, here and here.

A class is free to implement comparison any way it chooses, and it can choose to make comparison against None mean something (which actually makes sense; if someone told you to implement the None object from scratch, how else would you get it to compare True against itself?).


Strings

Roughly speaking, Python has 3 methods of indicating a string.

`keys` and single characters go between single quotes.
"sentences go between double quotes"
"""docstrings and multi-line strings
   go between tripled double quotes"""

Also nicely illustrated here.

And whatever you do, be consistent. So no more of this:

suit = random.choice(['D', "S", 'C', 'H'])

That S goes between single quotes, just like the rest of them.


Naming

Illegal names

The following is quite important.

Don't EVER name a variable or function after a built-in. It confuses everyone including (possibly, although you usually get away with it) the interpreter.

round
sum

You got those as function and variable names. Don't.

Consider naming something after a built-in illegal.

Functions and variables

Function, method and variable names are all snake_case. Lowercase with underscores as separators. If you feel the need to indicate a function is part of a group of functions, like you did with the dealer functions, consider making them methods part of a class. Problem solved.

Global variables which will never changed (think constants, except Python doesn't have them) are in UPPER_CASE.

I don't know what bob and joe are doing in your program, but names should be effective in telling the world what they mean. Those don't cut it.

joe = [0, 1]

Not a clue.

QuitorNah()

Dubious at best. Don't try to make your function names funny. We got comments for that.

z = bust(x, playerMoney)
if z == 0:
    return playerMoney

Why is that called z? Only use z if it has a meaning, like in a 3D graph as third axis. Here, it's a bad name. Might even want to drop the z entirely and simply return when bust(x, playerMoney) is False.


Miscellaneous

There's a lot of code in there that looks like it's not doing anything.

def round(playerMoney):
    PcardUpdate(0, 0, playerMoney)
    playerMoney = playerMoney  # <----

When you run out of money, the game will halt. Not really a problem, but somewhat unexpected. After all, you keep asking me after every round whether I want to quit. Why not asking me at the end of each game whether I want a new one?

On top of that, consider leaving a trailing space or newline behind a question, so the answer isn't typed directly next to the question. Makes it all just a little neater.

I know most people have a nice big monitor nowadays, but it's recommended to stay below 80 characters wide when writing Python. Better to use a couple of lines more than to have a line of 200 characters wide. Scrolling up and down is easy, scrolling sideways is more tedious.

While I mentioned being consistent in your code, being consistent in what you show the user is perhaps just as important. Use proper capitalisation and spelling whenever possible.

Oh, and is your Card.getNumber() doing what you think it's doing?

def getNumber(self):
    return self.number[1]

self.number shouldn't be a list.


There's plenty more, but I think I gave you enough examples on how to improve your code. Here's your exact code, just a little prettier. Good luck implementing the rest of the points and I might write next review about actual code flow :-)

import random
import turtle


updater = turtle.Turtle()
updater.ht()
updater.speed(0)
updater.up()
updater.goto(30, 0)
turtle.speed(0)
playerMoney = 1000
turtle.ht()


class Card:
    def __init__(self, number=None, suit=None):
        if suit is None:
            suit = random.choice(['D', 'S', 'C', 'H'])
            number = random.choice(
                ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']
            )
            if number in ['K', 'Q', 'J']:
                number = [number, 10]
            else:
                number = [number, number]
        self.suit = suit
        self.number = number

    def getSuit(self):
        return self.suit

    def getNumber(self):
        return self.number[1]

    def writing(self, x, y):
        turtle.up()
        turtle.goto(x, y)
        turtle.down()
        turtle.write(self.number[0])
        turtle.up()
        turtle.right(90)
        turtle.forward(25)
        turtle.left(90)
        turtle.write(self.suit)
        turtle.left(90)
        turtle.forward(50)
        turtle.right(90)

    def ace(self):
        if 'A' in self.number:
            x = int(input("Would you like your ace to equal 11 or 1?"))
            if x == 11:
                self.number[1] = 11
            elif x == 1:
                self.number[1] = 1
            else:
                ace(self)


def setup(x, y):
    turtle.up()
    turtle.goto(x, y)
    turtle.down()
    for _ in range(2):
        for _ in range(2):
            turtle.forward(50)
            turtle.right(90)
            turtle.forward(100)
            turtle.right(90)
        turtle.forward(50)


def drawcard(x, y):
    turtle.up()
    turtle.goto(x-25, y+30)
    turtle.down()
    for _ in range(2):
        turtle.forward(50)
        turtle.right(90)
        turtle.forward(100)
        turtle.right(90)


def PcardUpdate(x, card_sum, playerMoney):
    updater.clear()
    updater.goto(30, 20)
    updater.write("Player Card sum: " + str(x))
    updater.goto(30, 0)
    updater.write("Dealer Card sum: " + str(card_sum))
    updater.goto(30, -20)
    updater.write("Player Money: " + str(playerMoney))


def bust(x, playerMoney):
    if x > 21:
        print("Bust!")
        return 0


def is_hit(current_player):
    x = -25 + (current_player * 50)
    choice = input('Hit or stand?')
    while choice.lower() not in ['hit', 'stand']:
        choice = input('Hit or stand')
    if choice.lower() == 'Hit':
        print('Hit it is')
        current_player += 1
        XtraCard = Card()
        XtraCard.writing(x, -55)
        drawcard(x, -55)
        XtraCard.ace()
        return [XtraCard.getNumber(), current_player]
    elif choice.lower() == 'stand':
        print('Ok')
        return [0, 0]
    else:
        print("I didn't understand")
        is_hit(current_player)


def DHit(current_player, playerhand, card_sum):
    x = -25 + (current_player * 50)
    current_player += 1
    XtraCard = Card()
    XtraCard.writing(x-55, 100)
    drawcard(x, 120)
    if XtraCard.getNumber() == 'A':
        if card_sum + 11 > 21:
            return [1, current_player]
        elif card_sum + 11 == 21:
            return [11, current_player]
        else:
            return [11, current_player]
    return [XtraCard.getNumber(), current_player]


def Dchecker(card_sum, x):
    if card_sum > 21:
        return 'dealer bust'
    elif card_sum > x and card_sum < 21:
        return 'dealer win'
    elif card_sum == x:
        return 'push'
    elif card_sum == 21:
        return 'dealer win'


def howmuch(player1):  # how much the player wants to bet
    bet = int(input("How much would you like to bet? (whole number)"))
    while bet > player1:
        bet = int(
            input("You can't bet more than you have.")
        )
    while bet < 0:
        bet = int(
            input("How much would you like to bet? (whole positive number)")
        )
    while bet == 0:
        print("No empty bets allowed.")
        bet = int(
            input("How much would you like to bet? (whole positive number)")
        )
    return bet


def want_to_quit():
    choice = input("Would you like to quit?")
    while choice.lower() not in ['yes', 'no']:
        print("Sorry I didn't understand.")
        choice = input("Would you like to quit")
    if choice.lower() == 'yes':
        print("Ok, bye")
        return 'bye'
    elif choice.lower() == 'no':
        print("Ok")
        return


def play_round(playerMoney):
    PcardUpdate(0, 0, playerMoney)
    playerMoney = playerMoney
    bet = howmuch(playerMoney)
    PcardUpdate(0, 0, playerMoney)
    playerMoney -= bet
    counter = [0, 1]
    joe = [0, 1]
    setup(-100, 150)
    setup(-100, -25)

    # dealer cards
    Dcard1 = Card()
    Dcard1.writing(-75, 100)

    # player cards
    Pcard1 = Card()
    Pcard2 = Card()

    # writes the card down
    Pcard1.writing(-75, -55)
    Pcard2.writing(-25, -55)

    # checks to see if Pcard is ace
    Pcard1.ace()
    Pcard2.ace()
    x = Pcard1.getNumber() + Pcard2.getNumber()
    if x == 21:
        print("Blackjack!")
        playerMoney = playerMoney + 2 * bet
        return playerMoney
    PcardUpdate(x, Dcard1.getNumber(), playerMoney)
    while counter != [0, 0]:
        counter = is_hit(counter[1])
        x += counter[0]
        PcardUpdate(x, Dcard1.getNumber(), playerMoney)
        z = bust(x, playerMoney)
        if z == 0:
            return playerMoney

    card_sum = Dcard1.getNumber()
    if card_sum == 'A':
        card_sum = 11
    result = 'current_player'

    while result not in ['dealer bust', 'dealer win', 'push']:
        joe = DHit(joe[1], x, card_sum)
        card_sum += joe[0]
        PcardUpdate(x, card_sum, playerMoney)
        result = Dchecker(card_sum, x)

    if result == 'dealer bust':
        print('Dealer bust!')
        playerMoney += 2*bet
        return playerMoney

    elif result == 'dealer win':
        print('Dealer win!')
        return playerMoney

    elif result == 'push':
        print('Push!')
        playerMoney += bet
        return playerMoney

while True:
    playerMoney = play_round(playerMoney)
    if playerMoney == 0:
        print("You ran out of money :(")
        break
    last_round = want_to_quit()
    turtle.clear()
    if last_round == 'bye':
        break
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks for the response! I figured out that trinket.io doesn't use traditional python so I moved onto the pycharm IDE. The misc stuff you pointed out really helped me cut out some unnecessary code. \$\endgroup\$
    – Jerry Zhou
    Commented May 14, 2018 at 23:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.