首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python剪刀通过一个类来处理游戏

Python剪刀通过一个类来处理游戏
EN

Code Review用户
提问于 2019-11-01 21:00:18
回答 2查看 2.8K关注 0票数 6

最初的灵感来自于这个Python初学者,它促使我用我的天赋和我的Python经验重写了一堆东西:第一次尝试: Python摇滚剪刀

好吧,所以我看了上面的帖子,觉得无聊,需要在工作中消磨一个小时。所以我杀了一个小时-我拿他们的RPS游戏,把它变成一个班级,使它看起来不那么邪恶/丑陋。

虽然这绝不是一个完全成熟的程序,我已经创建了干净和真正彻底的测试,这是我至少可以要求投入的东西。

运行非常干净,并使用了许多字符串,这是最初的灵感帖子的操作。但是,它也有很多文档字符串。整个游戏驻留在一个类中,并通过类之类的方式调用。

,因为这个版本使用f-字符串,所以必须有Python3.6或更高版本才能使用此程序/代码.

rps.py

代码语言:javascript
复制
import random


class RockPaperScissors:
    """
    Class to handle an instance of a Rock-Paper-Scissors game
    with unlimited rounds.
    """

    def __init__(self):
        """
        Initialize the variables for the class
        """
        self.wins = 0
        self.losses = 0
        self.ties = 0
        self.options = {'rock': 0, 'paper': 1, 'scissors': 2}

    def random_choice(self):
        """
        Chooses a choice randomly from the keys in self.options.
        :returns: String containing the choice of the computer.
        """

        return random.choice(list(self.options.keys()))

    def check_win(self, player, opponent):
        """
        Check if the player wins or loses.
        :param player: Numeric representation of player choice from self.options
        :param opponent: Numeric representation of computer choice from self.options
        :return: Nothing, but will print whether win or lose.
        """

        result = (player - opponent) % 3
        if result == 0:
            self.ties += 1
            print("The game is a tie! You are a most worthy opponent!")
        elif result == 1:
            self.wins += 1
            print("You win! My honor demands a rematch!")
        elif result == 2:
            self.losses += 1
            print("Haha, I am victorious! Dare you challenge me again?")

    def print_score(self):
        """
        Prints a string reflecting the current player score.
        :return: Nothing, just prints current score.
        """
        print(f"You have {self.wins} wins, {self.losses} losses, and "
              f"{self.ties} ties.")

    def run_game(self):
        """
        Plays a round of Rock-Paper-Scissors with the computer.
        :return: Nothing
        """
        while True:
            userchoice = input("Choices are 'rock', 'paper', or 'scissors'.\n"
                               "Which do you choose? ").lower()
            if userchoice not in self.options.keys():
                print("Invalid input, try again!")
            else:
                break
        opponent_choice = self.random_choice()
        print(f"You've picked {userchoice}, and I picked {opponent_choice}.")
        self.check_win(self.options[userchoice], self.options[opponent_choice])


if __name__ == "__main__":
    game = RockPaperScissors()
    while True:
        game.run_game()
        game.print_score()

        while True:

            continue_prompt = input('\nDo you wish to play again? (y/n): ').lower()
            if continue_prompt == 'n':
                print("You are weak!")
                exit()
            elif continue_prompt == 'y':
                break
            else:
                print("Invalid input!\n")
                continue

欢迎任何建议和意见,因为这是一个粗略的尝试。:)

EN

回答 2

Code Review用户

发布于 2019-11-02 22:59:17

格式与命名

  • 根据PEP 8,所有行应为<= 79个字符
  • userchoice -> user_choice (考虑到您有opponent_choice)
  • continue_prompt -> user_choice (在使用它的上下文中,它实际上是用户对继续提示符的选择/响应,而不是继续提示本身)

Documentation

random_choice的docstring可以改进。与其逐字重复代码(实现)中正在发生的事情,不如以一种方式记录它,这样读者就不需要阅读实现就可以知道该方法将做什么:

代码语言:javascript
复制
def random_choice(self) -> str:
    """
    Randomly chooses rock, paper, or scissors.
    :return: 'rock', 'paper', or 'scissors'
    """

清洁/规范化用户输入

您已经对用户输入调用了lower(),这是很好的,但是您也应该在它上调用strip()。否则,带有前导或尾随空格的用户选择将被视为无效输入(例如,‘摇滚乐,摇滚乐,摇滚乐)

效率

每次对random_choice的调用都会调用self.options字典上的list(),这将在每个调用上重新创建相同的选项列表。考虑只在__init__中创建一次列表:

代码语言:javascript
复制
def __init__(self):
    ...
    self.options = {'rock': 0, 'paper': 1, 'scissors': 2}
    self.choices = list(self.options.keys())

然后我们可以在random_choice中使用它:

代码语言:javascript
复制
def random_choice(self):
    return random.choice(self.choices)

在验证用户选择“rock”、“纸张”或“剪刀”的输入时:

代码语言:javascript
复制
if user_choice in self.choices:
    ...

类结构

由于您的类已经在处理交互式用户输入,所以我认为您提示用户再玩一轮的代码应该驻留在类中。那么,任何想要利用你的班级发起一个互动的多轮摇滚剪刀游戏,只需做game.run_game()

出于同样的原因,对print_score()的调用应该在类内的游戏协调逻辑中;您类的客户端不应该直接调用它。

我认为,如果您将用户输入的交互式提示和检索提取到他们自己的方法中,将更容易阅读。

代码语言:javascript
复制
def player_choice(self) -> str:
    """
    Prompts player for choice of rock, paper, or scissors.
    :return: 'rock', 'paper', or 'scissors'
    """
    while True:
        user_choice = input("Choices are 'rock', 'paper', or 'scissors'.\n"
                            "Which do you choose? ").lower().strip()
        if user_choice in self.choices:
            return user_choice

        print("Invalid input, try again!")
代码语言:javascript
复制
def player_wants_to_play_again(self) -> bool:
    """
    Prompts player to play again.
    :return: True if the player wants to play again.
    """
    prompt = "\nDo you wish to play again? (y/n): "
    valid_choices = {'y', 'n'}
    while True:
        user_choice = input(prompt).lower().strip()
        if user_choice in valid_choices:
            return user_choice == 'y'

        print("Invalid input!")

那么您的主要游戏方法可能如下所示:

代码语言:javascript
复制
def run_one_round(self):
    user_choice = self.player_choice()
    opponent_choice = self.random_choice()
    print(f"You've picked {user_choice}, and I picked {opponent_choice}.")
    self.check_win(self.options[user_choice],
                   self.options[opponent_choice])
    self.print_score()

def run_game(self):
    while True:
        self.run_one_round()
        if not self.player_wants_to_play_again():
            print("You are weak!")
            break

通过这样的结构,我们不再需要调用exit() (它退出Python解释器)来突破主游戏循环。请注意,使用exit()处理程序流中的非异常场景通常被认为是不好的形式,也就是说,如果允许程序正常终止而不必求助于exit(),则应该这样做。

奖金:使用自定义Enum

提高清晰度

在最初的程序中,隐式约定是精确的字符串rockpaperscissors表示每个玩家可以做出的选择,因此是特殊的。您可以通过查看字典self.options来观察这一点,它将上面的字符串映射到整数,因此我们可以在以后使用check_win中的模块化算法对它们进行比较。这听起来像是一种情况,即使用自定义enum.Enum类型可能有助于使事情更加明确。

让我们定义一个名为EnumChoice,它可以接受三个值中的一个:ROCKPAPERSCISSORS。最酷的是,我们可以让Choice负责以下所有工作:

  • str转换到Choice (如果不能转换提供的字符串,抛出一个异常)
  • 为每个Choice定义一个规范的字符串表示,例如“岩石”、“纸”和“剪刀”(从Choicestr的转换)
  • 使Choices具有可比性,这样,如果您有两个Choices X和Y,您可以比较它们以确定哪一个会赢

守则:

代码语言:javascript
复制
from enum import Enum


class Choice(Enum):
    ROCK = 0
    PAPER = 1
    SCISSORS = 2

    @classmethod
    def from_str(cls, s: str) -> "Choice":
        try:
            return {
                "r": cls.ROCK,
                "rock": cls.ROCK,
                "p": cls.PAPER,
                "paper": cls.PAPER,
                "s": cls.SCISSORS,
                "scissors": cls.SCISSORS
            }[s.strip().lower()]
        except KeyError:
            raise ValueError(f"{s!r} is not a valid {cls.__name__}")

    def __str__(self) -> str:
        return self.name.lower()

    def beats(self, other: "Choice") -> bool:
        return (self.value - other.value) % 3 == 1

互动会议显示了它的行动:

代码语言:javascript
复制
>>> list(Choice)
[<Choice.ROCK: 0>, <Choice.PAPER: 1>, <Choice.SCISSORS: 2>]

>>> Choice.from_str('rock')
<Choice.ROCK: 0>

>>> Choice.from_str('paper')
<Choice.PAPER: 1>

>>> Choice.from_str('scissors')
<Choice.SCISSORS: 2>

>>> print(Choice.ROCK)
rock

>>> print(Choice.PAPER)
paper

>>> print(Choice.SCISSORS)
scissors

>>> Choice.ROCK == Choice.ROCK
True

>>> Choice.ROCK.beats(Choice.SCISSORS)
True

>>> Choice.PAPER.beats(Choice.ROCK)
True

>>> Choice.SCISSORS.beats(Choice.PAPER)
True

让我们在RockPaperScissors中使用它来看看它的外观。这里是__init__

代码语言:javascript
复制
def __init__(self):
    self.wins = 0
    self.losses = 0
    self.ties = 0
    self.choices = list(Choice)

现在,random_choiceplayer_choice都返回Choice而不是str,这使得这些方法的类型签名更具表现力:

代码语言:javascript
复制
def random_choice(self) -> Choice:
    return random.choice(self.choices)

def player_choice(self) -> Choice:
    prompt = ("\nChoices are 'rock', 'paper', or 'scissors'.\n"
              "Which do you choose? ")
    while True:
        try:
            return Choice.from_str(input(prompt))
        except ValueError:
            print("Invalid input, try again!")

当我们从上述两种方法返回字符串时,有必要在文档中澄清三条字符串中只有一条将被返回:“rock”、“纸张”或“剪刀”。使用Choice,我们不再需要这样做,因为所有这些信息都在其定义中显式地列出。

类似地,check_win现在采用两个Choices而不是两个ints作为参数。

代码语言:javascript
复制
def check_win(self, player_choice: Choice, opponent_choice: Choice):
    if player_choice == opponent_choice:
        self.ties += 1
        print("The game is a tie! You are a most worthy opponent!")
    elif player_choice.beats(opponent_choice):
        self.wins += 1
        print("You win! My honor demands a rematch!")
    else:
        self.losses += 1
        print("Haha, I am victorious! Dare you challenge me again?")

使用Choice的完整代码可以找到在这个要点里

票数 5
EN

Code Review用户

发布于 2019-11-02 19:56:13

我认为使用字典存储胜负值是有意义的:

代码语言:javascript
复制
    def __init__(self):
        """
        Initialize the variables for the class
        """
        self.options = {'rock': 0, 'paper': 1, 'scissors': 2}
        self.outcome_count = {
            "tie": 0,
            "win": 0,
            "loss": 0,
        }

这使check_win更加“机械”,因为您现在可以通过名称和在静态数据中查找结果来引用结果,而不需要一堆if/else:

代码语言:javascript
复制
    def check_win(self, player, opponent):
        """
        Check if the player wins or loses.
        :param player: Numeric representation of player choice from self.options
        :param opponent: Numeric representation of computer choice from self.options
        :return: Nothing, but will print whether win or lose.
        """

        result = ["tie", "win", "loss"][(player - opponent) % 3]

        self.outcome_count[result] += 1

        outcome_message = {
            "tie":  "The game is a tie! You are a most worthy opponent!",
            "win":  "You win! My honor demands a rematch!",
            "loss": "Haha, I am victorious! Dare you challenge me again?",
        }
        print(outcome_message[result])

当然,它最终使print_score变得不那么容易解释了:

代码语言:javascript
复制
    def print_score(self):
        """
        Prints a string reflecting the current player score.
        :return: Nothing, just prints current score.
        """
        wins   = self.outcome_count["win"]
        losses = self.outcome_count["loss"]
        ties   = self.outcome_count["tie"]
        print(f"You have {wins} wins, {losses} losses, and {ties} ties.")

最后,我认为通过编写run_game循环可以稍微清晰一些。

代码语言:javascript
复制
        while True:
            userchoice = input("Choices are 'rock', 'paper', or 'scissors'.\nWhich do you choose? ").lower()
            if userchoice in self.options.keys():
                break
            print("Invalid input, try again!")

我发现一个没有else的明确的“早期退出”更容易遵循请注意,这种情况不是倒过来的,在这种情况下,我认为这有助于提高清晰度。,不过如果它不是常规的大代码库,这可能会让人不快。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/231706

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档