通过构建游戏学习-Python(一)

通过构建游戏学习 Python(一)

原文:zh.annas-archive.org/md5/8d68d722c94aedcc91006ddf3f78c65a

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

2018 年 9 月,我正在教一些学生使用 Python 进行游戏编程和自动化。然后,我意识到是时候创建一本书,不仅提供关于使用 Python 进行游戏编程丰富内容的信息,还展示如何制作和部署模仿真实世界著名游戏(如 Flappy Bird 和 Angry Birds)的游戏。我希望为你提供成为真正的 Python 游戏开发人员所需的所有基本要素和原语。这本书不是你通常的传统 Python 理论书籍;我们的方法将尽可能地实用。每一章将包含一个单一但强大的真实世界游戏示例,这不仅会很有趣,还会教导你编程范式,这将是成为熟练的 Python 开发人员的第一步。

Python 是 2018/19 年最广泛使用的编程语言之一,根据 Stack Overflow 和 TIOBE 进行的调查,它的受欢迎程度增长的速度预计不会很快减少。如果你观察大型科技公司用于处理业务的工具,你会发现他们高度依赖 Python,因为它易于使用和快速原型设计。不仅如此,你还会发现 Python 可以用于开发从数据科学到高端网络应用程序等各种应用,当你开始学习 Python 的基础知识时,你将能够几乎创造任何你想要的东西。

学习 Python 有很多理由,其中一个重要的原因是 Python 社区。世界上许多最优秀的开发人员不断为 Python 社区做出贡献,添加新的库/模块和功能。如果你想要快速创建新的东西,这些库将非常有帮助。因此,Python 专注于产品而不是陷入低级编程的例行程序和复杂性,这使它成为初学者最喜爱的编程语言。

在本书中,我们将首先介绍一些重要的编程概念,如变量、数字、布尔逻辑、条件和循环。在建立了核心编程概念的坚实基础之后,我们将进入数据结构和函数等高级部分。随着章节难度的增加,学习的速度也会加快。在完成第七章《列表推导和属性》之后,我们将完全掌握所有基础知识,可以应用于创建高级内容,如 Flappy Bird 模拟器、Angry Bird 模拟器和 AI 玩家。在每一章中,都会有一个“游戏测试和可能的修改”主题,迫使你思考如何处理错误以及如何完善程序。

本书的要求

为了更好地掌握本书中所述的每个主题,我鼓励你跟着源代码和示例一起学习。为了正确编写代码,你需要在你的计算机上安装 Python。我使用的是 Python 的最新版本(截至 2019 年 9 月),即 3.7 版本,但你可以使用任何新于 3.5+的版本。Python 的彻底安装过程将在第一章中涵盖,根据你使用的操作系统(Linux、macOS 或 Windows)。你还需要一个正常运行的互联网连接来下载 GitHub 代码和 Python 第三方库。我们将在本书的后面安装不同的 Python 库,包括 PyGame、Pymunk 和 PyOpenGL。对于每一个库,安装过程将在相关章节中进行介绍。在使用这些模块时,我们的程序会变得更长,因此我们强烈建议你使用一个好的 Python 文本编辑器。我将使用 PyCharm IDE 来使用 Python 创建复杂的游戏,它的安装也在第一章中介绍。除了这些软件要求,本书没有特定的要求。

这本书是为谁准备的

本书适用于任何想学习 Python 的人。您可以是初学者,也可以是之前尝试学习过但无聊的课程或书籍使您偏离轨道的人,或者是想要提高技能的人。本书将帮助您以最有趣的方式获得核心知识并提高技能:通过构建游戏。它主要关注使用 Python 模块 PyGame、PyOpenGL 和 Pymunk 进行 GUI 编程。学习者不需要具备编程技能,因为本书将涵盖您在 Python 方面需要了解的一切。我们将通过构建三个迷你游戏来学习turtle模块,并且您将学会如何创建自己的 2D 游戏,即使您是完全的初学者。如果您曾经想过使用 Python 的 PyGame 模块进行游戏开发,那么本书适合您。

为了充分利用本书

为了充分利用本书中提供的信息,建议您跟随示例进行操作。不需要具备 Python 的先验知识,但是对算术和逻辑操作等数学概念的经验对于深入理解代码是必不可少的。基于 Python 的应用程序不限于任何特定的操作系统,所以所需的只是一个体面的代码编辑器和一个浏览器。在整本书中,我们使用的是 PyCharm Community 2019.2 编辑器,这是一个开源编辑器,可以免费下载。

下载示例代码文件

您可以从您在www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support注册,将文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. www.packt.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载”

  4. 在“搜索”框中输入书名,然后按照屏幕上的说明操作。

文件下载后,请确保使用以下最新版本的解压缩软件解压缩文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Learning-Python-by-building-games。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有其他代码包,来自我们丰富的书籍和视频目录,可以在github.com/PacktPublishing/上找到。去看看吧!

代码实例

访问以下链接,查看代码运行的视频:

bit.ly/2oE9mHV

使用的约定

本书中使用了许多文本约定。

CodeInText:指示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。例如:“截图显示了编辑后的python_ex_1.py文件。”

代码块设置如下:

n = int(input("Enter any number"))
for i in range(1,100):
    if i == n:
        print(i)
        break

当我们希望引起您对代码块的特定部分的注意时,相关行或项目以粗体设置:

def fun(b):
    print("message")
    a = 9 + b
    move_player(a)

fun(3)    

任何命令行输入或输出都以以下形式编写:

>>> cd Desktop 

粗体:表示新术语,重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。例如:“在安装程序中,请确保您勾选了“将 Python 添加到 PATH”框。”

警告或重要提示会以这种方式出现。提示和技巧会以这种方式出现。

第一章:了解 Python-设置 Python 和编辑器

Python 在数据和分析行业中臭名昭著,但在游戏行业中仍然是一个隐藏的工具。在使用其他游戏引擎(如 Unity 和 Godot)制作游戏时,我们倾向于将设计逻辑与核心编程原则相结合。但在 Python 的情况下,主要是问题分析和编程范式的融合。程序流程或结构是一个与其编程范式交织在一起的序列。编程范式正如其名称所示,它便于程序员以最经济和高效的方式编写问题的解决方案。例如,用两行代码而不是十行代码编写程序是使用编程范式的结果。程序流程分析或结构分析的目的是揭示有关需要调用各种设计模式的程序的信息。

在本章中,我们将学习以下主题:

  • 使用 Python 介绍编程

  • 安装 Python

  • Python 的构建模块

  • 安装 PyCharm IDE

  • 编写Hello World之外的编程代码

技术要求

以下是您在本书中需要的最低硬件要求的列表:

  • 具有至少 4GB RAM 的工作 PC

  • 外部鼠标适配器(如果您使用笔记本电脑)

  • 至少需要 5GB 的硬盘空间来下载外部 IDE 和 Python 软件包

您需要以下软件才能充分利用本书(我们将在本章中下载所有这些软件):

查看以下视频以查看代码的运行情况:

bit.ly/2o2pVgA

使用 Python 介绍编程

编程的古老格言如下:

“编码基本上是用于开发应用程序、网站和软件的计算机语言。没有它,我们将无法使用我们所依赖的主要技术,如 Facebook、智能手机、我们选择查看最喜欢的博客的浏览器,甚至博客本身。这一切都运行在代码上。”

我们对此深表认同。计算机编程既可以是一项有益的工作,也可能是一项乏味的活动。有时,我们可能会遇到无法找到程序中捕获的异常(程序的意外行为)的调整的情况,后来我们发现错误是因为错误的模块或不良的实践。编写程序类似于写作文章;首先,我们必须了解文章的模式;然后,我们分析主题并写下来;最后,我们检查语法。

与写作文章的过程类似,编写代码时,我们必须分析编程语言的模式或语法,然后分析问题,然后编写程序。最后,我们检查它的语法,通常是通过 alpha 和 beta 测试来完成的。

本书将尝试让您成为一个可以分析问题、建立高贵逻辑并提出解决问题的想法的人。我们不会让这段旅程变得单调;相反,我们将通过在每一章节中构建游戏来学习 Python 语法。到本书结束时,您将会像一个程序员一样思考——也许不是专业的程序员,但至少您将已经掌握了使用 Python 制作自己程序的技能。

在本书中,您将学习到两个关键的内容:

  • 首先,您将学习 Python 的词汇和语法。我不是指学习 Python 的理论或历史。首先,我们必须学习 Python 的语法;然后,我们将看看如何使用该语法创建语句和表达式。这一步包括收集数据和信息,并将其存储在适当的数据结构中。

  • 然后,您将学习与调用适当方法的想法相对应的程序。这个过程包括使用在第一步收集的数据来获得预期的输出。这第二步不是特定于任何编程语言。这将教我们各种编程原型,而不仅仅是 Python。

在学习了 Python 之后,学习任何其他编程语言都会更容易。您将观察到的唯一区别是其他编程语言的语法复杂性和程序调试工具。在本书中,我们将尽可能多地学习各种编程范式,以便我们可以开始编程生涯。

您对 Python 还有疑问吗?

让我们看一下一些使用 Python 制作的产品:

  • 没有提到谷歌的清单是不完整的。他们在他们的网络搜索系统和页面排名算法中使用它。

  • 迪士尼使用 Python 进行创意过程。

  • BitTorrent 和 DropBox 都是用 Python 编写的。

  • Mozilla Firefox 用它来探索内容,并且是 Python 软件包的主要贡献者。

  • NASA 用它进行科学研究。

清单还在继续!

让我们简单地看一下代码程序是如何工作的。

解释代码程序

为了简单地解释代码程序是如何工作的,让我们以制作煎蛋卷为例。您首先从食谱书中学习基础知识。首先,您收集一些器具,并确保它们干净干燥。之后,您打蛋,加盐和胡椒,直到混合均匀。然后,您在不粘锅中加入黄油,加入蛋液,煮熟,甚至可以倾斜锅来检查煎蛋卷的每个部分是否煮熟。

在编程方面,首先,我们谈论收集我们的工具,比如器具和鸡蛋,这涉及收集将被我们程序中的指令操纵的数据。之后,我们谈论煮鸡蛋,这就是你的方法。我们通常在方法中操纵数据,以便以对用户有意义的形式得到输出。这里,输出是一个煎蛋卷。

给程序提供指令是程序员的工作。但让我们区分客户和程序员。如果您使用的产品是让您向计算机发出指令来为您执行任务,那么您是客户,但如果您设计了为您为所有人创建的产品完成任务的指令,这表明您是程序员。只是“为一个人”还是“为所有人”决定了用户是客户还是程序员。

我们将在 Windows 命令提示符或 Linux 终端中使用的一些指令是用来打开我们机器的目录。有两种执行此操作的方法。您可以使用图形用户界面,也可以使用终端或命令提示符。如果您在相应的字段中键入dir命令,那么您现在正在告诉计算机在该位置显示目录。在任何编程语言中都可以做同样的事情。在 Python 中,我们有模块来为我们做这个。我们必须在使用之前导入该模块。Python 提供了许多模块和库来执行此类操作。在诸如 C 之类的过程式编程语言中,它允许与内存进行低级交互,这使得编码更加困难,但使用 Python 可以更容易地使用标准库,这使得代码更短更易读。《如何像计算机科学家一样思考学习 Python》的作者大卫·比兹利曾经被问到,“为什么选择 Python?”他简单地回答说,“Python 只是更有趣和更高效”。

与 Python 交谈

Python 已经存在了很多年(将近 29 年),尽管它经历了许多升级,但它仍然是最容易让初学者学习的语言。这主要是因为它可以与英语词汇相关联。类似于我们用英语单词和词汇做陈述,我们可以用 Python 语法编写陈述和操作,命令可以解释、执行并给我们提供结果。我们可以用条件和流控制来反映某物的位置,比如去那里作为一个命令。学习 Python 的语法非常容易;真正的任务是利用 Python 提供的所有资源来构建全新的逻辑,以解决复杂的问题。仅仅学习基本的语法和写几个程序是不够的;你必须练习足够多,以便能够提出解决现实问题的革命性想法。

我们在英语词典中有很多词汇。与英语词典不同,Python 只包含少量单词,我们通常称之为保留字。总共有 33 个。它们是指示 Python 解释器执行特定操作的指令。修改它们是不可能的——它们只能用于执行特定任务。此外,当我们调用打印语句并在其中写一些文本时,预期它会打印出该消息。如果你想制作一个从用户那里获取输入的程序,调用打印语句是无用的;必须调用输入语句才能实现这一点。以下表格显示了我们的 33 个保留字:

False class finally is return
None continue for lambda try
True def from nonlocal while
and del global not with
as elif if or yield
assert else import pass
break except in raise

这些单词都可以在我们的英语词典中找到。此外,如果我们在词典中搜索单词return,它只会给我们返回原始位置的动词含义。Python 中也使用相同的语义;当你在函数中使用 return 语句时,你是在从函数中取出一些东西。在接下来的章节中,我们将看到所有这些关键字的用法。

现在我们已经开始学习如何通过检查其关键字来使用 Python 进行对话,我们将安装 Python。做好准备,打开你的机器,开始一些有趣的事情。

安装 Python

在本节中,我们将看看如何在 Windows 和 macOS 上安装 Python。

对于 Windows 平台

Python 不会预装在 Windows 上。我们必须从官方网站手动下载并安装它。让我们看看如何做到这一点:

  1. 首先,打开你喜欢的浏览器,打开以下网址:www.Python.org/

  2. 你将被引导到下图所示的页面。一旦你被重定向到 Python 的官方网站,你会看到三个部分:下载、文档和工作。点击页面底部的下载部分:

  1. 你会看到一个文件列表,如下截图所示。选择适合你平台的文件。在本节中,我们将看一下 Windows 的安装,所以我们会点击 Windows 可执行文件链接。如下截图所示:

  1. 点击后,你将得到一个需要下载的文件。打开下载的文件后,你将得到安装程序,如下所示:

  1. 在安装程序中,确保您选中“将 Python 添加到 PATH”框。这将在我们的环境变量中放置 Python 库文件,以便我们可以执行我们的 Python 程序。之后,您将收到有关其成功安装的消息:

  1. 按下 Windows 键+R打开运行,然后在运行选项卡中键入cmd打开 Windows 命令提示符。然后,在命令 shell 中键入Python

如果您得到前面截图中显示的 Python 版本,那么 Python 已成功安装在您的计算机上。恭喜!现在,您可以通过使用 Python 编写您的第一个程序来动手实践。

如果出现错误提示Python is not recognized as an internal or external command,则必须显式将 Python 添加到路径环境变量中。按照以下步骤执行:

  1. 打开控制面板,导航到“系统和安全”,然后转到“系统”以查看有关您系统的基本信息。

  2. 打开高级系统设置,然后选择“环境变量...”。

  3. 在“变量”部分,搜索“路径”。选择“路径”变量,然后按“编辑...”选项卡。

  4. 在“编辑环境变量”选项卡中单击“新建”。

  5. 添加此路径,使其指向您的 Python 安装目录,即 C:\Users\admin\AppData\Local\Programs\Python\Python37\。

  6. 单击“确定”按钮以保存这些更改:

现在,我们已成功在 Windows 上安装了 Python。如果您使用的是 Mac,下一节将帮助您也访问 Python。

对于 Mac 平台

Python 在 Mac OS X 上预先安装。要检查您安装的 Python 版本,您应该打开命令行并输入Python --version。如果您得到 3.5 或更新的版本号,您就不需要进行安装过程,但如果您有 2.7 版本,您应该按照以下说明下载最新可用版本:

  1. 打开浏览器,输入www.Python.org/downloads/。您将被发送到以下页面:

  1. 单击 macOS 64 位/32 位安装程序。您将获得一个.pkg文件。下载它。然后,导航到已安装的目录并单击该安装程序。您将看到以下选项卡。按“继续”以启动安装程序:

每当您下载 Python 时,一堆软件包将安装在您的计算机上。我们不能直接使用这些软件包,所以我们应该为每个独立的任务单独调用它们。要编写程序,我们需要一个环境,我们可以在其中调用 Python,以便它可以为我们完成任务。在下一节中,我们将探索 Python 提供的用户友好环境,我们可以在其中编写自己的程序并运行它们以查看它们的输出。

现在,您已在 Mac OS X 上安装了 Python 3.7 版本,您可以打开终端并使用python --version命令检查您的 Python 版本。您将看到 Python 2.7.10。原因是 Mac OS X 预先安装了 Python 2.7+版本。要使用更新的 Python 版本,您必须使用python3命令。在终端中键入以下命令并观察结果:

python3 --version

现在,为了确保 Python 使用您刚刚安装的较新版本的解释器,您可以使用一种别名技术,将当前工作的 Python 版本替换为 Python3。要执行别名,您必须按照以下步骤执行:

  1. 打开终端并输入nano ~/.bash_profile命令以使用 nano 编辑器打开 bash 文件。

  2. 接下来,转到文件末尾(在导入路径之后)并键入alias python=python3命令。要保存 nano 文件,请按Ctrl + X,然后按Y保存。

现在,再次打开您的终端,并输入我们之前使用的相同命令来检查我们拥有的 Python 版本。它将更新到较新版本的 Python。从现在开始,为了从 Mac 运行任何 Python 文件,您可以使用这个 Python 命令,后面跟着文件的签名或文件名。

介绍 Python Shell 和 IDLE

Python Shell 类似于 Windows 的命令提示符和 Linux 和 Mac OS X 的终端,您可以在其中编写将在文件系统中执行的命令。这些命令的结果会立即在 shell 中打印出来。您还可以使用任何终端中的 Python 命令(> python)直接访问此 shell。结果将包含由于代码执行不正确而导致的异常和错误,如下所示:

>>> imput("Enter something")
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    imput()
NameError: name 'imput' is not defined

>>> I love Python
SyntaxError: invalid syntax

正如您所看到的,我们遇到了一个错误,Python IDE 明确告诉我们我们遇到的错误名称,在这种情况下是NameError(一种语法错误)。SyntaxError是由于代码的不正确模式而发生的。在前面的代码示例中,当您编写I love Python语法时,这对 Python 解释器来说什么都不意味着。如果要纠正这个问题,您应该编写正确的命令或正确定义某些内容。写imput而不是 input 也是语法错误。

逻辑错误或语义错误即使您的程序语法正确也会发生。然而,这并不能解决您的问题领域。它们很难追踪,因此很危险。程序完全正确,但并没有解决它本来要解决的任何问题。

当您在计算机上下载 Python 软件包时,一个名为 IDLE(Python 内置 IDE)的Python 集成开发环境IDE)会自动下载到您的计算机上。您可以在搜索栏中输入IDLE来进入这个环境。IDLE 是一个免费的开源程序,提供了两个界面,您可以在其中编写代码。我们可以在 IDLE 中编写脚本和终端命令。

现在我们已经熟悉了在 Python Shell 中不应该做什么,让我们谈谈 Python Shell 的细节——这是一个您可以编写 Python 代码的环境。

Python Shell 的细节

正如我们之前提到的,在这一部分,我们将参观 Python 的细节。这包括 Python 的内置 shell,Python 的文本编辑器(通常称为 Python 脚本)和 Python 文档页面。

按照以下步骤了解 Python Shell 的细节:

  1. 当您打开 Python Shell 时,您会看到以下窗口。您在 shell 中看到的第一件事是 Python 的当前版本号:

  1. 在 Python shell 中,有三个角括号相邻放置在一起,就像这样:>>>。您可以从那里开始编写您的代码:

  1. 按下F1打开 Python 文档,或转到帮助选项卡,单击 Python Docs F1(在 Windows 机器上)。要在线访问文档,请转到docs.python.org/3/

希望您现在对 Python Shell 已经很熟悉了。我们将在 shell 中编写大量代码,因此请确保通过自定义或长时间玩耍来熟悉它。完成后,您可以继续下一节,在那里您将学习在编写第一个 Python 程序之前需要了解的内容。

Python 的基本组成部分

在编写 Python 程序时,我们使用一些常规模式。Python 作为一种高级语言,不关心低级例程,但具有与它们交互的能力。Python 由六个构建块组成。用 Python 制作的每个程序都围绕着它们展开。这些构建块是输入、输出、顺序执行、条件、递归和重用。现在让我们来详细了解一下它们:

  • 输入:输入无处不在。如果你用 Python 制作一个应用程序,它主要处理用户输入的格式,以便收集有意义的结果。Python 中有一个内置的input()方法,因此我们可以从用户那里获取数据输入。

  • 输出:在我们操纵用户输入的数据之后,是时候向用户呈现它了。在这一层,我们利用设计工具和演示工具来格式化有意义的输出并发送给用户。

  • 顺序执行:这保留了语句的执行顺序。在 Python 中,我们通常使用缩进,即表示作用域的空格。任何在零级缩进的命令都会首先执行。

  • 条件:这些为程序提供流程控制。基于比较,我们制定逻辑,使代码流动,并执行或跳过它。

  • 递归:这是需要做的任何事情,直到满足某些条件。我们通常称它们为循环

  • 重用:编写一次代码,使用无数次。重用是一种范式,我们编写一组代码,给它一个引用,并在需要时使用它。函数和对象提供了可重用性。

在 Python Shell 中编写程序对大多数程序员来说可能很容易调试,但从长远来看可能会产生额外的开销。如果你想保存代码以备将来参考,或者你想编写多行语句,你可能会被 Python 解释器的功能不足所压倒。为了解决这个问题,我们必须创建一个脚本文件。它们被称为脚本,因为它们允许你在单个文件中编写多行语句,然后立即运行。当我们有多个数据存储和文件要处理时,这将非常方便。你可以通过扩展名来区分 Python 文件和其他文件,也就是.py。你还应该将 Python 脚本文件保存为.py扩展名。

要从终端或 Windows 命令提示符中运行你的脚本文件,你必须告诉 Python 解释器通过文件名运行该文件,就像这样:

$ Python Python_file_name.py

在上述命令中,$是操作系统提示符。首先,你必须使用Python命令调用 Python 解释器,并告诉它执行其旁边的文件名。

如果你想在终端中查看Python文件的内容,请使用以下命令:

$ cat Python_file_name.py
$ nano Python_file_name.py

要退出 Python 终端,在终端中写入exit()命令。

现在我们已经学会了如何打开和退出 Python 环境的界面,我们必须了解它的构建模块。许多初学者错误地认为程序只有两个构建模块:输入和输出。在下一节中,我们将看到如何通过使用编程的六个构建模块来驳斥这一假设。

编程最困难的部分是学习编程范式,比如面向对象编程、DRY 原则或线性时间复杂度模型。如果你掌握了这些原型,学习任何新的编程语言都将变得轻而易举。话虽如此,使用 Python 学习所有这些范式要比 Java 或 C#容易得多,因为在 Python 中,代码会更短,语法也更符合英语习惯:

在我们编写第一个程序之前,我们将安装另一个 IDLE,以备后面的章节中我们将编写复杂的游戏。在这些类型的游戏中,IDLE 提供的功能是不够的,因此我们将看到如何在下一节中安装 PyCharm——一个高级的 IDLE。

安装 PyCharm IDE

在本章的前面,我们发现了 IDLE。我们已经看到了一个环境,我们可以在其中编写代码并立即获得输出。但是,您可以想象一下,如果我们有很多代码要一次执行,可能是 1000 行代码,一次执行一行。我们必须通过编写脚本来解决这个问题,这是 Python 代码的集合。这将一次执行,而不是在 IDLE 的 shell 中逐行执行。

如果您想编写脚本,请按照以下步骤操作:

  1. 从您的 PC 中打开搜索选项卡,然后键入IDLE

  2. 点击文件选项卡。

  3. 按下 New File。

  4. 将生成一个新文件。您可以在单个文件中编写多个表达式、语句和命令。以下屏幕截图的左侧显示了 Python 脚本,您可以在其中编写多行语句,而以下屏幕截图的右侧显示了 Python Shell,您将在其中执行脚本并获得即时结果:

在编写脚本完成后,您必须在运行之前保存它。要保存文件,请转到文件并单击保存。通过在末尾放置.py扩展名为您的脚本提供适当的文件名,例如test.py。按下F5执行您的脚本文件。

在本书中,我们将构建许多游戏,其中我们将处理图像、物理、渲染和安装 Python 包。这个 IDE,也就是 IDLE,无法提供智能 IDE 功能,比如代码完成、集成和插件以及包的分支。因此,我们必须升级到最好的 Python 文本丰富的 IDE,也就是 PyCharm IDE。让我们开始吧:

  1. 访问www.jetbrains.com/pycharm/下载 PyCharm 环境。安装 PyCharm 与安装任何其他程序一样简单。从网站下载安装程序后,点击该安装程序。您应该会看到以下窗口:

  1. 点击按钮并将其安装在适当的驱动器上。安装完成后,在搜索栏中搜索PyCharm并打开它。您应该会看到以下窗口:

  2. 现在,点击+创建新项目并给您的项目命名。要创建新的 Python 文件,请在项目名称上单击左键,单击 New,然后单击 Python File 选项卡:

现在,我们拥有了掌握本书所需的一切——我的意思是工具,但显然,我们必须学习 Python 的每种可能的范式来掌握 Python 的概念。现在您已经全副武装了这些工具,让我们编写我们的第一个有效的 Python 程序,No Hello World

编程代码没有 Hello World

在编程世界中有一个传统,即将Hello World打印为我们的第一个程序。让我们打破常规,使我们的第一个程序成为从用户那里获取输入并将其打印到控制台的程序。按照以下步骤执行您的第一个程序:

  1. 打开您的 IDLE 并输入以下命令:
 >>> print(input("Enter your Name: "))
  1. 按下Enter执行命令。您将收到一条消息,上面写着输入您的姓名:。输入您的姓名并按Enter。您将看到输出打印您刚刚传递的姓名。

我们在这里使用了两个命令,也称为函数。我们将在接下来的章节中学习它们。现在让我们来看看这两个函数:

  • input()是 Python 的内置函数,将从用户那里获取输入。空格也包括在字符中。

  • print()是 Python 的内置函数,将打印括号内传递的任何内容。

现在我们已经开始使用 Python 的内置 IDLE 编写我们的第一个程序,轮到您测试 IDLE 的工作原理了。由于我们将使用 IDLE 构建大量游戏,请确保您熟悉其界面。本章学习的核心编程模块,如 Python 关键字和输入-打印函数,非常重要,因为它们帮助我们构建可以从用户那里获取输入并显示的程序。

总结

在本章中,我们对 Python 的基础知识进行了概述,并学习了它与英语的词汇有多么相似。我们在计算机上安装了 Python 软件包,并查看了 Python 的预安装集成开发环境(IDE)IDLE。我们看到了如何在 Python IDE 上编写脚本以及如何执行它们。然后,我们在计算机上安装了功能丰富的 Python 文本编辑器 PyCharm IDE。我们编写了我们的第一个 Python 程序,它能够从用户那里获取输入并在屏幕上显示。

本章中您所学到的技能对于构建程序的流程至关重要。例如,我们的程序能够处理输入/输出数据。任何用 Python 制作的游戏都必须对用户或玩家进行交互,这是通过输入和输出界面来实现的。在本章中,我们学习了如何从用户那里获取输入并显示它。随着我们继续阅读本书,我们将探索各种构建程序的方式,包括处理来自鼠标、键盘和屏幕点击的用户事件。

下一章将至关重要,因为我们将学习 Python 的基本要素,如值、类型、变量、运算符和模块。我们还将开始构建一个井字棋游戏。

第二章:学习 Python 的基础知识

Python 不需要在游戏开发中,设计和分析被认为是编程之前完成的步骤。设计和分析要求我们集思广益,模拟程序,并格式化输入。所有这些程序都与数据有关。数据可以是简单的数字列表,也可以是复杂的天气历史。这些数据有自己的类型和结构。数据需要有自己的存储位置,以便我们可以引用它。Python 提供了数据的抽象形式,以对象的形式,这样我们可以创建嵌套的数据结构。

本章将带您体验 Python 中的核心编程范式的过山车之旅。我们将首先学习可用的不同数据类型以及将它们捕捉到变量或存储单元中的方法。我们将学习使用math模块进行不同的数学运算(算术和三角)的方法。在本章结束时,我们将使用在本章学到的知识制作我们的第一个游戏-井字棋。

在本章中,我们将涵盖以下主题:

  • 处理值和数据

  • 变量和关键字

  • 运算符和操作数

  • 在代码中编写注释

  • 请求用户输入

  • 字符串操作

  • 构建你的第一个游戏-井字棋

  • 可能的错误和警告

  • 游戏测试和可能的修改

技术要求

您需要满足以下要求才能充分获得本章的全部好处:

观看以下视频以查看代码的运行情况:

bit.ly/2o6Kto2

处理值和数据

软件的评价取决于其处理数据的能力。每个程序都有自己的数据库设计和实现。数据库是一个模式,数据以这样的方式存储,以便可以快速和安全地检索和操作。据推测,像 Facebook 和 Twitter 这样的社交网络每天收集 17 亿人的数据。这么庞大的数据量,每天都在收集,应该得到妥善处理,因为我们没有足够的内存来存储和处理它。因此,Python 提供了灵活的内置方法,以便对这些数据集进行映射、过滤和减少,以便可以更快地存储和获取以进行处理。

Python 在存储数据方面非常快速。它与 Hadoop 等大数据平台的集成,继承了其兼容性,是我们在大型数据集中使用 Python 的主要原因。强大的包,如 NumPy、pandas 和 scikit-learn,为当今的数据和分析需求提供数据支持。

值是程序计算的一些属性的表示。在这里,属性是任何对象的属性。例如,当我们谈论一个人时,我们通过姓名、年龄和身高来引用他们。这些属性有一个r-value(属性的内容)和一个l-value(内存位置)与它们相关联。属性的内容指的是存储为变量内容的值,而内存位置指的是存储值的物理位置。例如,name = "Python"有一个name变量作为属性;它的r-value 是Python,它的l-value 是 Python 解析器自动分配的唯一 ID,作为名称属性的内存位置。

在 Python 中,值以对象的形式存储。对象有四个特点:ID、命名空间、类型和值。让我们看一个简单的例子,揭示对象的这些特点。

>>> player_age = 90

当创建player_age变量时,它的实例被创建,我们称之为对象。每当创建一个对象时,它都会接收一个唯一的内存存储位置,即一个唯一的 ID 号,并动态地分配一个类型,即一个整数,因为我们将 90 分配给它。之后,将 player 变量添加到命名空间中,以便我们可以检索其值,即 90。以下图尝试简化这个解释:

每当执行任何赋值语句时,解析器都会创建一个对象,该对象从中获取唯一的内存 ID,我们可以引用变量的值。由于 Python 是一种动态类型的语言,它通过分析分配给变量的值动态地分配变量的类型。最终,它将该变量添加到全局命名空间,以便在您想要获取该变量时,可以使用变量名。在这里,memory_ID是指向对象值的位置。一些编程语言,如 C,称之为指针。

每个这些值都有与之关联的类型。1是一个整数,a是一个字符,Hello World是一个字符串。Hello World是一系列字符,因为它定义了一系列字符,所以它被称为字符串。在前一章中,我们看到了一个示例,我们要求用户输入。每当用户输入一些内容时,它被视为字符串。这些值在编程中定义对象。让我们以鹦鹉为例。它将有一个作为字符串的名称,一个作为整数的年龄,以及一个作为男性或女性的性别,用MF表示,这些都是字符。在 Python 中,字符也被表示为字符串。为了验证这一点,我们有type方法。type方法表示如下:

>>> type('a')

前面命令的输出将是<class 'str'>,这意味着字符也是字符串的一部分。要检查任何值的类型,我们可以使用相同的type方法:

>>> type(1)
>>> type('Hello World')

前面的命令将分别显示类别为intstr

现在,让我们谈谈数字。数字有两种类型:整数和小数。正如我们所看到的,整数是整数,但小数是浮点数。我们在 Python 中称小数为浮点数,因为它们以浮点格式表示,如下所示:

>> type(3.4)

前面命令的输出是<class 'float'>

您可以使用print方法在终端中打印这些值。print方法在括号内接受值,并在解释器中给出结果,如下所示:

>>> print(1)

如果您在print语句中放入1,它将打印1作为数字。但是,当您在双引号中放入1时,它将打印1作为字符串,如下所示:

>> print("1")
<class 'str'>

任何数字、文本或特殊符号,如@、\(、%或*,您放在单引号或双引号中最终都将成为字符串。以下是字符串的一个示例:`1`、`Hello`、`False`、`#\)(#`。

当您在print语句中的两个值之间放置逗号时,它会在它们之间放置一个空格,如下所示:

>>> print("abc","abc")

前面的代码将给出abc abc的输出,但它们不再被视为字符串。这是我们在 Python 中看到的第一个语义错误。我们将abc作为字符串传递,但结果是一个非类型:

>>> type(print("abc","abc"))
<class 'NoneType'>

这是一个语义错误的完美例子。我们得到了输出而没有任何错误,但是我们没有得到我们想要的结果。

您还可以检查整数。不可能用逗号在它们之间打印整数。Python 解释器将逗号转换为传递的每个值之间的空格:

>>> print(0,000,000)

这个命令将给我们一个0 0 0的结果。每个逗号都被转换为空格并打印出来。如果您检查从函数返回的值的类型,它也将是NoneType

现在我们知道了值和类型,让我们熟悉一下变量和关键字。

变量和关键字

编程就是接受和操作我们学到的值。我们在接受和操作这些值时使用变量,以便将来可以引用它们。变量就像盒子,您可以在其中放入不同的东西,并在需要时取出。变量是通过名称创建的,并为其分配一个值。

我们使用等号(=)来进行赋值语句。变量是通过赋值语句创建的;例如:

>>> myAge = 24
>>> info = "I love Python"
>>> isHonest = True

在这里,我们使用赋值语句创建了三个变量。在第一条命令中,我们创建了myAge变量并为其分配了一个整数。在 Python 中,您不必显式指定变量的类型,因为 Python 会在内部执行此操作。这就是使 Python 成为动态类型语言的原因。在第二条命令中,我们创建了info变量并为其分配了一个字符串。最后,我们创建了isHonest变量并为其分配了一个布尔值。

布尔类型是逻辑类型。它们要么是True,要么是False。创建布尔变量与创建其他变量相同,例如,is_hungry = True

变量是基本的数据存储单元。我们一次可以将一个值分配给一个变量。每当您将另一个值分配给相同的变量名称时,它将覆盖原始值。例如,在这里,我们将info变量设置为字符串,但是如果我用另一个值替换它,比如integer,这是有效的:

>>> info = 23

如果您在 Python 中创建变量,它将为每个变量创建单独的内存引用。因此,每当您用另一个值替换相同的变量时,该特定位置的值将被检索并用新值覆盖。变量名称是保留内存位置中值的指针。您不能在一个变量中存储多个值。您必须使用高级数据结构来实现这一点。我们将在接下来的章节中介绍这一点(第四章:数据结构和函数:用 AI 的味道完善您的游戏)。

将多个变量分配给不同的变量可以在一行代码中完成。我们可以用单个赋值语句来分配它们。变量的名称应该在左侧给出,它们之间应该用逗号隔开。您可以使用以下命令在一行中创建尽可能多的具有不同数据类型的变量:

>>> even, odd, num = 2, 3, 10

您可以通过直接在终端中写入变量的名称来查看变量的值:

>>> even

如果您只在脚本中写变量的名称,那么不会打印出值。相反,它会终止。如果要在屏幕上打印出东西,您必须使用print()方法。要打印任何变量的值,请在 shell 或脚本中键入print(variable_name),如下所示:

>>> print(even)

如果要查看存储在变量中的值的类型,可以调用type()方法。为此,请将变量的名称放在括号内:

>>> type(even)

上述命令将给出<class 'int'>的输出,这意味着可以在变量中存储整数值。

我们还可以在 Python 中将相同的值分配给多个变量。在上述命令中,我们不是分配多个值,而是将单个值分配给它,如下所示:

>>> even, num = 10

在上述命令中,我们将整数值 10 分配给了两个不同的变量evennum

Python 不需要变量实例化和声明。因此,在 Python 中不需要保留内存空间。当我们使用赋值语句创建变量时,Python 会在内部执行此操作。

Python 已经将 33 个单词保留为关键字,用于特定功能。我们不能使用它们来命名变量。Python 会使用内置脚本检查变量的名称与这些关键字,每当它检测到其中一个单词时,它将抛出语法错误,如下例所示:

>>> and = 23

前面的命令不会被执行,也不能用作变量名,因为它是一个关键字。Python 用它来执行一些逻辑操作。但是,如果您创建一个名为And的变量并为其赋值,Python 将为您创建And变量。对于 Python 来说,Andand是不同的。它是大小写敏感的语言。

为了避免变量名的任何问题,我们可以遵循一些简单的规则。我们将在下一节中介绍这些规则。

变量命名规则

通常我们选择有意义的变量名,因为从长远来看,可能会出现我们完全忘记代码顺序和流程的情况,而没有适当名称的变量会造成混乱。尽管您可以按照一些规则创建任何名称的变量,但强烈建议创建有意义的变量名。比如,您正在制作一个游戏,想要为玩家的生命值创建一个变量;将该变量命名为a不是一个好的做法。相反,您应该将其命名为player_Health,这样对您和可能查看您代码的人来说,这个变量中的代码是清晰的。

通常从编程的角度来看,有两种有效地给变量命名的方式。其中两种被广泛称为驼峰命名法帕斯卡命名法。观察先前定义的变量的命名约定,playerHealth,变量的第一个字符应该是小写的,其他所有字符都应该是大写的。同样,在帕斯卡命名法中,变量的每个第一个字符都应该是大写的。因此,使用帕斯卡命名法,先前定义的变量可以写成PlayerHealth。您可以使用其中任何一种来命名您的变量。

您的变量名可以是任意长度。它可以包含大写字母(A-Z)、小写字母(a-z)、数字(0-9)和下划线(_)的组合。下划线用于在两个单词之间区分变量中的两个实体。例如,player_Health变量由两个单词组成。我们在它们之间使用下划线。或者,您也可以使用驼峰命名法,其中您将第一个单词以小写字母开头,第二个单词的第一个字母大写,例如,playerHealth

我们还可以在变量名的开头使用下划线。如果它被用作其他库的库,我们会在我们的代码中使用它。我们也可以在递归语句中使用它,就像这个例子:

>>> _age = 34

在命名变量时,我们需要遵循一些规则,否则 Python 会将其声明为非法并抛出语法错误。以下截图显示了一些非法的赋值语句:

为了消除前述错误,我们必须遵循一些规则。有些是强制性的,而有些只是良好的实践:

  • 我们给变量取一个有意义的名字。将年龄变量命名为age比将其命名为a更有意义。

  • 在命名变量时,我们不能使用特殊符号(@、#、$和%)。例如,n@me 不是一个有效的变量名。

  • 变量名不应以数字开头。45 age 不是一个合适的变量名,Python 会报错。

  • 使用大写名称声明常量,例如,>>> PI = 3.14

  • 使用驼峰命名法创建变量名是一个好的做法,例如,>>> myCountry = "USA"

现在我们已经了解了变量和关键字是什么,以及在命名它们时要遵循的一些规则。现在,让我们继续看看运算符和操作数是什么。

运算符和操作数

数学和编程是两个密切相关但又不同的领域。前者处理理论并提供制定的原则来解决任何问题领域,而后者处理使用这些原则来解决业务领域。编程就是接受数据并使用模型以及适当的数学运算对其进行操作。运算符用于执行这些操作。Python 中有算术和逻辑运算符。

运算符是执行加法、乘法、除法等计算的符号。诸如+-/之类的符号用于执行这些操作。运算符应用的值称为操作数。以下是一些运算符的示例:

>>> 3 + 4
>>> 14 - 5 - 9
>>> 2 * 4

在前面的例子中,第一次操作的结果是 7,第二次操作的结果是 0,最后一次操作的结果是 8。您可以在 shell 中添加或减去任意数量的数字。在这里,所有数字都是操作数,而+-*等符号是运算符。

另一个 Python 中重要的运算符是除法(/)。在 Python 3.x 中,除法操作的结果是浮点数,例如:

>>> 10 / 4

前面的操作给出了一个结果为 2.5。这与使用计算器得到的结果相同。

在 Python 2.x 中,解释器会截断小数部分,并给出一个结果为 2。如果您想在 Python 3.x 中获得相同的结果,应该使用地板除法(//);例如:

>>> 10 // 4

前面的操作将给我们一个结果为 2,而不是 2.5。

让我们回顾一下我们迄今为止学到的内容,即值、变量和运算符。让我们将所有这些组合成一个语句。这被称为表达式:

>>> x = 10 + 2 * 5
>>> x

您可以将所有这些组合在一起,以制作任何类型的表达式。赋值操作是使用最简单的表达式。我们在创建变量时看到了赋值操作。

当表达式中使用多个运算符时,这些运算的顺序变得重要。我们将在下一节中介绍操作的顺序。

操作顺序

让我们回顾一下我们在学校时学到的基本数学。您可能听说过 BODMAS 规则或 PEDMAS 规则。每当我们的表达式中使用多个运算符时,都会按照这个优先级规则执行操作。括号内的操作、指数、除法、乘法、加法和减法按照这个顺序执行:

  • 括号/方括号:这个符号具有最高的优先级,这意味着括号内的操作首先完成。通过在表达式中使用括号,您告诉解释器强制执行某个表达式。例如,在(10-5)+5*6中,括号内的操作首先完成,即10-5,然后进行乘法。

  • 指数/幂:在括号内的操作完成后,指数操作会首先执行。9**0+1的输出不是 9,而是 1。首先执行指数操作,然后执行加法。

  • 除法:除法操作在指数操作之后进行,包括除法,如果它不在括号内。例如,10 / 2 + 3 + 9 / 3是 11 而不是 5。如果表达式是10 / (2 +3) + 9 /3,输出将是 5。

  • 乘法:它的优先级与除法相同。但是,如果表达式中既有除法又有乘法,操作是从左到右顺序执行的。从左到右扫描,如果我们在除法之前得到乘法,就先执行乘法。例如,3*4 / 3的输出是4而不是 3.999。

  • 加法和减法:这两个操作也具有相同的优先级。因此,我们根据从左到右扫描的顺序执行这些操作。例如,在5-5+6中,我们首先执行减法,然后执行加法,得到 6。

如果您仍然对 BODMAS/PEDMAS 规则感到困惑,您可以简单地使用括号来确保获得预期的结果。在下一节中,我们将学习两个重要的运算符://%。前者称为地板除法,而后者称为模运算符。

模运算符

之前,我们看到了如何使用地板除法(//)以及它如何为我们提供除法运算的商。但是,如果您想要除法的余数,可以使用取模运算符。取模运算符产生第一个操作数除以第二个操作数时的余数。取模运算符的符号是百分号(%)。以下屏幕截图显示了两个操作:第一个是地板除法,将得到商,而下一个是取模运算,将得到除法的余数:

当我们想要搜索数字模式并创建可以根据该模式划分数字的程序时,取模运算符非常有用。例如,我们可以检查任何数字与 2 之间的除法余数,以确定该数字是偶数还是奇数:

>>> 5 % 2

由于前面的操作给出了余数为 1,5 可以被认为是一个奇数。

前面的所有操作都非常基本,不需要任何艰苦的计算。但是,我们知道计算机以处理复杂任务而闻名。因此,在下一节中,我们将学习math模块,它能够执行复杂的数学运算,例如计算三角函数和复杂方程。

使用数学模块

数学不仅仅局限于加法和乘法。到目前为止,我们已经学习了各种算术运算。我们还没有涉及逻辑运算符和比较,因为这些将在下一章中介绍。为了涵盖许多数学领域,Python 给了我们一个强大的库,称为math模块。我们称包含代码的文件为模块。这些库也被称为内置库,因为它们在安装 Python 时预先打包。

它们是由 Python 制作的,我们可以随时在我们的代码中调用它们,而无需手动安装。如果要使用任何内置库的代码,必须首先调用它。调用它们意味着导入它们。要导入并使用内置库,我们使用import关键字。正如您可能还记得的,从上一章中,它是 Python 中具有特定目的的保留字。因此,import关键字将任何库导入您的代码中。例如,如果要导入math模块,只需编写以下内容:

>>> import math

您将立即看到下一行有一个空的 shell,就像这样:>>>

这只是指定您正在导入它。导入语句与打印或输入方法不同,后者会立即给我们一个响应。我们应该从该模块中调用某些内容,以便看到任何响应或结果。math模块为我们提供了许多操作。可以通过以下步骤访问这些操作:

  1. 打开您的 IDLE 并按F1打开文档。您将看到以下窗口:

  1. 现在,点击模块。您将看到一个包含模块列表的新窗口:

  1. 从该选项卡搜索数学模块,或者如果您想浏览以字母m开头的模块列表,只需在键盘上按M

有很多方法可以使用!不要被术语方法所压倒;我们已经专门介绍了面向对象编程的部分,我们将学习如何创建自己的方法。现在,只需将方法视为我们用来创建表达式的操作。数学模块提供的方法也将执行简单的算术运算和许多其他复杂的运算。如果您想要获得平方根,我们没有特定的运算符来执行,也不能执行复杂的数学运算;相反,您必须使用数学模块。我们将在下面的示例中查看平方根。

要获得一个数字的平方根,我们可以使用sqrt方法。查看sqrt方法的文档,了解更多信息并学习如何调用它。这非常容易!首先,我们写math,然后是一个句点(.),表示我们想要从 math 模块中使用某些东西并使用sqrt方法:

>>> import math
>>> math.sqrt(49)

49的平方根是 7。我们的解释器打印出 7.0,因为sqrt执行了一个浮点运算。

如果您没有导入math模块,而是直接调用了sqrt,您将收到以下错误:

您可能还记得我们讨论print()函数时,我们没有使用任何模块来调用它,因为它是一个内置函数。但是,这个sqrt()函数不是内置的。它来自 Python 的内置库。虽然我们不必像其他第三方模块一样安装它,但在使用它提供的任何功能之前,我们必须导入它。Python 提供的所有模块都是小写的。

我们可以从 math 模块调用一系列函数和常量。这使我们能够进行支持复杂数学计算的多种操作。如果要打印 PI 的值,可以使用math模块,如下所示:

  1. 首先,我们用>>> import math导入它。

  2. 然后,我们使用module_name并提供一个句点(.)来指定我们要使用该模块和我们要执行的类型操作,例如>>> math.pi

您可以使用数学函数执行代数、对数、三角、双曲和各种其他操作。但是,这个模块不能对复数执行数学运算,例如z = a + ib

对于那些复数类型,我们必须导入cmath模块。导入和使用这个模块也类似于math模块。

如果您想使用math模块提供的函数与print()input()的调用,而不需要加句点,可以使用以下命令:

>>> from math import *

在前面的命令中,*表示您想要导入所有内容。它经典地表示从 math 模块中导入所有内容。现在,如果您想从 math 模块调用任何函数,可以直接调用,类似于我们对输入和打印函数所做的操作:

>>> factorial(4)

前面的函数将被完美执行,并给我们一个结果为 24。

也许您会想为什么模块的概念没有在本书的开头解释。很简单!我们刚刚学习了运算符、操作和表达式,这意味着很容易与数学模块相关联。我们从math模块调用的每个函数都包含运算符、操作数和表达式,但它的实现对我们来说是隐藏的。例如,我们只需使用sqrt函数执行平方根操作,但我们不知道如何用表达式和逻辑来做平方根。在接下来的章节中,当我们涵盖流程控制和函数时,我们将学习这个。因此,模块为我们提供了一种执行高级操作的方式,而无需知道它们是如何工作的。但是,如果您想制作自己的库和模块,那么接下来的章节将帮助您。

如果您想了解有关模块和函数的更多信息,可以简单地使用help命令。Python help命令将为您提供内置函数、模块和关键字的完整文档列表,如以下示例所示:

>>> help([object])
>>> help(input)
Help on built-in function input in module builtins:

input(prompt=None, /)
    Read a string from standard input. The trailing newline is stripped.

    The prompt string, if given, is printed to standard output without a
    trailing newline before reading input.

    If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise 
    EOFError.

    On *nix systems, readline is used if available.

关于值和类型的讨论就到此为止。现在,让我们看看如何使我们的代码更易读和可重用,也就是说,其他人应该能够轻松阅读我们的代码。我们谈到了在命名变量时应遵循的规则和约定,这也导致了可读性。有两种使代码易读的方法:

  • 在程序中写注释。

  • Pythonic 的方法是创建一个函数。

我们可以通过注释向程序添加注释,这将在下一节中介绍。

在代码中编写注释

即使您正在制作一款普通的软件,它也必须以某种方式与数据进行交互。最终,您的代码将变得更长、更复杂,变得难以管理、阅读和理解。虽然我们最终会理解我们编写的代码,但从长远来看,这将变得更加困难。如果您有 50,000 行代码,并且想要调试其中的语义和逻辑错误,那么很难搜索和索引它们。因此,注释非常有用。注释是一种在代码中写下注释的方式,以便任何试图阅读您的代码的人都知道该程序在做什么。Python 不会解释注释,这意味着每当 Python 解析器看到语句以井号符号 (#) 开头时,它的执行将被跳过。

Python 设计模式可能会很复杂,这使得任何天真的程序员都很难查看代码并理解它在做什么。因此,我们在我们的母语中为程序添加简单的注释,解释为什么我们要编写特定的代码。以 # 开头的注释是单行注释。如果您在包含井号的行下面写点东西,它不会被视为注释。这在以下代码中显示:

>>> # this is single line comment
>>> but this is not comment

在 Python 中,没有多行注释。人们通常认为三个双引号 (""" """) 用于多行注释,但这是不正确的。在 Python 3.x 中,三引号内的字符串被视为常规字符串。您可以使用三个双引号来删除断开的字符串。当字符串的范围没有完全封闭时,字符串被视为断开,就像这个例子中一样:

>>> 'Hey it's me'

前面的字符串是用单引号创建的。字符串中使用了撇号,这会使解释器产生困惑,因为它认为 hey it 是一个字符串,而忽略了 s me。这是一个断开的字符串。您遇到的文本并不是每一段都在字符串中。如果您在 IDLE 中运行此代码,将会得到以下语法错误:

要消除此错误,您可以使用三引号。三引号将删除断开的字符串,即使您的字符串中出现双引号或单引号:

>>> """ Hey! it's me """
>>> """ He said, "How may I help you" """

许多人认为前面的代码行代表多行注释,并做了这样的事情:

您可以清楚地看到,它不是忽略执行该命令,而是通过为我们创建一个字符串来反映我们的命令。如果我们没有将值封装在三个双引号中的变量中,它将被视为垃圾收集器,并给我们一个字符串。许多人因其作为文档字符串的行为而将其误认为是多行注释。文档字符串是放置在函数、模块或类顶部的字符串。例如,这是执行加法操作的函数:

def add:

显然,我们还没有学会如何创建函数,但您可以了解到三个双引号用于提供有关函数、类和模块的一些信息。因此,有些人认为它是多行注释。您可以看出它不是多行注释,因为三引号内的注释可以通过 Python 的特殊函数访问。

由于这是文档字符串,我们可以通过 obj.__doc__ 访问它。由于它可以被方法访问,并且解释器不会忽略它,因此它不能被视为多行注释。因此,我们可以得出结论,Python 中只有单行注释。如果我们确实想要多行注释,应该使用三个双引号,但我们必须确保将它们放在函数、类或模块的定义之上。

在以下代码中,\n 表示换行。这将导致代码中的换行。正如我们所看到的,以下代码在第一行打印 hey,在下一行打印 it's me

>>> print("hey \n it's me")
hey
it's me

从中,我们可以得出关于注释的以下结论:

  • 注释是多余的。它们只是告诉我们每行代码在做什么:
      >>> print(customer_info)  # printing customer information
  • 注释可能包含有关代码的有用信息,甚至一些我们无法通过查看代码提取的关键信息:
      >>> d = (400, 200) # d is for display of game console 400*200
      >>> TEMP = 23 # temperature is in Celsius

正如我们在上一章中讨论的,我们在创建程序时必须遵循一种方便的模式。虽然这不是强制性的,但这总是一个很好的做法。在Python 的基本组成部分,第一个块是请求用户输入,这将是我们下一次讨论的主题。

请求用户输入

编程的基本组成之一是让用户使用键盘输入数据。无论是用于管理工具还是游戏,所有应用程序都应该从用户那里获取输入。在用户管理应用程序中,我们收集用户信息,如他们的姓名、地址和年龄,并将其插入到数据库中。在游戏中,我们从键盘上获取用户输入以进行移动。根据用户按下的键,我们可以让角色执行一些动作。例如,按下键盘上的Shift键将使角色跳跃。因此,每个应用程序都必须用户友好,这意味着它必须使用户与应用程序进行交互。

让用户在键盘上输入一些内容并将其存储在变量中,以便我们在需要时可以进一步处理,这是一个常见的做法。Python 具有内置函数来从用户那里获取输入,这意味着您无需导入或安装任何内容即可使用此函数。input()函数用于从用户那里获取输入:

>>> input()

当您输入上述命令时,它将为您提供一个写东西的地方。解释器在用户按下键盘上的按钮并按下Enter之前会保持其其他执行。按下Enter键后,程序将恢复并给出用户的文本输入。

以下屏幕截图显示了input()函数在 Python 中的工作方式:

在上面的屏幕截图中,我们使用了input()方法并输入了字符串'I love Python'。黑色文本颜色是用户输入的内容,解释器立即给出了一些输出,这是用户输入的相同字符串。您可以将输入文本存储到变量中,以便我们可以对其进行计算:

>>> message = input()

现在,我们已经看到了如何从用户那里输入数据。向用户提供消息或提示,告诉他们需要在该字段中输入什么,这总是一个很好的做法。消息或提示应作为input方法括号内的字符串给出,如下所示:

>>>user_name = input(" \n Enter your name? : \n")
Enter your name? :
John Doe #this is input from user
'John Doe' #printing content of user_name

在上面的示例中,当用户输入内容并按下Enter时,我们的程序会接受用户的输入并执行指定的任务。但是,如果您想要创建一个应用程序,其中要连续从用户那里获取数据,我们必须使用循环。我们将在接下来的章节中学习循环:

>>> while True:
        input("Enter user_names: \n")

上述语句连续从用户那里获取输入。即使按下Enter或输入return关键字后,它也不会停止。在上述命令中,while用于循环。True是表示逻辑和布尔代数的真值的布尔类型。布尔类型要么是True,要么是False。因此,while True语句意味着其中的代码应该无限运行,要求用户无限输入。其结果如下:

调用input()方法时,您在键盘上输入的任何内容都将以字符串形式呈现,即使您输入的是整数,就像在示例中一样:

>>> a = input()
1 #store integer 1 to the variable a

如果你使用type方法检查a变量的类型,即>>> type(a),你会看到一些意想不到的结果。我们从用户那里输入1并将其存储在变量a中。当我们检查存储在a变量中的值的类型时,它不会是一个整数。相反,它会显示str class: <class 'str'>,这意味着你通过调用input()方法在键盘上输入的任何内容都将是字符串类型。但有时,也许我们希望用户输入的整数保持为整数。在这种情况下,我们必须执行类型转换,这将在下一节中介绍。

类型转换或类型转换

有时你可能希望将用户输入的数据作为整数使用。我们看到,用户输入的数据将是一个字符串,即使它是一个整数,就像这个例子中一样:

>>> age = input("Enter your age? \n")
>>> Enter your age?
29
>>> type(age)
<class 'str'>
>>> age
'29'

年龄是用数字表示的。然而,在前面的代码中,它是一个字符串。因此,我们必须将它转换为整数,以便用户输入的信息对计算有意义。这种转换称为类型转换。然而,如果你在不将其转换为适当类型的情况下对这个值进行一些计算,你的结果将是不可取的。例如,如果你想通过将 29 加 2 来改变年龄的值,你不能将它从 29 改变为 31。这是因为字符串不支持增量;相反,它们支持连接:

>>> age 
'29'
>>> age + 2
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    age + 2
TypeError: can only concatenate str (not "int") to str

因此,如果你想使用输入的年龄作为整数,我们必须使用类型转换方法。这些方法也是 Python 的内置函数。其中一些如下:

  • int(arg1, base): 这个方法将任何其他数据类型转换为整数。如果你在int函数的括号中放入一个字符串,它将把它转换为整数。arg1是要转换的字符串,基本参数表示数据的基数是一个字符串:
      >>> a = int("10101", 2)
      >>> a
      21 #conversion from string to integer

      >>> b = int("255")
      >>>b
      255
  • float(): 这个方法将任何整数转换为浮点数,就像这个例子中一样:
      >>> float(3)
      3.0 #this is floating point number           
  • str(): 这个方法将任何其他数据类型转换为字符串,就像这个例子中一样:
      >>> str(255)
      '255'
  • ord(): 这个方法将字符类型转换为整数,并返回它的 ASCII 值,就像这个例子中一样:
       >>> ord('a')
       97  #ASCII value of a is 97

其他函数,如tuple()list()set()dict(),将在接下来的章节中介绍。

现在你已经熟悉了 Python 的第一个构建块,即从用户那里输入数据,让我们看看如何使用 Python 提供的不同功能来格式化这些数据。在下一节中,我们将看一下字符串操作,这将反过来调用 Python 提供的不同方法来操作用户输入的数据。

字符串操作

任何数据类型,无论是文本、整数还是布尔值,用双引号(" ")或单引号(' ')括起来,在 Python 中都被视为字符串。字符串值揭示了数据的广泛含义。存储为字符串的数据可以很容易地访问,但不能被改变。因此,它被视为不可变的数据类型。让我们看一下下面的代码:

>>> msg = "happy birthday"
>>> msg.upper() # upper() is inbuilt method of string class that converts string to upper case
'HAPPY BIRTHDAY'
>>> msg
'happy birthday'

在前面的代码中,我们创建了msg变量并将一个字符串存储在其中。我们使用了string类的内置方法来操作该字符串,当我们将msg变量打印回来时,它保持不变。这意味着字符串是不可变的数据类型。如果你想改变字符串的内容,你应该完全覆盖它,就像这个例子中一样:

>>> msg = msg.upper()
>>> msg
'HAPPY BIRTHDAY'

字符串不支持项目赋值。如果你想向字符串添加一个项目,你必须创建一个全新的字符串。因此,Python 的这个特性使它成为不可变的,就像这个例子中一样:

>>> str1 = "John"
>>> str1[0] = "Hello"
Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    str1[0] = "Hello"
TypeError: 'str' object does not support item assignment

要使用字符串的内置函数,你必须在字符串上调用一个方法。让我们看一下我们可以在内置方法中使用的模式,即"String".method_name()

>>> "Python".capitalize() #capitalize first letter of string
Python
>>> "xyz".join("pqr") #joins every letter of string "pqr" with xyz except for first and last letter
'pxyzqxyzr'

#len function does not have to call like this, call simply len() with string passed inside parenthesis
>>> len("Python") #prints length of string
6

您可以使用方括号访问字符串的每个元素。我们应该将位置放在方括号内。这些位置在 Python 中称为索引。字符串的索引从 0 开始,从左到右每次增加 1:

>>> info = "Python"
>>> info[2] 
t
>>> info[0]
P

您可以在以下图表中观察索引模式。在这里,我们有一个Python字符串。字符串的索引从 0 开始。对于紧挨着具有索引的每个元素,都会增加一个单位到前一个元素的索引。这被称为正索引:

字符串还支持负索引。如果您想要从字符串中获取最后几位数字,可以给出一个-1索引,如下所示:

>>> info = "Python"
>>> info[-1]
n
>>> info[-3]
h

现在,我们已经学会了如何根据索引提取字符串的特定元素。但是,如果您想从字符串中提取多个元素,可以使用字符串切片操作。切片操作与比萨切片相同,表示我们按顺序取出字符串的一些部分。字符串切片可以使用与从字符串中提取单个字符时相同的方括号进行。这两种操作的区别在于,当我们用冒号扩展我们的方括号并为其提供开始、结束(不包括)和步长索引时,就会看到区别。尽管字符串切片的理论可能看起来很复杂,但编程起来很容易。让我们看一个例子来澄清这一点:

>>> email = "johndoe@gmail.com"

假设我们想要从这个电子邮件地址中提取一个人的名字。我们必须跟踪所有的索引才能做到这一点:

由于我们正在切片字符串的一些部分,我们必须将其想象为一个容器,其中每个字符都有其索引,以便更容易地引用它们。要实现字符串切片,请按照以下步骤进行:

  1. 使用name_of_string[start: stop]:我们使用[step]命令进行字符串切片。这里,start是起始索引,stop是一个排他位置,这意味着如果您在其上放置一个索引,-1 的元素将被包括在内,但在停止索引处的元素将被排除在外。这里,步骤是可选的。我们将在接下来的章节(第三章:流控制:为您的游戏构建决策制定者)中讨论步骤索引位置。

  2. 首先决定需要提取什么。您不能随机提取字符串的任何部分。它应该是按顺序进行的。例如,您不能使用单个命令提取jomail。我们可以提取johndoe,因为每个元素都是按顺序排列的。让我们尝试从我们的代码中提取它:

  >>> email = "johndoe@gmail.com"
  >>> email[0:7:] # 0 is starting position, 7 is stopping position and it is not included
  'johndoe'
  >>> email[:7:] #empty starting position also means start from 0 index
  'johndoe'

在上述代码中,email[0:7:]email[:7:]告诉我们,第一个索引 0 是字符串的起始索引,这意味着我们要从start打印。您也可以放置 0,表示默认状态,start将从起始位置打印。第二个索引 7 是停止位置,但它是一个排他位置,这意味着解释器将打印直到e字符,但不包括@,因为@在位置 7。最后,第三个索引位置是步长。我们在这里放置一个空格,表示它应该默认持有的值,这意味着我们打印时不跳过任何数字。如果您将步骤放置为>>> email[0:7:2],您将得到jhde作为输出;它将在每个字符之间跳过一个字符。

我们还可以对字符串执行加法和乘法运算。将两个字符串连接在一起称为连接。我们使用+*等运算符来执行字符串操作,就像这个例子中一样:

>>> "-" * 50 #this will create 50 hyphen or dashes (-)
'--------------------------------------------------'

>>> "a" * 4
'aaaa'

但是,您不能将两种字符串类型相乘。其中一个必须是字符串,另一个必须是整数,如果我们希望对字符串执行乘法操作。

>>> "a" * "b"
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    "a" * "b"
TypeError: can't multiply sequence by non-int of type 'str'

如果您还想要添加字符串,那么操作数必须都是字符串。否则,它将抛出类型错误:

>>> str1 = "Happy"
>>> str2 = "Birthday"
>>> str3 = "John"
>>> str1 + str2 + str3
'HappyBirthdayJohn' 

>>> str1 + 45 # YOU CANNOT ADD STRING AND INTEGER
Traceback (most recent call last):
  File "<pyshell#28>", line 1, in <module>
    str1 + 45
TypeError: can only concatenate str (not "int") to str

现在我们已经了解了字符串操作的基本知识,比如赋值、连接和赋值,我们将学习字符串格式化。如果我们需要根据输入更改文本的格式,这是一个重要的概念。

字符串格式化

字符串格式化是我们通过用变量的内容替换占位符来构建字符串的地方。我们应用%(取模运算符)来执行字符串格式化。如果要指定数字作为占位符,使用%d。如果是字符串,使用%s作为占位符。字符串格式化的结果也是一个字符串。让我们看一个小例子:

>>> key = "love"
>>> value = 13

#lets use string formatting technique
>>> print(" I %s number %d"%(key,value))
'I love number 13'

在上述代码中,%s的位置被 key 变量的值替换,%d的位置被 value 变量的值替换。因此,%d%s是占位符。你不能在%d的位置上分配字符串值,也不能在%d上分配整数值。

传递的值的数量必须与字符串中使用的格式序列的数量相匹配。否则,它将抛出类型错误,如下所示:

>>> '%s %d %s'%("Hello",1)
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    '%s %d %s'%("Hello",1)
TypeError: not enough arguments for format string

你也可以使用 Python 的内置格式函数来格式化字符串。使用这个函数进行格式化相对容易。我们可以使用大括号{}作为占位符,而不是使用占位符或格式序列,如%d%s。我们还可以在大括号内指定数字,以便格式化为特定值,如下所示:

>>> print(" I love {}".format("Python"))
'I love Python'

>>> print(" I love {0} and I hate {1}".format("Python", "Java"))
'I love Python and I hate Java'

>>> print(" I love {1} and I hate {0}".format("Python","Java"))
'I love Java and I hate Python'

现在我们熟悉了 Python 的核心编程范式,让我们跳到下一节,在那里我们将学习制作我们的第一个游戏:井字棋

制作你的第一个游戏-井字棋

Python 语言是一种跨平台语言,这意味着我们可以为任何设备制作游戏。然而,在这里,我们将更多地关注逻辑及其实现,而不是针对特定平台进行编码。与其他语言相比,使用 Python 编写游戏更简单,因为它的语法更短,提供了丰富的内容库,使生产速度更快。话虽如此,如果在编码之前不制定计划,那就不那么容易了。我们必须将游戏实体分解成部分,以便每个实体都可以轻松调试。从现在开始,在制作游戏时,我们将遵循以下一般步骤:

  • 头脑风暴和信息收集

  • 选择合适的代码编辑器

  • 编程模型

  • 用户交互-用户输入/操作

到目前为止,我们已经涵盖了各种主题,包括变量、运算符、表达式、从用户那里获取输入并将其打印给用户。现在让我们将所有这些技术应用到我们的第一个游戏中。

头脑风暴和信息收集

在我们开始编码之前,让我们考虑一下游戏的设计和界面。拿出你的笔和纸,开始思考游戏的界面吧!到目前为止我们学到了关于 GUI 的什么?显然没有!这意味着我们必须为我们的第一个游戏使用一个简单的界面。在学习了 Python 的一些高级概念之后,我们将稍后对其进行修改。井字棋是一个需要从用户那里获取输入并根据玩家的移动放置 X 或 O 的游戏。因此,我们的界面应该是这些符号的占位符。我们将制作一个包含_的简单界面。下划线(_)将是我们的占位符,我们将根据玩家的选择放置 X 或 O。

_ | _ | _
_ | _ | _
_ | _ | _                    

上述代码显示了我们游戏的简单布局。它包含_(下划线)作为占位符和|来分隔符号。

_ | _ | O
_ | X | _
_ | _ | X

正如你所看到的,每当玩家迈出一步时,我们就用与该用户决定相对应的符号替换下划线。现在,我们有了一个游戏的基本界面。

现在我们已经规划了界面,我们需要解决如何跟踪下划线的位置以及如何找出在哪里用适当的符号替换下划线的问题。我们可以为这些下划线中的每一个分配一个数字,并告诉用户选择一个数字。然后,根据该数字,我们可以将其符号分配到该位置,如下所示:

0 | 1 | 2
3 | 4 | 5
6 | 7 | 8

现在,我们已经收集了足够的信息来开始我们的简单游戏。在复杂的现实世界游戏中,头脑风暴和信息收集过程大约需要 6 个月。现在,让我们来选择一个代码编辑器。

选择适当的代码编辑器

我们已经在我们的机器上安装了 Python,并查看了 Python 的预安装编辑器 IDLE。我们将在这个项目中使用该编辑器。让我们开始吧:

  1. 在搜索栏中搜索 IDLE 并打开它。你会得到以下 Shell:

这个终端或 Shell 通常用于在 Shell 内立即解释命令。这意味着一次只执行一个命令,但我们必须写很多行代码来制作我们的游戏。因此,用这个 Shell 编写游戏是不可能的。我们必须创建一个文件,可以在其中写入多行代码并一次性执行它们。Python 为我们提供了脚本来解决这个问题。

  1. 点击文件,然后新建文件,或按Ctrl + N。将打开一个新的脚本文件,我们可以在其中写入多行代码。

  1. 在窗口顶部,我们会看到未命名,这意味着我们还没有保存我们的文件。让我们先保存,因为无论如何我们都必须保存。按下Ctrl + S进行保存。我已经将其保存为first_game.py

现在我们已经选择了适合开发的 IDE,让我们开始为游戏开发我们的模型。

编程模型或建模

在编程中,模型是表示程序中数据流的一种方式。在我们的游戏中,这是关于如何使用作为用户输入获得的数据。在头脑风暴和信息收集部分,我们揭示了一些信息,讨论了位置以及如何将每个数字分配给代表玩家选择的位置。模型不包含演示逻辑;相反,它将处理数据逻辑。计算机不关心布局或界面。另一方面,用户需要一个界面来做出反应。因此,每个程序都有前端和后端。前端是应用程序中看到的一切,无论是应用程序的美学部分还是可见部分。在大型项目中,用户体验UX)设计师主要在前端工作。后端不关心设计,它只关心应用于数据层的算法和安全性。模型被用作前端和后端之间的通信方式。

计算机不关心模型如何呈现数据,但用户应该以信息丰富和美观的方式从模型中获取数据。因此,我们制作了简单的布局,如下所示:

_ | _ | _
_ | _ | _
_ | _ | _   

让我们开始创建我们的演示层模型:

#this code is written as scripts
game_board = ['_'] * 9 #this will create 9 underscores
print(game_board[0] + '|' + game_board[1] + '|' + game_board[2])
print(game_board[3] + '|' + game_board[4] + '|' + game_board[5])
print(game_board[6] + '|' + game_board[7] + '|' + game_board[8])

上面的代码代表了我们游戏的布局。它显示给用户。让我们逐行分解一下:

  • game_board = ['_'] * 9: 这个语句创建了 9 个下划线,作为我们游戏角色的占位符。它存储在game_board变量中。正如你可能记得的,一个变量不能存储多个值。如果我们对同一个变量执行多次赋值,变量将存储最后添加到它的值。因此,这个棋盘不是简单类型的变量。这是一个list变量。我们可以在列表中存储多个数据。让我们打印一下棋盘的值:
      >>> board = ['_'] * 9
      >>> board 
      ['_', '_', '_', '_', '_', '_', '_', '_', '_'] # 9 underscores is 
        stored in board list
  • >>> print(game_board[0] + '|' + game_board[1] + '|' + game_board[2]): 上面的命令打印了布局的第一行。在本章的前面,我们已经学习了print语句。括号内的任何内容(字符串或变量值)都会被print语句原样打印出来。我们传递了board[0]来获取棋盘的第一个元素,也就是第一个下划线(_)。我们在每个下划线之间打印一个分隔符(|)。上面语句的输出是_ | _ | _ 

  • 我们必须再次打印前面的布局两次,这意味着我们必须使用两个print语句:

      >>> print(game_board[3] + '|' + game_board[4] + '|' + game_board[5])
      >>> print(game_board[6] + '|' + game_board[7] + '|' + game_board[8])
  • 插入在方括号中的数字是我们通常在编程中称为索引的位置。这指的是list变量的某个位置。列表索引总是从零开始:
     >>> board = [1,2,3,4,5,6]
     >>> board[0] # this will give value 1 from "board" list
     >>> board[5] # this will give value 6 from "board" list
  • 下面的代码显示了我们井字棋游戏的最终布局。确保将程序编写为脚本,并按F5运行它:
        game_board = ['_'] * 9
        print(game_board[0] + '|' + game_board[1] + '|' + game_board[2])
        print(game_board[3] + '|' + game_board[4] + '|' + game_board[5])
        print(game_board[6] + '|' + game_board[7] + '|' + game_board[8])

        #output
        """ 
            _ | _ | _
            _ | _ | _
            _ | _ | _   

        """ 
  • 在前面的代码中,我们做了两件事:首先,在我们的布局的每个位置上打印了下划线,然后我们为每个位置分配了一个数字:
      0th | 1st | 2nd
      3rd | 4th | 5th     
      6th | 7th | 8th

现在,我们已经开发了代表游戏基本布局的编程模型,是时候让编程模型与游戏玩家进行交互了。在下一节中,我们将学习如何接受用户输入并对其进行操作,以便与我们游戏的模型进行交互。

用户交互-用户输入和操作

我们正在为用户制作游戏。因此,我们应该制作一个界面,使我们的应用程序更加用户友好。我们在上一节中已经做到了这一点。现在,我们必须从用户那里接受一些输入,并通过模型将其放到布局中。我们知道从用户那里接受输入的一个简单方法是使用input()方法。现在让我们使用它。

为了解决这个问题,我们需要考虑:我们应该从用户那里输入什么?是一个符号,比如 X/O,还是位置?

将输入作为符号是没有意义的,因为在接受输入后,我们应该知道在哪里放置它。因此,我们可以从用户那里接受位置,并自动将符号放入我们的代码中:

#code from models  

#...................................................................

#code for user input

while True:
     pos = input(" Enter any position you want from (0-8): \n")
     pos = int(pos)
     game_board[pos] = 'X'
     print(game_board[0] + '|' + game_board[1] + '|' + game_board[2])
     print(game_board[3] + '|' + game_board[4] + '|' + game_board[5])
     print(game_board[6] + '|' + game_board[7] + '|' + game_board[8])

让我们一部分一部分地来分解这个问题:

  • while True:这将无限次运行。我们在请求用户输入部分看到了这种情况。因此,我们将无限次地从用户那里获取输入数据,这意味着我们的游戏循环没有终止。

  • pos = input("输入您想要的任何位置(0-8):\n"):这个语句将从用户那里接受 0 到 8 的位置输入,并将其存储在pos变量中。

  • 存储在pos变量中的数据将是一个字符串,但位置应该是一个整数。因此,我们必须使用int方法将其转换为整数。然后,我们将整数存储在pos变量中,如x = int(x)

  • game_board[pos] = 'X':这个语句将X分配给用户选择的位置。pos变量包含了用户在上一个命令中选择的 0 到 8 的位置。现在,我们将X分配给该位置,取代下划线,如下所示:

      0th | 1st | 2nd
      3rd | 4th | 5th     
      6th | 7th | 8th

如果用户输入4,那么我们将在第4个位置放置X,如下所示:

      0th | 1st | 2nd
      3rd |  X  | 5th     
      6th | 7th | 8th
  • 在我们为指定位置分配了玩家符号之后,我们必须使用这三个打印语句再次打印棋盘。它应该保持在循环内,因为我们必须在用户从键盘输入新位置时每次都打印棋盘。

现在我们已经完成了渲染布局和用户输入模型的制作,我们可以运行游戏并观察输出。你将看到的游戏可能不太吸引人,因为它没有一个合适的布局,也不会有我们井字棋游戏应该有的许多功能。在未来的章节中,我们将尽可能地使游戏可玩。现在,让我们来看看可能在我们的游戏中遇到的错误和警告。

可能的错误和警告

到目前为止,我们只涵盖了 Python 的基本基础知识,因此到目前为止,你可能还没有发现太多语义错误。但是,你可能已经习惯了语法错误。首先,变量命名可能会导致错误。如果你不遵循变量命名的规则或约定,你可能会遇到以下错误:

>>> my name = "John Doe"
SyntaxError: invalid syntax

前面的名称是无效的,因为在创建变量名称时不能使用空格。你可以在它们之间加一个下划线来指定它由两个单词组成。my_name是一个变量的有效名称。

如果你拼错了变量名,你会立即得到一个错误。假设你创建了一个名为Msg的变量,并将其用作msg。将返回一个错误,说明这是错误的定义。Python 是大小写敏感的,这意味着Truetrue在 Python 中是不同的。如果你将一个变量命名为True,那将是非法的,因为它是 Python 的关键字之一。

然而,你可以将一个变量命名为true

>>> True = 45
SyntaxError: can't assign to keyword 
>>> true = 45
>>> true
45

同样的规则也适用于命名模块。在本章中,我们学习了如何导入math模块并使用它的方法。然而,如果你拼错了模块的名称,长期来看会导致许多问题。在 IDLE 上不会立即看到错误;你必须编译你的脚本才能看到。因此,在 IDLE 上调试要困难得多。因此,请确保你正确拼写所有的模块和它们的方法。

>>> import math将成功地将math模块导入到你的项目中,但如果你使用错误的模块名称,你将得到以下错误:

>>> import Math
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    import Math
ModuleNotFoundError: No module named 'Math'

还有另一种比语法错误更危险的错误类型;这些被称为语义错误。语义错误发生在我们没有得到预期结果时。它们不会被解释器检测到,因此很难调试。我们可能由于错误执行表达式而出现语义错误。如果我们对优先规则不够重视,最终会导致程序出现错误的语句。

1 + 3**2表达式的输出是 10,而不是 16。然而,我们可以通过在括号中包围该语句来强制解释器打印 16。(1 + 3) **2将给我们 16。

现在你已经学会了如何纠正程序中遇到的错误,让我们来了解一下修改我们的第一个井字棋游戏的可能方法。

游戏测试和可能的修改

有几种方法可以找到游戏中的错误。首先,你可以向你的朋友们求助,让他们玩你的游戏。你第一次测试游戏时收集到的建议被称为 alpha 测试,它是任何游戏开发生命周期中的一个重要部分。通过采访收集足够的信息后,你可以开始修改你的游戏。

到目前为止我们学到的东西还不足以使我们的游戏更有吸引力。我们将在接下来的章节中学习一些主题,并相应地修改我们的井字棋游戏。

我们在本节制作的游戏很单调,没有激励用户去玩,但通过制作它,我们学到了很多东西。我们看了使用模型和视图概念创建游戏的基本过程。视图指的是我们呈现数据的布局,帮助我们通过界面与用户进行交互,而模型指的是我们在程序和用户之间传递数据的方式。我们还没有涵盖高级的 Python 语言范式,因此我们的能力有限。这意味着本章的游戏很简单。然而,在我们学习条件、循环和函数后,我们将对这个游戏进行修改。

以下是我们可以对游戏进行的一些可能修改:

  • 让我们分析一下我们的代码及其局限性。我们告诉用户明确输入 0 到 8 之间的数字来指定用户的移动。如果用户没有输入数字而输入了字符串会怎么样?我们的程序将终止循环并因异常而崩溃。因此,我们将进行的第一个修改是限制用户只能输入数字。如果他们输入其他内容,我们可以打印一个用户友好的消息,而不是让程序崩溃。这个概念被称为异常处理,将在下一章中介绍。

  • 目前,这个游戏只能与一个玩家一起玩,但井字棋是一个多人游戏。因此,我们必须学习条件和流控制,这将帮助我们实现玩家之间的过渡。我们将在下一章中做到这一点。

  • 当用户捕获整行、整列或对角线时,他们应被视为游戏的赢家,并且游戏应完成执行。然而,我们还没有创建任何逻辑来使玩家成为赢家。到目前为止,我们所学的还不够,但在完成下一章之后,我们将能够对我们的游戏进行重大改变。

通过查看我们可以对我们的游戏进行的修改,我们可以看到在下一章中我们有更大的事情要做。尽管我们在本章中获得的知识足以创建一个编程模型并允许我们与单个玩家互动,但这对我们与多个玩家互动来说还不够,这需要对循环和条件结构有很好的理解。我们将在下一章中涵盖这些概念。

总结

在本章中,我们涵盖了 Python 的两个基本构建模块:输入和提供格式化输出。在本章中,我们查看了 Python 的内置数据类型,并首先学习了不同数据值及其类型,如整数、字符串、浮点数、布尔值和无值。我们通过学习变量、数字和math模块来了解 Python 生态系统。我们看到了如何使用math模块,并对需要遵循的变量创建和使用模块的规则和约定有了很好的理解。如果你想用 Python 开始你的编程生涯,这些主题是必不可少的。这些主题不仅为 Python 打下了坚实的基础,还教会了你在编程中需要遵循和消除的好坏实践,即使你是一名熟练的 Python 程序员。编码不仅仅是编写代码——它是关于以可读和可用的方式呈现信息。因此,我们看到了如何在编程中使用注释来使我们的代码更易读和可重用于其他程序员。

使用户输入数据,然后在我们的程序中使用它是使应用程序用户友好的唯一方法。因此,我们学习了如何使用户输入数据并将其存储在结构中,以便进一步操作时更容易访问。我们最终看到了input()方法的不寻常的工作行为,它将我们的整数或布尔输入数据转换为字符串。由于这个原因,我们学习了类型转换方法,并看到使用 Python 的内置方法执行数据转换是多么容易。

字符串是最基本和原始的数据类型,用于存储文本。我们专门讨论了字符串的创建和操作。我们学习了如何访问字符串的元素。我们还了解到字符串赋值是不可能的,因此我们得出结论字符串是不可变的。我们学习了字符串类的基本方法,如 capitalize、join、upper、lower 和 len。我们研究了字符串的两种格式化技术,即%s%d,它们被用作占位符和格式化方法。你可以使用其中任何一个,尽管最好在这样做之前对每一个都有所了解。

然后,我们建立了我们的第一个游戏。我们看到建立游戏不仅仅是编码。我们需要经历各种过程,如头脑风暴、建模和用户交互。我们学习了模型和视图如何协同工作。然后,我们制作了一个简单的游戏,并有机会复习到目前为止学到的一切。最后,我们提出了一些我们可以对井字棋游戏进行的修改。随着我们在这本书中的进展,每个修改都将被涵盖。在下一章中,我们将学习流控制以及如何为我们的游戏构建决策者。

第三章:流程控制 - 为你的游戏构建决策制造者

Python 最大的福祉之一是自动化。当我们谈论自动化时,没有令人震惊的逻辑;一切都取决于条件和分支的力量。它们控制程序执行时的顺序。任何程序在其基本阶段都是通过模拟制作的。每当我们在真实环境中部署这些程序时,我们都会被各种噪音和意外行为所压倒。为了防止这种行为,条件起着重要作用。流程控制根据当前的布尔逻辑决定如何执行程序的特定部分。我们在上一章中涵盖了语句和运算符等主题,这些主题在创建布尔逻辑时非常有用。这些语句用于执行算术计算。在本章中,我们将看到如何操作这些语句,这将导致真或假的布尔逻辑。

在本章的中途,我们将学习循环,这是一种重要的技术,可以使我们足够有能力使代码更短更强大。本章将是一个完整的包,其中包括核心编程、条件和递归编程。我们将通过引入布尔逻辑和流程控制来完善上一章中制作的井字棋游戏。

本章将涵盖以下主题:

  • 布尔逻辑和逻辑运算符

  • 条件语句

  • 迭代

  • forwhile 循环

  • 为我们的井字棋游戏制作游戏控制器

技术要求

您需要以下要求才能完成本章:

查看以下视频以查看代码的运行情况:

bit.ly/2pvpBas

理解布尔逻辑和逻辑运算符

没有一天会过去我们不会说布尔类型要么是True要么是False。我们使用这些关键字来制定逻辑,以确定我们是否要执行某部分代码。让我们从现实生活中的角度来谈谈布尔类型。如果我们饿了,我们就吃点东西。如果我们累了,我们就休息。让我们将这些情景转化为适当的布尔语句:

  • is_hungry = True吃点东西 || is_hungry = False: 不吃

  • is_tired = True休息 || is_tired = False: 做你的工作

你根据手头的布尔逻辑执行这些日常任务。现在,让我们把它与编程联系起来;你可以使用两组基于布尔数据类型的代码:

  • (True): 做某事 || (False): 做某事

我们使用布尔表达式来制作这种类型的逻辑。我们在上一章中看到了如何创建表达式。将变量和运算符结合起来将给我们一个简单的表达式形式,就像这个例子:

>>> y
>>> x = y + 6 * 7 

然而,布尔表达式有点不同。它们不是给出整数结果,而是提供TrueFalse的结果。布尔表达式的最简单形式可以使用双等号运算符(==)制作。不要将其与单等号(=)混淆。单等号用于赋值,而双等号(==)用于检查它们是否相等,就像这个例子:

>>> 5 == 5
True
>>> "Python" == "Java"
False

如果您比较两种不同类型的数据,结果始终是False

>>> "5" == 5 # String(5) not equal to int(5)

您可以始终进行类型转换以使您的逻辑为True

>>> int("5") == 5
True
>>> "5" == str(5)
True

要检查任何布尔变量的类型,可以使用type()方法并获得<class 'bool'>的输出,这意味着TrueFalse是布尔类型的值:

>>> logic = False
>>> type(logic)
'<class 'bool'>'

布尔逻辑也可以与比较运算符一起使用。我们将在下一节中学习如何使用比较运算符创建语句。

比较运算符

任何结果为TrueFalse的表达式都是布尔表达式。这些布尔表达式不能没有比较和逻辑运算符。我们已经看过基本的比较运算符(==);然而,还有六个我们需要了解的(<><=>=!=is)。让我们看看它们的运行情况:

  • 5 < 10:5 小于 10,结果为True

  • 5 > 10:5 大于 10,结果为False

  • 10 <= 5:10 小于或等于 5,结果为False。10 既不小于也不等于 5。

  • 10 >= 5:10 大于或等于 5,结果为True。10 大于 5。

  • 10 != 10:10 不等于 10,结果为False。10 等于 10。

  • 5 是 5:5 和 5 相同,所以结果是True。然而,5是 5,所以结果是False

您可以将前面的数字存储在不同的变量中,并在 IDLE 上尝试相同的布尔表达式,以获得以下结果:

>>> v1 = 5
>>> v2 = 10
>>> v1 < v2
True
>>> v1 > v2
False
>>> v2 <= v1
False
>>> v2 >= v1
True
>>> v2 != v2
False
>>> v1 is v2 
False
>>> v1 is v1
True

为了使适用于现实世界的逻辑,我们需要能够同时组合不同的比较操作并立即提供结果的运算符。这些类型的运算符被称为逻辑运算符。在下一节中,我们将学习不同类型的逻辑运算符以及它们的使用方式。

逻辑运算符

运算符被广泛分类为算术运算符、比较运算符和逻辑运算符。我们已经涵盖了算术和比较运算符;现在是时候涵盖逻辑运算符了。

您可以将逻辑运算符与逻辑门(andornot)联系起来,逻辑门是任何数字电路的基本构建模块。它们有两个输入,但是通过某些电路计算,我们只得到一个输出。电路处理是通过andornot门完成的。类似于逻辑门的数字电路,逻辑运算符可以有许多条件传递,但最终输出将是TrueFalse。这里,conditions指的是我们使用比较运算符制作的布尔表达式。这三种基本逻辑运算符的工作原理如下:

  • and:两个条件用一个and运算符连接,即condition_onecondition_two。当这些条件中的每一个也为True时,整个条件将为True。如果与and运算符连接的条件中的任一条件为False,结果将为False。让我们看一个例子:
      >>> condition_one = 5 > 2 #True
      >>> condition_two = 6 < 10 #True
      >>> condition_one and condition_two
      True
      >>> condition_two = 6 > 10
      >>> condition_one and condition_two
      False

and运算符的真值表,根据布尔或逻辑表达式的组合,将其功能值设置为TrueFalse,如下所示:

  • or:与and运算符相同——两个条件用一个or运算符连接。如果要添加更多条件,可以添加更多or运算符。对于or运算符,如果连接到它的两个条件都为False,结果将为False;否则,结果将为True。让我们看一个例子:
 >>> 4 < 10 or 5 == 5
 True
 >>> 4 <= 10 or 100 < 50
 True
 >>> 10 <= 4 or 100 < 50
 False

or运算符的真值表,根据布尔或逻辑表达式的组合,将其功能值设置为TrueFalse,如下所示:

  • not:这个运算符颠倒逻辑的类型。它将False变为True,反之亦然。因此,它被称为逻辑反转器。它只带有一个条件,如下所示:
      >>> not (5 < 4) # condition 5 < 4 is False
      True
      >>> not True
      False

not运算符的真值表,根据布尔或逻辑表达式的组合,将其功能值设置为TrueFalse,如下所示:

在 Python 中,也可以用 1 和 0 表示TrueFalse。因此,我们可以得出结论,任何非零整数都可以单独使用逻辑运算符来构成条件,就像这个例子:

>>> 1 and 1
1
>>> 1 and 0
0
>>> 1 or 0
1
>>> 49 or True
49

学习不同类型的运算符非常有趣,但现在我们将转到一个部分,在那里你将学习如何使用这些条件(由比较和逻辑运算符制作)来做出几个决定。条件语句在任何现实场景中都非常实用。我很兴奋地想要学习它们,你呢?

条件语句

到目前为止,我们已经学习了使用比较和逻辑运算符制作条件。现在,我们将讨论如何评估这些条件。条件语句是在我们想要计算这些条件的结果并相应地控制程序流程时非常有用的工具。正如我们已经知道的那样,这些条件的结果将是TrueFalse。因此,根据我们使用的布尔类型,条件语句将执行代码的某部分。我们在 Python 中使用if语句来执行条件语句。在写下if关键字后,我们将条件放在其旁边。条件可以是单个的,也可以是许多逻辑运算符的组合。我们用冒号结束if语句;随后的语句应该正确缩进。看看以下例子:

#filename: conditionals.py

if (True):
  #Do something

以下图表示了实现条件语句的布尔逻辑:

在使用 Python 时,请注意以下事项:

  • 冒号(::如果你想在 Python 中声明作用域,在其中可以编写多个语句,你需要使用冒号(:)来指定。大多数编程语言使用花括号({})来实现这一点,但是 Python 在定义作用域和块语句的范围时有些奇怪,比如函数、if语句、类和循环。然而,一旦你熟悉使用这个,你会觉得很有趣,并且能够在一秒钟内区分出用 Python 编写的代码和其他语言的代码。

  • 缩进空格):在我们用冒号定义作用域之后,我们可以进入其作用域。在其作用域内编写的任何后续语句都应以统一的空格开始,我们称之为缩进。你可以按下Tab键为每个语句提供统一的缩进。大多数初学者犯的错误都是由于不正确的缩进。如果你没有提供正确的缩进,你将会收到 Python 解释器的以下警告:

If语句评估逻辑语句。每当该语句为真时,将执行其缩进的语句;否则,将被跳过。你还可以添加pass关键字,告诉解释器不要执行缩进块内的任何内容,就像这个例子一样:

>>> if ( 4 == 4):
          pass
>>> #does not print anything

正如我们已经知道的,布尔语句将产生TrueFalse。如果条件为True,则if语句内的缩进代码将被执行,但如果条件为False,则else部分内的缩进代码将被执行。让我们看一个例子:

>>> number = 1
>>> if number > 0:
        print("Number is positive")
    else:
        print("Number is negative")
Number is positive
>>>

以下图表示了使用条件语句实现检查数字是正数还是负数的程序的流程图:

你可以看到,我们已经为TrueFalse逻辑创建了两个条件分支。根据布尔逻辑的结果,流程控制被转移到程序的两侧。因此,条件语句也被称为分支

尽管我们的代码能够执行具有两个分支的代码,但我们的代码中存在一些小差距。如果数字变量包含零,则既不是正数也不是负数。因此,我们必须为此条件添加一个条件。每当我们需要计算逻辑的两个以上分支时,我们可以进行链接条件。我们可以使用链接序列添加任意数量的条件。要使用任何其他编程语言执行链接条件,我们使用else if命令。Python 通过使用elif制作不同的命令。让我们看一个例子:

>>> number = input("Enter any number: ")
>>> number = int(number) #converting string to integer
>>> if number > 0:
        print("Number is Positive")
    elif number == 0:
        print("Number is Zero")
    else:
        print("Number is Negative")
Enter any number: 0
Number is Zero
>>>

我们可以在一个条件语句中放置任意数量的条件。我们称这些为嵌套条件。让我们来看一个例子:

>>> number = 10
>>> if number > 0:
        if number % 2 == 0:
                print("Number is positive and even")
        else:
                print("Number is positive and odd")      
Number is positive and even 

在上面的例子中,外部条件包含两个子分支条件,在第一个分支中,我们检查偶数。下一个默认条件是检查奇数。在这个例子中,我们使用一个简单的单一语句来制定条件,但是在嵌套条件中,条件可以通过逻辑运算符变得复杂,就像这个例子中一样:

>>> number = 4
>>> if number > 0:
        if number % 2 == 0 and number < 10:
                print("Number {} is small even & positive number".format(number))
Number 4 is small even & positive number

现在你知道如何使用多个条件语句做出决策,我们将看一下一个非常实用的主题,称为迭代。这使我们能够执行一系列指令。这将重复执行,直到达到某个条件为止。

迭代

假设你想写一个程序,你必须打印你的名字 100 次。到目前为止,我们所学到的规定,最简单的方法是使用打印语句 100 次。但是如果你想打印你的名字 10000 次呢?连续写 2/3 页的打印语句不是好的编程。在这种情况下,我们必须使用循环。循环将帮助我们在数据集上进行迭代,直到满足条件。在每次迭代中,执行代码的一部分,并且我们必须每次更新迭代变量。以下是一个迭代变量的示例:

>>> i = 0
>>> i = i + 1

我们使用增量和减量单位更新迭代变量。在这里,我们通过将1加到i的值来更新i的值。这被称为增量。您也可以从中减去 1,这被称为减量。每次我们在缩进循环中执行代码时,我们使用增量或减量语句更新迭代。

同样,有一种相对更简单和更快的实现增量和减量语句的方法。您可以使用以下语句执行多个操作:

  • +=将一个数字添加到变量中,并在其过程中更改变量。

  • -=将变量与一个值相减,并将新值设置为其结果变量。

  • *=将变量乘以一个值,并改变变量的结果。

  • /=将变量与值相除,并将结果放在结果变量上。

让我们看一个例子来看看它的效果:

>>> value = 4
>>> value += 5
>>> print(value)
9

增量和减量运算符的有效性可以通过循环看出,我们多次重复一组操作。让我们看看使用 for 和 while 循环的循环。我们将首先学习for循环。

for 循环

每当你想在数据集中循环,比如在一系列数字范围内,在某个文件中,或者在一些确定的单词集合中,我们使用for循环。它也被称为确定循环。除非你的项目桶中还有某个项目,否则它将迭代。for循环在桶的末尾终止。在这里,桶是一个代表项目列表的隐喻,比如数字列表、单词列表或序列,就像这个例子中:

>>> for i in range(10):
         print(i, " John Doe") #range(10) gives [0,1,2,3,4,5,6,7,8,9]
0  John Doe
1  John Doe
2  John Doe
3  John Doe
4  John Doe
5  John Doe
6  John Doe
7  John Doe
8  John Doe
9  John Doe

在上面的代码中,range()方法用于创建一个数字列表。range(10)提供了一个从09的数字列表。它被存储为[0,1,2,3,4,5,6,7,8,9]

在第一次迭代中,i的值变为0,它执行for循环块中的代码,并自动更改i的值为该列表的下一个元素,如下所示:

>>> for i in [6,7,8]:
         print(i)
6
7
8

您还可以在包含单词或文本的数据中进行循环。在我们循环列表中的每个单词时,迭代变量将包含一个单词作为值,就像这个例子中一样:

>>> for name in ['Tom','Harry','Ricky','Matt']:
             print(name)
Tom
Harry
Ricky
Matt

在上面的例子中,迭代变量是name变量,每次它在列表中迭代时,它都会获取它的值并将其存储在name中。因此,我们只能在for循环的主体中使用name变量。除了for循环中的迭代变量之外,不能使用其他变量。这在下面的代码中显示:

>>> person_names = ['Tom','Harry','Ricky','Matt']
>>> for name in person_names:
         print(person_names)
Traceback (most recent call last):
   File "<pyshell#26>", line 1, in <module>
     for name in person_name:
NameError: name 'person_name' is not defined

在上面的例子中,person_names是一种可以存储数组项的变量类型。这个变量被称为列表。我们将在下一章中介绍列表。这里,迭代变量是name,它是用for循环声明的。然而,在 for 循环的主体中,我们没有使用name变量。相反,我们使用了person_names,这导致了NameError。因此,迭代变量只能在for循环的主体中使用。

我们将要介绍的下一种循环类型是while循环,它将执行类似于for循环的操作,但有一些调整。while循环通常用于我们不关心循环终止点的情况。

while 循环

Python 中的另一种迭代形式可以使用while循环来执行。让我们回顾一下for循环的特点:它用于迭代有限序列的元素,无论是作为数字、单词或文件的列表。如果要使用for循环,必须有一个终止点。在使用for循环时,我们也不关心终止条件。当它达到项目或序列的末尾时,它就会终止。那么,如果我们想要根据自定义条件终止循环呢?在这种情况下,while循环是最合适的循环。我们可以制定一个自定义条件,在这个条件下,我们可以使用while循环来终止递归。

whilefor循环都将执行不断的循环。在每次迭代中,它们都将执行循环的主体。forwhile循环的主要区别在于,while循环必须在声明中声明更新语句和终止条件。首先,我们必须创建一个迭代变量,然后我们必须创建一个终止条件,以指定循环的停止点。在每次迭代中,我们必须更新迭代变量,就像这样:

>>> i = 0
>>> while (i < 10):
        print("John Doe")
        i = i + 1
John Doe
John Doe
John Doe
John Doe
John Doe
John Doe
John Doe
John Doe
John Doe
John Doe

在上面的例子中,我们创建了一个迭代变量i,并将其赋值为 0。之后,我们使用了while循环。为了使用这个循环,我们使用了while关键字,然后跟着它的终止条件。我们告诉解释器,我们希望运行这个循环,直到i小于 10。如果i等于或大于 10,我们希望终止这个循环。

之后,我们加上一个冒号来指定我们循环的范围。然后,我们为它添加了一个简单的打印语句,每次运行这个循环时都会执行。最后,我们添加了一个i = i + 1语句来指定更新条件。这将改变i的值为新值,并增加一。这很重要,这样我们就不会陷入无限循环。如果删除更新条件,循环将无限次运行,Python 终端将不会对用户的响应进行交互。创建无限循环的一种方法是使用没有终点的条件,就像这个例子中一样:

>>> while True:
         print("Infinite loop")

上面的循环是一个无限循环,因为while关键字没有终点或终止点。如果我们能够将True关键字更改为False,那么这个循环就会终止:

>>> condition = True
>>> while condition:
        print("This will run only one time")
        condition = False
This will run only one time

在下一节中,我们将学习循环模式,以便了解循环在底层是如何工作的。

循环模式

forwhile循环之间可能存在权衡,但在我们想要循环遍历已知元素列表或文件内容时,两者都能很好地工作。我们可以使用这些循环来排列或排序列表或文件中的元素。for循环不能无限次循环,但while循环可以使用永远不会发生的条件来实现。循环的主要目的是从特定文件或列表中获取项目,以便我们可以进一步处理它们。我们可以根据扫描数据集时的最小和最大值或重要和多余的值对这些项目进行排序。

循环模式的构造包含以下三个要点:

  • 制作一个迭代变量。可以有一个或多个。它们用于构造表示循环终止点的条件。

  • 在循环体内部进行一些计算,以便我们可以逐个操作循环获取的数据项。我们还可以在循环体内部更改迭代变量的值,这在while循环的情况下通常会这样做。

  • 寻找可能的基本条件,以便终止循环。否则,将导致无限循环。我们必须观察循环结束后的结果变量。

如果要演示循环模式的构造和工作范例,最好使用带有项目列表的循环。在下面的示例中,我们将编写一个程序,该程序将获取一个数字列表并检查列表中的最小数。

我们可以用两种方法做到这一点。Python 使得编程对于天真或专业人士都很容易。他们有各种实现相同逻辑的方法,但最常见的是使用 Python 的内置方法,如min()max()。它们分别从 Python 的数字列表中获取最小和最大的数字。

>>> numbers = [113,115,55,66,65,90]
>>> min(numbers)
55
>>>max(numbers)
115

编写程序的第二种方法是制定我们自己的逻辑。由于此列表中有许多项目,因此我们应立即决定使用循环,这意味着我们必须重复进行一些比较。因此,如果要重复执行任务,最好使用循环。现在,我们需要决定使用for还是while循环。在这里最好使用for循环,因为for循环适用于有限列表。每次迭代迭代变量时,它将包含列表中的一个元素,以便我们可以重复与前一个元素进行比较。一开始,我们没有任何东西可以作为最小数。因此,我们必须创建一个变量,其中将包含None值。这意味着我们没有任何值。第一次迭代后,我们将其值分配给列表的第一个元素。让我们看看它是如何工作的:

>>> smallest_number = None
>>> for item in [113,115,55,66,65,90]:
            if smallest_number is None or item < smallest_number:
                     smallest_number = item
>>> print("Smallest:", smallest_number)
Smallest: 55

让我们把前面的代码分解成以下几个部分:

  • 在第一条语句中,smallest_number = NoneNone赋值给比较变量。我们使用None而不是其他任何数字,以便在比较时不会漏掉任何数字。

  • 在第二条语句中,我们将item作为迭代变量,它将读取一个数字列表。在每次迭代中,它将存储该列表中的元素。在第一次迭代中,item 的值为 113。在第二次迭代中,item 的值为 115;在第三次迭代中,值为 55;依此类推。

  • 我们现在在for循环的循环体内部,需要构建一个比较语句。首先,我们需要检查最小数是否为None,以确保我们从基础开始。之后,我们将检查列表中的当前项目是否小于smallest_number。第一次迭代的第二个条件为 False,但第一个条件,即smallest_numberNone,即为 True,这意味着我们将进入条件体。我们将smallest_value赋值为列表的第一个项目,即 113。

  • 在第二次迭代中,项目是 115。我们进入for循环并检查 115 是否小于smallest_numbe的值,即 113。这是False,因此它不会进入条件的主体;相反,它跳到第三次迭代。

  • 在第三次迭代中,项目是 55。我们将检查条件,即检查项目的值(即 55)是否小于smallest_number的值,即 113。条件(55 <113)为True,因此将smallest_number变量的值更改为 55。for循环将迭代直到该列表的最后一个数字。它将在每次迭代中使用相同的比较操作,以给出最小值,即 55。

通过仅更改比较运算符,我们可以编写一个程序,该程序将打印列表中的最大数字。我们可以使用item > largest_number语句而不是使用item < smallest_number语句来获取最大数字,如下所示:

>>> largest_number = None
>>> for item in [113,115,55,66,65,90]:
            if largest_number is None or item > largest_number:
                    largest_number = item
>>> print("Largest: ",largest_number)
Largest: 115

在下一节中,我们将看看如何使用两个不同的语句break 和 continue,以改变或跳过迭代的顺序。

中断和继续语句

在编写程序时,有时您希望跳过语句的执行或强制停止迭代。这些操作由continuebreak语句处理。它们可以在多种用例中发挥作用,例如当您希望使程序对列表的元素进行排序或在满足if条件时中断循环。continue语句用于跳过程序的执行。我们在循环的主体内使用这些语句。我们可以使用这些语句将元素从列表中排序出来。即使我们使用这两个语句,由于break将停止循环,使continue语句无效,因此我们不能在单个循环中使用这两个语句。我们可以在条件语句中使用这些语句。每当满足条件时,我们要么中断要么跳过迭代。让我们编写一个可以对列表的元素进行排序的程序:

>>> items = [1,5,7,8,"Free","spam",False,89,90,11,"Python"]
>>> refined_items = []
>>> for item in items:
        if type(item) != int:
                continue
        else:
                 refined_items.append(item)
>>> print(refined_items)
[1,5,7,8,89,90,11]

在上述代码中,我们通过在输出列表中保留整数来完善列表的元素。其他数据值,如字符串和布尔值,都被删除。首先,我们循环整个列表,并在每次迭代时将元素存储在项目变量中。我们使用type()方法检查存储在项目变量中的数据的类型。如果存储在项目中的值的类型不是整数,则使用continue语句来推断如果不是整数,则不执行任何操作。如果项目的类型是布尔值或字符串,则使用continue语句跳过该迭代。但是,如果存储在项目变量中的值的类型是整数,则将执行代码的 else 部分中的语句。我们将取该整数项目并将其添加到名为refined_items的新输出列表中。在检查每个元素之后,我们打印精炼的列表,这是数字的最终集合。

如果使用break语句,直到元素 8 之前的情况将保持不变。但是,我们的输出将受限于[1,5,7,8],而不是打印其他元素[89,90,11]。这是因为break语句将在将元素 8 附加到列表后停止迭代。因此,我们可以得出结论,每当 Python 解释器触发break语句时,循环将终止:

>>> items = [1,5,7,8,"Free","spam",False,89,90,11,"Python"]
>>> refined_items = []
>>> for item in items:
        if type(item) != int:
                 break
        else:
                 refined_items.append(item)
>>> print(refined_items)
[1,5,7,8]

我们知道,在实际环境中部署程序时,这些程序将习惯于我们的代码无法处理的不同情况。在这种情况下,我们的程序将终止,这对用户或游戏玩家会产生负面影响。因此,我们必须以这样的方式编写代码,使我们的代码可以适用于任何情况,即使遇到意外错误或异常。编程中的这种强大技术称为异常处理,这是我们将在下一节中介绍的内容。

使用 try 和 except 处理异常

在上一章中,我们创建了一个简单的井字棋游戏。我们在该章节的末尾讨论了一些修改。由于代码的不足,无法处理用户输入除整数以外的情况,因此建议进行一些修改。如果我们的用户将字符串输入为游戏的位置变量,会发生什么?将抛出以下异常:

在上述截图中,我们可以看到我们的代码无法处理用户输入的字符串。只有当我们输入整数时,我们的代码才能正常运行。如果用户错误地输入任何其他数据值,程序将崩溃。本主题的目的是处理这种类型的错误。我们有两种类型的错误:语法错误和异常错误。以下代码显示了两种类型的示例:

>>> print("Hey! it's me")))
SyntaxError: invalid syntax

每当您输入错误的语句时,它都会抛出一个错误消息,即语法错误。在这里,我们使用了比正常情况多两个括号来包围打印语句,这是不正确的。因此,Python 解析器会抛出语法错误。删除这两个额外的括号以消除语法错误:

>>> a = 34
>>> a / 0
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    a / 0
ZeroDivisionError: division by zero

现在,Python 解析器抛出了一个异常错误。即使您的 Python 语法是正确的,也会发生这种错误。这可能是数学错误或逻辑错误。在数学上,您不能将任何数字除以零。这会导致一个无限大,这在 Python 中没有定义。因此,我们遇到了一个异常。有不同类型的异常。如果您想知道您遇到的异常的名称,请在收到异常后检查代码的最后一条语句。在我们的错误消息中,最后一条语句说ZeroDivisionError。因此,我们遇到了一个ZeroDivisionError异常。如果您遇到这些异常中的任何一个,那么您的代码很可能已经崩溃。因此,我们的井字棋游戏也崩溃了,因为它无法处理除整数以外的输入数据。

现在,我们的主要目标是使我们的代码可靠,即使我们的代码遇到异常,也不会崩溃,而是向用户提供友好的消息。在上面的示例中,我们可以发送用户一条消息,说您不能将任何数字除以零,而不是终止程序。这个过程称为异常处理。这些都是在 Python 中的tryexcept块中完成的。

如果您对代码是否出错不确定,您应该始终使用tryexcept块。您的主要代码,可能会遇到异常,应该放在try块内。然后,如果它遇到异常,Python 解析器应该执行except块内的代码。以下示例应该让您更清楚:

>>> a = 34
 #INSIDE TRY BLOCK: put code that can give you error or exception
>>> try:
       print(a/0) #this will give you exception
    except:
       print("You cannot divide any number by Zero. It is Illegal!") #message to user

You cannot divide any number by Zero. It is Illegal!

上述代码显示了处理这些错误有多么容易。您将主要代码放在try块内,如果遇到异常,主要代码将不会被执行。相反,将执行except块内的代码,这种情况下是一个用户友好的消息。您还可以使用pass,以便在不向用户提供消息的情况下终止程序。

使用 except 关键字,您还可以显式传递异常的名称。但是,请确保您知道要遇到的正确异常名称。在这种情况下,我们知道我们将遇到 ZeroDivisionError,因此我们可以在 except 块中写入异常名称,如下所示:

>>> a = 34
 #INSIDE TRY BLOCK: put code that can give you error or exception
>>> try:
        print(a/0) #this will give you exception
    except ZeroDivisionError:
        pass

现在,让我们看看如何利用我们迄今为止学到的一切知识来完善我们的井字棋游戏。我们将使用条件、循环和异常处理来修改我们在上一章中编写的代码。

为我们的井字棋游戏制作游戏控制器

在上一章中,我们构建了一个简单的井字棋游戏。由于在本章中我们已经学习了条件和循环,现在我们能够对我们的游戏进行一些改进。我们将对我们的游戏进行以下更改:

  • 我们必须使游戏成为多人游戏,这意味着我们必须对程序进行修改,以便两名玩家可以玩我们的游戏。我们可以制作可以切换玩家的条件。

  • 当我们谈到异常处理时,我们看到我们的游戏无法处理用户输入的字符串数据。我们可以使用 tryexcept 块来处理该异常。

  • 我们无法确定我们在上一章中编写的代码的获胜者。现在我们已经了解了 if 条件,我们可以想出一些逻辑来检查玩家是否获胜。

我们将通过集思广益的方式开始我们的游戏开发过程,以收集有关游戏的一些关键信息。

集思广益和信息收集

我们的代码缺乏的一个重要特性是可读性。在上一章的游戏代码中,我们没有一个合适的方法来跟踪游戏板的位置。我们的代码可以做的第一件事是制作一个 choices 列表,其中包含玩家可以做出的所有选择。在这里,选择是用于井字棋板位置的。在井字棋游戏中,玩家可以在 0 到 8 之间选择数字,如果它们没有被其他玩家占据的话,这些数字就是占位符。

我们在代码中必须添加的第二件事是我们如何切换轮到谁了。由于我们只有两名玩家,我们可以让玩家 1 先行动并进行游戏的第一步。因此,制作逻辑会更容易。我们将制作一个布尔变量,并将其值从 True 更改为 False,以便我们可以制作一个条件来改变玩家的回合,如下所示:

  • playerOne = True 确保是玩家 1 的回合。

  • playerOne = False 将允许玩家 2 在我们的游戏板上行动。

我们必须遵循井字棋游戏的规则,以便根据玩家在游戏板上占据的位置来确定任何玩家是否获胜。如果玩家 X 或 O 占据了整行、整列或对角线的井字棋板,那么该玩家将被视为获胜者。这在下面的截图中有所体现:

修改模型

我们在上一章中编写的程序只能让一个玩家玩游戏。由于井字棋是一款多人游戏,应该对程序进行修改,以便多名玩家可以玩这个游戏。为此,我们必须做两件事:

  • 跟踪棋盘上的空位。

  • 制作一个条件来切换玩家的回合。

对于这两种修改,我们必须创建一个变量,可以跟踪游戏板上的每个空位和占用位置:

#code is written in same previous file

choices = []
for pos in range(0,9):
      choices.append(str(pos+1))

如果您使用 >>> print(choices) 变量,这将导致一个值列表:['1', '2', '3', '4', '5', '6', '7', '8', '9']。它们是我们棋盘游戏的位置。

现在,如果您想打印棋盘的布局,您不能使用上一章的代码。我们将使用 choices 变量,而不是使用 game_board 变量。这与上一个示例中的工作方式相同。我们将在每一行之间添加一行短划线:

#board layout
print('\n')
print('|' + choices[0] + '|' + choices[1] + '|' + choices[2] + '|')
print('----------')
print('|' + choices[3] + '|' + choices[4] + '|' + choices[5] + '|')
print('----------')
print('|' + choices[6] + '|' + choices[7] + '|' + choices[8] + '|')

#output
''' 
|1|2|3|
----------
|4|5|6|
----------
|7|8|9|

'''

我们游戏中的主要问题可能出现在用户输入非数字的情况下。例如,如果玩家输入字符串,游戏将以异常终止,我们不希望发生这种情况。正如您可能记得的那样,我们使用异常处理来避免这种情况。我们将在下一节中将其添加到我们的游戏中。

处理游戏的异常

让我们运行到目前为止我们制作的游戏,并在输入字段中输入一个字符串而不是整数值。你将得到以下异常:

我们不希望我们的程序在用户与我们的游戏交互时出现错误时崩溃。相反,我们可以发送给他们一个用户友好的消息,说这不是有效的,只按整数键。让我们通过使用trycatch块来处理这种类型的异常,如下所示:

while True:
    print('\n')
    print('|' + choices[0] + '|' + choices[1] + '|' + choices[2] + '|')
    print('----------')
    print('|' + choices[3] + '|' + choices[4] + '|' + choices[5] + '|')
    print('----------')
    print('|' + choices[6] + '|' + choices[7] + '|' + choices[8] + '|')
    #above code is to print board layouts

    try:
        choice = int(input("> ").strip())
    except:
        print("Please enter only valid fields from board (0-8)")
        continue

当我们运行我们的程序时,我们将得到以下输出:

在这里,我们将 Python 字符串值输入到输入字段中。而不是使程序崩溃,我们得到了一条消息,说请只输入来自棋盘的有效字段。这是使用tryexcept块处理异常的一种方便的方法。我们使用了前一章的相同主循环,它将无限次循环。在try块的主体中,我们保留可能引发异常的代码。strip()是一个字符串方法,将从用户的输入中删除空格。我们必须使用int方法对用户输入进行类型转换,以便将以字符串形式的输入数据转换为整数。如果我们遇到异常,我们将执行except块中的代码。continue关键字将使主循环再次从头开始运行,如果我们遇到异常。

必须添加到我们的井字游戏中的主要功能是多人游戏,这样两个玩家可以轮流玩同一场游戏。这个切换功能将在下一节中添加。

切换玩家的轮次

使用 Python 编写程序让两个玩家玩游戏很容易,你只需要创建一个布尔变量来表示当前玩家是谁。然后,根据布尔的两个值,TrueFalse,我们可以改变谁在玩游戏。但是,如果你想添加超过两个玩家,这个想法就行不通了。我们将使用以下布尔值:

  • Is_Current_One = True:当前玩家是玩家 1 或 X。

  • Is_Current_One = False:当前玩家是玩家 2 或 O。

这在以下代码中显示:

#creating Boolean variable
Is_Current_One = True #default player is player X

#first move is done by player X
while True:
    #put code of board layouts here
    if Is_Current_One:
        print("Player X")
    else:
        print("Player O")

    #put try and except block here
    #---------------------------------------------
    #code to put either X or O on position selected by user
    if Is_Current_One:
        choices[choice-1] = 'X'
    else:
        choices[choice-1] = 'O'
    #code to toggle between True and False
    Is_Current_One = not Is_Current_One

让我们将前面的代码分成几个部分,以便更好地理解它:

  • 我们有一个主循环,它将无限次运行,直到触发break语句。我们已经学会了break关键字将终止我们的循环。在主循环的主体中,我们打印出是玩家 X 还是 O 轮到了,以使玩家意识到轮到他们了。

  • 我们创建了一个名为Is_Current_One的布尔变量,它被赋予了一个值True。这意味着第一个移动的玩家将是玩家X。如果我们将这个变量设为 False,那么第一个移动的默认玩家将是玩家O

  • 在主循环内,我们创建了一个条件来检查玩家X或玩家O是否已将XO放置在棋盘布局上。choices[]变量反映了棋盘的位置。choice是用户的输入,我们将其减去 1,因为我们的 choices 变量是一个列表类型。我们知道列表索引从索引 0 开始,但我们已经输入了用户输入从 1 到 9。因此,我们将choice输入变量减去 1 以适应这个列表变量。

  • >>> Is_Current_One = not Is_Current_One语句将在玩家之间切换。正如我们之前提到的,如果Is_Current_OneTrue,那么玩家就是X,现在,我们还制定了一个条件,这样我们就可以在下一次迭代中将 True 改为 False,这样玩家O就可以进行下一步。

让我们通过运行我们的脚本文件来看看我们现在在做什么。您将在 shell 中看到以下结果打印出来:

现在,我们已经创建了我们的游戏,它可以接受用户的输入,并将其放在井字游戏板上。我们已经制定了一些逻辑来改变轮到谁了。我们还能够使用trycatch块处理游戏中可能出现的异常。

我们一直在快速进展,但我们的游戏还不完整。我们还没有制定任何逻辑,如果玩家占据一行、一列或三个对角线单元,他们就会成为赢家。我们将在下一节中完成这一点。

使玩家成为赢家

井字游戏是一个很容易制作的游戏,但建立这个游戏的主要目的是涵盖 Python 的几乎所有核心编程范式,比如变量、数字、模型、内置方法、循环、分支和异常处理。现在,我们的游戏已经足够好,可以供两个玩家玩,但多人游戏最终只能有一个赢家。因此,我们必须制定全新的逻辑,以奖励玩家如果他们赢了。我们需要涵盖三种用例,如下:

  • 如果井字游戏板的整行被一个玩家占据,那个玩家将成为赢家。

  • 如果棋盘的整列被一个玩家占据,那个玩家将成为赢家。

  • 如果棋盘的整个对角线被一个玩家占据,那个玩家将成为赢家。

让我们打印我们的游戏板布局,以及它们的位置,这样我们就可以在制定前述条件时跟踪棋盘的所有位置:

| 1 | 2 | 3 |
 ----------
| 4 | 5 | 6 |
 ----------
| 7 | 8 | 9 |

由于我们必须循环遍历从 1 到 9 的所有这些位置,我们需要使用for循环。由于我们有一个有限的数字列表,所以使用for循环很容易。我们必须制定两个条件来检查玩家是否占据了整行或整列。在处理行和列之后,我们将独立检查对角线条件:

  • 对于行:如果任何用户占据[1,2,3],[4,5,6],[7,8,9],那个特定的玩家将被视为赢家。

  • 对于列:如果任何用户占据[1,4,7],[2,5,8],[3,6,9],那个特定的玩家将被视为赢家。

然而,choice变量中的位置范围是从 0 到 8,即['0','1','2','3','4','5','6','7','8'],因此索引 0 表示棋盘的第一个位置,索引 1 表示棋盘的第二个位置,依此类推。

我们一直在使用一个while True语句作为我们的主循环。让我们修改一下,这样我们的代码将一直运行,直到有一个玩家成为赢家。我们将运行我们的主循环,直到won=False。如果我们得到了游戏的赢家,我们将改变won变量的值为True,这样主循环就会结束:

won = False #at first we don't have any winner
while not won:
    #code from previous topics
    #logic to make any player winner:
    for pos_x in range(0,3):
        pos_y = pos_x * 3 

        #for row condition:
        if (choices[pos_y] == choices[(pos_y + 1)]) and (choices[pos_y] 
           == choices[(pos_y + 2)]):
            #code to change won to True
            won = True #main loop will break

        #column condition:
        if (choices[pos_x] == choices[(pos_x + 3)]) and (choices[pos_x] 
          == choices[(pos_x + 6)]):
            won = True #main loop will break

在前面的代码中,我们制定了两个条件来检查玩家是否是赢家。我们制定了won变量来跟踪任何玩家是否赢了。如果任何玩家占据了整行或整列,我们将使won变量的值为 True,我们的循环将中断,这意味着我们将结束游戏。然而,我们还没有给用户一个关于成为赢家的消息。让我们编写一些代码,在检查行和列条件之后,告诉用户他们是赢家:

while not won:
    #code from previous topic
    for pos_x in range(0,3):
        pos_y = pos_x * 3

    #add condition for row and column here

#print who is winner
print("Player " + str(int(Is_Current_One + 1)) + " won, Congratulations!")    

我们用print方法编写的语句可能会引起混淆,因为str(int(Is_Current_One + 1))命令。这里,Is_Current_One要么是True,要么是False。然而,它也对应着 1 或 0,其中 1 是True,0 是False。如果玩家X是赢家,那么赢家是玩家 1,但轮到了玩家O,也就是玩家 2。

因此,我们必须将这个加到 1,这样当前玩家就确定为赢家,而不是下一个玩家。由于这是一个双人游戏,这是有道理的。让我们运行我们的代码来检查结果:

我们还没有完成 - 我们还必须添加一个条件来检查对角线是否也被玩家占据。让我们现在添加这个条件:

| 1 | 2 | 3 |
 ----------
| 4 | 5 | 6 |
 ----------
| 7 | 8 | 9 |

如果任何玩家占据位置[1,5,9]或[3,5,7],他们将被视为赢家。然而,我们的choices变量是一个包含所有位置的列表。它的索引从 0 开始,这意味着如果你想定位玩家 1 的位置,你应该传递choices[0],就像这样:

while not won:
    #code from previous topic
    for pos_x in range(0,3):
        pos_y = pos_x * 3
    #add condition for row and column here

    #diagonal condition here:
    if ((choices[0] == choices[4] and choices[0] == choices[8]) or
        (choices[2] == choices[4] and choices[4] == choices[6])):
         won = True

#print who is winner
print("Player " + str(int(Is_Current_One + 1)) + " won, Congratulations!")  

现在,让我们再次运行游戏,检查这个条件是否正常工作:

最后,我们完成了我们的游戏!我们能够在游戏中包含许多功能,比如异常处理、多人游戏模式和使玩家成为赢家的逻辑。然而,我们仍然需要通过添加用户定义的函数来完善这个游戏,以便我们可以打印我们的棋盘布局。这将遵循 DRY 原则,并将在下一章中介绍。

总结

本章为我们提供了一个关于 Python 编程范式核心主题的过山车之旅。我们涵盖了流程控制以及如何使用分支和循环来实现它们。我们学习了如何制定条件并将其传递给条件语句。然后,基于这些条件,我们能够在语句的执行之间进行切换。我们看到了如何使用 Python 的循环和分支来自动化事物。我们使用if关键字传递了多个可能的条件,并且根据布尔表达式的结果来控制程序的流程。我们还学习了不同类型的循环,并看到了如何使用它们来迭代一个项目或对象的列表。然后,我们看到了如何使用 try 和 except 块来处理异常。

最后,我们通过结合本章学到的不同范式,使我们的井字棋游戏比以往任何时候都更具可玩性。我们添加了tryexcept块,以便捕获和正确处理任何异常。我们还添加了多人游戏模式和使玩家成为赢家的逻辑等功能。这使得游戏非常互动。最后,我们使用条件和循环制作了一个游戏控制器。然而,我们不会止步于此;在接下来的章节中将进行更多的修改。

下一章对我们来说将是改变生活的。到目前为止,我们只使用了 Python 的内置函数,比如min()max()input()。在下一章中,我们将看到如何制作我们自己的函数,并使用它们,以便我们可以使我们的游戏更易读和可重用。我们将涵盖列表、集合和字典等数据结构,以便我们知道如何管理和存储更复杂的数据集。不过,不要被所有这些陈述所压倒。你已经走了这么远,现在正处于成为熟练的 Python 程序员的边缘。在进入下一章之前,请确保你对我们迄今所学的所有主题都感到舒适。

posted @ 2024-04-18 10:59  绝不原创的飞龙  阅读(19)  评论(0编辑  收藏  举报