Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a more pythonic way to write multiple comparisons

Tags:

python

I am writing a simple card game (Similar to Snap). I've got it working, without problems, but I feel that there ought to be a more elegant solution. Given a set of win conditions:
Y beats R
R beats B
B beats Y
etc

I want to compare the two player's cards and assign both cards to the winner. Caveat: I'm teaching at secondary school level (no OOP) and want to be able to discuss the resulting code with students.

I've left the final condition as an elif, as I wanted to go back and add extra cards to the list of options

The if - elif chain works without problems; I was wondering if there was a more elegant solution.

    #I have code that randomly selects from a list, but this is the basic 
    #idea:
    p1=input("enter r,y or b")  
    p2=input("enter r,y or b")  

    stack1=[]  
    stack2=[]  

    if   p1=="r" and p2=="b":  
        stack1.extend([p1,p2])  
    elif p1=="y" and p2=="r":  
        stack1.extend([p1,p2])  
    elif p1 =="b" and p2 =="y":  
        stack1.extend([p1,p2])  
    elif p2 =="r" and p1 =="b":  
        stack2.extend([p1,p2])  
    elif p2 =="y" and p1 =="r":  
        stack2.extend([p1,p2])             
    elif p2 =="b" and p1 =="y":  
        stack2.extend([p1,p2])  

    print(stack1)  
    print(stack2)  

I've excerpted the code from the remainder - the cards are all randomly generated, so no user input is actually required.

like image 615
MisterPhil Avatar asked Jun 11 '19 12:06

MisterPhil


2 Answers

Create a new dictionary with Y, R, B each mapping to 0, 1, 2.

win_map = {"Y": 0, "R": 1, "B": 2}

We can see a cyclic relationship here. 0 beats 1, 1 beats 2, and 2 beats 0. The first two cases are easy to determine using a simple >, but taking the third case into account needs another method. With a bit of ingenuity, we can see that we can "wrap" by adding 1 and using a modulo operation %. (0+1) % 3 == 1, (1+1) % 3 == 2, and (2+1) % 3 == 0, and these 3 cases are the only cases where a winner is determined.

if (win_map[p1] + 1) % 3 == win_map[p2]: ...  # p1 wins
else if (win_map[p2] + 1) % 3 == win_map[p1]: ... # p2 wins

I am not sure how well this will be conveyed to students though, but it is a cleaner solution.

Note: this method will not work with more cards, as the cyclic relationship will be broken.

like image 177
RocketLL Avatar answered Nov 19 '22 02:11

RocketLL


So your win conditions look like a collection of (winner, loser) pairs and comparing your (p1, p2) input to them looks like the simplest thing to do.

win_conditions = {
    ('y', 'r'),
    ('r', 'b'),
    ('b', 'y')
}

p1=input("enter r,y or b")
p2=input("enter r,y or b")

stack1=[]
stack2=[]

if (p1, p2) in win_conditions:
    stack1.extend([p1,p2])
elif (p2, p1) in win_conditions:
    stack2.extend([p1,p2])
else:
    raise ValueError('{} and {} cannot beat each other.'.format(p1, p2))

Note that the code can be simplified if you assume that the win conditions are exhaustive.

I think you will do your students a favor if you show them how to improve readability by encansulating low-level operations in functions with proper names so that the intent is more obvious.

def beats(p1, p2):
    return (p1, p2) in win_conditions

if beats(p1, p2):
    stack1.extend([p1,p2])
elif beats(p2, p1):
    stack2.extend([p1,p2])
else:
    raise ValueError('"{}" and "{}" cannot beat each other.'.format(p1, p2))

Maybe you can find a better name for whatever you want to achieve by extending the list.

like image 3
Stop harming Monica Avatar answered Nov 19 '22 02:11

Stop harming Monica