0
\$\begingroup\$

I recently (Yesterday) started working with python and my goal is to not use any tutorials etc where I have to follow along and make a small project. My first project I was recommended to do was a simple game of Rock, Paper, Scissors. I made it in about thirty minutes and it works fine. It goes up to 3 points for each game etc. It's super simple, but now since I'm done with it I'm hoping to find a way to simplify the code, if there is any other way. I really stretched it out since I'm using basics with no help.

# Rock Papers Scissors
import random

option = ["Rock", "Paper", "Scissor"]
game = True


def start_game():
    point_player = 0
    point_ai = 0
    game_end = True
    while game:
        rock = False
        paper = False
        scissor = False
        if point_player == 3:
            game_end = False
        elif point_ai == 3:
            game_end = False
        if game_end:
            print("Choose an option. (R,P,S)")
            selection = input()
            if selection == 'R':
                selection = 'Rock'
                rock = True
            elif selection == 'P':
                selection = 'Paper'
                paper = True
            elif selection == 'S':
                selection = 'Scissor'
                scissor = True
            else:
                print("That's not an option")
                exit()
            print("=======================")
            print("You chose " + selection)
            ai_response = random.choice(option)
            print("The A.I chose " + ai_response)
            print("=======================")
            if ai_response == "Rock":
                while rock:
                    print("Oh! That's a tie no points.")
                    break
                while scissor:
                    print("Oh, you lost :( +1 to A.I")
                    point_ai = point_ai + 1
                    break
                while paper:
                    print("Looks like you beat the A.I! +1 Point")
                    point_player = point_player + 1
                    break
            elif ai_response == "Paper":
                while rock:
                    print("Oh, you lost :( +1 to A.I")
                    point_ai = point_ai + 1
                    break
                while scissor:
                    print("Looks like you beat the A.I! +1 Point")
                    point_player = point_player + 1
                    break
                while paper:
                    print("Oh! That's a tie no points.")
                    break
            elif ai_response == "Scissor":
                while rock:
                    print("Looks like you beat the A.I! +1 Point")
                    point_player = point_player + 1
                    break
                while scissor:
                    print("Oh! That's a tie no points.")
                    break
                while paper:
                    print("Oh, you lost :( +1 to A.I")
                    point_ai = point_ai + 1
                    break
            print("You have " + str(point_player) + " points")
            print("The A.I has " + str(point_ai) + " points.")
            print("=======================")
        else:
            if point_ai > point_player:
                print("The A.I won! RIP U")
            else:
                print("Dang! You won this time.")
            exit()


print("|=========================|")
print("|    Rock Paper Scissor   |")
print("|       (R) (P) (S)       |")
print("|=========================|")
print("Would you like to start? y/n")
answer = input()
if answer == "y":
    print("Welcome to the game of Rock Paper Scissors")
    start_game()
elif answer == "n":
    print('Well ok then.. Fine leave')
    exit()
else:
    print("That's not an option.")
\$\endgroup\$
  • 2
    \$\begingroup\$ Do not do while stuff:... break just use an if. ALso don't save both user input in a string as a value, and as a boolnea in a second variable that's useless \$\endgroup\$ – azro May 5 at 21:02
  • 1
    \$\begingroup\$ A simplification can be to replace the choices internally by numbers 0, 1 and 2. Then choice x beats (x-1)%3 and is beaten by (x+1)%3. \$\endgroup\$ – Michael Butscher May 5 at 21:06
1
\$\begingroup\$
game_end = True
while game:        
    if point_player == 3:
        game_end = False
    elif point_ai == 3:
        game_end = False
    if game_end:

        ...


    else:
        if point_ai > point_player:
            print("The A.I won! RIP U")
        else:
            print("Dang! You won this time.")
        exit()

Could be simplified to:

while True:        
    if point_player == 3 or point_ai == 3:
        break

        ...

if point_ai > point_player:
    print("The A.I won! RIP U")
else:
    print("Dang! You won this time.")

All of the

while rock:
    print("Oh! That's a tie no points.")
    break

Could be written as:

if rock:
    print("Oh! That's a tie no points.")

Instead of point_ai = point_ai + 1, you can shorten that to point_ai += 1. I would also separate out the code that decides who wins as it makes it a little bit easier to understand what is going on with the logic.

    winner = 'tie' # should be either 'player', 'tie', or 'ai'
    if ai_response == "Rock":
        if scissor: 
            winner = 'ai'
        if paper: 
            winner = 'player'
    elif ai_response == "Paper":
        if rock: 
            winner = 'ai'
        if scissor: 
            winner = 'player'
    elif ai_response == "Scissor":
        if rock: 
            winner = 'player'
        if paper: 
            winner = 'ai'

    if winner == 'tie':
        print("Oh! That's a tie no points.")
    if winner == 'player':
        print("Looks like you beat the A.I! +1 Point")
        player_points += 1
    if winner == 'ai':
        print("Oh, you lost :( +1 to A.I")
        ai_points += 1

It feels weird having the rock, paper, scissors Booleans to represent the choice the player made, and then having a string for the choice the ai made. I would use the same data type for both. If we stuck with using strings for both, and with some changes to some variable names, we get:

while True:
    if player_points == 3 or ai_points == 3:
        break
    print("Choose an option. (R,P,S)")
    player_choice = input()
    if player_choice == 'R':
        player_choice = 'Rock'
    elif player_choice == 'P':
        player_choice = 'Paper'
    elif player_choice == 'S':
        player_choice = 'Scissor'
    else:
        print("That's not an option")
        exit()
    print("=======================")
    print("You chose " + player_choice)
    ai_choice = random.choice(option)
    print("The A.I chose " + ai_choice)
    print("=======================")

    winner = 'tie' # should be either 'player', 'tie', or 'ai'
    if ai_choice == "Rock":
        if player_choice == 'Scissor': 
            winner = 'ai'
        if player_choice == 'Paper': 
            winner = 'player'
    elif ai_choice == "Paper":
        if player_choice == 'Rock': 
            winner = 'ai'
        if player_choice == 'Scissor':
            winner = 'player'
    elif ai_choice == "Scissor":
        if player_choice == 'Rock': 
            winner = 'player'
        if player_choice == 'Paper': 
            winner = 'ai'

    if winner == 'tie':
        print("Oh! That's a tie no points.")
    if winner == 'player':
        print("Looks like you beat the A.I! +1 Point")
        player_points += 1
    if winner == 'ai':
        print("Oh, you lost :( +1 to A.I")
        ai_points += 1

    print("You have " + str(player_points) + " points")
    print("The A.I has " + str(ai_points) + " points.")
    print("=======================")

That looks worse in some ways, mostly because of the longer variable names, but everything is going to get better soon. I think now it's about time to break this game up into multiple functions. I like to have all of my print statements in a separate from the logic. In this case, I'm going to keep all of the print statements inside of the start_game function, and we'll put all of the logic outside of the start_game function. This helps us easily read through and understand what is going on, and then if we're having a problem, we can investigate the particular function whose logic is producing incorrect results. This also makes it easier to unit test the logic.

def get_players_choice():
    print("Choose an option. (R,P,S)")
    player_choice = input()
    if player_choice == 'R':
        player_choice = 'Rock'
    elif player_choice == 'P':
        player_choice = 'Paper'
    elif player_choice == 'S':
        player_choice = 'Scissor'
    else:
        player_choice = False

    return player_choice

def get_ai_choice():
    return random.choice(option)

def calculate_winner(player_choice, ai_choice):
    ''' returns either 'player', 'tie', or 'ai' '''
    winner = 'tie'
    if ai_choice == "Rock":
        if player_choice == 'Scissor': 
            winner = 'ai'
        if player_choice == 'Paper': 
            winner = 'player'
    elif ai_choice == "Paper":
        if player_choice == 'Rock': 
            winner = 'ai'
        if player_choice == 'Scissor':
            winner = 'player'
    elif ai_choice == "Scissor":
        if player_choice == 'Rock': 
            winner = 'player'
        if player_choice == 'Paper': 
            winner = 'ai'
    return winner

def display_results(winner):
    if winner == 'tie':
        print("Oh! That's a tie no points.")
    elif winner == 'player':
        print("Looks like you beat the A.I! +1 Point")
    elif winner == 'ai':
        print("Oh, you lost :( +1 to A.I")

    print("You have " + str(player_points) + " points")
    print("The A.I has " + str(ai_points) + " points.")
    print("=======================")

def start_game():
    player_points = 0
    ai_points = 0
    while True:
        if player_points == 3 or ai_points == 3:
            break

        player_choice = get_players_choice()
        if player_choice is False:
            print("That's not an option")
            exit()

        print("=======================")
        print("You chose " + player_choice)
        ai_choice = get_ai_choice()
        print("The A.I chose " + ai_choice)
        print("=======================")

        winner = calculate_winner(player_choice, ai_choice)

        if winner == 'player':
            player_points += 1
        elif winner == 'ai':
            ai_points += 1

        display_results(winner)

    if ai_points > player_points:
        print("The A.I won! RIP U")
    else:
        print("Dang! You won this time.")

Now a little improvement as we refactor. This is kind of like chess, the more you do it, the more patterns just jump out at you. Let's start with the get_players_choice function. One pattern is that when asking for input from the user, instead of requiring 'R', 'P', 'S', we can grab the first character, lowercase it, and then check against that. We would then be accepting 'r', 'R', 'rock', 'Rock', or anything else that starts with the letter 'r'. Some other things:

    print("Choose an option. (R,P,S)")
    player_choice = input()

Is the same as

player_choice = input("Choose an option. (R,P,S)")

While I said I like to keep the print statements and the logic separate, display_results is a function that is obviously all about displaying text on the screen, and in the same way it would be expected that a function called get_players_choice is doing more than just logic. We're going to be using the input function, and that requires asking the user for input, and it requires telling them if the input is invalid. Anyhow, here is my revised get_players_choice function.

def get_players_choice():
    while True:
        player_choice = input("Choose an option (R,P,S), or push enter to exit")

        if not player_choice: # If they pushed enter
            return False

        first_char = player_choice[0].lower()

        if first_char == 'r':
            return 'Rock'
        elif first_char == 'p':
            return 'Paper'
        elif first_char == 's':
            return 'Scissor'

        print("That's not an option")

That display_choices and display_results can actually be combined together. And we end up with this as the final result. There are still some additional improvements that can be made, but what those are matters on what your goals are. Is it important that the code can be extended easily? Is performance important? Do you like object oriented designs? Etc.

''' A Rock Papers Scissors game '''
import random

def get_players_choice():
    while True:
        player_choice = input("Choose an option (R,P,S), or push enter to exit: ")

        if not player_choice: # If they pushed enter
            return False

        first_char = player_choice[0].lower()

        if first_char == 'r':
            return 'Rock'
        if first_char == 'p':
            return 'Paper'
        if first_char == 's':
            return 'Scissor'

        print("That's not an option")


def get_ais_choice():
    return random.choice(["Rock", "Paper", "Scissor"])

def calculate_winner(player, ai):
    ''' Takes either 'Rock', 'Paper', or 'Scissors', for both the player and the ai.
        returns either 'player', 'tie', or 'ai' '''

    if ai == "Rock":
        if player == 'Scissor': return 'ai'
        if player == 'Paper':   return 'player'

    elif ai == "Paper":
        if player == 'Rock':    return 'ai'
        if player == 'Scissor': return 'player'

    elif ai == "Scissor":
        if player == 'Rock':   return 'player'
        if player == 'Paper':  return 'ai'

    return 'tie'

def display_round_outcome(player_choice, ai_choice, winner, player_points, ai_points):
    print("=======================")
    print("You chose " + player_choice)
    print("The A.I chose " + ai_choice)
    print("=======================")

    if winner == 'tie':
        print("Oh! That's a tie no points.")
    elif winner == 'player':
        print("Looks like you beat the A.I! +1 Point")
    elif winner == 'ai':
        print("Oh, you lost :( +1 to A.I")

    print(f"You have {player_points} points")
    print(f"The A.I has {ai_points} points.")
    print("=======================")


def start_game():
    player_points = 0
    ai_points = 0

    while True:
        if player_points >= 3 or ai_points >= 3:
            break

        player_choice = get_players_choice()
        ai_choice = get_ais_choice()

        if player_choice is False:
            break

        winner = calculate_winner(player_choice, ai_choice)

        if winner == 'player':
            player_points += 1
        elif winner == 'ai':
            ai_points += 1

        display_round_outcome(player_choice, ai_choice, winner, player_points, ai_points)

    if ai_points > player_points:
        print("The A.I won! RIP U")
    elif ai_points == player_points:
        print("It's a tie.")
    else:
        print("Dang! You won this time.")


print("|=========================|")
print("|    Rock Paper Scissor   |")
print("|       (R) (P) (S)       |")
print("|=========================|")
print("Would you like to start? y/n")
answer = input()
if answer == "y":
    print("Welcome to the game of Rock Paper Scissors")
    start_game()
elif answer == "n":
    print('Well ok then.. Fine leave')
else:
    print("That's not an option.")

Let me know what questions you have, and what I wrote to quickly and didn't do a good job in explaining me decisions with. But I hope that kind of helps seeing how I would refactor this code, and hopefully you can see how much easier it is to read and debug the final version when you have everything split chunks of code. The difference is a lot more obvious when it's complicated code that you wrote a long time ago, or that something else wrote, and you're trying to figure out what is going on.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

This general topic is called refactoring. I could link you to one of my many favorite books and websites on the topic but I'll leave some general pieces of advice.

A good start is to break your program into smaller chunks of functionality. Make sure they're well named and easy to understand, in other words imagine you were someone who's never seen this code and had to fix an issue with it.

If a file is growing too large, you may want to break it into several files each with their own sub-functionality.

There are many great sites and talks on the topic both within the Python programming language and beyond. I encourage you to look into them after breaking down the first pieces of advice and sharing this code with a peer and receiving feedback from them. See what you understand, and ask for clarification about the parts you don't. Keep an open mind and know that much of this is subjective.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.