Trouble executing statement within a nested if block

Having trouble with printing the # win message and # lose message, noted by comment in following code:

def guess_checker():
    new_pen = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    egg_spots = [0, 3, 5, 7, 9]
    turns = 3         
    while turns != 0: 
        user_guess = input("Enter a nest number to check if it has an egg: ")
        if user_guess.isdigit() == False:
            print("Please enter a positive, whole number to check.")            
        elif user_guess.isdigit() == True:
            user_guess = int(user_guess)            
            if user_guess > len(new_pen):
                print("Your pen doesn't have that many nests! Try again!")
            elif user_guess in egg_spots:
                print("You found an egg! You win!")  # win message
                break
            elif user_guess not in egg_spots:
                turns = turns - 1
                print("That nest doesn't have an egg. Remaining guesses:", turns)
            elif turns == 0:
                print("You're out guesses, sorry! Better cluck next time!")  # lose message
                break
    
    print("Your pen was:")
    print(new_pen)

guess_checker()

I’ve simplified the function for readability. In truth, the lists new_pen and egg_spots are returned by another function, then passed into guess_checker() as parameters. I was originally trying to use ascii art to display the win/loss message, but I couldn’t get that to print either. The entire program functions as expected, except for the this strange anomaly (anomalous to me). Are my indents wrong? Thank you!

If you are asking input from user you should be careful:

>>> '1²'.isdigit()
True
>>> int('1²')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '1²'

For more robust solution I would go for try…except while converting to int (and checking whether int is positive)

1 Like

Your indents are ok. Some of the logic is superfluous, and some is
wrong. Let’s look:

 turns = 3
 while turns != 0:
     user_guess = input("Enter a nest number to check if it has an egg: ")
     if user_guess.isdigit() == False:

You can just say:

     if not user_guess.isdigit():

for this. But it’s ok.

         print("Please enter a positive, whole number to check.")
     elif user_guess.isdigit() == True:

You don’t need this test. You know it’s true, because the previous
if-statement didn’t match.

         user_guess = int(user_guess)
         if user_guess > len(new_pen):
             print("Your pen doesn't have that many nests! Try again!")
         elif user_guess in egg_spots:
             print("You found an egg! You win!")  # win message
             break

This (above) should work, to my eye.

         elif user_guess not in egg_spots:

Again, you don’t need this test. You know it isn’t in egg_spots
because the previous if-statement didn’t match.

             turns = turns - 1
             print("That nest doesn't have an egg. Remaining guesses:", turns)
         elif turns == 0:
             print("You're out guesses, sorry! Better cluck next time!")  # lose message
             break

This last elif can’t match, and therefore the lose message can never
print. It can’t match because you’ve got a branch for user_guess in egg_spots and a branch for user_guess not in egg_spots. There’s no
value for user_guess which fails both those tests, and therefore
control can never reach this test.

Your while loop already waits for the turns to run out. Just put the
lose message after the end of the loop.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

Oh happy days! I hadn’t thought about that. Thanks, Cameron

1 Like

I hadn’t thought of that. Thank you!

Thank you, Cameron. I’ve implemented your advice.

(Discourse trimmed my citation. Grr.) I had said: “You can just say: if not user_guess.isdigit(): for this. But it’s ok.”

I wanted to revisit this. Aivar Paalberg pointed out that it isn’t ok.

Here is a classic situation where we can choose between the Look before
You Leap (LBYL) and Ask for Forgiveness (AFF) patterns. Normally these
are presented simply as a matter of preferred style, but here the choice
highlights a real semantic difference between the approaches.

First up, the above is LBYL, because you’re examining the string to see
if you can convert it to an int.

The AFF approach looks like this:

 try:
     user_guess = int(user_guess)
 except ValueError:
     print("That is not a number!")
     continue
 ... use the number from user_guess below ...

This is AFF: try to convert to an int and handle the exception if that
fails.

Why does it matter? Because the LBYL approach tries to reproduce the
tests which take place inside the int() call. And as Aivar
demonstrated, reproduces them incorrectly in that .isdigit() will
“pass” a string which int() will still not accept.

This is not a cue to write a better if-statement test! It is a cue to
say: maybe we should not try to prevalidate this string, because
int() is what we want to achieve, and it does its own validation. So
treat it as a black box whose internals are unknown.

So here, LBYL is both reliable and inherently correct.

Why might we ever want to precheck? We do that when the function we’re
calling is not exactly what we want to achieve, but only correct for
certain inputs. Then we precheck to ensure we’re providing values
where that function will produce correct results (“correct” in the wider
context of what we want our programme to do.)

This then is an argument for making your own “convert the user input”
function: often the available libraries do not provide exactly what
you need, and you need to build someout from them.

Consider this:

 def parse_user_guess(user_guess):
     value = int(user_guess)
     # some additional checks
     if value < 0 or value > 10:
         raise ValueError(f'user guess {user_guess!r} produces a value out of range (0 through 10): {value!r}')
     return value

Thn your main code can do this:

 try:
     user_guess = parse_user_guess(user_guess)
 except ValueError:
     print("That is not a number!")
     continue
 ... use the number from user_guess below ...

without all the complex post checks (other than “is the guess
correct?”).

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like