Reputation: 47
I did a senior Python backend engineer interview that involved implementing a Rock Paper Scissors game. The result was not pretty. Their engineer said I have clumsy implementation all over the place. I did an upgrade afterwards, but still have one issue that confuses me. They would like me to change my original implementation of the game rules to a better abstraction.
My original implementation is:
def _outcome(self, player_move, ai_move):
"""Determine the outcome of current round.
Paper beats (wraps) rock.
Rock beats (blunts) scissors.
Scissors beats (cuts) paper.
Parameters
----------
player_move : MoveChoice
Player's move for current round.
ai_move : MoveChoice
AI's move for current round.
Returns
-------
outcome : Outcome
Outcome of the current round.
"""
if player_move == 1 and ai_move == 2 or \
player_move == 2 and ai_move == 3 or \
player_move == 3 and ai_move == 1:
return self.computer.name
elif player_move == 1 and ai_move == 3 or \
player_move == 2 and ai_move == 1 or \
player_move == 3 and ai_move == 2:
return self.player.name
else:
return 'Draw'
with a mapping:
MOVE_CHOICE = {
1: 'rock',
2: 'paper',
3: 'scissors',
}
I have a role.Player
and role.Computer
class defined elsewhere. Their critiques are:
Their suggestions are:
So far I've created Enums for Moves and Outcome:
class MoveChoice(Enum):
ROCK = auto()
PAPER = auto()
SCISSORS = auto()
class Outcome(Enum):
WIN = auto()
LOSE = auto()
DRAW = auto()
But I'm having trouble abstracting the game rules like they asked. How would I do that?
Upvotes: 0
Views: 512
Reputation: 3518
Based on the code you've shown and the recommendations they gave you, I would do something like this:
from enum import Enum, auto
class MoveChoice(Enum):
ROCK = auto()
PAPER = auto()
SCISSORS = auto()
class Outcome(Enum):
WIN = auto()
LOSE = auto()
DRAW = auto()
WIN_MAPPING = {
MoveChoice.ROCK: MoveChoice.SCISSORS,
MoveChoice.SCISSORS: MoveChoice.PAPER,
MoveChoice.PAPER: MoveChoice.ROCK,
}
def outcome(player_move, ai_move):
if player_move == ai_move:
return Outcome.DRAW
elif WIN_MAPPING[player_move] == ai_move:
return Outcome.WIN
else:
return Outcome.LOSE
Note that I've changed your method to a function. This isn't a recommendation on overall program structure, it's just to present the logic as a more self-contained example.
As Luke Nelson pointed out in the comments, the interviewers suggested that the win mapping map each move to a list of moves it beats. I missed this detail. While it doesn't make any practical difference in Rock Paper Scissors, it is more generalized, and would make it easier if they then ask you to expand the rule set to something like Rock Paper Scissors Lizard Spock.
The only thing that would change about the mapping would be turning each value into a single-element list:
WIN_MAPPING = {
MoveChoice.ROCK: [MoveChoice.SCISSORS],
MoveChoice.SCISSORS: [MoveChoice.PAPER],
MoveChoice.PAPER: [MoveChoice.ROCK],
}
Then the outcome
function, instead of checking if the computer's move equals the value, would have to check if it's in the value (since the value is now a list).
def outcome(player_move, ai_move):
if player_move == ai_move:
return Outcome.DRAW
# elif WIN_MAPPING[player_move] == ai_move:
# Above line is replaced with:
elif ai_move in WIN_MAPPING[player_move]:
return Outcome.WIN
else:
return Outcome.LOSE
Upvotes: 2