Welcome back! There are a lot of good things to say about your program, and a few not-so-good. First, the good:
Your code breakdown is uneven. Consider this code:
def main():
wins = 0
losses = 0
draws = 0
while True:
os.system('cls' if os.name == 'nt' else 'clear')
print(f"Wins: {wins}\nLosses: {losses}\nDraws: {draws}")
wins, losses, draws = results(wins, losses, draws)
if play_again() == '2':
break
Let's break those lines down. The key point I want to make is to keep neighboring statements at a similar level of abstraction.
First, you initialize your variables:
wins = 0
losses = 0
draws = 0
Because you are not using a class, and are not using globals (which would be appropriate in this scenario, IMO), you are stuck with doing variable initialization here. I suggest that you make this consistent with how you update the variables after each game:
wins, losses, draws = starting_scores()
Now, starting_scores
could just return 0,0,0
or it could load from a saved-game file. But it makes the initialization sufficiently abstract, and it also spells out what you are doing.
Next, you loop:
while True:
...
if play_again() == '2':
break
The while True ... break
could be rewritten to use a boolean variable. That's not super-critical, since the value of that variable is determined at only a single location. I consider the break to be equivalent in this case.
However, the comparison == '2'
is not acceptable! Why? Because that's a detail, and your function name play_again
should take care of that detail for you! Don't ask a question and then interpret the answer. Make your question-asking code handle the interpretation for you. Obviously play_again
is short for "do you want to play again?" and '2'
is not a valid answer. True
or False
are valid answers, so the code should look like:
while True:
...
if not play_again():
break
Finally, the inside of your loop has the same problem:
os.system('cls' if os.name == 'nt' else 'clear')
print(f"Wins: {wins}\nLosses: {losses}\nDraws: {draws}")
wins, losses, draws = results(wins, losses, draws)
What are you doing here? Well, you are clearing the screen, showing a summary of the games played, and playing one more round of the game. So say that!
clear_screen()
show_statistics(wins, losses, draws)
wins, losses, draws = play_one_round(wins, losses, draws)
Use appropriate data structures.
Your main
code passes three variables to your play-game code. That code then returns three data items in a tuple, which you unpack into three variables.
In fact, you never use one of those variables without also having the others at hand. This should tell you that you are dealing with one aggregate data item, instead of three independent pieces of data. If that's true, just treat the scores as a single item:
def main():
scores = starting_scores()
while True:
clear_screen()
show_statistics(scores)
scores = rock_paper_scissors(scores)
if not play_again():
break
Similarly, you can treat the scores as an aggregate until you have to update them:
# NB: was `results(wins, losses, draws):`
def rock_paper_scissors(scores):
player = player_choice()
computer = computer_choice()
outcome = game_outcome(player, computer)
show_results(outcome, player, computer)
new_scores = update_scores(scores, outcome)
return new_scores
At this point, the "play one game" has also become a collection of abstract statements. But notice that I'm treating scores
as an opaque blob that I don't need to deal with: I just pass it along to the lower levels, with another data item describing the update to make.
Be consistent!
I notice that when asking the player to choose rock, paper, or scissors, you allow them to type in an answer. But given a Yes/No question, you require a selection of either 1
or 2
. That's consistently surprising. When I ran your code, I wanted to keep typing my answers. (I kept hitting 'y' to play again.)
I suggest you either present the Rock/Paper/Scissors options as a menu, or present the Yes/No options as a string input and look for 'y' or 'n'. Making the interface that much more consistent will be an improvement.
Use data, or code. Not both.
This one is a little subtle, but take a look:
if comp_choice == 'Paper':
print(results_msg(comp_choice, player_choice, 'lost'))
losses += 1
else:
print(results_msg(player_choice, comp_choice, 'won'))
wins += 1
What's significant here is that you have an if/then
statement that decides whether you won or lost. And then you pass that into your results_msg
function as a string parameter. The result of this is that you have a string parameter to be substituted that gives information you already knew: whether the player won or lost.
Let's look at results_msg
:
def results_msg(x, y, result):
message = f"{x} beats {y}. You {result}!"
return message
You have to consider that Python f-strings are code. And they're a pretty compact form of code, compared to the horror of str.format()
. So writing:
print(results_msg(player_choice, comp_choice, 'won'))
is not really an improvement on writing:
print(f"{player} beats {computer}. You won!")
It's not clearer. It's not shorter. It does avoid problems with changing the text of the message, although there isn't much text in the message to change.
I don't think you need to hoist the f-string up into the calling function. I do think you should not pass 'won' or 'lost' as a parameter: you already decided you won or lost. Call a separate function instead.
if ...
win_message(player_choice, comp_choice)
else:
lose_message(player_choice, comp_choice)
Note that this will appear to conflict with the code structure I showed above- because in that code structure, I chose to treat the result as data, not code. I'm not saying you have to use data, or that you have to use code. I'm saying that you should pick one and stick with it. If you determine your outcome as code, go ahead and hard-code the outcome. If you determine your outcome as data, go ahead and treat it as data.
And as a side note, strings with substitutions in them make it hard to do i18n. So there's nothing wrong with having an array of totally spelled out messages at the bottom. It also gives a bit more "flavor" if you customize the verbs:
"Rock breaks scissors. You won!",
"Scissors cuts paper. You won!",
"Paper covers rock. You won!",
...