Posts: 158
Threads: 27
Joined: Jan 2019
Trying to learn more pythonic ways of doing things.
How'd I do? Any suggestions welcome, please.
import random
combinations = {"Paper": {"Rock": "Win",
"Paper": "Draw",
"Scissors": "Lose"},
"Rock": {"Paper": "Lose",
"Rock": "Draw",
"Scissors": "Win"},
"Scissors": {"Paper": "Win",
"Scissors": "Draw",
"Rock": "Lose"}
}
scoring = {"Win": 1, "Draw": 0, "Lose": -1}
user_choice = ""
score = 0
while user_choice.title() != "Quit":
print("\nPaper, Rock, Scissors, or Quit")
user_choice = input("Please enter your choice: ").title()
computer_choice = list(combinations)[random.randint(0, 2)]
try:
result = combinations[user_choice][computer_choice]
score += scoring[result]
print("Your choice %s: Computer choice %s: You %s: Your Score %d" %
(user_choice, computer_choice, result, score))
except:
if user_choice == "Quit":
print("Final score: %d" % score)
else:
print("Invalid Entry")
input()
Posts: 4,220
Threads: 97
Joined: Sep 2016
You should use except KeyError: . You should always catch exceptions as precisely as possible, so other error can propagate correctly.
Posts: 158
Threads: 27
Joined: Jan 2019
(Jan-24-2019, 11:55 PM)ichabod801 Wrote: You should use except KeyError: . You should always catch exceptions as precisely as possible, so other error can propagate correctly.
Very good tip. Thank you.
Posts: 8,168
Threads: 160
Joined: Sep 2016
maybe a better choice of data structure, using f-strings, random.choice
import random
game = {"Paper":[("Rock", "Win"),
("Paper", "Draw"),
("Scissors", "Lose")],
"Rock":[("Scissors", "Win"),
("Rock", "Draw"),
("Paper", "Lose")],
"Scissors":[("Paper", "Win"),
("Scissors", "Draw"),
("Rock", "Lose")]}
scoring = {"Win": 1, "Draw": 0, "Lose": -1}
score = 0
while True:
print("\nPaper, Rock, Scissors, or Quit")
user_choice = input("Please enter your choice: ").title()
if user_choice == "Quit":
break
try:
computer_choice, result = random.choice(game[user_choice])
except KeyError:
print("Invalid Entry")
else:
score += scoring[result]
print(f"Your choice {user_choice} | Computer choice {computer_choice} | You {result} | Your Score {score}")
input(f"Final score: {score}") Further steps - maybe using collections.namedtuple
Posts: 1,950
Threads: 8
Joined: Jun 2018
Jan-25-2019, 09:30 AM
(This post was last modified: Jan-25-2019, 11:14 AM by perfringo.)
Instead of verbose dictionary one can combine Python indices and % with some math.
We order elements so that next beats previous:
>>> choices = ['Rock', 'Paper', 'Scissors'] The problem is, that 'previous' to 'Rock' should be 'Scissors' which is actually last in list. Winning combinations using indices are:
0 -> 2 (Rock beat Scissors)
1 -> 0 (Paper beat Rock)
2 -> 1 (Scissors beat Paper)
As length of choices is 3 then we can use Python % to do something like that:
0 -> 2 ---> (0 - 2) % 3 -> (2 - 0) % 3 ---> 1 -> 2 ---> 1 beats 2
1 -> 0 ---> (1 - 0) % 3 -> (0 - 1) % 3 ---> 1 -> 2 ---> 1 beats 2
2 -> 1 ---> (2 - 1) % 3 -> (1 - 2) % 3 ---> 1 -> 2 ---> 1 beats 2
This means that winning side (i.e 'previous') returns 1 and loosing side (i.e 'next') returns 2. You can test it to hold true with the opposite as well (loosing combinations are 2 -> 0, 0 -> 1 and 1 -> 2)
% with negative numbers:
>>> (0 - 2) % 3
1
>>> (0 - 1) % 3
2
>>> (1 - 2) % 3
2 So we can do something like (no data validation applied, just to show how to use previous principle to determine winner):
>>> choices = ['Rock', 'Paper', 'Scissors']
>>> user_choice = # get user choice and return corresponding index in choices
>>> computer_choice = random.choice(range(3)) # or random.choice(len(choices))
>>> if user_choice == computer_choice:
... # draw
>>> elif (user_choice - computer_choice) % 3 < (computer_choice - user_choice) % 3:
... # user wins
... else:
... # computer wins
I'm not 'in'-sane. Indeed, I am so far 'out' of sane that you appear a tiny blip on the distant coast of sanity. Bucky Katt, Get Fuzzy
Da Bishop: There's a dead bishop on the landing. I don't know who keeps bringing them in here. ....but society is to blame.
Posts: 8,168
Threads: 160
Joined: Sep 2016
(Jan-25-2019, 09:30 AM)perfringo Wrote: Instead of verbose dictionary one can combine Python indices and % with some math. Please, that would be way more complex and unreadable
Quote:Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Posts: 1,950
Threads: 8
Joined: Jun 2018
(Jan-25-2019, 09:37 AM)buran Wrote: Please, that would be way more complex and unreadable
Quote:Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
I do agree with zen. I haven't proposed that this is the 'right' solution to the problem. This is just example how one can approach and solve this task.
My problem with this dictionary that this is so not DRY and is error prone. There is 4 repetitions of words 'Scissors', 'Rock' and 'Paper'; not to mention quite a complicated (handwritten) structure. What is the probability to have typo while writing it? In my mind Python should not to be used as typing machine; we should let Python to do the job.
I would argue that everything what we don't know is complicated. Almost everything we encounter first time seems complicated.
I would argue that it is matter of judgement what constitutes simple or complicated. If this 'algorithm' is moved into function would it make it simple? The code is actually quite simple, underlying principle may be 'foreign' (i.e. 'how the hell this works').
I would argue that Bloom's taxonomy (link to wikipedia article as well) which defines objectives for K12 education has sound principles. If one is not able to apply and synthesise then one haven't learnt it (yet). Most valuable is to combine, apply and synthesise knowledge from different fields.
For me personally (while back) was 'complicated' that -1 % 3 and -4 % 3 are 2. But it just me :-)
I'm not 'in'-sane. Indeed, I am so far 'out' of sane that you appear a tiny blip on the distant coast of sanity. Bucky Katt, Get Fuzzy
Da Bishop: There's a dead bishop on the landing. I don't know who keeps bringing them in here. ....but society is to blame.
Posts: 158
Threads: 27
Joined: Jan 2019
Jan-26-2019, 06:16 PM
(This post was last modified: Jan-26-2019, 06:16 PM by Clunk_Head.)
(Jan-25-2019, 08:20 AM)buran Wrote: maybe a better choice of data structure, using f-strings, random.choice
code removed for brevity
Further steps - maybe using collections.namedtuple
I really like your suggestion of random.choice.
I like f-strings for their adherence to zop "simple is better than complex" and "practicality beats purity", and they are nice and fast. However, I am so used to using % substitution and I'm a bit curmudgeonly. I should make that transition as well. I guess I need to retrain my fingers to fing some f-strings in the future. One question about f-strings, can you print a single { in an f-string?
(Jan-25-2019, 09:30 AM)perfringo Wrote: Instead of verbose dictionary one can combine Python indices and % with some math.
We order elements so that next beats previous:
>>> choices = ['Rock', 'Paper', 'Scissors'] The problem is, that 'previous' to 'Rock' should be 'Scissors' which is actually last in list. Winning combinations using indices are:
0 -> 2 (Rock beat Scissors)
1 -> 0 (Paper beat Rock)
2 -> 1 (Scissors beat Paper)
As length of choices is 3 then we can use Python % to do something like that:
0 -> 2 ---> (0 - 2) % 3 -> (2 - 0) % 3 ---> 1 -> 2 ---> 1 beats 2
1 -> 0 ---> (1 - 0) % 3 -> (0 - 1) % 3 ---> 1 -> 2 ---> 1 beats 2
2 -> 1 ---> (2 - 1) % 3 -> (1 - 2) % 3 ---> 1 -> 2 ---> 1 beats 2
This means that winning side (i.e 'previous') returns 1 and loosing side (i.e 'next') returns 2. You can test it to hold true with the opposite as well (loosing combinations are 2 -> 0, 0 -> 1 and 1 -> 2)
% with negative numbers:
>>> (0 - 2) % 3
1
>>> (0 - 1) % 3
2
>>> (1 - 2) % 3
2 So we can do something like (no data validation applied, just to show how to use previous principle to determine winner):
>>> choices = ['Rock', 'Paper', 'Scissors']
>>> user_choice = # get user choice and return corresponding index in choices
>>> computer_choice = random.choice(range(3)) # or random.choice(len(choices))
>>> if user_choice == computer_choice:
... # draw
>>> elif (user_choice - computer_choice) % 3 < (computer_choice - user_choice) % 3:
... # user wins
... else:
... # computer wins
I like where you are going with this solution. The concept of repeating anything in a program does bother me. While my solution may have its own elegance for a problem of this limited complexity it would be useless for a game with any more complexity. Even an intelligence for a game as simple as tic tac toe would overload my method.
On the other hand I have different fingers and I agree with buran on readability.
(Jan-25-2019, 09:37 AM)buran Wrote: (Jan-25-2019, 09:30 AM)perfringo Wrote: Instead of verbose dictionary one can combine Python indices and % with some math. Please, that would be way more complex and unreadable
Quote:Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
I'm playing with both points of view and I'm hoping to come up with a good cinergy. I'm considering combining your solution with an enumeration for readability. Does that sound readable and simple? Guess we'll see soon.
Thanks everyone for great suggestions so far.
Posts: 8,168
Threads: 160
Joined: Sep 2016
Jan-26-2019, 06:56 PM
(This post was last modified: Jan-26-2019, 06:56 PM by buran.)
(Jan-26-2019, 06:16 PM)Clunk_Head Wrote: I like f-strings for their adherence to zop "simple is better than complex" and "practicality beats purity", and they are nice and fast. However, I am so used to using % substitution and I'm a bit curmudgeonly. I should make that transition as well. I guess I need to retrain my fingers to fing some f-strings in the future. One question about f-strings, can you print a single { in an f-string?
Actually, you are way behind. %-style is the oldest one. Then, there was str.format() method. And f-strings are newest addition. You may want to take a look at https://pyformat.info/
https://docs.python.org/3.7/library/stri...i-language
with f-strings you can do
>>> a = 3
>>> b = 5
>>> print(f'{a} + {b} = {a+b}')
>>> 3 + 5 = 8 and example how to print single {
>>> print('{{ this is {}'.format('example'))
{ this is example
Posts: 158
Threads: 27
Joined: Jan 2019
Jan-27-2019, 03:19 AM
(This post was last modified: Jan-27-2019, 03:20 AM by Clunk_Head.)
(Jan-25-2019, 09:30 AM)perfringo Wrote: Instead of verbose dictionary one can combine Python indices and % with some math.
We order elements so that next beats previous:
>>> choices = ['Rock', 'Paper', 'Scissors'] The problem is, that 'previous' to 'Rock' should be 'Scissors' which is actually last in list. Winning combinations using indices are:
0 -> 2 (Rock beat Scissors)
1 -> 0 (Paper beat Rock)
2 -> 1 (Scissors beat Paper)
As length of choices is 3 then we can use Python % to do something like that:
0 -> 2 ---> (0 - 2) % 3 -> (2 - 0) % 3 ---> 1 -> 2 ---> 1 beats 2
1 -> 0 ---> (1 - 0) % 3 -> (0 - 1) % 3 ---> 1 -> 2 ---> 1 beats 2
2 -> 1 ---> (2 - 1) % 3 -> (1 - 2) % 3 ---> 1 -> 2 ---> 1 beats 2
This means that winning side (i.e 'previous') returns 1 and loosing side (i.e 'next') returns 2. You can test it to hold true with the opposite as well (loosing combinations are 2 -> 0, 0 -> 1 and 1 -> 2)
% with negative numbers:
>>> (0 - 2) % 3
1
>>> (0 - 1) % 3
2
>>> (1 - 2) % 3
2 So we can do something like (no data validation applied, just to show how to use previous principle to determine winner):
>>> choices = ['Rock', 'Paper', 'Scissors']
>>> user_choice = # get user choice and return corresponding index in choices
>>> computer_choice = random.choice(range(3)) # or random.choice(len(choices))
>>> if user_choice == computer_choice:
... # draw
>>> elif (user_choice - computer_choice) % 3 < (computer_choice - user_choice) % 3:
... # user wins
... else:
... # computer wins
import random
moves = {"Rock": 0, "Paper": 1, "Scissors": 2}
results = {-1: "Lose", 0: "Draw", 1: "Win"}
score = 0
while True:
print("\nPaper, Rock, Scissors, or Quit")
user_choice = input("Please enter your choice: ").title()
if user_choice == "Quit":
break
computer_choice = random.choice(list(moves))
try:
if user_choice != computer_choice:
if moves[user_choice]%3 > moves[computer_choice]%3:
result = 1
else:
result = -1
else:
result = 0
score += result
print(f"User choice: {user_choice}, Computer choice: {computer_choice}, You {results[result]}, Score: {score}")
except KeyError:
print("Invalid Entry")
input(f"Final score: {score}") Here is the application of all of the suggestions. I have to say that unless there is a tremendous speed gain or space savings that the mod(3) solution sacrifices readability. I'm going to try a different approach but I don't think I'm a fan of it.
|