2

I've just started learning programming some time ago and been playing with Python a bit. I wrote a small program that rolls dice based on user input (number of sides on the die and number of dice). Here's the code:

from random import randrange


def number_of_sides():
    n = input("How many sides? ")  # Get input from the user
    if n.isdigit():  # Check if the input is a digit
        n = int(n)  # If it is, turn it into an integer
        return n  # And return the value of n
    else:
        print("Invalid input. ")  # If test returns false, rerun function
        number_of_sides()


def number_of_dice():
    m = input("How many dice? ")  # Get input from the user
    if m.isdigit():  # Check if the input is a digit
        m = int(m)  # If it is, turn it into an integer
        return m  # And return the value of m
    else:
        print("Invalid input. ")  # If test returns false, rerun function
        number_of_dice()


def play_again() -> object:  # Checks if user answered yes or no, then reruns everything or exits with exit code 0
    answ = input("Do you want to play again?(yes/no) ")
    if answ == "yes":
        dice_roll()
    elif answ == "no":
        print("Ok then")
        return True
    else:
        print("Input invalid, trying again")
        play_again()


def dice_roll():  # sides - for number of sides, dice - for number of dice.
    sides = number_of_sides()  # Whatever number the function returns
    dice = number_of_dice()  # Whatever number the function returns
    results = [] # empty list, here is where results will be appended
    for i in range(1, dice + 1):  # That returns values for each dice specified in the dice variable
        throw = randrange(1, sides + 1)
        results.append(throw)
        results_str = " | ".join(str(i) for i in results)  # Turn the list into string, separate results with a pipe
    print(results_str)  # Print the results of throws
    play_again()  # Ask the user to play again


dice_roll()

Everything works ok when I provide valid input (so digits like 6 and 6 for sides and dice), it crashes, however, when I try providing invalid input first. For some reason the number_of_sides() and number_of_dice() functions return None when they're first fed invalid input and then crash the code in the for loop in the dice_roll() function:

How many sides? a
Invalid input. 
How many sides? 6
How many dice? 6
Traceback (most recent call last):
  File "<directory>/dice_roll.py", line 48, in <module>
    dice_roll()
  File "<directory>/dice_roll.py", line 41, in dice_roll
    throw = randrange(1, sides + 1)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Process finished with exit code 1

I tested these functions separately and they seem to be working fine. This snippet:

def number_of_sides():
    n = input("How many sides? ")
    if n.isdigit():
        n = int(n)
        print(n, ", ", type(n))
        return n
    else:
        print(n, ", ", type(n))
        number_of_sides()

Returns str if you provide a string and int if you provide it with digits. I would like to know what may be causing this weird behaviour.

1
  • You need to return something from your else blocks in number_of_sides and number_of_dice. If there is no explicit return statement, Python functions implicitly return None. I would advise against using recursion/mutual recursion here. It is only going to make it hard to reason about. Commented Jun 20, 2017 at 7:39

3 Answers 3

3

The problem is that you are calling number_of_dice recursively, but you ignore its result. You should replace the recursive call with:

return number_of_dice()

or even better, get rid of recursion by using a simple while loop:

def number_of_dice():
    while True:
        m = input("How many dice? ")  # Get input from the user
        if m.isdigit():  # Check if the input is a digit
            m = int(m)  # If it is, turn it into an integer
            return m  # And return the value of m
        else:
            print("Invalid input. ")  

The same, obviously, holds for the other function.

Sign up to request clarification or add additional context in comments.

2 Comments

how about EAFP?
@AzatIbrakov yes, replacing if with try came to my mind too, but that's not directly related to the question.
1

I seem that the functions number_of_sides() and number_of_dice() only return a valid value when the first input is correct otherwise they loop themselves but the new input will not be returned to the main function. I think recursivity is not good in that case.

You could transform number_of_dices like this :

def number_of_sides():
    nb_sides = input("How many sides? ")

    while (nb_sides.isdigit())==False:
        print("Invalid input. ")
        nb_sides=input("How many sides? ")

    return int(nb_sides)

For your information, you can also use :

import pdb
pdb.set_trace()

to pause the code in a specific line

3 Comments

I can't seem to grasp why they do not return new input after they're looped.
In fact, after futher investigations, you are missing the return statement in your functions :
def number_of_sides(): n = input("How many sides? ") # Get input from the user if n.isdigit(): # Check if the input is a digit n = int(n) # If it is, turn it into an integer return n # And return the value of n else: print("Invalid input. ") # If test returns false, rerun function return number_of_sides()
0

"return" statement from your fail case is missing in the 2 statements

from random import randrange


def number_of_sides():
    n = input("How many sides? ")  # Get input from the user
    if n.isdigit():  # Check if the input is a digit
        n = int(n)  # If it is, turn it into an integer
        return n  # And return the value of n
    else:
        print("Invalid input. ")  # If test returns false, rerun function
        return number_of_sides()


def number_of_dice():
    m = input("How many dice? ")  # Get input from the user
    if m.isdigit():  # Check if the input is a digit
        m = int(m)  # If it is, turn it into an integer
        return m  # And return the value of m
    else:
        print("Invalid input. ")  # If test returns false, rerun function
        return number_of_dice()


def play_again() -> object:  # Checks if user answered yes or no, then reruns everything or exits with exit code 0
    answ = input("Do you want to play again?(yes/no) ")
    if answ == "yes":
        dice_roll()
    elif answ == "no":
        print("Ok then")
        return True
    else:
        print("Input invalid, trying again")
        play_again()


def dice_roll():  # sides - for number of sides, dice - for number of dice.
    sides = number_of_sides()  # Whatever number the function returns
    dice = number_of_dice()  # Whatever number the function returns
    results = [] # empty list, here is where results will be appended
    for i in range(1, dice + 1):  # That returns values for each dice specified in the dice variable
        throw = randrange(1, sides + 1)
        results.append(throw)
        results_str = " | ".join(str(i) for i in results)  # Turn the list into string, separate results with a pipe
    print(results_str)  # Print the results of throws
    play_again()  # Ask the user to play again


dice_roll()

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.