Python Forum

Full Version: Roshambo with only 1 if switch
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
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()
You should use except KeyError:. You should always catch exceptions as precisely as possible, so other error can propagate correctly.
(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.
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
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
(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.
(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 :-)
(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.
(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
(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.
Pages: 1 2