4
\$\begingroup\$

I am studying and practicing Python; this is my Rock-Paper-Scissors game I've built from scratch. I saw this task is for beginners. When writing this in all the examples I saw dozens of lines of code with if elif else statements. I decided to write the shortest program for this game.

I'm just looking for any comments, improvements or bad habits. Any comments at all are welcome!

import random

my_win = 0
my_loss = 0
my_tie = 0


def game():

    global my_tie, my_loss, my_win

    var = {'scissors':(0,1,0),
           'paper':(0,0,1),
           'rock':(1,0,0)}

    user = input("Please choice scissors, paper or rock: ")
    while user not in ['scissors', 'paper', 'rock']:
            user = input("Please choice scissors, paper or rock: ")
    computer = random.choice(['scissors','paper','rock'])

    print(f"USER - {user} \nCOMPUTER - {computer}")

    for k, v in var.items():
        if k == user:
            one = int(v.index(1))
        if k == computer:
            two = int(v.index(1))

    if one < two:
        print(f"USER with {user} - WIN!")
        my_win += 1
    elif one == two:
        print("==TIE==")
        my_tie += 1
    else:
        print(f"COMPUTER with {computer} - WIN!")
        my_loss += 1
    

    def results():

        print ("You win %d times!" % my_win)
        print ("You lose %d times!" % my_loss)
        print ("You tie %d times!" % my_tie)


if __name__ == "__main__":
    game()
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Are you sure your code is functionally correct? I might be wrong but it looks to me like if the user selects rock and the computer selects paper the user will win since 0<2 \$\endgroup\$ Commented Jun 23, 2020 at 19:26
  • \$\begingroup\$ Thank you!) It's really my fail with rules :) \$\endgroup\$
    – edrrujqg
    Commented Jun 24, 2020 at 20:43
  • \$\begingroup\$ Welcome to Code Review! I have rolled back Rev 4 → 3 Please see What to do when someone answers. \$\endgroup\$ Commented Jun 24, 2020 at 22:22

4 Answers 4

3
\$\begingroup\$

There are multiple things you could improve in your code.

The name of the game

I feel that it'd be better to call your game method run_game. You should also run it multiple times in a while loop, asking the user each time if they want to exit. When they say they want to exit, you can call the results function to display the results.

The results

Not only do you never use the results function, but you've indented it too far so it's inside the game function. Make sure it's top-level so that other modules can use it. Also, you have spaces before the print statements, but that's a minor thing.

I assume you're designing this module so that other modules can use it, but if not, make the global variables local and get rid of the if at the end.

Better names for your result variables would be num_wins, num_losses, and num_ties.

Your results function would also be better if it were named print_results, since the current name implies that it returns the results.

Storing the choices

First of all, don't name your variables var. choices is a better name. Also, you can just use a list instead of a dictionary, and use the indices to calculate who won. You could also reuse your choices list while taking user input and while choosing a move for the computer.

choices = ['paper', 'scissors', 'rock']

Also, rather than using the names user and computer, call the variables where you store the user's and computer's choices something like user_move and comp_move

user_move = input("Please choice scissors, paper or rock: ")
while user_move not in choices:
        user_move = input("Please choice scissors, paper or rock: ")
comp_move = random.choice(choices)

one and two and finding the winner

To get the value associated with a key in a dictionary, you can simply do my_dict[my_key]. It's not necessary to loop through the entire list and not break even once you've found the necessary key. You could have replaced that for loop with this (note that there's no need to use int() on the value)

one = choices[user_move].index(1)
two = choices[comp_move].index(1)

However, we have changed the choices variable to be a list of strings, and anyways, your logic won't work in case the user selects "rock" and the computer selects "paper", as @ThisIsAQuestion pointed out.

This way accounts for that error (I also renamed one and two to user_ind and comp_ind):

user_ind = choices.index(user_move)
comp_ind = choices.index(comp_move)

if user_ind == comp_ind:
  num_ties += 1
elif user_ind - comp_ind < 2:
  num_wins += 1
else:
  num_losses += 1

However, this isn't very clear. A better way to do it would be like this, by checking if the computer's move is one index ahead (after using %, of course)

if user_move == comp_move:
  num_ties += 1
elif choices[(choices.index(user) + 1) % len(choices)] == comp_move:
  num_losses += 1
else:
  num_wins += 1

This second approach treats choices like a cycle rather than just a linear list. The % is to avoid accessing an index beyond the range of the list - it comes back to 0 if it's 3 (You can always hardcode 3 instead of len(choices)).

The resulting code

import random

num_wins = 0
num_losses = 0
num_ties = 0

def run_game():
  choices = ['paper', 'scissors', 'rock']
  user_move = input("Please choice scissors, paper or rock: ")
    while user_move not in choices:
      user_move = input("Please choice scissors, paper or rock: ")
  comp_move = random.choice(choices)

  if user_move == comp_move:
    num_ties += 1
    print(f"BOTH with {user_move} - TIE")
  elif choices[(choices.index(user) + 1) % len(choices)] == comp_move:
    num_losses += 1
    print(f"COMPUTER with {comp_move} - WIN!")
  else:
    num_wins += 1
    print(f"USER with {user_move} - WIN!")

def print_results():
  print("You win %d times!" % num_wins)
  print("You lose %d times!" % num_losses)
  print("You tie %d times!" % num_ties)

if __name__ == "main":
  continue = 'y'
  while continue.lower() == 'y':
    run_game()
    continue = input('Enter "y"/"Y" to continue')
  print_results()

Edit: Using the walrus operator, as Aivar Paalberg suggested, would simplify the while loops.

\$\endgroup\$
7
  • \$\begingroup\$ thank you for your answer and your help. this is important for me. But with your code USER can't win))) run_game() Please choice scissors, paper or rock: rock Computer move is - scissors COMPUTER with scissors - WIN! Like in real cassino ;) I'm thin mistake here: elif choices[(choices.index(user) + 1) % len(choices)] \$\endgroup\$
    – edrrujqg
    Commented Jun 24, 2020 at 21:43
  • 1
    \$\begingroup\$ Sorry, I forgot == comp_move. Good catch there. Can you check again? \$\endgroup\$
    – user
    Commented Jun 24, 2020 at 21:56
  • 1
    \$\begingroup\$ yeah, now it's working right :) \$\endgroup\$
    – edrrujqg
    Commented Jun 24, 2020 at 22:09
  • \$\begingroup\$ thank you for your answer. Your solution is very helpful and give important experience for me. I'm hope this my code is not very ugly and scary professionals :) \$\endgroup\$
    – edrrujqg
    Commented Jun 24, 2020 at 22:11
  • \$\begingroup\$ and I'm think "continue" - this is not good variable name ;) \$\endgroup\$
    – edrrujqg
    Commented Jul 1, 2020 at 20:01
5
\$\begingroup\$

I'll just go over some small improvements I noticed you can make.

my_win = 0
my_loss = 0
my_tie = 0


def game():

    global my_tie, my_loss, my_win

Global variables should be avoided whenever possible. The only reason I can see that these are globals is because of results() but you never actually call results(). It's probably better to just leave these as local variables.

var = {'scissors':(0,1,0),
       'paper':(0,0,1),
       'rock':(1,0,0)}

for k, v in var.items():
    if k == user:
        one = int(v.index(1))
    if k == computer:
        two = int(v.index(1))

A couple of comments here. First, you don't make use of the fact that your values in your dictionary are tuples instead of just ints. You can just use 0, 1, and 2 as values. Second, you're not taking advantage of the thing that dictionaries are best at: indexing. An improvement here would be:

var = {'scissors':1,
       'paper':2,
       'rock':0}

one = var[user]
two = var[computer]

Next:

if one < two:
    print(f"USER with {user} - WIN!")
    my_win += 1
elif one == two:
    print("==TIE==")
    my_tie += 1
else:
    print(f"COMPUTER with {computer} - WIN!")
    my_loss += 1

As I said in the comments, I don't think this is functionally correct. Either combination of the user and the computer choosing paper and rock will cause the rock to win. One of many fixes you could do is to add and abs(one - two) == 1 as a condition to your first if statement.

def results():

    print ("You win %d times!" % my_win)
    print ("You lose %d times!" % my_loss)
    print ("You tie %d times!" % my_tie)

This function is defined inside your game() function and is never called. Because your variables are local (as I suggested), you might want to put wins, losses, and ties as arguments for this function. You also don't have a way to play the game more than once, making this function pretty unhelpful. I'll leave adding a way to play multiple games to you since that's more of a code upgrade than a code review.

Here is the complete code with all my proposed changes:

import random

def game():
    my_win = 0
    my_loss = 0
    my_tie = 0

    var = {'scissors':1,
           'paper':2,
           'rock':0}

    user = input("Please choice scissors, paper or rock: ")
    while user not in ['scissors', 'paper', 'rock']:
            user = input("Please choice scissors, paper or rock: ")
    computer = random.choice(['scissors','paper','rock'])

    print(f"USER - {user} \nCOMPUTER - {computer}")

    one = var[user]
    two = var[computer]

    if one < two and abs(one - two) == 1:
        print(f"USER with {user} - WIN!")
        my_win += 1
    elif one == two:
        print("==TIE==")
        my_tie += 1
    else:
        print(f"COMPUTER with {computer} - WIN!")
        my_loss += 1
    
    results(my_win, my_loss, my_tie)

def results(win, loss, tie):

    print ("You win %d times!" % win)
    print ("You lose %d times!" % loss)
    print ("You tie %d times!" % tie)


if __name__ == "__main__":
    game()
\$\endgroup\$
3
  • \$\begingroup\$ Thank you for your answer and support. This is really helping and important for me. But I'm have a questions - I am understand that using global variables is not good way and I'm read about avoiding global, but if this counters will be local variables - I'm cant play n-times and will see results after it. Because of that I'm use this simple counter. And I'm have mistake when copy-paste code here - results() not in game() function - thank you. After your comment I'm rewrite code to add multiple games. THANK YOU! I'm hope this my code is not very ugly and scary professionals :) \$\endgroup\$
    – edrrujqg
    Commented Jun 24, 2020 at 20:59
  • 1
    \$\begingroup\$ @edrrujqg There's a few ways you can get around global variables. You could have your loop that allows you to play the game multiple times inside your game() method. You could also have your game() method return something that represents what happened in the round like a string or an int or a tuple. The best way I think would be to make a class that handles playing the game, but I don't know if you've learned about classes yet. Glad I could help! \$\endgroup\$ Commented Jun 25, 2020 at 12:00
  • \$\begingroup\$ yes. I'm read about classes and I'll try except global variables with using classes. Thank you! \$\endgroup\$
    – edrrujqg
    Commented Jul 1, 2020 at 19:33
4
\$\begingroup\$

Functionally not 100% similar but shorter version of code. Taking advantage of walrus operator and modulo 3 tricks:

from random import choice

choices = ['rock', 'paper', 'scissors']
results = ['user wins', 'computer wins', 'draw']
totals = [0, 0, 0]

while True:

    while (user_choice := input('Please choose {}, {} or {}:   '.format(*choices))) not in choices:
        continue

    computer_choice = choice(range(3))
    result_indice = (choices.index(user_choice) - computer_choice) % 3 - 1
    result = results[result_indice]
    totals[result_indice] += 1
    print(f'user: {user_choice}, computer: {choices[computer_choice]}, result: {result}')
    print(*[f'{result}: {total}' for result, total in zip(results, totals)])

    if input('Enter y or Y to continue: ').lower() != 'y':
        break
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Nice job using the walrus operator. As long as the OP doesn't want to run the game from different modules, it's a really good solution. +1 \$\endgroup\$
    – user
    Commented Jun 26, 2020 at 18:59
  • \$\begingroup\$ Thank you for your answer. This is really short example :) \$\endgroup\$
    – edrrujqg
    Commented Jul 1, 2020 at 19:29
1
\$\begingroup\$

Thank's all for yours answers and solutions! I'm read every example and teaches myself something new. Guys - you are great! This is my solution. And of course I except from this global variables.

import random

class game():

    num_wins = 0
    num_losses = 0
    num_ties = 0

    def run_game():
   
        choices = ["paper", "scissors", "rock"]
    
        user_move = input("Please choice scissors, paper or rock: ")
        while user_move not in choices:
            user_move = input("Please choice scissors, paper or rock: ")
        comp_move = random.choice(choices)

        if user_move == comp_move:
            game.num_ties += 1
            print(f"BOTH with {user_move} - TIE")
        elif choices[(choices.index(user_move) + 1) % len(choices)] == comp_move:
            game.num_losses += 1
            print(f"COMPUTER with {comp_move} - WIN!")
        else:
            game.num_wins += 1
            print(f"USER with {user_move} - WIN!")
        
        return game.num_wins, game.num_losses, game.num_ties

    def print_results():
        print("You win %d times!" % game.num_wins)
        print("You lose %d times!" % game.num_losses)
        print("You tie %d times!" % game.num_ties)

if __name__ == "__main__":
    end = ""
    while end.lower() != "y":
        game.run_game()
        end = input("Enter 'y'/'Y' to continue ")
    game.print_results()
    game.num_wins, game.num_losses, game.num_ties = 0, 0, 0
\$\endgroup\$
2
  • 1
    \$\begingroup\$ By the way, I forgot to add in my answer that you can use f-strings for printing out the results. You might want to try that \$\endgroup\$
    – user
    Commented Jul 1, 2020 at 19:34
  • \$\begingroup\$ yeah, thank you! I saw that I was using two different options and decided to leave both as a reminder to myself that I'm can do print() in different ways. I think that in real work I'm need to follow in one style of code. \$\endgroup\$
    – edrrujqg
    Commented Jul 1, 2020 at 19:41

Your Answer

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

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