I am trying to become a better coder, which includes getting rid of my \'hard-coding\' habits to keep my programs dynamic and easy to maintain.
Right now I am writing a
What I initially thought about the Rock–Paper–Scissors (RPS) rules:
Apparently (thanks to [Wikipedia]: Rock–paper–scissors), for a balanced game (odd number of elements):
Each element beats half of the other ones (and as a consequence, loses to the other half)
This is a generalization of the 3 element (RPS) game (and also applies to RPSLS)
Here's what the above rule looks like when put into code (I've also redesigned it to correct some errors in your snippet). All the "magic" happens in outcome.
code00.py:
#!/usr/bin/env python3
import sys
_elements_list = [
["Rock", "Paper", "Scissors"],
["Rock", "Paper", "Scissors", "Spock", "Lizard"], # !!! The order is DIFFERENT (RPSSL) than the name of the game: RPSLS !!!
]
elements_dict = {len(item): item for item in _elements_list}
del _elements_list
def get_users_choices(valid_options):
ret = [-1] * 2
for i in (0, 1):
user_choice = None
while user_choice not in valid_options:
user_choice = input("Enter user {0:d} option (out of {1:}): ".format(i + 1, valid_options))
ret[i] = valid_options.index(user_choice)
return ret
def outcome(idx0, idx1, count): # Returns -1 when 1st player wins, 0 on draw and 1 when 2nd player wins
if idx0 == idx1:
return 0
index_steps = [-i * 2 - 1 for i in range(count // 2)] # Index steps (n // 2 items) from current index: {-1, -3, -5, ...} (negative values mean: before)
idx0_beat_idxes = [(idx0 + i + count) % count for i in index_steps] # Wrap around when reaching the beginning of the list
if idx1 in idx0_beat_idxes:
return -1
return 1
def main():
element_count = 3 # Change it to 5 for RPSLS
if element_count <= 2:
raise ValueError("Can't play game")
elements = elements_dict.get(element_count)
if not elements:
raise ValueError("Invalid option count")
choices = get_users_choices(elements)
res = outcome(*choices, element_count)
if res == 0:
print("'{0:s}' and '{1:s}' are DRAW.".format(elements[choices[0]], elements[choices[1]]))
elif res < 0:
print("'{0:s}' WINS over '{1:s}'.".format(elements[choices[0]], elements[choices[1]]))
else:
print("'{0:s}' LOSES to '{1:s}'.".format(elements[choices[0]], elements[choices[1]]))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main()
print("\nDone.")
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q057491776]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32 Enter user 1 option (out of ['Rock', 'Paper', 'Scissors']): Rock Enter user 2 option (out of ['Rock', 'Paper', 'Scissors']): Scissors 'Rock' WINS over 'Scissors'. Done.
List is a good idea. In your case validoptions = ["rock", "paper", "scissors"]
you can see, everything beats the only one before it (the "paper"
beats the "rock"
, the "rock"
beats the "scissors"
and the "scissors"
beats the "paper"
. So if you sort it that way, it is solvable with the use of the indexes only. If you want to increase the choices, you can, but tke care, only the odd numbers will provide fair game.
In general if you make a list of options
, with a length of length
, then:
if u1 == u2:
#it is a draw
elif u2input in validoptions[u1 - int((length-1)/2):u1]:
#player1 has won
else:
#player2 has won
This is really cool! So, I think I would use a dictionary to control what loses to what:
dict_loss = dict()
dict_loss['paper']='scissors'
dict_loss['scissors']='rock'
dict_loss['rock']='paper'
Then the players make a choice and you just check if their choices fall into the dictionaries:
player_1='paper'
player_2='rock'
if player_2 in dict_loss[player_1]:
print("Player 2 Wins")
else:
if player_1 in dict_loss[player_2]:
print("Player 1 Wins")
else:
print("DRAW")
You can extend the dictionary with the new objects you get, I'm not sure how Pans, swords and riffles work, but you can do:
dict_loss['paper']=['scissors', 'riffle']
if paper loses to riffles, and so on...
Hope this helps, if you have any "data structure" restrictions let me know and I will try to think of something different.
As the rules are not clearly defined, it is not trivial to give a one-size-fits-all-solution. I would probably assume there is some cyclic definition if "win/lose", giving me modulo-calculus such as e.g.:
winner = ["None", "Player 1", "Player 2"]
win_index = (u1 - u2) % len(validoptions)
print("Winner: " + winner[win_index])
Perhaps it is also interesting to take a look at: https://en.wikipedia.org/wiki/Rock%E2%80%93paper%E2%80%93scissors#Additional_weapons.