Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a function recursively for user input

I'm trying to make a rock-paper-scissors game, and am trying to verify the input.

def player1():
    x = (raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ")).lower()
    if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
        return x[0]
    else:
        print "Error - wrong input!"
        player1() #I know I can run a While loop, but I need to run it this way.

print(player1())

If I enter the right input on the first try, everything works fine. But if I enter wrong input on first try, and enter the right input on the second time, I get None in the output, instead of the first letter of the RPS options.

What am I missing?

like image 407
JMG Avatar asked Sep 10 '25 10:09

JMG


2 Answers

You're going to want to loop on input. What you're doing currently is recursively calling player1, and the recursive case doesn't have an explicit return value (hence, None is returned).

The way you do this is simple: while there is invalid input, prompt again. I'm using a modified version of this in the vein of the "while True break" style; it accomplishes the same goal. We loop indefinitely, and if the condition we want is valid, we return; otherwise, we prompt for input and loop again.

def player1():
    while True:
        x = raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ").lower()
        if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
            return x[0]
        else:
            print "Error - wrong input!"

As an alternative to that if statement, there is a slightly more clean way to express it via the in operator.

if x in ('r', 'p', 's', 'rock', 'paper', 'scissors'):

As an addendum to your original question (as it says that you have to do it recursively), I must strongly caution you against doing any input evaluation through recursion. Python has a call stack size of around 1,000, which means that you have a very finite (yet reasonably large) amount of tries before the program irrecoverably crashes.

Not just that, but your operation stack will be unnecessarily filled with method calls that behave in a similar fashion to a loop. For memory's sake in addition to the absolute recursion ceiling, do not use recursion for this.

If you absolutely must, and again I strongly advise against doing this, then you simply have to return from your iterative case.

def player1():
    x = (raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ")).lower()
    if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
        return x[0]
    else:
        print "Error - wrong input!"
        return player1() #I know I can run a While loop, but I need to run it this way.

print(player1())
like image 141
Makoto Avatar answered Sep 12 '25 23:09

Makoto


An improved version of @Makoto's example:

def player1():
    x = raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ").lower()
    while True:
        if x in ['r', 'p', 's', 'rock', 'paper', 'scissors']:
            return x[0]
        else:
            print "Error - wrong input!"
            x = raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ").lower()

This is a little more concise than many or expressions which gets a bit unwieldy if you have a lot of conditions you want to check!

A little explanation:

We're checking to see x (our user input) is in pre-defined list of valid inputs.

An even more generalized version of this which becomes reusable:

Example: (reusable, non-recursion:)

#!/usr/bin/env python


from __future__ import print_function  # For Python 2/3 compat


try:
    input = raw_input  # For Python 2/3 compat
except NameError:
    pass


def prompt(prompt="Enter: ", valid=None):
    s = input(prompt)
    while valid and s not in valid:
        print("Invalid input! Please try again. Valid inputs are {0:s}".format(" ".join(valid)))
        s = input(prompt)
    return s


x = prompt("Enter action ([r]ock, [p]aper, [s]cissors): ", ["r", "p", "s", "rock", "paper", "scissors"])

Demo:

$ python foo.py
Enter action ([r]ock, [p]aper, [s]cissors): a
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): foo
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): r

$ python foo.py
Enter action ([r]ock, [p]aper, [s]cissors): a
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): foo
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): rock

PS: Sorry I didn't answer your question using recursion. IHMO this is not a good use-case for recursion. Oh well :) However; this is pretty easy to change:

Example: (reusable, recursive)

def userprompt(prompt="Enter: ", valid=None):
    s = input(prompt)
    while valid and s not in valid:
        print("Invalid input! Please try again. Valid inputs are {0:s}".format(" ".join(valid)))
        s = userprompt(prompt, valid)
    return s
like image 31
James Mills Avatar answered Sep 12 '25 23:09

James Mills