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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With