Python 中的剪刀石头布抽象
Rock Paper Scissors abstraction in Python
我做了一个高级 Python 后端工程师面试,其中涉及实现剪刀石头布游戏。结果并不漂亮。他们的工程师说我到处都有笨拙的实施。之后我做了升级,但仍然有一个问题让我感到困惑。他们希望我将游戏规则的原始实现更改为更好的抽象。
我原来的实现是:
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'
有映射:
MOVE_CHOICE = {
1: 'rock',
2: 'paper',
3: 'scissors',
}
我在别处定义了 role.Player
和 role.Computer
class。他们的批评是:
- 使用字符串(玩家姓名或“draw”)来表示回合的结果
- 没有游戏规则的抽象,只有几个大的布尔表达式 1 对 1 测试所有组合
他们的建议是:
- 将移动(石头、布、剪刀)建模为枚举
- 将回合结果(赢、输、平)建模为枚举
- 将游戏规则建模为需要 2 次移动和 returns 结果的函数,或者作为将移动映射到它获胜的移动列表的字典
到目前为止,我已经为移动和结果创建了枚举:
class MoveChoice(Enum):
ROCK = auto()
PAPER = auto()
SCISSORS = auto()
class Outcome(Enum):
WIN = auto()
LOSE = auto()
DRAW = auto()
但我无法按照他们的要求抽象出游戏规则。我该怎么做?
根据你展示的代码和他们给你的建议,我会这样做:
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
请注意,我已将您的方法更改为函数。这不是对整体程序结构的建议,只是将逻辑呈现为一个更独立的示例。
如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.
唯一会改变映射的是将每个值变成一个单元素列表:
WIN_MAPPING = {
MoveChoice.ROCK: [MoveChoice.SCISSORS],
MoveChoice.SCISSORS: [MoveChoice.PAPER],
MoveChoice.PAPER: [MoveChoice.ROCK],
}
然后 outcome
函数,而不是检查计算机的移动是否 等于 值,必须检查它是否 在 值(因为值现在是一个列表)。
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
我做了一个高级 Python 后端工程师面试,其中涉及实现剪刀石头布游戏。结果并不漂亮。他们的工程师说我到处都有笨拙的实施。之后我做了升级,但仍然有一个问题让我感到困惑。他们希望我将游戏规则的原始实现更改为更好的抽象。
我原来的实现是:
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'
有映射:
MOVE_CHOICE = {
1: 'rock',
2: 'paper',
3: 'scissors',
}
我在别处定义了 role.Player
和 role.Computer
class。他们的批评是:
- 使用字符串(玩家姓名或“draw”)来表示回合的结果
- 没有游戏规则的抽象,只有几个大的布尔表达式 1 对 1 测试所有组合
他们的建议是:
- 将移动(石头、布、剪刀)建模为枚举
- 将回合结果(赢、输、平)建模为枚举
- 将游戏规则建模为需要 2 次移动和 returns 结果的函数,或者作为将移动映射到它获胜的移动列表的字典
到目前为止,我已经为移动和结果创建了枚举:
class MoveChoice(Enum):
ROCK = auto()
PAPER = auto()
SCISSORS = auto()
class Outcome(Enum):
WIN = auto()
LOSE = auto()
DRAW = auto()
但我无法按照他们的要求抽象出游戏规则。我该怎么做?
根据你展示的代码和他们给你的建议,我会这样做:
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
请注意,我已将您的方法更改为函数。这不是对整体程序结构的建议,只是将逻辑呈现为一个更独立的示例。
如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.
唯一会改变映射的是将每个值变成一个单元素列表:
WIN_MAPPING = {
MoveChoice.ROCK: [MoveChoice.SCISSORS],
MoveChoice.SCISSORS: [MoveChoice.PAPER],
MoveChoice.PAPER: [MoveChoice.ROCK],
}
然后 outcome
函数,而不是检查计算机的移动是否 等于 值,必须检查它是否 在 值(因为值现在是一个列表)。
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