【Dison夏令营 Day 01】如何用Python写一个游戏“石头剪刀布”

欢迎你们踏入这个充满无限可能性的编程世界!作为一名热爱Python的开发者,我深感编程的魅力,并希望与你们一同分享这份乐趣和激情。编程,不仅仅是一种技能,更是一种思维方式和解决问题的工具。在Python的世界里,你们将会学习到如何用代码构建出令人惊叹的项目,从简单的文本处理到复杂的机器学习模型,每一步的进步都会让你们感受到编程的魔力。

我们生活在一个数字化、智能化的时代,编程已经成为了这个时代的通用语言。掌握编程,不仅能让你们在未来的职业生涯中更具竞争力,还能让你们更好地理解和参与这个瞬息万变的世界。因此,我诚挚地邀请你们加入我们的行列,一同探索Python的奥秘,体验编程的乐趣。无论你是初学者还是已经有一定编程经验的朋友,这里都将是你学习和成长的乐园。让我们一起用代码创造美好的未来吧!

在本教程中,您将学习如何

  • 编写自己的剪刀石头布游戏
  • 使用 input() 接收用户输入
  • 使用 while 循环连续玩几个游戏
  • 使用枚举函数清理代码
  • 字典定义更复杂的规则

什么是剪刀石头布?

你可能玩过剪刀石头布。也许你曾用它来决定谁请客吃饭,或者谁能优先选择球队的球员。

如果您不熟悉,剪刀石头布是一种两人或多人玩的手牌游戏。参与者说 “石头、剪子、布”,然后双手同时握成石头(握拳)、布(手心朝下)或剪刀(伸出两根手指)的形状。规则很简单:

  • 石头击碎剪刀。
  • 覆盖石头。
  • 剪刀剪布。

既然已经掌握了规则,就可以开始思考如何将它们转化为 Python 代码了。

用 Python 玩一次剪刀石头布游戏

利用上面的描述和规则,你可以制作一个剪刀石头布游戏。在开始之前,您需要导入用于模拟计算机选择的模块:

	import random

太棒了现在,你可以使用随机工具中的不同工具来随机化电脑在游戏中的操作。现在怎么办?由于用户也需要能够选择他们的操作,因此您需要的第一件合乎逻辑的事情就是找到一种接收用户输入的方法。

获取用户输入

获取用户输入在 Python 中非常简单。这里的目标是询问用户想选择什么操作,然后将选择赋值给一个变量:

这里代码我就保持美式键盘风格,简化学习难度(但是外国石头剪刀布的游戏名称和国内些许不一样,叫Rock Paper Scissors,就像俄罗斯方块叫Tetris)

user_action = input("Enter a choice (rock, paper, scissors): ")

这将提示用户输入一个选择,并将其保存到一个变量中,以供以后使用。既然用户已经选择了一项操作,计算机就需要决定该怎么做。

让电脑做出选择

剪刀石头布的竞技游戏涉及策略。与其为此开发一个模型,不如让计算机随机选择一个动作,这样可以节省一些时间。随机选择是让计算机选择伪随机值的好方法。

你可以使用 random.choice() 让电脑随机选择动作:

possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)

这样就可以从列表中随机选择一个元素。您还可以打印用户和计算机做出的选择:

print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")

打印用户和计算机的操作对用户很有帮助,而且还能帮助你在稍后调试,以防结果出现问题。

决定胜负

既然两个玩家都做出了选择,那么就需要一种方法来决定谁赢了。使用 if ... elif ... else 块,您就可以比较玩家的选择并决定胜负:

if user_action == computer_action:
    print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
    if computer_action == "scissors":
        print("Rock smashes scissors! You win!")
    else:
        print("Paper covers rock! You lose.")
elif user_action == "paper":
    if computer_action == "rock":
        print("Paper covers rock! You win!")
    else:
        print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
    if computer_action == "paper":
        print("Scissors cuts paper! You win!")
    else:
        print("Rock smashes scissors! You lose.")

通过先比较平局条件,可以避免很多情况。如果不这样做,就需要检查 user_action 的每个可能操作,并与 computer_action 的每个可能操作进行比较。如果先检查平局条件,那么只需对 computer_action 进行两次条件检查,就能知道计算机选择了什么。

就是这样!综合起来,您的代码现在应该是这样的:

import random

user_action = input("Enter a choice (rock, paper, scissors): ")
possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)
print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")

if user_action == computer_action:
    print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
    if computer_action == "scissors":
        print("Rock smashes scissors! You win!")
    else:
        print("Paper covers rock! You lose.")
elif user_action == "paper":
    if computer_action == "rock":
        print("Paper covers rock! You win!")
    else:
        print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
    if computer_action == "paper":
        print("Scissors cuts paper! You win!")
    else:
        print("Rock smashes scissors! You lose.")

您现在已经编写了代码,可以接收用户输入,为计算机选择随机操作,并决定胜负!但这只能让你在程序运行结束前玩一场游戏。

连续玩几个游戏

虽然单人剪刀石头布游戏超级有趣,但如果能连续玩几个游戏不是更好吗?循环是创建重复事件的好方法。特别是,您可以使用 while 循环来无限期地玩游戏:

import random

while True:
    user_action = input("Enter a choice (rock, paper, scissors): ")
    possible_actions = ["rock", "paper", "scissors"]
    computer_action = random.choice(possible_actions)
    print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")

    if user_action == computer_action:
        print(f"Both players selected {user_action}. It's a tie!")
    elif user_action == "rock":
        if computer_action == "scissors":
            print("Rock smashes scissors! You win!")
        else:
            print("Paper covers rock! You lose.")
    elif user_action == "paper":
        if computer_action == "rock":
            print("Paper covers rock! You win!")
        else:
            print("Scissors cuts paper! You lose.")
    elif user_action == "scissors":
        if computer_action == "paper":
            print("Scissors cuts paper! You win!")
        else:
            print("Rock smashes scissors! You lose.")

    play_again = input("Play again? (y/n): ")
    if play_again.lower() != "y":
        break

请注意上面突出显示的几行。重要的是要检查用户是否还想继续游戏,如果不想就中断游戏。如果不进行检查,用户就会被迫继续游戏,直到使用 Ctrl+C 或类似方法终止控制台。

再次播放的检查是对字符串 "y "的检查。但像这样对特定内容进行检查可能会使用户更难停止游戏。如果用户输入 "yes "或 "no "呢?字符串比较往往很棘手,因为你永远不知道用户会输入什么。他们可能全小写,也可能全大写,甚至两者混合。

下面是几个不同字符串比较的结果:

>>> play_again = "yes"
>>> play_again == "n"
False
>>> play_again != "y"
True

嗯。这不是你想要的。如果用户输入 “yes”,本以为可以再次游戏,但却被踢出了游戏,他们可能会不太高兴。

使用 enum.IntEnum 描述操作

由于字符串比较可能导致如上所示的问题,因此最好尽可能避免字符串比较。然而,您的程序首先会要求用户输入一个字符串!如果用户错误地输入了 "Rock "或 "rOck "怎么办?大小写很重要,所以它们不会相等:

print("rock" == "Rock")

由于大小写很重要,"r "和 "R "并不等同。一个可行的解决方案是用数字来代替。给每个操作分配一个数字可以省去一些麻烦:

ROCK_ACTION = 0
PAPER_ACTION = 1
SCISSORS_ACTION = 2

这样,您就可以通过分配的编号来引用不同的操作。整数不会像字符串那样存在比较问题,因此可以使用。现在,您可以让用户输入一个数字,然后直接与这些值进行比较:

user_input = input("Enter a choice (rock[0], paper[1], scissors[2]): ")
user_action = int(user_input)
if user_action == ROCK_ACTION:
    # Handle ROCK_ACTION

由于 input() 返回的是字符串,因此需要使用 int() 将返回值转换为整数。然后就可以将输入值与上述每个操作进行比较。这种方法效果很好,但可能需要正确命名变量才能跟踪它们。更好的方法是使用 enum.IntEnum 并定义自己的操作类!

使用 enum.IntEnum 可以创建属性并为其赋值,与上图类似。这样可以将动作归类到自己的命名空间中,使代码更具表现力,从而有助于清理代码:

from enum import IntEnum

class Action(IntEnum):
    Rock = 0
    Paper = 1
    Scissors = 2

这将创建一个自定义动作,您可以用它来引用您支持的不同类型的动作。它的工作原理是将其中的每个属性分配给您指定的值。

比较仍然简单明了,现在它们有了一个与之相关的有用的类名:

>>> Action.Rock == Action.Rock
True

由于成员值相同,因此比较结果相同。类名还能让人更清楚地看出您要比较的是两个操作。

您甚至可以从一个 int 创建一个 Action:

>>> Action.Rock == Action(0)
True
>>> Action(0)
<Action.Rock: 0>

Action 会查看传入的值并返回相应的 Action。这很有帮助,因为您现在可以将用户输入作为一个 int,并从中创建一个 Action。再也不用担心拼写问题了!

程序的流程(图)

虽然 "剪刀石头布 "看似并不复杂,但仔细考虑玩 "剪刀石头布 "的步骤非常重要,这样才能确保您的程序涵盖所有可能的情况。对于任何项目,即使是小项目,绘制出所需行为的流程图并围绕流程图执行代码都是很有帮助的。使用项目列表也能达到类似的效果,但较难捕捉到循环和条件逻辑等内容。

流程图不必过于复杂,甚至不必使用真正的代码。只需提前描述所需的行为,就能帮助您在问题发生之前解决它们!

下面是一个描述剪刀石头布游戏的流程图:

在这里插入图片描述
每个玩家选择一个动作,然后决出胜负。这个流程图对于您编码的单个游戏来说是准确的,但对于现实生活中的游戏来说却不一定准确。在现实生活中,玩家会同时选择他们的行动,而不是像流程图所示那样一次选择一个。

不过,在编码版本中,这种做法是可行的,因为玩家的选择对电脑是隐藏的,而电脑的选择对玩家也是隐藏的。两名玩家可以在不同时间做出选择,而不会影响游戏的公平性。

流程图可以帮助您及早发现可能出现的错误,还可以让您了解是否需要添加更多功能。例如,下面的流程图描述了如何反复玩游戏,直到用户决定停止:

在这里插入图片描述
在不编写代码的情况下,您可以看到第一个流程图没有办法再次播放。这种方法可以让你在编程前解决类似的问题,从而帮助你创建更整洁、更易于管理的代码!

将代码拆分成函数

现在,您已经使用流程图勾勒出了程序的流程,您可以尝试组织代码,使其更接近您所确定的步骤。一种自然的方法是为流程图中的每个步骤创建一个函数。函数是将大段代码分隔成更小、更易于管理的代码块的好方法。

您不一定需要创建一个函数来再次执行条件检查,但如果您愿意,也可以这样做。如果还没有,可以先导入随机并定义 Action 类:

import random
from enum import IntEnum

class Action(IntEnum):
    Rock = 0
    Paper = 1
    Scissors = 2

希望到目前为止这一切看起来都很熟悉!下面是 get_user_selection()的代码,它不接收任何参数,只返回一个 Action:

def get_user_selection():
    user_input = input("Enter a choice (rock[0], paper[1], scissors[2]): ")
    selection = int(user_input)
    action = Action(selection)
    return action

现在你再也不用担心将来添加或删除操作的问题了!通过测试,您可以看到代码是如何提示用户并返回与用户输入值相关的操作的:

>>> get_user_selection()
Enter a choice (rock[0], paper[1], scissors[2]): 0
<Action.Rock: 0>

现在需要一个函数来获取计算机的操作。与 get_user_selection()函数一样,该函数不需要参数并返回一个 Action。由于 Action 的取值范围在 0 到 2 之间,因此需要在此范围内生成一个随机数。random.randint() 可以帮你做到这一点。

random.randint() 会返回一个介于指定最小值和最大值(含)之间的随机数值。你可以使用 len() 来帮助确定代码中的上限:

def get_computer_selection():
    selection = random.randint(0, len(Action) - 1)
    action = Action(selection)
    return action

由于 Action 值从 0 开始计数,而 len() 从 1 开始计数,因此必须执行 len(Action) - 1。

测试时,不会出现提示。它只会返回与随机数相关的操作:

>>> get_computer_selection()
<Action.Scissors: 2>

看起来不错!接下来,您需要一个决定胜负的方法。这个函数将接受两个参数,即用户的操作和计算机的操作。它不需要返回任何内容,因为它只会将结果显示到控制台:

def determine_winner(user_action, computer_action):
    if user_action == computer_action:
        print(f"Both players selected {user_action.name}. It's a tie!")
    elif user_action == Action.Rock:
        if computer_action == Action.Scissors:
            print("Rock smashes scissors! You win!")
        else:
            print("Paper covers rock! You lose.")
    elif user_action == Action.Paper:
        if computer_action == Action.Rock:
            print("Paper covers rock! You win!")
        else:
            print("Scissors cuts paper! You lose.")
    elif user_action == Action.Scissors:
        if computer_action == Action.Paper:
            print("Scissors cuts paper! You win!")
        else:
            print("Rock smashes scissors! You lose.")

这与您用来决定胜负的第一次比较非常相似。现在,您可以直接比较 Action 类型,而不必担心那些讨厌的字符串!

您甚至可以通过向 determine_winner() 传递不同的选项来测试它,看看会打印出什么结果:

>>> determine_winner(Action.Rock, Action.Scissors)
Rock smashes scissors! You win!

既然您是从数字创建操作,那么如果用户试图从 3 创建操作,会发生什么情况?请记住,您目前定义的最大数字是 2:

>>> Action(3)
ValueError: 3 is not a valid Action

哎呀你不希望发生这种情况。您可以在流程图的什么地方添加一些逻辑,以确保用户输入了有效的选择?

在用户做出选择后立即进行检查是合理的:

在这里插入图片描述

如果用户输入的数值无效,则重复上述步骤获取用户的选择。对用户选择的唯一真正要求是它必须介于 0 和 2 之间(包括 0 和 2)。如果用户的输入超出了这个范围,就会出现 ValueError 异常。为了避免向用户显示默认的错误信息,可以对异常进行处理。

现在,您已经定义了一些反映流程图步骤的函数,您的游戏逻辑变得更加有条理和紧凑了。现在您的 while 循环只需包含这些内容:

while True:
    try:
        user_action = get_user_selection()
    except ValueError as e:
        range_str = f"[0, {len(Action) - 1}]"
        print(f"Invalid selection. Enter a value in range {range_str}")
        continue

    computer_action = get_computer_selection()
    determine_winner(user_action, computer_action)

    play_again = input("Play again? (y/n): ")
    if play_again.lower() != "y":
        break

这样看起来是不是干净多了?请注意,如果用户未能选择一个有效的范围,就会使用 continue 而不是 break。这使得代码继续循环的下一次迭代,而不是中断循环。

Rock Paper Scissors … Lizard Spock

如果你看过《生活大爆炸》,那么你可能对 Rock Paper Scissors Lizard Spock 并不陌生。如果不熟悉,那么这里有一张图,描述了这个游戏和决定胜负的规则:

在这里插入图片描述
您可以使用上面学到的工具来实现这个游戏。例如,你可以添加到 Action 中,并为 lizard 和 Spock 创建值。然后,你只需修改 get_user_selection() 和 get_computer_selection(),将这些选项纳入其中。不过,更新 determine_winner() 会耗费大量精力。

与其在代码中添加大量 if … elif … else 语句,不如使用字典来帮助显示操作之间的关系。字典是显示键值关系的好方法。在这种情况下,键可以是一个动作(如剪刀),而值可以是它击败的动作列表。

那么,只有三个选项的 determine_winner() 会是什么样子呢?每个动作只能打败一个其他动作,因此列表中只能包含一个项目。下面是您之前的代码:

def determine_winner(user_action, computer_action):
    if user_action == computer_action:
        print(f"Both players selected {user_action.name}. It's a tie!")
    elif user_action == Action.Rock:
        if computer_action == Action.Scissors:
            print("Rock smashes scissors! You win!")
        else:
            print("Paper covers rock! You lose.")
    elif user_action == Action.Paper:
        if computer_action == Action.Rock:
            print("Paper covers rock! You win!")
        else:
            print("Scissors cuts paper! You lose.")
    elif user_action == Action.Scissors:
        if computer_action == Action.Paper:
            print("Scissors cuts paper! You win!")
        else:
            print("Rock smashes scissors! You lose.")

现在,您可以有一本字典来描述胜利条件,而不是与每个 "Action "进行比较:

def determine_winner(user_action, computer_action):
    victories = {
        Action.Rock: [Action.Scissors],  # Rock beats scissors
        Action.Paper: [Action.Rock],  # Paper beats rock
        Action.Scissors: [Action.Paper]  # Scissors beats paper
    }

    defeats = victories[user_action]
    if user_action == computer_action:
        print(f"Both players selected {user_action.name}. It's a tie!")
    elif computer_action in defeats:
        print(f"{user_action.name} beats {computer_action.name}! You win!")
    else:
        print(f"{computer_action.name} beats {user_action.name}! You lose.")

您仍然要像以前一样,首先检查平局条件。但是,我们不再比较每个操作,而是比较 user_input 与 computer_action 之间的操作。由于键值对是一个列表,因此可以使用成员运算符 in 来检查其中是否包含某个元素。

由于不再使用冗长的 if … elif … else 语句,因此为这些新操作添加检查相对比较简单。您可以先将 lizard 和 Spock 添加到 Action 中:

class Action(IntEnum):
    Rock = 0
    Paper = 1
    Scissors = 2
    Lizard = 3
    Spock = 4

接下来,添加图表中的所有胜利关系。确保在 "Action"下面添加,这样 victories 就能引用 "Action "中的所有内容:

victories = {
    Action.Scissors: [Action.Lizard, Action.Paper],
    Action.Paper: [Action.Spock, Action.Rock],
    Action.Rock: [Action.Lizard, Action.Scissors],
    Action.Lizard: [Action.Spock, Action.Paper],
    Action.Spock: [Action.Scissors, Action.Rock]
}

请注意,现在每个 Action 都有一个包含两个元素的列表。而在剪刀石头布的基本实现中,只有一个元素。

因为你有意编写了 get_user_selection(),以适应新的动作,所以你不需要修改任何代码。get_computer_selection() 也是如此。由于 Action 的长度发生了变化,随机数的范围也会随之改变。

看看现在的代码有多简短和易于管理!要查看完整程序的全部代码,请展开下面的方框。

Rock Paper Scissors Lizard Spock

import random
from enum import IntEnum

class Action(IntEnum):
    Rock = 0
    Paper = 1
    Scissors = 2
    Lizard = 3
    Spock = 4

victories = {
    Action.Scissors: [Action.Lizard, Action.Paper],
    Action.Paper: [Action.Spock, Action.Rock],
    Action.Rock: [Action.Lizard, Action.Scissors],
    Action.Lizard: [Action.Spock, Action.Paper],
    Action.Spock: [Action.Scissors, Action.Rock]
}

def get_user_selection():
    choices = [f"{action.name}[{action.value}]" for action in Action]
    choices_str = ", ".join(choices)
    selection = int(input(f"Enter a choice ({choices_str}): "))
    action = Action(selection)
    return action

def get_computer_selection():
    selection = random.randint(0, len(Action) - 1)
    action = Action(selection)
    return action

def determine_winner(user_action, computer_action):
    defeats = victories[user_action]
    if user_action == computer_action:
        print(f"Both players selected {user_action.name}. It's a tie!")
    elif computer_action in defeats:
        print(f"{user_action.name} beats {computer_action.name}! You win!")
    else:
        print(f"{computer_action.name} beats {user_action.name}! You lose.")

while True:
    try:
        user_action = get_user_selection()
    except ValueError as e:
        range_str = f"[0, {len(Action) - 1}]"
        print(f"Invalid selection. Enter a value in range {range_str}")
        continue

    computer_action = get_computer_selection()
    determine_winner(user_action, computer_action)

    play_again = input("Play again? (y/n): ")
    if play_again.lower() != "y":
        break

就是这样!你已经用 Python 代码实现了rock paper scissors lizard Spock。仔细检查一下,确保没有遗漏任何东西,然后试玩一下。

结论

恭喜您您刚刚完成了第一个 Python 游戏!现在,您已经知道如何从零开始创建剪刀石头布游戏,并能以最小的代价扩展游戏中可能的操作数量。

在本教程中,您将学会如何

  • 编写自己的剪刀石头布游戏
  • 使用 input() 接收用户输入
  • 使用 while 循环连续玩几个游戏
  • 使用枚举函数清理代码
  • 字典描述更复杂的规则

这些工具将在你的编程冒险中继续帮助你。如果您有任何问题,请随时在下面的评论区联系我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值