I have been dipping my toe into programming for a number of years, mainly following tutorials and then giving up when trying to build my own projects. This time I bit the bullet and didn't give in and wrote the below. Can someone tell me how I can improve on this for future projects as i want to start building bigger things.

import random

def round_one():
print("Welcome to Round 1! To get through this round you need to roll lower than a 3!")
decision = str(input(""))
if decision.lower() == "y":
dice = random.randint(1, 6)
if dice <= 3:
print(dice)
print("You have won this round!! You can proceed to Round 2!!")
round_two()
if dice > 3:
print(dice)
print("You lose!!")
if decision.lower() == "n":
print("Goodbye!!")

def round_two():
print("Welcome to Round 2! To get through this round you need to roll higher than a 3!")
decision = str(input(""))
if decision.lower() == "y":
dice = random.randint(1, 6)
if dice >= 3:
print(dice)
print("You have won this round!! You can proceed to Round 2!!")
round_three()
if dice < 3:
print(dice)
print("You lose!!")
if decision.lower() == "n":
print("Goodbye!!")

def round_three():
print("Welcome to the final round! To get through this round you need to roll a 6!")
decision = str(input(""))
if decision.lower() == "y":
dice = random.randint(1, 6)
if dice == 6:
print(dice)
print("You have won the competition!! Congratulations!!")
if dice != 6:
print(dice)
print("You lose!!")
if decision.lower() == "n":
print("Goodbye!!")

def main():
player_name = str(input("What is your name? "))
print ("Welcome to the dice game {}! Here you will battle it out in 3 different battles using dice".format (player_name))
print ("Let the battle commence!!")
round_one()

if __name__ == "__main__":
main()


### Get a book

Tutorials are fine to get understanding of what your behavior should be while coding (this is important too); but to know what to code you should read books. And manuals too.

### Unneeded pieces of code

print("Are you ready? Y/N ")
decision = str(input(""))


Here, you're asking user to input something and then you call the input function with "" argument. input argument is meant to be the user prompt. If you need to add extra new line - it's written as '\n'. input returns a str, so you don't need to cast it into str (but you should cast it if you need something else like int). So this code should be just

decision = input("Are you ready? Y/N \n")


This makes the code shorter and easier to read. You can get info on input here (that's the manual). Next, this:

    if dice >= 3:
print(dice)
print("You have won this round!! You can proceed to Round 2!!")
round_three()
if dice < 3:
print(dice)
print("You lose!!")


Second if is mutually exclusive with the first one, so it should be else instead. print(dice) is repeated in both branches, so it can be moved before them.

    print(dice)
if dice >= 3:
print("You have won this round!! You can proceed to Round 2!!")
round_three()
else:
print("You lose!!")


You can call .lower() once after input().

str.format looks better as an f-string:

print (f"Welcome to the dice game {player_name}! Here you will battle it out in 3 different battles using dice")


### Calling functions from functions

That can be bad. No, it's totally OK while you're in control of what's happening; but calling function spends some resources (namely stack memory) that are freed on function return, and if you're out of that resources - guess what - it's stack overflow. Here you can't get a stack overflow, but still you should be more careful. What about returning True if you can continue and False if not?

def round_one():
...
if dice>=3:
...
return True
else:
....
return False
...
return False #if decision wasn't y or n

def main():
...
if round_one():
if round_two():
round_three()


Oh, now functions are prettier, but you have an ugly if...if... in main(). Be sure you had this ugliness in stack before, but can you do something about it? All three functions are almost the same, with different messages and numbers. Let's try to make them one function with an argument:

def round(number):
to_win = 3 if number<3 else 6
print(f"Welcome to Round {number}! To get through this round, you need to roll higher than a {to_win}!")
decision = input("Are you ready? Y/N \n").lower()
if decision == "y":
dice = random.randint(1, 6)
print(dice)
if dice >= to_win:
print(f"You have won this round!! You can proceed to Round {number+1}!!")
return True
else:
print("You lose!!")
return False
if decision == "n":
print("Goodbye!!")
return False


And now main() may turn into for or while loop:

def main():
player_name = str(input("What is your name? "))
print (f"Welcome to the dice game {player_name}! Here you will battle it out in 3 different battles using dice")
print ("Let the battle commence!!")
for number in [1,2,3]: #you can write this as range(1,4) if you wish
if not round(number):
break


There are some minor problems left like 'round 3' instead of 'final round' in output, but you can solve them introducing new variables, depending on number, like to_win one.

• Thank you for your comments Pavlo, I want to try and get away from books and tutorials as that what I've done in the past and when I've come away to try and write something my mind goes blank and then I go back to the resource I was using and just follow the examples from there and pretend that I know what I am doing, this time round I skipped that and just started writing. I will look at reformatting what I've written into a simpler script and then move on to the next project. Jan 4 at 20:52

The name of the game is reduction of redundancy. You can already tell that your three rounds have many, many aspects that are identical. You can reduce this to one function with the right parameters and lambdas.

Move your Are you ready? Y/N prompt into the input call.

Take advantage of the else statement, or early returns, instead of restating the negative version of your predicates.

Start getting comfortable with type hints and f-strings.

import random
from typing import Optional, Callable

def do_round(
index: int,
predicate_name: str,
predicate: Callable[[int], bool],
victory_message: Optional[str] = None,
) -> bool:
print(f"Welcome to Round {index}! To get through this round you need to {predicate_name}!")
decision = str(input("Are you ready? Y/N "))
if decision.lower() == "n":
print("Goodbye!!")
return False

die = random.randint(1, 6)
print(die)
if predicate(die):
if victory_message is None:
victory_message = f"You have won this round!! You can proceed to Round {index + 1}!!"
print(victory_message)
return True

print("You lose!!")
return False

def main() -> None:
player_name = str(input("What is your name? "))
print(f"Welcome to the dice game {player_name}!"
f"\nHere you will battle it out in 3 different battles using dice"
f"\nLet the battle commence!!")

for index, args in enumerate(
(
('roll lower than a 3', lambda x: x < 3),
('roll higher than a 3', lambda x: x > 3),
('roll a 6', lambda x: x == 6,
'You have won the competition!! Congratulations!!'),
), 1
):
if not do_round(index, *args):
return

if __name__ == "__main__":
main()