0

So I am trying to make a simple blackjack game in python 3 and everything works just fine, except that I can't stand. If I input 2 it won't do anything. Thanks in advance. EDIT 1: Hitting works well. EDIT 2: Posted the entire script, so you can reproduce the problem I'm facing, as @roganjosh suggested.

from random import shuffle
import sys


def deal(deck, player, dealer):
    shuffle(deck)

    for _ in range(2):
        player.append(deck.pop())
        dealer.append(deck.pop())

def score(hand):
    non_aces = [c for c in hand if c != 'A']
    aces = [c for c in hand if c == 'A']

    sum = 0

    for card in non_aces:
        if card in 'JQK':
            sum += 10
        else:
            sum += int(card)

    for card in aces:
        if sum <= 10:
            sum += 11
        else:
            sum += 1

    return sum

def display_info(player, dealer, stand):
    print("Your cards: [{}] ({})".format(']['.join(player), score(player)))
    if stand:
        print("Dealer cards: [{}] ({})".format(']['.join(dealer), score(dealer)))
    else:
        print("Dealer cards: [{}] [?]".format(dealer[0]))

def results(player, dealer, hand, stand):
    if score(player) == 21 and hand:
        print("Blackjack! You won!")    
        sys.exit()    
    elif score(player) > 21:
        print("Busted. You lost!")   
        sys.exit()     
    if stand:
        if score(dealer) > 21:
            print("Dealer busted. You won!")
        elif score(player) > score(dealer):
            print("You beat the dealer! You won!")
        elif score(player) < score(dealer):
            print("You lost!")
        else:
            print("Push. Nobody wins or losses.")
        sys.exit()

def hit_stand(deck, player, dealer, hand, stand):
    print("What would you like to do")
    print("[1] - Hit\n[2] - Stand")
    choice = input("> ")
    hand = False
    if choice == '1':
        player.append(deck.pop())
    elif choice == '2':
        stand = True
        while score(dealer) <= 16:
            dealer.append(deck.pop())

if __name__ == '__main__':
    deck = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']*4
    player = []
    dealer = []
    standing = False
    first_hand = True
    deal(deck, player, dealer)
    while True:
        display_info(player, dealer, standing)
        results(player, dealer, first_hand, standing)
        hit_stand(deck, player, dealer, first_hand, standing)
martineau
  • 119,623
  • 25
  • 170
  • 301
Maria Laura
  • 103
  • 4
  • 2
    Where is `score()` defined? – roganjosh Mar 02 '19 at 08:40
  • @roganjosh Above hit_stand(). Anyway, score() works well and I thought there is no need to share it. – Maria Laura Mar 02 '19 at 08:44
  • Ok, but we can't actually run this code, so it's not a [mcve]. We can't move forwards if we don't actually see some minimal code that reproduces the error. – roganjosh Mar 02 '19 at 08:45
  • @roganjosh I posted the entire script. – Maria Laura Mar 02 '19 at 08:51
  • When you use input() and enter only numbers the "choice" variable will be int. to fix this problem just convert it to str() – Abbas Dehghan Mar 02 '19 at 08:54
  • 1
    @AbbasDehghan no it won't. `input()` _always_ returns a string. You could have tested this very quickly. Note that the question is tagged Python 3.x, so it doesn't behave like `input` from Python 2.7 - that call to `eval()` has been scrapped – roganjosh Mar 02 '19 at 08:55

2 Answers2

2

You are not checking the result after the player chooses to stand. Since you only deal() once before the while True loop, you just get an infinite condition if you choose to stand repeatedly. Calculate the score after the dealer has drawn all their cards.

def hit_stand(deck, player, dealer, hand, stand):
    print("What would you like to do")
    print("[1] - Hit\n[2] - Stand")
    choice = input("> ")
    hand = False
    if choice == '1':
        player.append(deck.pop())
    elif choice == '2':
        stand = True
        while score(dealer) <= 16:
            print(score(dealer))
            dealer.append(deck.pop())
        display_info(player, dealer, stand)
        results(player, dealer, first_hand, stand) # HERE

On a somewhat unrelated note, crashing out of the game after the final score is determined is not very elegant. You will want to look at a better construct than while True: and sys.exit() to control flow, but that's an exercise for you.

Finally, you should not be using sum as a variable name inside score() because this is a built-in function that you're redefining. Use something like total so that you don't risk masking the built-in function itself.

roganjosh
  • 12,594
  • 4
  • 29
  • 46
  • Thank you, that fixed my initial problem, but it seems that another issue appeared. If you look at display_info() you can see that if stand is true, I should be able to see the dealer's cards. But instead of that I still see a question mark instead of dealer's second card. – Maria Laura Mar 02 '19 at 09:10
  • @MariaLaura exactly the same issue. You didn't call `display_info()` a second time. I have edited. – roganjosh Mar 02 '19 at 09:14
  • @Mari that is a new problem and a new question that you can solve if you read about python scopes: https://stackoverflow.com/questions/291978/short-description-of-the-scoping-rules - modifying stand inside hit_stand will not modify standing outside of it - you need return stand as assign it to standing – Patrick Artner Mar 02 '19 at 09:15
  • 1
    @PatrickArtner in this case, I think the OP has a slightly-off mental model of what the `while True` loop actually does here. In reality, we just get a single iteration in the `while` loop, and the rest plays out in the functions before the game crashes out. With a bit more familiarity, I think there's a few small changes that could clean up the flow, but that's one I'll leave to Maria :) – roganjosh Mar 02 '19 at 09:18
  • Another fundamental issue in this code is that `hit_stand` looks like it's trying to use `stand` as an *output* variable, and there is no such thing in Python. (We can see that the value of `stand` is never read before it is assigned in this function.) One a related note, the variable `standing` is set in the main loop and then nothing else in the OP's code will ever modify its value. – Mr. Snrub Mar 02 '19 at 19:55
  • @Mr.Snrub there are issues with `stand` but not how you say. You can pass arguments to functions with whatever name you like and they will be assigned a name in the local scope. Although the `stand`/`standing` variable isn't particularly useful here, there's nothing wrong with setting it to `True` and then passing to the `results` function. – roganjosh Mar 02 '19 at 20:41
  • @roganjosh yep sure we can pass arguments and have them renamed, no problem. In the OPs code the `stand` parameter is apparently intended as an output variable as previously mentioned. Your modified code fixes that, which is good, but do note that the value of `stand` as an input variable is never used, so for the cleanest code it should be removed from the function arguments. (Not a hard requirement, but it's always good to eliminate extraneous or dead code.) – Mr. Snrub Mar 02 '19 at 20:49
  • @Mr.Snrub oh, yeah, I'm not gonna argue that. As I said in an earlier comment, there are things that Maria can clean up but it's not worth me incorporating into an answer (IMO) because they seem pretty capable at working things out themselves and that's better than just being told – roganjosh Mar 02 '19 at 20:52
0

The answer from @roganjosh is correct, but I wanted to add another note because I think it's a really important point about how Python works. It's something that people who came from a background in C/C++/Java (i.e. pretty much all of us) need to un-learn when using Python.

As I stated in the comments above, in the original code from Maria Laura, it looks like the call to hit_stand is intended to use some variables (such as stand) as output variables, and in Python we cannot have "output variables" in a function call. But Maria Laura mentioned that "hitting works well", which means that the parameter player was getting modified. So, if we cannot have "output variables", then why was the value of player getting modified by the function hit_stand?

When the code calls hit_stand, five objects are passed in to the function:

  • A list object, which is given the name deck,
  • A list object, which is given the name player,
  • A list object, which is given the name dealer,
  • A Boolean object, which is given the name hand,
  • A Boolean object, which is given the name stand

The code outside this function also has names (deck, player, dealer, first_hand, standing) pointing to these same five objects. Within the code of hit_stand the .append() method is called on the player and dealer list objects, and the .pop() method is called on the deck object, so all those objects are mutated. The names from the calling scope still point to those same objects, so those names will now see those changes.

The story for hand and stand is different. Inside the hit_stand function, hand and stand are assigned new values with the = operator. As outlined in this excellent write-up from Fredrik Lundh, the = operator in Python does not "change" a variable, it just takes an object and binds it to a name. The objects themselves did not change, rather they were replaced with new Boolean objects. So the variable standing in the outer scope is still pointing to its original Boolean object, and the variable stand within the function is pointing to a brand-new Boolean object, different from the one in the outer scope. There is nothing we can do to the variables hand and stand which will be seen in the outside scope, there cannot be such thing as "pass by reference" or "output parameter" like we have in other languages.

It's a concept that can seem very foreign at first, until we un-learn what we have learned in our C/C++/Java education.

Mr. Snrub
  • 322
  • 2
  • 13
  • So you are telling me that in hit_stand(), hand = False and stand = True does nothing to the first_hand and standing variables? If so, what are you suggesting me to do? – Maria Laura Mar 03 '19 at 07:51
  • @MariaLaura Yep exactly. I'd recommend you add a `return` statement to return all the values you're going to use. As I explained above, technically you don't need this for `deck`, `player`, and `dealer`, but it's still better to `return` these values too, to make it clear to the reader that these are function outputs. So I'd say add `return (deck, player, dealer, hand, stand)` to the end of `hit_stand()`, and change the function call to `(deck, player, dealer, first_hand, standing)=hit_stand(deck, player, dealer, first_hand, standing)`. Yes it's wordy, but minimizes the chance for confusion. – Mr. Snrub Mar 03 '19 at 21:26