Just for the record, when I saw the abuse of global
arguments above and a ton of Python program level looping just to handle a lot of the game functionality, etc., I immediately thought this could be completely redesigned around a class
to handle the actual game itself, track score, etc. for the entire game, and pretty much eliminate the reliance on globals and passing variables around outside the class.
As your Python Principles course didn't touch upon class
objects, W3Schools has a half-decent rough explanation of a class and how it works with internal variables and methods/functions here, though you're bound to learn classes in-depth if you take more advanced Python courses.
This said, using a class
to instantiate the game itself actually alleviates much of your global
abuse and much of the passing of score objects around as 'program level' objects, and keeps everything as part of a singular game
object when you use a class
to define the game object, its mechanics, and its variables internal to itself, so it's available to the game
object as you go. Also makes having to pass data between the various functions and methods a lot easier, since everything's referred to as an object within the game
instance of RockPaperScissors
itself! Cool, huh?
Anyways, I rewrote your game mechanics and functionality as a class
, and kept all the base functionality you do for your game mechanics as part of the class (choosing randomly, checking if a win/loss/tie and keeping track of scores, actually running the game, and actually handling 'wrong' inputs of choices), but made a much simpler mechanism to handle the mapping of string-to-numeric-value mapping of choices using a dict
ionary instead within the class.
I moved the checking for continuing playing, however, to outside the class as part of the actual Python 'program' execution.
The game
is initially created as an instance of the RockPaperScissors
class, and we just straight refer to the game
object outside the class for running a round of RPS and outputting the current scores; everything in terms of score, game mechanics, etc. is all kept within the game
object as variables or methods within the class itself.
I also rewrite your functions for the mechanics to be snake_case
instead of camelCase
, but keep most of the stuff the same, just slightly more Pythonic (with if
/elif
instead of more than one if statement, etc.)
import random
class RockPaperScissors:
"""
Class to handle an instance of a Rock-Paper-Scissors game
with unlimited rounds.
"""
def __init__(self):
"""
Initialize the variables for the class
"""
self.wins = 0
self.losses = 0
self.ties = 0
self.options = {'rock': 0, 'paper': 1, 'scissors': 2}
def random_choice(self):
"""
Chooses a choice randomly from the keys in self.options.
:returns: String containing the choice of the computer.
"""
return random.choice(list(self.options.keys()))
def check_win(self, player, opponent):
"""
Check if the player wins or loses.
:param player: Numeric representation of player choice from self.options
:param opponent: Numeric representation of computer choice from self.options
:return: Nothing, but will print whether win or lose.
"""
result = (player - opponent) % 3
if result == 0:
self.ties += 1
print("The game is a tie! You are a most worthy opponent!")
elif result == 1:
self.wins += 1
print("You win! My honor demands a rematch!")
elif result == 2:
self.losses += 1
print("Haha, I am victorious! Dare you challenge me again?")
def print_score(self):
"""
Prints a string reflecting the current player score.
:return: Nothing, just prints current score.
"""
print(f"You have {self.wins} wins, {self.losses} losses, and "
f"{self.ties} ties.")
def run_game(self):
"""
Plays a round of Rock-Paper-Scissors with the computer.
:return: Nothing
"""
while True:
userchoice = input("Choices are 'rock', 'paper', or 'scissors'.\n"
"Which do you choose? ").lower()
if userchoice not in self.options.keys():
print("Invalid input, try again!")
else:
break
opponent_choice = self.random_choice()
print(f"You've picked {userchoice}, and I picked {opponent_choice}.")
self.check_win(self.options[userchoice], self.options[opponent_choice])
if __name__ == "__main__":
# Initialize an instance of RockPaperScissors for us to refer to
game = RockPaperScissors()
# Keep playing the came repeatedly, stop playing by just exiting
# the entire program directly.
while True:
game.run_game() # Run a round of RPS
game.print_score() # Print the score(s) after the round
# Find out if we want to continue playing or not.
while True:
continue_prompt = input('\nDo you wish to play again? (y/n): ').lower()
if continue_prompt == 'n':
# Exit the game directly after printing a response.
print("You are weak!")
exit()
elif continue_prompt == 'y':
# Break the continue prompt loop and keep playing.
break
else:
# Bad input was given, re-request if we want to play again.
print("Invalid input!\n")
continue
Now, this code has absolutely no explanation of what each function does, etc. per line of code within the class (though I provide docstrings to explain things!), even though I comment what we do in the outer block that actually runs the code.
THIS BEING SAID, I have a version of this that has much more thorough comments throughout the entire codebase (including docstrings)
A complete explanation of the code and what each bit does is detailed in a GitHub GIST located here as the rps.py
file in the Gist because the number of lines doubles when you include all my comments.
(This also has a rags-to-riches request for a review of this rewrite at Python Rock-Paper-Scissors via a class to handle the game if you want to see people review it! I'll also provide the polished version later in a separate gist!)