Arcade-游戏编程教程-全-

Arcade 游戏编程教程(全)

原文:Program Arcade Games

协议:CC BY-NC-SA 4.0

一、开始之前…

这一介绍性章节有两个部分:

  • 设置您的计算机来编写游戏。
  • 技术领域的工作和职业前景。

安装和启动 Python

要开始,需要安装两个程序:Python 和 Pygame。Python 是我们将用来编程的计算机语言,Pygame 是一个命令库,它将帮助编写游戏变得更容易。

Windows 安装

如果您使用的计算机已经安装了 Python 和 Pygame,您可以跳过这一步。但是如果你想在自己的 Windows 电脑上设置 Python 和 Pygame,也不用担心。这很容易。

Run the Python installer downloaded from http://ProgramArcadeGames.com/python-3.4.3.msi   Run the Pygame installer downloaded from http://ProgramArcadeGames.com/pygame-1.9.2a0.win32-py3.4.msi

一旦安装好所有的东西,通过选择集成开发环境(IDLE)启动 Python,如图所示。

A978-1-4842-1790-0_1_Figa_HTML.jpg

启动 Python

上面提供的文件来自 http://www.python.org/download/ 的 Python 下载页面,Pygame 文件最初来自 https://bitbucket.org/pygame/pygame/downloads

Note

Python 和 Pygame 有很多版本。获得正确的版本并让它们一起工作可能会很复杂。我推荐使用 ProgramArcadeGames.com 上的链接,而不是从 Python 和 Pygame 网站下载。

如果你必须使用与这里所列不同的 Python 版本,可以在这个网站找到匹配的 Pygame 版本: www.lfd.uci.edu/∼gohlke/pythonlibs/#pygame

Mac 安装

Mac 的安装有点复杂,但还不算太糟。以下是步骤。

Open up a terminal window. Click on “Finder” then “Applications” and then open “Utilities.”

A978-1-4842-1790-0_1_Figb_HTML.jpg

Starting a terminal window   Double-click on “Terminal.”

A978-1-4842-1790-0_1_Figc_HTML.jpg

Starting a terminal window   We can issue commands to the Mac in the old-school style by typing them rather than pointing and clicking. We are going to start by typing in a command you probably don’t have yet. This command is gcc. Type this and hit the Enter key. Your Mac will recognize that you don’t have this command and offer to install it for you. Go ahead and do this. (If instead it says error: no input files you already have gcc, so go on to the next step.)

A978-1-4842-1790-0_1_Figd_HTML.jpg

Starting a terminal window   Install XQuartz from: http://xquartz.macosforge.org .   Line by line, copy and paste the following items into your terminal window: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/ master/install)" sudo brew doctor brew update brew install python3 brew install sdl sdl_image sdl_mixer sdl_ttf portmidi mercurial   If you want support for MP3s and movies, you can try adding smpeg. I’ve found support for this to be kind of spotty, so my recommendation is to skip this and use Ogg Vorbis files instead. But if you’d like to try, use these commands: brew install --HEAD https://raw.github.com/Homebrew/homebrew- headonly/master/smpeg.rb   Now you have all the supporting libraries. Let’s finally install Pygame. Replace YourName with your account name. If you don’t know what your account name is, type ls /Users to see all the user accounts on your computer. cd /Users/YourName/Downloads hg clone https://bitbucket.org/pygame/pygame cd pygame cd src pip3 install /Users/YourName/Downloads/pygame

此时,Pygame 和 Python 应该已经在您的系统上启动并运行了。Python 没有提供编辑文件的方法,所以你需要下载一个 IDE,比如 Wing IDE ( http://wingware.com/downloads )或者 PyCharm ( https://www.jetbrains.com/pycharm/download/ ),或者其他编辑器。

Unix 安装

Unix 和类似 Unix 的发行版可能会附带一个 Pygame 包,或者能够轻松获得一个包。如果你想从源代码编译,这是我在 Linux Mint 上用过的( http://www.linuxmint.com/ ):

# Load required packages

sudo apt-get install mercurial libsdl1.2-dev

sudo apt-get install libasound2-doc libglib2.0-doc python3-dev

sudo apt-get install libsdl-ttf2.0-dev  libsdl-image1.2-dev

sudo apt-get install libsdl-mixer1.2-dev libportmidi-dev

sudo apt-get install libavformat-dev libswscale-dev

sudo apt-get install libfreetype6-dev

sudo apt-get install libsmpeg-dev

# Use mercurial to clone current code

hg clone https://bitbucket.org/pygame/pygame

# Build and install

cd pygame

sudo python3 setup.py

UNIX 平台上最大的风险是您的默认 Python 版本可能是 2.x 系列,并且该代码不能与本书中的代码示例一起工作。确保您已经并且正在使用 Python 3.x。

可选 Wing IDE

Python 附带了一个编辑器和一个开发代码的环境。不幸的是,它不是很好。使用 Python 的默认编辑器时,可能会遇到以下两个问题:

问题 1。当处理多个文件时,很难跟踪所有打开的文件。运行程序前很容易忘记保存文件。当这种情况发生时,程序用保存的旧代码而不是新代码运行。这就很混乱了。

问题 2。如果绘制图形的程序中出现错误,Python 程序将会崩溃并挂起。一旦程序崩溃,就很难关闭。描述崩溃原因的错误消息通常被隐藏起来,很难找到。见下图。

A978-1-4842-1790-0_1_Fige_HTML.jpg

Python 程序在空闲时挂起

Wing 编辑器通过为每个文件使用一个带选项卡的编辑器来解决问题 1。它还会提示在运行程序之前保存所有文件。在 Wing 调试器下运行的程序不会像问题 2 中描述的那样挂起;相反,编辑器会立即将用户带到导致错误的代码行。见下图。

A978-1-4842-1790-0_1_Figf_HTML.jpg

Python 程序挂在 Wing IDE 中

因此,尽管这是第三个需要安装的东西,我还是推荐使用 Wing 编辑器。有一个免费版本叫做 wingware.com/downloads/wingide-101/ 的 Wing IDE 101。

不需要商业版附带的那些花里胡哨的东西,但是它们很好。当你开始输入变量名时,这个程序通常会自动填充变量名来帮助你。如果你有额外的钱,并想节省时间,你可能想拿起商业版本。

在网站上的视频中,我要么使用默认的 Python 编辑器,要么使用 Wing 编辑器。也可以使用许多其他编辑器:

在一些开发者中,讨论“哪个是最好的编辑器?”类似于让一群人聚在一起讨论“哪个是最好的宗教?”。最好是挑自己喜欢的,然后和其他人回避这个话题。

查看文件扩展名

更改您的 windows 配置以显示文件扩展名是一个好主意。一个文件通常有一个类似于Book report.docx的名字,其中.docx告诉计算机它是一个微软 Word 兼容的文档。默认情况下,如果安装了处理扩展的程序,Windows 会隐藏.docx扩展名。如果您正在编程,文件名的这种隐藏部分可能会很烦人。

对于 Windows 7,要显示文件扩展名,请打开计算机的控制面板。找到“文件夹选项”的选项单击“查看”选项卡,然后取消选择“隐藏已知文件类型的扩展名”选项

对于 Windows 8,按 Windows-E 键打开文件浏览器。然后点击“查看”标签,并确保“文件扩展名”已被选中。

学习制作游戏并获得报酬

当你开始学习编程时,你可能很快会发现它看起来像工作。我们都知道我们宁愿翘班去魔兽世界或 Eve Online 或其他游戏中淘金,对吗?那么为什么要学习编程呢?一个人从中能得到什么?

A978-1-4842-1790-0_1_Figg_HTML.jpg

成袋的钱

学习如何制作游戏并获得报酬?好吧,我不会付钱给你,但是如果你学编程,有很多人会付钱给你。以下是如何获利:

Learn to program games.   Have fun making your own games.   Select a favorite job offer.   Profit.

听着,不是吗???在这个计划里!

好好想想。你可以玩游戏,但任何人都可以这样做。仔细想想,在电子游戏上表现出色真的不算是人生中的一大成就。或者你可以学习创造游戏。人们关心这个。

从本书中获得最大收益

伟大的篮球运动员练习。伟大的程序员也是如此。

想让你在这里的时间变得有价值吗?回答章节问题!不要跳过它们。他们是理解材料所必需的。

做练习!这就更重要了。仅仅通过阅读材料来学习和仅仅通过阅读一本书来成为一名专业的篮球运动员是一样有用的。

练习!你可能会看到其他不需要练习的人。这不公平。或者,你可能比其他人更聪明,他们开始做得比你好,因为他们努力了,而你没有。这也不公平。这就是生活。习惯吧。练习。

你在课堂上读这本书吗?太好了。你知道你可以节省时间,从网上复制答案和练习吗?你也可以给自己买一张健身房会员卡,让别人帮你健身。这也说得通。

说真的,你到底在想什么抄袭别人?如果你不想做这份工作,现在就停止阅读,开始填写麦当劳的申请表。

不做作业是学不到的。做阅读。做练习。

发送反馈

如果你发现书中有任何错误或遗漏,请发电子邮件给我。我希望这是最好的资源。

保罗·文森特·克雷文博士

计算机科学系系主任

美国爱荷华州印第安诺拉辛普森学院,邮编:50125

保罗·克拉文@simpson.edu

二、创建自定义计算器

使用 Python 可以做的最简单的事情之一就是将它用作一个奇特的计算器。等等,计算器不是游戏。为什么我们要谈论计算器?乏味的....

嘿,要计算物体掉落,子弹乱飞,高分,我们需要计算。此外,任何真正的极客都会认为计算器是一个玩具,而不是一个酷刑设备!让我们用计算器开始我们的游戏教育。别担心,我们会在第六章前开始制图。

一个简单的计算器程序可以用来向用户询问信息,然后计算一些无聊的事情,如抵押贷款,或者更令人兴奋的事情,如泥球抛向空中的轨迹。

下图显示了一个计算动能的示例程序,这是我们在游戏物理引擎中可能需要做的事情。

A978-1-4842-1790-0_2_Figa_HTML.jpg

用 Python 计算动能

作为一个程序来做这件事最大的好处是能够隐藏方程式的复杂性。用户需要做的只是提供信息,他或她就能以一种易于理解的格式得到结果。任何类似的定制计算器都可以在智能手机上运行,让人们可以在旅途中轻松完成计算。

印刷

打印文本

一个程序如何把东西打印到屏幕上?代码很简单。只需要一行代码:

print("Hello World.")

A978-1-4842-1790-0_2_Figb_HTML.jpg

这个程序将“Hello World”打印到屏幕上。继续前进,进入空闲提示,看看它是如何工作的。尝试打印其他单词和短语。计算机会很乐意打印出你喜欢的任何东西,不管是真是假。

其他计算机编程语言的“Hello World”程序是什么样子的?查看维基百科。他们保存了一套用多种不同的计算机编程语言编写的“Hello World”程序: http://en.wikipedia.org/wiki/Hello_world_program_examples

看到有多少种不同的计算机语言是很有趣的。你可以通过“Hello World”程序的简单程度来了解一门语言有多复杂。

记住,Python 中的打印命令很简单。用print就行了。在print命令之后是一组括号()。这些括号内是应该打印到屏幕上的内容。使用括号向函数传递信息是数学和计算机语言中的标准做法。

学数学的学生学习使用圆括号来计算像$ sin(\ theta)= cos(\ frac { \ pi } { 2 }-\ theta)\(这样的表达式。\)sin\(和\)cos$是函数。传递给这些函数的数据在括号内。在我们的例子中,不同的是传递的信息是文本。

请注意,要打印的文本周围有双引号。如果打印语句在文本周围有引号,计算机将按原样打印出来。例如,该程序将打印 2+3:

print("2 + 3")

打印表达式的结果

下一个程序在\(2+3\)附近没有引号,计算机将把它作为一个数学表达式来计算。它将打印 5 而不是 2+3。

print(2 + 3)

下面的代码将生成一个错误,因为计算机将尝试将“Hello World”作为一个数学表达式来计算,但这根本行不通:

print(Hello World)

上面的代码会打印出一个错误SyntaxError: invalid syntax,,这是计算机的说法,表示不知道“Hello”和“World”是什么意思。

另外,请记住,这是单引号:,这是双引号:"。如果我要求一个双引号,这是一个常见的错误,写"",实际上是一个双,双引号。

打印多个项目

一条print语句可以一次输出多项内容,每项内容用逗号分隔。例如,这段代码将打印出Your new score is 1040:

print("Your new score is", 1030 + 10)

下一行代码将打印出Your new score is 1030+10。这些数字没有加在一起,因为它们在引号内。引号内的任何内容,计算机都视为文本。计算机之外的任何东西都认为是数学语句或计算机代码。

print("Your new score is", "1030 + 10")

逗号在引号内还是引号外?

下一个代码示例根本不工作。这是因为引号和 1030+10 之间没有逗号分隔文本。起初,可能看起来有一个逗号,但逗号在引号内。分隔要打印的术语的逗号必须在引号之外。如果程序员想要打印一个逗号,那么它必须在引号内:

print("Your new score is," 1030 + 10)

下一个例子是可行的,因为有一个逗号分隔两个术语。它打印:

Your new score is, 1040

注意,只有一个逗号打印出来。引号外的逗号分隔术语,引号内的逗号打印出来。打印第一个逗号;第二个用于分隔术语。

print("Your new score is,", 1030 + 10)

转义码

如果用引号来告诉计算机你想打印的文本串的开始和结束,程序如何打印出一组双引号呢?例如:

print("I want to print a double quote " for some reason.")

A978-1-4842-1790-0_2_Figc_HTML.jpg

这个代码不起作用。计算机查看字符串中间的引号,并认为这是文本的结尾。然后它不知道如何处理命令for some reason,引号和字符串的结尾会让计算机更加困惑。

有必要告诉计算机,我们想把中间的双引号当作文本,而不是字符串结尾的引号。这很简单:只要在引号前加上一个反斜杠,告诉计算机它是字符串的一部分,而不是终止字符串的字符。例如:

print("I want to print a double quote \" for some reason.")

这两个字符\"的组合称为转义码。几乎每种语言都有。因为反斜杠被用作转义码的一部分,所以反斜杠本身必须被转义。例如,以下代码无法正常工作:

print("The file is stored in C:\new folder")

为什么?因为\n是转义码。要打印反斜杠,必须像这样对它进行转义:

print("The file is stored in C:\\new folder")

还有一些其他重要的转义码需要知道。下面是重要转义码的表格:

| 转义码 | 描述 | | --- | --- | | \' | 单引号 | | \" | 双引号 | | \t | 标签 | | \r | 回车(向左移动) | | \n | 换行(向下移动) |

什么是回车,什么是换行?试试这个例子:

print("This\nis\nmy\nsample.")

该命令的输出是:

This

is

my

sample.

\n是一个换行符号。它将光标移动到计算机将向下打印一行文本的位置。计算机将所有文本存储在一个很长的行中。由于放置了\n字符,它知道在不同的行上显示文本。

更复杂的是,不同的操作系统对行尾有不同的标准。

| 转义码 | 描述 | | --- | --- | | `\r\n` | CR+LF:微软视窗 | | `\n` | LF:基于 UNIX 的系统,以及更新的 MAC | | `\r` | CR:基于 Mac 的旧系统 |

通常你的文本编辑器会帮你处理这个问题。Microsoft Notepad 则不然,在 Notepad 中打开的 UNIX 文件看起来很糟糕,因为行尾根本不显示,或者显示为黑框。每个程序员都应该在他们的电脑上安装一个好的文本编辑器。我推荐 Sublime ( www.sublimetext.com/ )或者 Notepad++ ( http://notepad-plus-plus.org/ )。

评论

评论很重要(即使电脑忽略了)!

有时代码需要向阅读它的人做一些额外的解释。为此,我们向代码中添加注释。注释是给阅读代码的人看的,而不是给计算机看的。

创建评论有两种方法。第一种是使用#符号。计算机将忽略 Python 程序中出现在#之后的任何文本。例如:

# This is a comment, it begins with a # sign

# and the computer will ignore it.

print("This is not a comment, the computer will")

print("run this and print it out.")

如果一个程序在引号之间有一个#符号,它不会被当作一个注释。程序员可以通过在一行代码前面放一个#符号来禁用它。也可以在行尾添加注释。

print("A # sign between quotes is not a comment.")

# print("This is a comment, even if it is computer code.")

print("Hi") # This is an end-of-line comment

可以在一行中使用三个单引号来分隔注释,从而注释掉多行代码。

print("Hi")

’’’

This is

a

multi

line

comment. Nothing

Will run in between these quotes.

print("There")

’’’

print("Done")

大多数专业的 Python 程序员只会对叫做 docstrings 的东西使用这种类型的多行注释。Docstrings 允许文档与代码一起编写,然后自动提取到打印文档、网站和集成开发环境(ide)中。对于一般的注释,#标签效果最好。

即使你是唯一一个阅读你写的代码的人,注释也可以帮助节省时间。添加“处理外星人炸弹”的注释可以让你快速记住那段代码做了什么,而不必阅读和破译它。

赋值运算符

我们如何在游戏中存储分数?或者跟踪敌人的健康状况?我们需要做的是赋值操作符。

运算符是类似于+-的符号。赋值运算符是=符号。它将一个值存储到一个变量中,供以后使用。下面的代码将把 10 赋给变量x,然后打印存储在 x 中的值。

看看下面的例子。

# Create a variable x

# Store the value 10 into it.

x = 10

# This prints the value stored in x.

print(x)

# This prints the letter x, but not the value in x

print("x")

# This prints "x= 10"

print("x=", x)

变量在引号之外,而不是在里面。

Note

上面的清单还展示了打印内部引号x和外部引号x的区别。如果一个x在引号内,那么计算机打印x。如果x在引号之外,计算机将打印x的值。对于学习编程的人来说,对“引号内还是引号外”这个问题感到困惑是很常见的。

A978-1-4842-1790-0_2_Figd_HTML.jpg

赋值语句(使用=操作符的一行代码)不同于你在数学中学到的代数等式。不要认为它们是一样的。赋值运算符的左侧必须正好有一个变量。那里可能没有别的东西。

等号/赋值运算符的右边是一个表达式。表达式是计算结果为值的任何东西。检查下面的代码。

x = x + 1

上面的代码显然不可能是代数等式。但是它对计算机是有效的,因为它是一个赋值语句。数学方程不同于赋值语句,即使它们有变量、数字和等号。

语句上面的代码获取当前值x,加 1,并将结果存储回x

扩展我们的示例,下面的语句将打印数字 6。

x = 5

x = x + 1

print(x)

语句按顺序运行。计算机不会向前看。在下面的代码中,计算机将在第 2 行打印出 5,然后第 4 行打印出 6。这是因为在第 2 行,给 x 加 1 的代码还没有运行。

x = 5

print(x) # Prints 5

x = x + 1

print(x) # Prints 6

下一个语句是有效的,将运行,但它是无意义的。计算机会给 x 加 1,但结果不会被存储或打印出来。

x + 1

下面的代码将打印 5 而不是 6,因为程序员忘记将x + 1的结果存储回变量x中。

x = 5

x + 1

print(x)

下面的语句无效,因为等号的左边不仅仅是一个变量:

x + 1 = x

Python 还有其他类型的赋值操作符。它们允许程序员很容易地修改变量。例如:

x += 1

上面的语句相当于编写下面的代码:

x = x + 1

还有用于加法、减法、乘法和除法的赋值运算符。

变量

变量以小写字母开头。

变量应该以小写字母开头。变量可以以大写字母或下划线开头,但这是特殊情况,不应该在正常情况下使用。在第一个小写字母之后,变量可以包括大写和小写字母,以及数字和下划线。变量不能包含空格。

变量区分大小写。如果程序员没有预料到,这可能会令人困惑。在下面的代码中,输出将是 6 而不是 5,因为有两个不同的变量,xX

x = 6

X = 5

print(x)

Python 的官方风格指南(是的,程序员真的写了一本关于风格的书)说 Python 中的多字变量名应该用下划线隔开。比如用hair_style,不用hairStyle。就我个人而言,我并不太关心这个规则,因为我们引入的下一种语言 Java 有完全相反的风格规则。我曾经尝试在这样的章节中教授 Java 风格的规则,但是后来我开始收到 Python 爱好者的恐吓信。这些人来到我的网站,对我糟糕的风格感到震惊。

琼·里弗斯没有这些人,所以我放弃了,现在尝试使用适当的风格指南。

以下是一些可以使用和不可以使用的变量名示例:

| 合法变量名 | 非法变量名 | 合法,但不正当 | | --- | --- | --- | | `first_name` | `first name` | `FirstName` | | `distance` | `9ds` | `firstName` | | `ds9` | `%correct` | `X` |

只有在变量值永远不会改变的情况下,才允许使用像MAX_SPEED这样的大写变量名。不是可变的变量叫做常数。

经营者

对于更复杂的数学运算,可以使用常见的数学运算符。还有一些不常见的:

| 操作员 | 操作 | 示例方程 | 示例代码 | | --- | --- | --- | --- | | + | 添加 | 3 + 2 | a = 3 + 2 | | - | 减法 | 3 - 2$ | a = 3 - 2 | | * | 增加 | 3′2 | a = 3 * 2 | | / | 分开 | ![ $$ \frac{10}{2} $$ ](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_2_Chapter_TeX2GIF_IEq1.gif) | a = 10 / 2 | | // | 楼层划分 | 不适用的 | a = 10 // 3 | | ** | 力量 | 2 3 | a = 2 ** 3 | | % | 系数 | 不适用的 | a = 8 % 3 |

地板除法总是将答案四舍五入到最接近的整数。例如,11//2将是 5,而不是 5.5,99//100将等于 0。

在 Python 中,并置乘法不起作用。下面两行代码将不起作用:

# These do not work

x = 5y

x = 5(3/2)

有必要使用乘法运算符来使这些代码行工作:

# These do work

x = 5 * y

x = 5 * (3 / 2)

运算符间距

运算符前后可以有任意数量的空格,计算机可以很好地理解。例如,这三行中的每一行都是等价的:

x=5*(3/2)

x = 5 * (3 / 2)

x      =5     *(    3/   2)

Python 的官方风格指南说每个操作符前后都应该有一个空格。(你一直很想知道,对吧?好了,python 代码的官方风格指南叫做 PEP-8 ( http://www.python.org/dev/peps/pep-0008/ )。更多精彩请查阅)。在上面的三行代码中,最有风格的是第 2 行。

操作顺序

Python 将使用标准数学表达式中预期的相同运算顺序来计算表达式。例如,该等式不能正确计算平均值:

average = 90 + 86 + 71 + 100 + 98 / 5

完成的第一个操作是 98/5。计算机计算出:

 $$ 90+86+71+100+\frac{98}{5} $$ 而不是所期望的:

使用括号可以解决这个问题:

average = (90 + 86 + 71 + 100 + 98) / 5

触发函数

三角函数用于计算等式中的正弦和余弦。默认情况下,Python 不知道如何计算正弦和余弦,但是一旦导入了适当的库,它就可以了。单位为弧度。

# Import the math library

# This line is done only once, and at the very top

# of the program.

from math import *

# Calculate x using sine and cosine

x = sin(0) + cos(0)

自定义公式计算器

一个程序可以用 Python 计算一辆汽车用 10.5 加仑汽油行驶 294 英里的里程。

m = 294 / 10.5

print(m)

这个程序可以通过使用变量来改进。这允许在不修改等式的情况下在代码中容易地改变值。

m = 294

g = 10.5

m2 = m / g # This uses variables instead

print(m2)

好的变量名很重要。

这个节目本身其实很难理解。没有上下文,变量mg没有什么意义。通过使用适当命名的变量,可以使程序更容易理解:

miles_driven = 294

gallons_used = 10.5

mpg = miles_driven / gallons_used

print(mpg)

现在,即使一个非程序员也可能看着这个程序并对它的功能有一个很好的想法。另一个好的和不好的变量命名的例子:

# Hard to understand

ir = 0.12

b = 12123.34

i = ir * b

# Easy to understand

interest_rate = 0.12

account_balance = 12123.34

interest_amount = interest_rate * account_balance

在空闲编辑器中,可以编辑先前的行,而无需重新键入。将光标移动到该行并按回车键即可。它将被复制到当前行。

>>>提示符下输入 Python 代码很慢,一次只能输入一行。也不可能保存代码以便其他人可以运行它。谢天谢地,有一种更好的方法来输入 Python 代码。

可以使用脚本输入 Python 代码。脚本是一系列将被一次执行的 Python 代码行。要创建脚本,打开一个新窗口,如下图所示。

A978-1-4842-1790-0_2_Fige_HTML.jpg

输入脚本

您可能希望使用不同的程序来创建您的脚本,如 Wing IDE 或 PyCharm。这些程序比 Python 自带的空闲程序更容易、更强大。

进入计算汽油里程的 Python 程序,然后保存文件。将文件保存到闪存驱动器、网络驱动器或您选择的其他位置。Python 程序应该总是以.py结尾。见下图。

A978-1-4842-1790-0_2_Figf_HTML.jpg

保存脚本

点击运行菜单,选择运行模块,运行输入的程序。尝试将程序更新为行驶里程和使用加仑数的不同值。

小心,常见错误!

从这一点开始,几乎所有输入的代码都应该在脚本/模块中。不要在 IDLE >>>提示符下输入你的程序。在此键入的代码不会被保存。如果发生这种情况,就需要重新开始。这是新程序员非常容易犯的错误。

如果这个程序能够与用户交互并询问用户行驶的英里数和使用的加仑数,那么它将会更加有用。这可以通过input语句来完成。参见下面的代码:

# This code almost works

miles_driven = input("Enter miles driven:")

gallons_used = input("Enter gallons used:")

mpg = miles_driven / gallons_used

print("Miles per gallon:", mpg)

运行这个程序会向用户询问英里和加仑,但它会产生一个奇怪的错误,如下图所示。

A978-1-4842-1790-0_2_Figg_HTML.jpg

运行 MPG 程序时出错

这个错误的原因可以通过稍微修改程序来说明:

miles_driven = input("Enter miles driven:")

gallons_used = input("Enter gallons used:")

x = miles_driven + gallons_used

print("Sum of m + g:", x)

运行上面的程序会产生如下所示的输出。

A978-1-4842-1790-0_2_Figh_HTML.jpg

添加不正确

这个程序不会把这两个数字加在一起:它只是把一个数字放在另一个数字之后。这是因为程序不知道用户将输入数字。用户可以输入 Bob 和 Mary,将这两个变量加在一起就是 BobMary,这样更有意义。

为了告诉计算机这些是数字,有必要用一个int( )或一个float( )包围input函数。前者用于整数,后者用于浮点数。

最终工作程序:

# Sample Python/Pygame Programs

# Simpson College Computer Science

#http://programarcadegames.com/

#http://simpson.edu/computer-science/

# Explanation video:http://youtu.be/JK5ht5_m6Mk

# Calculate Miles Per Gallon

print("This program calculates mpg.")

# Get miles driven from the user

miles_driven = input("Enter miles driven:")

# Convert text entered to a

# floating point number

miles_driven = float(miles_driven)

# Get gallons used from the user

gallons_used = input("Enter gallons used:")

# Convert text entered to a

# floating point number

gallons_used = float(gallons_used)

# Calculate and print the answer

mpg = miles_driven / gallons_used

print("Miles per gallon:", mpg)

另一个例子,计算物体的动能:

# Sample Python/Pygame Programs

# Simpson College Computer Science

#http://programarcadegames.com/

#http://simpson.edu/computer-science/

# Calculate Kinetic Energy

print("This program calculates the kinetic energy of a moving object.")

m_string = input("Enter the object’s mass in kilograms: ")

m = float(m_string)

v_string = input("Enter the object’s speed in meters per second: ")

v = float(v_string)

e = 0.5 * m * v * v

print("The object has " + str(e) + " joules of energy.")

为了缩短程序,可以将input语句嵌套到float语句中。例如,这几行代码:

milesDriven = input("Enter miles driven:")

milesDriven = float(milesDriven)

执行与此行相同的操作:

milesDriven = float(input("Enter miles driven:"))

在这种情况下,input功能的输出被直接输入到float功能中。哪一个都行,选择哪个是程序员的喜好问题。然而,能够理解这两种形式是很重要的。

回顾

多项选择测验

What is the correct code to print out the words ’Hello World’ to the screen? print (Hello World)   print ’Hello World’   print {’Hello World’}   print Hello World   print [’Hello World’]   print ("Hello World")   print {’Hello World’}     What does this code output? a = 1 b = 3 print(a + b) A+B   a+b   "a+b"   4   1+3   Nothing, the code is invalid.     What does this code output? a = 1 b = 3 print("a+b") A+B   a+b   "a+b"   The values of a and b added together.   10   Nothing, the code is invalid.     What does this code output? print("The answer to 10+10 is," 10+10) The answer to 10+10 is 10+10   The answer to 10+10 is 20   The answer to 10+10 is10+10   The answer to 10+10 is20   The answer to 10+10 is,10+10   The answer to 10+10 is,20   Nothing, the code is invalid.     What does this code output? # print("Hello") Hello   "Hello"   Nothing, there is an error in the code.   Nothing, the code is commented out.     What does this code output? x = 10 print("x") "x"   x   10   Nothing     What does this code output? x = 10 x + 1 print(x) 10   11   "x"   x   Nothing     What does this code output? x = 10 + 6 / 2 x = x + 1 print(x) 8   9   13   14   Nothing     What does this code output? x = input("Enter a value:") print(x / 2) 10   0   The value the user entered.   The value the user entered divided by two.   An error because the value was not converted to a number.     What does this code output? print("Have a "great" day!") "Have a "great" day!"   Have a "great" day!   Have a great day!   Nothing, the double quotes make for a syntax error.     What does this code output? print("Save in c:\new folder") Save in c:\new folder   Save in c: ew folder   Save in c:ew folder   Nothing, the escape code makes for a syntax error.

简答工作表

Write a line of code that will print your name.   How do you enter a comment in a program?   What do the following lines of code output? ALSO: Why do they give a different answer? print(2 / 3) print(2 // 3)   Write a line of code that creates a variable called pi and sets it to an appropriate value.   Why does this code not work? A = 22 print(a)   All of the variable names below can be used. But which ONE of these is the better variable name to use? a A Area AREA area area_of_rectangle Area_Of_Rectangle   Which of these variables names are not allowed in Python? (More than one might be wrong. Also, this question is not asking about improper names, just names that aren’t allowed. Test them if you aren’t sure.) apple Apple APPLE Apple2 1Apple account number account_number account.number accountNumber account# pi PI fred Fred GreatBigVariable greatBigVariable great_big_variable great.big.variable 2x x2x total% #left   Why does this code not work? print(a) a = 45   Explain the mistake in this code: pi = float(3.14)   This program runs, but the code still could be better. Explain what is wrong with the code. radius = float(input("Radius:")) x = 3.14 pi = x area = pi  * radius ** 2 print(area)   Explain the mistake in the following code: x = 4 y = 5 a = ((x) * (y)) print(a)   Explain the mistake in the following code: x = 4 y = 5 a = 3(x + y) print(a)   Explain the mistake in the following code: radius = input(float("Enter the radius:"))   Do all these print the same value? Which one is better to use and why? print(2/3+4) print(2 / 3 + 4) print(   2 /    3+    4  )   What is a constant?   How are variable names for constants different than other variable names?   What is a single quote and what is a double quote? Give and label an example of both.   Write a Python program that will use escape codes to print a double quote and a new line using the Window’s standard. (Note: I’m asking for the Window’s standard here. Look it up out of Chapter 1.)   Can a Python program print text to the screen using single quotes instead of double quotes?   Why does this code not calculate the average? print(3 + 4 + 5 / 3)   What is an operator in Python?   What does the following program print out? x = 3 x + 1 print(x)   Correct the following code: user_name = input("Enter your name: )"   Correct the following code: value = int(input(print("Enter your age")))

锻炼

查看附录中本章附带的练习。

三、什么是计算机语言?

什么造就了计算机语言?为什么电脑会有?为什么有这么多不同的计算机语言?

做基本编程不需要理解这些问题的答案,就像开车不需要理解引擎如何工作一样。然而,要进步到一个更高的水平,它是。本章提供了一个简短的解释来帮助您开始。

编程简史

计算机是电子的,而且是数字化的。对计算机来说,一切都是用导线上没有电压或有电压来表示的。没有电压对电脑来说就是零,有电压就是一。如果没有多个 1 和 0 的组合,计算机实际上无法计算出比这更高的数。

在早期,开关被用来将 1 或 0 载入计算机内存。下图,由维基共享资源 ( http://en.wikipedia.org/wiki/File:Altair_Computer_Front_Panel.jpg )提供,展示了一台牛郎星 8800。前面板开关用于加载程序。灯光显示了产量。没有监视器。

A978-1-4842-1790-0_3_Figa_HTML.jpg

牛郎星 8800

每组开关代表一个数字。每一个数字都代表计算机要执行的数据或指令。这种只用 1 和 0 来表示数字的系统叫做二进制数字系统。这种类型的计算机语言被称为 1GL(第一代语言)。注意:没有一种语言叫做 1GL,它只是第一代语言的缩写。1GL 与机器的本地语言(机器语言)是一回事,其中数字代表程序的命令和数据。

二进制数通常以四个一组来表示。例如:

1010 0010 0011

数据和计算机指令都是以二进制存储的。机器语言是代表计算机解释的指令的二进制数。然而,并不是所有的二进制数据都是机器语言。文档、数据库和财务数字等数据也以二进制形式存储在计算机中。当然,这些数据不是由计算机运行的。

对通过开关输入程序的一个改进是十六进制代码的使用。大多数人使用的十进制数字是数字 0-9。十六进制使用数字 0–9 和 A–F 来表示一组四个交换机,或数字 0–15。请参见下表,了解二进制、十进制和十六进制之间的关系。

| 二进制的 | 小数 | 十六进制的 | | --- | --- | --- | | Zero | Zero | Zero | | one | one | one | | Ten | Two | Two | | Eleven | three | three | | One hundred | four | four | | One hundred and one | five | five | | One hundred and ten | six | six | | One hundred and eleven | seven | seven | | One thousand | eight | eight | | One thousand and one | nine | nine | | One thousand and ten | Ten | A | | One thousand and eleven | Eleven | B | | One thousand one hundred | Twelve | C | | One thousand one hundred and one | Thirteen | D | | One thousand one hundred and ten | Fourteen | E | | One thousand one hundred and eleven | Fifteen | F | | 1 0000 | Sixteen | Ten | | 1 0001 | Seventeen | Eleven |

为了使输入程序更容易,后来的计算机允许用户用汇编语言输入程序。每个命令都使用一个助记符,一个叫做编译器的程序会把助记符转换成代表命令的数字。汇编语言也被称为 2GL 语言,或第二代语言。

下图显示了一个示例汇编语言程序的一部分,也是由维基共享资源 ( http://en.wikipedia.org/wiki/File:Altair_Computer_Front_Panel.jpg )提供的

A978-1-4842-1790-0_3_Figb_HTML.jpg

示例汇编语言

虽然这是一个进步,但它仍然不太容易编程。下一代语言允许更高层次的抽象。第一批第三代语言(COBOL、FORTRAN 和 LISP)更容易理解和编程。

第二代和第三代语言使用一种叫做编译器的程序。编译器把用户输入的程序(称为源代码)转换成机器代码。然后程序员运行机器代码。原始源代码没有运行。

如果一个程序中有几段源代码,可以用一个叫做链接器的程序把它们链接成一个程序。链接器在编译器生成的机器码上运行,生成最终程序。这个最终的程序就是用户运行的,不需要原始的源代码。

A978-1-4842-1790-0_3_Figc_HTML.jpg

编译器和连接器

编译成机器语言的一个缺点是程序只适用于特定类型的机器。为 Windows 电脑编译的程序不能在苹果 Macintosh 电脑或 Linux 电脑上运行。

因为整个编译和链接步骤对新程序员来说可能很复杂,所以一些语言使用解释器来运行。这些程序查看源代码,并即时将其解释为机器语言指令。它还允许相同的程序在 Windows、Mac 和 Unix 计算机上运行,前提是每个平台都有可用的解释程序。

使用解释器的缺点是通过解释器操作比用机器的母语操作要慢。

A978-1-4842-1790-0_3_Figd_HTML.jpg

解释者

Python 是解释型语言的一个例子。用 Python 开发比 C 容易,但 Python 运行速度较慢,必须有 Python 解释器才能工作。

像 Java 这样的语言使用一个系统,在这个系统中,程序被编译成运行在 Java 虚拟机(JVM)上的机器代码,而不是实际的机器。另一种流行的语言是 C#,这是一种运行在虚拟执行系统(VES)虚拟机上的公共语言基础设施(CLI)语言。对这些的全面讨论超出了本书的范围,但是请随意阅读。

今天有许多不同的计算机语言。因为计算机执行如此多类型的任务,所以已经开发了专门用于这些任务的不同语言。像 C 这样的语言适合于操作系统和小型嵌入式计算机。像 PHP 这样的其他语言专门用于创建网页。Python 是一种通用语言,其特点是易于使用。

Tiobe 公司在其每月更新的索引中记录了各种编程语言的流行程度。这是一个好主意,看看这里,以及像骰子 ( http://www.dice.com/ ))这样的就业安置板,以了解什么语言是流行的。

令人欣慰的是,几乎所有的语言都有相同的共同点,一旦一种语言被学会,同样的理论将适用于其他语言。

对于有趣的计算历史,我推荐看罗伯特·X·克林格利的《书呆子的胜利》,这是一个关于计算起源的三集系列。这些电影非常有趣,你的整个家庭都会喜欢。如果你对阅读比对视频更感兴趣,我也推荐《偶然的帝国》这本书。

那些视频之后会发生什么?他们甚至没有报道互联网的诞生!要了解更多,请查看罗伯特·克瑞格利的视频系列《书呆子 2.0.1》。

回顾

多项选择测验

Which of these is the best example of a binary number 101101   82   3FA   GAF     Which of these is the best example of a decimal number? 101101   82   3FA   GAF     Which of these is the best example of a hexadecimal number? 101101   82   3FA   GAF     What is the decimal number equivalent to the binary number “100”? 1   2   3   4   8   None of the above     What is source code? Runs source code directly, without compiling   The machine code the computer runs   Converts source code to machine code   The program the developer types into the computer   Links machine code together into one big program     What is machine code? Runs source code directly, without compiling   Converts source code to machine code   The native code the computer runs   The program the developer types into the computer   Links machine code together into one big program     What is a compiler? The machine code the computer runs   Converts source code to machine code   The program the developer types into the computer   Links machine code together into one big program   Runs source code directly, without compiling     What is an interpreter? Converts source code to machine code   Runs source code directly, without compiling   The program the developer types into the computer   The machine code the computer runs   Links machine code together into one big program     What is a linker? Links machine code together into one big program   Converts source code to machine code   The program the developer types into the computer   The machine code the computer runs   Runs source code directly, without compiling     What is a first generation language (1GL)? Assembly language   Language like Python or C that has logical structures   Machine language   A language built for a specific purpose     What is a second generation language (2GL)? Language like Python or C that has logical structures   Assembly language   Machine language   A language built for a specific purpose     What is a third generation language (3GL)? A language built for a specific purpose   Assembly language   Machine language   Language like Python or C that has logical structures

简答工作表

Give an example of a binary number. (While a number such as 1 can be a binary, decimal, and hexadecimal number, try coming up with an example that better illustrates the differences between the different bases of numbers.)   Give an example of a decimal number.   Give an example of a hexadecimal number.   Convert the numbers 1, 10, 100, 1000, and 10000 from binary to decimal.   What is a compiler?   What is source code?   What is machine language? (Don’t just say binary. That’s not correct.)   What is a first generation language? (Don’t just say binary. That’s not correct.)   What is a second generation language?   What is a third generation language? (Explain, don’t just give one example.)   What is an interpreter and how does it differ from a compiler?   Search the Web and find some of the most popular programming languages. List the web site(s) you got the information from and what the languages are.   Look at the job boards and see what languages people are looking for. List the languages and the job board you looked at.   What is the difference between the syntax and semantics of a language?   Pick a piece of technology, other than a computer you use regularly. Briefly describe the hardware and software that run on it.

锻炼

查看附录中本章附带的练习。

四、问答游戏和if语句

我们如何判断一个玩家是否已经击败了高分?我们怎么知道他的生命是否已经耗尽?我们怎么知道她是否有打开锁着的门所需的钥匙?

我们需要的是if语句。if语句也称为条件语句。(当你想给每个人留下你有多聪明的印象时,你可以使用条件语句这个术语。)的if语句允许计算机做出决定。外面热吗?飞船已经到屏幕边缘了吗?从账户中取出的钱太多了吗?程序可以用if语句测试这些条件。

基本比较

这里有几个if语句的例子。第一部分设置了在if语句中使用的两个变量(ab)。然后两个if语句展示了如何比较变量,看是否一个大于另一个。

# Variables used in the example if statements

a = 4

b = 5

# Basic comparisons

if a < b:

print("a is less than b")

if a > b:

print("a is greater than b")

print("Done")

因为 a 小于 b,所以如果运行这段代码,第一条语句将会打印出来。如果变量 a 和 b 都等于 4,那么上面的两个if语句都不会打印出任何东西。数字 4 不大于 4,所以if语句会失败。

为了显示程序的流程,可以使用流程图。即使没有编程入门,大多数人也能理解流程图。看看你对下图的理解程度。

A978-1-4842-1790-0_4_Figa_HTML.jpg

流程图

这本书跳过了对流程图的深入研究,因为它很无聊。但是如果你想成为一名超级巨星程序员,请在 http://en.wikipedia.org/wiki/Flowchart 阅读更多信息。

前面的示例检查了大于或小于。相等的数字不会通过测试。要检查大于或等于a的值,以下示例显示了如何执行此操作:

if a <= b:

print("a is less than or equal to b")

if a >= b:

print("a is greater than or equal to b")

<=>=符号必须按顺序使用,不能有空格。比如=<不行,< =也不行。

在写出这些语句时,有些人喜欢使用≤符号。例如:

if a ≤ b:

这个≤符号实际上在程序中不起作用。再加上大多数人不知道如何在键盘上轻松打字。(以防你好奇,要键入它,按住“alt”键,同时在数字键盘上键入 243。)所以在写出代码的时候,要记住是<=而不是≤。

下一组代码检查两项是否相等。等于的运算符是==,不等于的运算符是!=。这是他们的行动。

# Equal

if a == b:

print("a is equal to b")

# Not equal

if a != b:

print("a and b are not equal")

学习何时使用=和==。

使用===的时候很容易混淆。如果你想知道它们是否相等,请使用==,如果你想赋值,请使用=

混合使用===运算符时最常见的两个错误如下所示:

# This is wrong

a == 1

# This is also wrong

if a = 1:

print("A is one")

停下来!请花点时间回头仔细研究最后两个代码示例。通过确保您了解何时使用===来节省时间。不要猜测。

刻痕

缩进很重要。只有当语句为真时,缩进的if语句下的每一行才会被执行:

if a == 1:

print("If a is one, this will print.")

print("So will this.")

print("And this.")

print("This will always print because it is not indented.")

缩进必须相同。这个代码不起作用。

if a == 1:

print("Indented two spaces.")

print("Indented four. This will generate an error.")

print("The computer will want you to make up your mind.")

一旦一个if语句结束,就不可能重新缩进再返回。必须再次进行测试。

if a == 1:

print("If a is one, this will print.")

print("So will this.")

print("This will always print because it is not indented.")

print("This will generate an error. Why it is indented?")

使用和/或

一个if语句可以通过用andor链接比较来检查多个条件。这些也被认为是运算符,就像+或- are 一样。

# And

if a < b and a < c:

print("a is less than b and c")

# Non-exclusive or

if a < b or a < c:

print("a is less than either b or c (or both)")

请重复一遍。

一个常见的错误是在根据多个条件检查变量时忽略变量。下面的代码不起作用,因为计算机不知道对照变量c检查什么。它不会假设对照a进行检查。

# This is not correct

if a < b or < c:

print("a is less than b and c")

布尔变量

Python 支持布尔变量。什么是布尔变量?布尔变量可以存储一个True或一个值False布尔代数是由乔治·布尔早在 1854 年开发的。要是他知道他的工作作为现代计算机逻辑的基础会变得多么重要就好了!

一个if语句需要一个表达式来计算TrueFalse。看起来奇怪的是,如果一个变量已经计算为TrueFalse,它实际上不需要做任何比较。

# Boolean data type. This is legal!

a = True

if a:

print("a is true")

我上学的时候,流行说一些虚假的话。等三秒钟,然后喊“不!”嗯,连你的电脑都认为这很无聊。如果您打算这样做,您必须从not操作符开始。下面的代码使用not在真和假之间翻转a的值。

# How to use the not function

if not(a):

print("a is false")

因为not是一个操作符而不是一个函数,圆括号是不必要的。这也是合法的:

# How to use the not function

if not a:

print("a is false")

也可以使用带有andor操作符的布尔变量。

a = True

b = False

if a and b:

print("a and b are both true")

谁知道真/假可能很难?

也可以将一个变量赋给比较结果。在下面的代码中,比较了变量ab。如果相等,c就是True;否则c将会是False

a = 3

b = 3

# This next line is strange-looking, but legal.

# c will be true or false, depending if

# a and b are equal.

c = a == b

# Prints value of c, in this case True

print(c)

零表示错误。其他都是真的。

可以创建一个条件不为真或假的if语句。这通常是不希望的,但是理解计算机在搜索问题时如何处理这些值是很重要的。下面的语句是合法的,将导致文本被打印出来,因为if语句中的值是非零的:

if 1:

print("1")

if "A":

print("A")

下面的代码不会打印出任何内容,因为 if 语句中的值为零,被视为 False。非零值被认为是真的。

if 0:

print("Zero")

在下面的代码中,第一个if语句似乎可以工作。问题是,即使变量a不等于b,它也总是触发为真。这是因为b本身被认为是真的。

a = "c"

if a == "B" or "b":

print("a is equal to b. Maybe.")

# This is a better way to do the if statement.

if a == "B" or a == "b":

print("a is equal to b.")

Else 和 Else If

下面的代码将从用户那里获取温度,如果温度高就打印出来。

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

print("Done")

如果程序员希望代码在不热的情况下执行,她可以使用else语句。注意在if语句中else是如何与i对齐的,以及它是如何跟随着一个冒号的,就像if语句一样。

if...else语句的情况下,一个代码块将总是被执行。如果语句评估为True,将执行第一个块;如果评估为False,将执行第二个块。

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

else:

print("It is not hot outside")

print("Done")

可以使用else...if语句将几个if语句链接在一起。Python 将其缩写为elif

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

elif temperature < 30:

print("It is cold outside")

else:

print("It is not hot outside")

print("Done")

在下面的代码中,即使用户键入 120 度,程序也会输出“外面很热”。为什么呢?代码如何修复?

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

elif temperature > 110:

print("Oh man, you could fry eggs on the pavement!")

elif temperature < 30:

print("It is cold outside")

else:

print("It is ok outside")

print("Done")

文本比较

可以使用if语句来检查文本。

user_name = input("What is your name? ")

if user_name == "Paul":

print("You have a nice name.")

else:

print("Your name is ok.")

只有当用户输入“Paul”时,前面的例子才会匹配。如果用户输入“保罗”或“保罗”,它将不起作用。

一个常见的错误是忘记了被比较字符串的引号。在下面的例子中,计算机会认为Paul是一个存储值的变量。它将标记一个错误,因为它不知道变量Paul中存储了什么。

user_name = input("What is your name? ")

if user_name == Paul: # This does not work because quotes are missing

print("You have a nice name.")

else:

print("Your name is ok.")

多种文本可能性

当将一个变量与多个可能的文本字符串进行比较时,记住比较必须包括该变量是很重要的。例如:

# This does not work! It will always be true

if user_name == "Paul" or "Mary":

相反,代码应该是:

# This does work

if user_name == "Paul" or user_name == "Mary":

这是因为任何非零值,计算机都认为是指True。所以对计算机来说"Mary"True是一回事,所以它会运行if语句中的代码。

不区分大小写的比较

如果程序需要匹配输入文本的大小写,最简单的方法就是将所有内容都转换成小写。这可以通过lower命令来完成。

学会麻木不仁。

下面的例子将获取用户输入的任何内容,将其转换为小写,然后进行比较。重要提示:不要将其与大写的字符串进行比较。如果用户输入被转换成小写字母,然后与大写字母进行比较,就不可能出现匹配。

user_name = input("What is your name? ")

if user_name.lower() == "paul":

print("You have a nice name.")

else:

print("Your name is ok.")

if 语句示例

下面的下一组示例代码贯穿了前面讨论的所有概念。

# Sample Python/Pygame Programs

# Simpson College Computer Science

#http://programarcadegames.com/

#http://simpson.edu/computer-science/

# Explanation video:http://youtu.be/pDpNSck2aXQ

# Variables used in the example if statements

a = 4

b = 5

c = 6

# Basic comparisons

if a < b:

print("a is less than b")

if a > b:

print("a is greater than than b")

if a <= b:

print("a is less than or equal to b")

if a >= b:

print("a is greater than or equal to b")

# NOTE: It is very easy to mix when to use == and =.

# Use == if you are asking if they are equal, use =

# if you are assigning a value.

if a == b:

print("a is equal to b")

# Not equal

if a != b:

print("a and b are not equal")

# And

if a < b and a < c:

print("a is less than b and c")

# Non-exclusive or

if a < b or a < c:

print("a is less than either a or b (or both)")

# Boolean data type. This is legal!

a = True

if a:

print("a is true")

if not a:

print("a is false")

a = True

b = False

if a and b:

print("a and b are both true")

a = 3

b = 3

c = a == b

print(c)

# These are also legal and will trigger as being true because

# the values are not zero:

if 1:

print("1")

if "A":

print("A")

# This will not trigger as true because it is zero.

if 0:

print("Zero")

# Comparing variables to multiple values.

# The first if statement appears to work, but it will always

# trigger as true even if the variable a is not equal to b.

# This is because "b" by itself is considered true.

a = "c"

if a == "B" or "b":

print("a is equal to b. Maybe.")

# This is the proper way to do the if statement.

if a == "B" or a == "b":

print("a is equal to b.")

# Example 1: If statement

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

print("Done")

# Example 2: Else statement

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

else:

print("It is not hot outside")

print("Done")

# Example 3: Else if statement

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

elif temperature < 30:

print("It is cold outside")

else:

print("It is not hot outside")

print("Done")

# Example 4: Ordering of statements

# Something with this is wrong. What?

temperature = int(input("What is the temperature in Fahrenheit? "))

if temperature > 90:

print("It is hot outside")

elif temperature > 110:

print("Oh man, you could fry eggs on the pavement!")

elif temperature < 30:

print("It is cold outside")

else:

print("It is ok outside")

print("Done")

# Comparisons using string/text

# Note, this example does not work when running under Eclipse

# because the input will contain an extra carriage return at the

# end. It works fine under IDLE.

userName = input("What is your name? ")

if userName == "Paul":

print("You have a nice name.")

else:

print("Your name is ok.")

回顾

多项选择测验

Which statement will check if a is less than b? if a less than b:   if a < b   if a > b   if a < b:   if (a < b)   if a >= b   if a <= b:     Which statement will check if a is equal to b? if a equals b:   if a = b   if a = b:   if a == b:   if a == b   if a === b   if a === b:     Which statement will check if a is less than or equal to b? if a < or = b:   if a <= b:   if a < = b:   if a >= b:   if a =< b:   if a < b or == b:   if a <== b:     Which statement will check if a is less b and less than c? if a < b and < c:   if a < b & c:   if a < b and a < c:   if a < b and c:     What will this code print? if 3 < 4:     print("A") else:     print("B")     print("C") A   A B   B   B C   A C   A B C   Nothing, the code won’t run.     What will this code print? if 3 < 4:     print("A") else:     print("B") print("C") A   A B   B   B C   A C   A B C   Nothing, the code won’t run.     What will this code print? a = True if a:     print("A") else:     print("B") print("C") A   A B   B   B C   A C   A B C   Nothing, the code won’t run.     What will this code print? a = True if not(a):     print("A") else:     print("B") print("C") A   A B   B   B C   A C   A B C   Nothing, the code won’t run.     What will this code print? if 4 < 4:     print("A") elif 3 < 4:     print("B") else:     print("C") print("D") A   A B   B   B D   A D   A C   Nothing, the code won’t run.

简答工作表

What is missing from this code? temperature = float(input("Temperature: ") if temperature > 90:     print("It is hot outside.") else:     print("It is not hot out.")   Write a Python program that will take in a number from the user and print if it is positive, negative, or zero. Use a proper if/elif/else chain; don’t just use three if statements.   Write a Python program that will take in a number from a user and print out Success if it is greater than -10 and less than 10, inclusive.   This runs, but there is something wrong. What is it? user_input = input("A cherry is a: ") print("A. Dessert topping") print("B. Desert topping") if user_input.upper() == "A":     print("Correct!") else:     print("Incorrect.")   There are two things wrong with this code that tests if x is set to a positive value. One prevents it from running, and the other is subtle. Make sure the if statement works no matter what x is set to. Identify both issues. x == 4 if x >= 0:     print("x is positive.") else:     print("x is not positive.")   What three things are wrong with the following code? x = input("Enter a number: ") if x = 3     print("You entered 3")   There are four things wrong with this code. Identify all four issues. answer = input("What is the name of Dr. Bunsen Honeydew’s assistant? ") if a = "Beaker":     print("Correct!")     else     print("Incorrect! It is Beaker.")   This program doesn’t work correctly. What is wrong? x = input("How are you today?") if x == "Happy" or "Glad":     print("That is good to hear!")   Look at the code below. Write you best guess here on what it will print. Next, run the code and see if you are correct. Clearly label your guess and the actual answer. Also, if this or any other example results in an error, make sure to state that an error occurred. While you don’t need to write an explanation, make sure you understand why the computer prints what it does. Don’t get caught flat-footed when you need to know later. x = 5 y = x == 6 z = x == 5 print("x=", x) print("y=", y) print("z=", z) if y:     print("Fizz") if z:     print("Buzz")   Look at the code below. Write you best guess on what it will print. Next, run the code and see if you are correct. x = 5 y = 10 z = 10 print(x < y) print(y < z) print(x == 5) print(not x == 5) print(x != 5) print(not x != 5) print(x == "5") print(5 == x + 0.00000000001) print(x == 5 and y == 10) print(x == 5 and y == 5) print(x == 5 or y == 5)   Look at the code below. Write you best guess on what it will print. Next, run the code and see if you are correct. print("3" == "3") print(" 3" == "3") print(3 < 4) print(3 < 10) print("3" < "4") print("3" < "10") print( (2 == 2) == "True" ) print( (2 == 2) == True ) print(3 < "3")   What things are wrong with this section of code? The programmer wants to set the money variable according to the initial occupation the user selects. print("Welcome to Oregon Trail!") print("A. Banker") print("B. Carpenter") print("C. Farmer") user_input = input("What is your occupation?") if user_input = A:     money = 100 else if user_input = B:     money = 70 else if user_input = C:     money = 50

锻炼

在附录中找到练习 3“创建一个测验”。

五、带有随机数和循环的猜谜游戏

开始学习图形之前的最后一步是学习如何循环一段代码。大多数游戏循环。他们一遍又一遍地重复同样的代码。例如,下面的数字猜谜游戏针对用户的每次猜测进行循环:

Hi! I’m thinking of a random number between 1 and 100.

--- Attempt 1

Guess what number I am thinking of: 50

Too high.

--- Attempt 2

Guess what number I am thinking of: 25

Too high.

--- Attempt 3

Guess what number I am thinking of: 17

Too high.

--- Attempt 4

Guess what number I am thinking of: 9

Too low.

--- Attempt 5

Guess what number I am thinking of: 14

Too high.

--- Attempt 6

Guess what number I am thinking of: 12

Too high.

--- Attempt 7

Guess what number I am thinking of: 10

Too low.

Aw, you ran out of tries. The number was 11.

等等,这跟图形和视频游戏有什么关系?很多。游戏显示的每一帧都是一次循环。你可能对游戏显示的每秒帧数(FPS)很熟悉。FPS 代表计算机每秒更新屏幕的次数。速率越高,游戏越流畅。(虽然超过 60 的 FPS 比大多数屏幕更新的速度都快,所以没有必要超过这个速度。)下图显示了游戏 Eve Online 和一个显示计算机每秒能够显示多少帧的图表。

A978-1-4842-1790-0_5_Figa_HTML.jpg

视频游戏中的 FPS

这些游戏中的循环就像下图中的流程图一样。尽管现代游戏很复杂,但这个循环的内部类似于我们在第二章所做的计算器程序。获取用户输入。执行计算。输出结果。在电子游戏中,我们试图每秒钟重复 60 次。

A978-1-4842-1790-0_5_Figb_HTML.jpg

游戏循环

甚至可以在其他循环中存在循环。真正的“循环循环”看看下图中的“绘制所有内容”框。这组代码循环遍历并绘制游戏中的每个对象。该循环位于绘制游戏每一帧的更大循环的内部,如下图所示。

A978-1-4842-1790-0_5_Figc_HTML.jpg

绘制所有循环

Python 中有两种主要类型的循环:for循环和while循环。如果你想重复一定的次数,使用for循环。如果你想重复直到发生什么事情(比如用户点击退出按钮),那么使用一个while循环。

例如,一个for循环可以用来打印所有的学生记录,因为计算机知道有多少学生。需要使用一个while循环来检查用户何时点击鼠标按钮,因为计算机不知道要等待多长时间。

对于循环

下面的for循环示例运行了五次print语句。它可以很容易地运行 100 或 1,000,000 次,只需将 5 改为所需的循环次数。请注意for循环与if语句的相似之处。两者都以冒号结尾,并且都使用缩进来指定语句影响哪些行。

for i in range(5):

print("I will not chew gum in class.")

I will not chew gum in class.

I will not chew gum in class.

I will not chew gum in class.

I will not chew gum in class.

I will not chew gum in class.

第 1 行的i是一个变量,记录程序循环了多少次。这是一个新变量,可以用任何合法的变量名命名。程序员经常使用i作为变量名,因为 I 是 increment 的缩写。这个变量有助于跟踪循环应该何时结束。

range函数控制循环中代码运行的次数。在这种情况下,五次。

下一个示例代码将打印五次“请”,以及“我可以去商场吗?”只有一次。“我能去商场吗?”未缩进,因此它不是for循环的一部分,并且在for循环完成之前不会打印。

for i in range(5):

print("Please,")

print("Can I go to the mall?")

Please,

Please,

Please,

Please,

Please,

Can I go to the mall?

下一个代码示例采用前面的示例并缩进第 3 行。这一更改将导致程序打印“请”和“我可以去商场吗?”五次。既然语句已经缩进了“我能去商场吗?”现在是for循环的一部分,将像单词“Please”一样重复五次。

for i in range(5):

print("Please,")

print("Can I go to the mall?")

Please,

Can I go to the mall?

Please,

Can I go to the mall?

Please,

Can I go to the mall?

Please,

Can I go to the mall?

Please,

Can I go to the mall?

下面的代码将打印数字 0 到 9。请注意,循环从 0 开始,不包括数字 10。人们很自然地认为range(10)应该包括 10,但是它刚好在 10 之下停止。

for i in range(10):

print(i)

0

1

2

3

4

5

6

7

8

9

一个程序不需要命名变量i;它可以被命名为其他名称。例如,如果一个程序员正在处理一个文本文件,她可能会使用line_number

如果一个程序员想从 1 到 10,而不是从 0 到 9,有几种方法可以做到。第一种方法是给range函数发送两个数字,而不是一个。第一个数字是起始值;第二个刚好超出结束值。

这确实需要一些练习来适应这个想法,即for循环将包括第一个数字,但不包括列出的第二个数字。下面的例子指定了(1,11)的范围,数字 1 到 10 被打印出来。包括起始数字 1,但不包括结束数字 11。

for i in range(1, 11):

print(i)

1

2

3

4

5

6

7

8

9

10

另一种打印数字 1 到 10 的方法是仍然使用range(10)并让变量i从 0 到 9。但是就在打印出变量之前,程序员给它加了一。这也适用于打印数字 1 到 10。两种方法都可以。

# Print the numbers 1 to 10.

for i in range(10):

print(i + 1)

用一以外的数字计数

如果程序需要按 2s 计数或使用其他增量,这很容易。就像以前一样有两种方法。最简单的方法是向 range 函数提供第三个数字,告诉它按 2s 计数。第二种方法是从 1 开始计数,但是将变量乘以 2。下面的代码示例显示了这两种方法。

# Two ways to print the even numbers 2 to 10

for i in range(2,12,2):

print(i)

for i in range(5):

print((i + 1) * 2)

2

4

6

8

10

2

4

6

8

10

也可以通过给距离函数一个负的步长来倒计数到零。在下面的例子中,从 10 开始,下降到 0,但不包括 0,并以-1 为增量。创建这些循环最困难的部分是不小心交换了开始和结束的数字。程序从较大的值开始,所以它先运行。正常的for循环从range函数中首先列出的最小值开始递增计数。

for i in range(10, 0, -1):

print(i)

10

9

8

7

6

5

4

3

2

1

如果程序需要迭代的数字没有形成一个简单的模式,就有可能从列表中取出数字。(对列表的全面讨论包含在第八章中。这只是你能做的预览。)

for i in [2,6,4,2,4,6,7,4]:

print(i)

这将打印:

2

6

4

2

4

6

7

4

嵌套循环

试着预测下面的代码会输出什么。然后输入代码,看是否正确。

# What does this print? Why?

for i in range(3):

print("a")

for j in range(3):

print("b")

下一个代码块与上面的代码块几乎相同。第二个for循环缩进了一个制表位,因此它现在嵌套在第一个for循环内。这极大地改变了代码的运行方式。试试看。

# What does this print? Why?

for i in range(3):

print("a")

for j in range(3):

print("b")

print("Done")

我不会告诉你代码是做什么的。去电脑前看看。

保持累计总数

处理循环的一个常见操作是保存运行总数。这种“累计”代码模式在本书中被大量使用。保持一个分数的运行总数,合计一个人的帐户交易,使用总数来找到平均值,等等。您可能希望将这个代码清单加入书签,因为我们会多次引用它。在下面的代码中,用户输入五个数字,代码对它们的值求和。

total = 0

for i in range(5):

new_number = int(input("Enter a number: " ))

total += new_number

print("The total is: ", total)

注意,第 1 行创建了变量total,并将其初始数量设置为零。很容易忘记需要创建变量并将其初始化为零。如果没有它,计算机会在碰到第 4 行时报错。它不知道如何将new_number加到total上,因为还没有给total赋值。

一个常见的错误是用itotal代替new_number。请记住,我们保存的是用户输入值的累计,而不是当前循环计数的累计。

说到当前循环计数,我们可以用循环计数值来解决一些数学运算。例如:

 $$ {\displaystyle {\sum}_{n=1}^{100}n} $$

如果你不熟悉这种公式,这只是一种奇特的表述方式:

s = 1 + 2 + 3 + 4 + 5...98 + 99 + 100

下面的代码将从 1 到 100 的所有数字相加。它演示了一个常见的模式,其中运行总数保存在一个循环中。这也使用一个单独的变量sum来跟踪运行总数。

# What is the value of sum?

sum = 0

for i in range(1, 101):

sum = sum + i

print(sum)

这里有一个不同的变体。它从用户那里获取五个数字,并计算用户输入零的次数:

total = 0

for i in range(5):

new_number = int(input( "Enter a number: "))

if new_number == 0:

total += 1

print("You entered a total of", total, "zeros")

理解嵌套的for循环和运行总数的程序员应该能够预测下面代码的输出。

# What is the value of a?

a = 0

for i in range(10):

a = a + 1

print(a)

# What is the value of a?

a = 0

for i in range(10):

a = a + 1

for j in range(10):

a = a + 1

print(a)

# What is the value of a?

a = 0

for i in range(10):

a = a + 1

for j in range(10):

a = a + 1

print(a)

这一部分不要看得太快。试一试,预测上面代码的输出。然后复制到一个 Python 程序里运行一下,看看自己是不是对的。如果你不是,找出原因。

循环示例

这个示例代码涵盖了常见的for循环,并展示了它们是如何工作的。

# Sample Python/Pygame Programs

#http://programarcadegames.com/

# Print ’Hi’ 10 times

for i in range(10):

print("Hi")

# Print ’Hello’ 5 times and ’There’ once

for i in range(5):

print("Hello")

print("There")

# Print ’Hello’ ’There’ 5 times

for i in range(5):

print("Hello")

print("There")

# Print the numbers 0 to 9

for i in range(10):

print(i)

# Two ways to print the numbers 1 to 10

for i in range(1, 11):

print(i)

for i in range(10):

print(i + 1)

# Two ways``to

for i in range(2, 12, 2):

print(i)

for i in range(5):

print((i + 1) * 2)

# Count down from 10 down to 1 (not zero)

for i in range(10, 0, -1):

print(i)

# Print numbers out of a list

for i in [2, 6, 4, 2, 4, 6, 7, 4]:

print(i)

# What does this print? Why?

for i in range(3):

print("a")

for j in range(3):

print("b")

# What is the value of a?

a = 0

for i in range(10):

a = a + 1

print(a)

# What is the value of a?

a = 0

for i in range(10):

a = a + 1

for j in range(10):

a = a + 1

print(a)

# What is the value of a?

a = 0

for i in range(10):

a = a + 1

for j in range(10):

a = a + 1

print(a)

# What is the value of sum?

sum = 0

for i``in

sum = sum + i

while 循环

当一个程序知道它需要重复一段代码一定的次数时,就会使用一个for循环。当一个程序需要循环直到一个特定的条件发生时,使用一个while循环。

奇怪的是,while循环可以用在任何使用for循环的地方。它可用于循环,直到增量变量达到某个值。如果一个while循环可以做任何事情,为什么还要有一个for循环呢?for循环更容易使用和编码。一个看起来像这样的for循环:

for i in range(10):

print(i)

…可以通过如下所示的while循环来完成:

i = 0

while i < 10:

print(i)

i = i + 1

while循环的第 1 行设置了一个“sentinel”变量,用于计算循环执行的次数。这在一个for循环中自动发生,减少了一行代码。第 2 行包含实际的while循环。while循环的格式与if语句非常相似。如果条件成立,循环中的代码将重复。第 4 行增加了增量值。在一个for循环中,这自动发生,消除了另一行代码。从代码中可以看出,for循环比while循环更紧凑,也更容易阅读。否则,程序会用一个while循环做所有的事情。

一个常见的错误是混淆了for循环和while循环。下面的代码显示了一个无法在for循环和while循环之间做出决定的程序员。

while range(10):

print(i)

不要将rangewhile循环一起使用!

range功能仅适用于for循环。不要与while回路一起使用!

使用增量运算符

增量运算符通常与while循环一起使用。可以简化代码:

i = i + 1

包含以下内容:

i += 1

while循环中,它看起来像是:

i = 0

while i < 10:

print(i)

i += 1

这也可以用减法和乘法来完成。例如:

i *= 2

与以下内容相同:

i = i * 2

看看你能不能想出这个会打印出什么:

i = 1

while i <= 2 ** 32:

print(i)

i *= 2

循环,直到用户想要退出

一个非常常见的操作是循环,直到用户执行退出请求:

quit = "n"

while quit == "n":

quit = input("Do you want to quit? ")

循环可能有几种退出方式。使用布尔变量来触发事件是一种处理方式。这里有一个例子:

done = False

while not done:

quit = input("Do you want to quit? ")

if quit == "y":

done = True

attack = input("Does your elf attack the dragon? ")

if attack == "y":

print("Bad choice, you died.")

done = True

尽管这并不完美,因为如果用户说她想退出,代码仍然会询问她是否想攻击龙。你怎么解决这个问题?

下面是一个使用while循环的例子,其中代码重复,直到值足够接近零:

value = 0

increment = 0.5

while value < 0.999:

value += increment

increment *= 0.5

print(value)

while 循环的常见问题

程序员想从 10 开始倒数。出了什么问题,如何解决?

i = 10

while i == 0:

print(i)

i -= 1

这个试图数到 10 的循环有什么问题?运行时会发生什么?应该怎么修?

i = 1

while i < 10:

print(i)

while 循环示例

这里有一个程序,涵盖了我们刚刚谈到的while循环的不同用法。

# Sample Python/Pygame Programs

#http://programarcadegames.com/

# A while loop can be used anywhere a for loop is used:

i = 0

while i < 10:

print(i)

i = i + 1

# This is the same as:

for i in range(10):

print(i)

# It is possible to short hand the code:

# i = i + 1

# With the following:

# i += 1

# This can be done with subtraction, and multiplication as well.

i = 0

while i < 10:

print(i)

i += 1

# What would this print?

i = 1

while i <= 2**32:

print(i)

i *= 2

# A very common operation is to loop until the user performs

# a request to quit

quit = "n"

while quit == "n":

quit = input("Do you want to quit? ")

# There may be several ways for a loop to quit. Using a boolean

# to trigger the event is a way of handling that.

done = False

while not done:

quit = input("Do you want to quit? ")

if quit == "y":

done = True

attack =``input

if attack == "y":

print("Bad choice, you died.")

done = True

value = 0

increment = 0.5

while value < 0.999:

value += increment

increment *= 0.5

print(value)

# -- Common problems with while loops --

# The programmer wants to count down from 10

# What is wrong and how to fix it?

i = 10

while i == 0:

print(i)

i -= 1

# What is wrong with this loop that tries

# to count to 10? What will happen when it is run?

i = 1

while i < 10:

print(i)

随机数

随机数在计算机科学中大量用于涉及游戏或模拟的程序。

randrange 函数

默认情况下,Python 不知道如何生成随机数。有必要让 Python 导入一个可以创建随机数的代码库。所以要使用随机数,首先应该出现在程序顶部的是一个import语句:

import random

就像 pygame 一样,重要的是不要创建与导入的文件同名的文件。创建一个名为random.py的文件将导致 Python 开始导入那个文件,而不是创建随机数的系统库。

之后,可以用randrange函数创建随机数。例如,这段代码创建从 0 到 49 的随机数。默认情况下,下限为 0。

my_number = random.randrange(50)

下一个代码示例生成 100 到 200 之间的随机数。就像range函数一样,第二个参数指定了一个不包含的上限。因此,如果您想要 200 以内的随机数,请指定 201。

my_number = random.randrange(100, 201)

如果你不想要一个数字,而是一个随机的项目呢?这需要一份清单。在第八章之前,我们不会详细介绍列表,但是为了给你一个从列表中随机选择一个项目的预览,请看下面:

my_list = ["rock", "paper", "scissors"]

random_index = random.randrange(3)

print(my_list[random_index])

random功能

所有先前的代码都生成整数。如果需要浮点数,程序员可以使用random功能。

下面的代码生成一个从 0 到 1 的随机数,例如 0.4355991106620656。

my_number = random.random()

通过一些简单的数学运算,这个数字是可以调整的。例如,下面的代码生成一个介于 10 和 15 之间的随机浮点数:

my_number = random.random() * 5 + 10

回顾

多项选择测验

What does this code print? for x in range(4):         print("Hello") The word Hello 3 times   The word Hello 4 times   The word Hello 5 times   It will print nothing   It will print Hello forever   Nothing, it won’t run     What does this code print? for y in range(4):         print("y") The numbers 0 to 3   The numbers 0 to 4   The numbers 1 to 3   The numbers 1 to 4   It will print “y” four times   Nothing, it won’t run     What does this code print? for y in range(4):         print(y) The numbers 0 to 3   The numbers 0 to 4   The numbers 1 to 3   The numbers 1 to 4   It will print “y” four times   Nothing, it won’t run     What does this code print? for y in range(1, 11):         print(y) The numbers 0 to 11   The numbers 1 to 11   The numbers 1 to 10   The numbers 0 to 10   Nothing, it won’t run     What does this code print? for y in range(2, 12, 2):         print(y + 1) The even numbers 2 to 12   The even numbers 2 to 10   The odd numbers 3 to 11   The odd numbers 3 to 13   Nothing, it won’t run     What does this code print? a=0 for i in range(10):     a += 1 print(a) 9   10   11   Nothing, it won’t run     What does this code print? a = 0 for i in range(10):     a += 1 for j in range(10):         a += 1 print(a) 10   20   18   100   Nothing, it won’t run     What does this code print? a = 0 for i in range(10):         for j in range(10):                 a += 1 print(a) 10   20   110   100   Nothing, it won’t run     What does this code print? a = 0 for i in range(10):         a += 1         for j in range(10):                 a += 1 print(a) 10   20   110   100   Nothing, it won’t run     When should a programmer use a for loop instead of a while loop? for loops are used to when there is a set number of loops   for loops are used to loop until a condition is true   while loops should always be used   for loops should always be used     What does this do? x = random.randrange(50) The number 50   A random integer 0 to 49 (inclusive)   A random integer 1 to 50 (inclusive)   A random integer 1 to 49 (inclusive)   A random integer 0 to 50 (inclusive)   A random integer 1 to 51 (inclusive)     What does this do? x = random.randrange(1, 50) A random integer 0 to 49 (inclusive)   A random integer 1 to 50 (inclusive)   A random integer 1 to 49 (inclusive)   A random integer 0 to 50 (inclusive)   A random integer 1 to 51 (inclusive)

  • 数字 50

What does this do? x = random.random() * 10 A random floating-point number from 0 to 10   A random integer from 0 to 10   A random integer from 0 to 9   A random integer from 0 to 1

简答工作表

注意:不要创建只循环一次的循环。这说不通。默认情况下,Python 会运行一次代码。避免这样的循环:

for i in range(1):

# Do something.

Write a Python program that will use a for loop to print your name 10 times, and then the word “Done” at the end.   Write a Python program that will use a for loop to print “Red” and then “Gold” 20 times. (Red Gold Red Gold Red Gold… all on separate lines. Don’t use \n.)   Write a Python program that will use a for loop to print the even numbers from 2 to 100, inclusive.   Write a Python program that will use a while loop to count from 10 down to, and including, 0. Then print the words “Blast off!” Remember, use a WHILE loop, don’t use a FOR loop.   There are three things wrong with this program. List each. print("This program takes three numbers and returns the sum.") total = 0 for i in range(3):      x = input("Enter a number: ")      total = total + i print("The total is:", x)   Write a program that prints a random integer from 1 to 10 (inclusive).   Write a program that prints a random floating-point number somewhere between 1 and 10 (inclusive). Do not make the mistake of generating a random number from 0 to 10 instead of 1 to 10.   Write a Python program that will:

  • 向用户询问七个数字
  • 打印这些数字的总和
  • 打印正条目的计数、等于零的条目数以及负条目数。使用一个ifelifelse链,而不仅仅是三个if语句。

Coin flip tosser:

  • 创建一个打印随机 0 或 1 的程序。
  • 打印头部或尾部,而不是 0 或 1。使用if语句来实现。不要从列表中选择,如章节所示。
  • 添加一个循环,使程序这样做 50 次。
  • 创建翻转的正面数量和反面数量的累计。

Write a program that plays rock, paper, scissors:

  • 创建一个随机打印 0、1 或 2 的程序。
  • 扩展程序,使其使用if语句随机打印石头、布或剪刀。不要从列表中选择,如章节所示。
  • 添加到程序中,这样它首先询问用户他们的选择。
  • (如果让他们输入 1、2 或 3,会更容易些。)
  • 添加一个条件语句来判断谁赢了。

锻炼

查看附录中本章附带的“骆驼”练习。

六、图形介绍

A978-1-4842-1790-0_6_Figa_HTML.jpg

现在你可以创建循环了,是时候继续学习如何创建图形了。本章涵盖:

  • 计算机如何处理 x,y 坐标。它不像你在数学课上学到的坐标系。
  • 如何指定颜色?有数百万种颜色可供选择,告诉计算机使用什么颜色并不像只说“红色”那么简单
  • 如何打开空白窗口进行绘图?每个艺术家都需要画布。
  • 如何画直线、矩形、椭圆和圆弧?

计算机坐标系统

笛卡尔坐标系,如下图所示( https://commons.wikimedia.org/wiki/File:Cartesian_coordinates_2D.svg ),是大多数人在绘制图形时习惯使用的系统。这是学校教的体系。计算机使用一个相似但有些不同的坐标系统。要理解它的不同之处,需要快速了解一下计算机的历史。

A978-1-4842-1790-0_6_Figb_HTML.jpg

笛卡尔坐标系

在 80 年代早期,大多数计算机系统是基于文本的,不支持图形。下一张图( https://en.wikipedia.org/wiki/File:Visicalc.png )显示了在 80 年代流行的苹果]电脑上运行的早期电子表格程序。当在屏幕上定位文本时,程序员从顶部开始,称之为第 1 行。屏幕继续向下显示 24 行,横向显示 40 个字符。

![A978-1-4842-1790-0_6_Figc_HTML.jpg 早期的苹果文本屏幕即使是纯文本,也可以通过使用键盘上的字符来制作基本的图形。看到下图中的这只小猫,仔细看看它是怎么画出来的。当制作这种艺术作品时,字符仍然从顶部的第一行开始定位。A978-1-4842-1790-0_6_Figd_HTML.jpg

文本屏幕

后来,字符集扩展到包括盒子和其他原始绘图形状。人物可以用不同的颜色来画。如图所示,图形变得更高级了。在网上搜索“ASCII 艺术”,可以找到更多的例子。

A978-1-4842-1790-0_6_Fige_HTML.jpg

Spaceware 文本屏幕

一旦计算机能够控制图形的单个像素,基于文本的坐标系统就被卡住了。

A978-1-4842-1790-0_6_Figf_HTML.jpg

计算机坐标系统

x 坐标的工作原理与笛卡尔坐标系相同。但是 y 坐标反了。与笛卡尔图形中的零 y 坐标位于图形底部不同,零 y 坐标位于计算机屏幕的顶部。随着 y 值的上升,计算机坐标位置在屏幕上下移,就像文本行而不是标准的笛卡尔图形。见上图。

另外,请注意,屏幕覆盖了右下象限,而笛卡尔坐标系通常聚焦于右上象限。可以在负坐标绘制项目,但它们将被绘制到屏幕之外。当形状的一部分不在屏幕上时,这很有用。计算机会计算出屏幕外是什么,程序员不需要太担心。

Pygame 库

为了使图形更容易处理,我们将使用“Pygame”Pygame 是一个其他人编写的代码库,它简化了:

  • 绘制图形形状
  • 显示位图图像
  • 有生命的
  • 与键盘、鼠标和游戏手柄互动
  • 播放声音
  • 检测物体碰撞的时间

Pygame 程序需要做的第一件事是加载和初始化 Pygame 库。每一个使用 Pygame 的程序都应该以下面几行开始:

# Import a library of functions called ’pygame’

import pygame

# Initialize the game engine

pygame.init()

如果您还没有安装 Pygame,那么在“开始之前”一节中可以找到安装 Pygame 的说明。如果你的电脑上没有安装 Pygame,当你试图运行import pygame时会得到一个错误。

重要提示:import pygame查找名为pygame的库文件。如果一个程序员创建了一个名为pygame.py的新程序,计算机将会导入那个文件!这将阻止任何 pygame 程序运行,直到那个pygame.py文件被删除。

彩色

接下来,我们需要添加定义程序颜色的变量。颜色由三种颜色定义:红色、绿色和蓝色。你听说过 RGB 显示器吗?这就是这个术语的由来。红绿蓝。对于旧的显示器,你可以坐得离显示器很近,分辨出单独的 RGB 颜色。至少在你妈妈告诉你不要坐得离电视太近之前。这对于今天的高分辨率显示器来说是很难做到的。

RGB 三元组的每个元素都是一个从 0 到 255 的数字。零表示没有颜色,255 表示显示器显示尽可能多的颜色。这些颜色以叠加的方式组合在一起,因此如果指定了所有三种颜色,监视器上的颜色将显示为白色。(这与墨水和颜料的工作原理不同。)

Python 中的列表用方括号或圆括号括起来。(第八章详细介绍了列表和两种类型的区别。)列表中的各个数字用逗号分隔。下面是一个创建变量并将它们设置为三个数列表的例子。稍后将使用这些列表来指定颜色。

# Define some colors

BLACK    = (   0,   0,   0)

WHITE    = ( 255, 255, 255)

GREEN    = (   0, 255,   0)

RED      = ( 255,   0,   0)

为什么这些变量都是大写的?记得从第二章回来,不变的变量叫做常数。我们不期望黑色的颜色会改变;它是一个常数。我们用全部大写字母来命名变量,以此来表示变量是常量。如果我们期望一种颜色发生变化,就像我们有sky_color在太阳下山时发生变化,那么这个变量应该全部是小写字母。

在空闲时使用交互式 shell,尝试定义这些变量并打印出来。如果上面的五种颜色不是你要找的颜色,你可以自己定义。要选择一种颜色,找到一个在线的“颜色选择器”,如下图所示。一个这样的拾色器在: http://www.colorpicker.com/

A978-1-4842-1790-0_6_Figg_HTML.jpg

颜色选择器

额外:一些颜色选择器以十六进制指定颜色。如果以 0x 开头,可以输入十六进制数。例如:

WHITE = (0xFF, 0xFF, 0xFF)

最终,程序在绘制圆弧时将需要使用π的值,因此这是我们在程序中定义包含π值的变量的好时机。(也可以从数学库中导入这个作为math.pi。)

PI = 3.141592653

打开窗户

到目前为止,我们开发的程序只能将文本打印到屏幕上。那些程序不像大多数现代程序那样打开任何窗口。打开窗口的代码并不复杂。下面是所需的代码,它创建一个宽度为 700 像素、高度为 500 的窗口:

size = (700, 500)

screen = pygame.display.set_mode(size)

为什么是set_mode?为什么不是open_window?原因是这个命令实际上可以做的不仅仅是打开一个窗口。它还可以创建以全屏模式运行的游戏。这将删除开始菜单,标题栏,并让游戏控制屏幕上的一切。因为这种模式使用起来稍微复杂一些,而且大多数人更喜欢窗口游戏,所以我们将跳过对全屏游戏的详细讨论。但是如果你想了解更多关于全屏游戏的信息,可以查看 pygame 的显示命令的文档。

还有,为什么是size = (700, 500)而不是size = 700, 500?同样的原因,我们把颜色定义放在括号里。Python 通常不能在一个变量中存储两个数字(高度和宽度)。唯一的方法是将数字存储为一个列表。列表需要圆括号或方括号。(从技术上讲,一组数字周围的括号更准确地称为元组或不可变列表。用方括号括起来的列表就叫做列表。一个有经验的 Python 开发人员可能不愿意将括号中的数字列表称为列表而不是元组。你也可以说size = 700, 500,它将默认为一个元组,但我更喜欢用括号。)列表在第八章的中有详细介绍。

要设置窗口的标题(显示在标题栏中),请使用以下代码行:

pygame.display.set_caption("Professor Craven’s Cool Game")

与用户互动

仅用目前编写的代码,程序就会创建一个窗口并立即挂起。用户不能与窗口交互,甚至不能关闭它。所有这些都需要编程。需要添加代码,以便程序在循环中等待,直到用户单击“退出”

这是程序中最复杂的部分,还不需要完全理解它。但是有必要对它的作用有个概念,所以花点时间研究一下,提出问题。

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

# --- Main event loop

for event in pygame.event.get(): # User did something

if event.type == pygame.QUIT: # If user clicked close

done = True # Flag that we are done so we exit this loop

# --- Game logic should go here

# --- Drawing code should go here

# First, clear the screen to white. Don’t put other drawing commands

# above this, or they will be erased with this command.

screen.fill(WHITE)

# --- Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# --- Limit to 60 frames per second

clock.tick(60)

最后,我们将添加代码来处理键盘和鼠标点击。该代码将位于第 9 行主事件循环注释的下方。用于确定子弹何时发射以及物体如何移动的代码将放在第 14 行游戏逻辑的注释下面。我们将在后面的章节中讨论这一点。要绘制的代码将放在下面,屏幕的第 20 行用白色填充。

事件处理循环

警惕!程序员遇到的最令人沮丧的问题之一是搞乱事件处理循环。这个“事件处理”代码处理所有的击键、鼠标按钮点击和其他几种类型的事件。例如,您的循环可能如下所示:

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.KEYDOWN:

print("User pressed a key.")

elif event.type == pygame.KEYUP:

print("User let go of a key.")

elif event.type == pygame.MOUSEBUTTONDOWN:

print("User pressed a mouse button")

事件(如按键)都在一个列表中。程序使用一个for循环来遍历每个事件。使用一系列的if语句,代码判断出发生了什么类型的事件,处理该事件的代码放入if语句中。

所有的if语句应该在一个for循环中一起运行。在复制和粘贴代码时,一个常见的错误是没有合并来自两个程序的循环,而是有两个事件循环。

# Here is one event loop

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.KEYDOWN:

print("User pressed a key.")

elif event.type == pygame.KEYUP:

print("User let go of a key.")

# Here the programmer has copied another event loop

# into the program. This is BAD. The events were already

# processed.

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.MOUSEBUTTONDOWN:

print("User pressed a mouse button")

第 2 行的for循环抓取了所有的用户事件。第 13 行的for循环不会抓取任何事件,因为它们已经在前一个循环中处理过了。

另一个典型问题是开始绘制,然后试图完成事件循环:

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.KEYDOWN:

print("User pressed a key.")

pygame.draw.``rect

# This is code that processes events. But it is not in the

# ’for’ loop that processes events. It will not act reliably.

if event.type == pygame.KEYUP:

print("User let go of a key.")

elif event.type == pygame.MOUSEBUTTONDOWN:

print("User pressed a mouse button")

这将导致程序忽略一些键盘和鼠标命令。为什么呢?for循环处理一个列表中的所有事件。因此,如果有两个键被点击,for循环将处理这两个键。在上面的例子中,if语句不在for循环中。如果有多个事件,if语句将只针对最后一个事件运行,而不是所有事件。

处理每一帧

游戏每一帧的基本逻辑和顺序:

  • 未完成时:
    • 对于每个事件(按键、鼠标点击等)。):
      • 使用一系列的if语句运行代码来处理每个事件。
  • 运行计算来确定物体移动到哪里,当物体碰撞时会发生什么,等等。
  • 清空屏幕。
  • 画出一切。

如果不把这些步骤混在一起,程序会更容易阅读和理解。不要做一些计算,一些绘图,一些更多的计算,一些更多的绘图。另外,看看这和第二章中的计算器是如何相似的。获取用户输入,运行计算,并输出答案。同样的模式也适用于此。

将图像绘制到屏幕上的代码发生在 while 循环中。时钟刻度设置为 10 时,窗口的内容将每秒绘制 10 次。如果它发生得太快,计算机就会变慢,因为它所有的时间都花在更新屏幕上了。如果它根本不在循环中,屏幕就不能正常重绘。如果绘图在循环之外,屏幕最初可能会显示图形,但如果窗口最小化,或者如果另一个窗口放在前面,图形将不会重新出现。

结束程序

现在,在空闲状态下运行这个 Pygame 程序时,点击一个窗口的“关闭”按钮仍然会导致程序崩溃。这是一个麻烦,因为它需要大量点击来关闭一个崩溃的程序。

问题是,即使循环已经退出,程序也没有告诉计算机关闭窗口。通过调用下面的命令,程序将关闭所有打开的窗口并根据需要退出。

pygame.quit()

清除屏幕

下面的代码清除了白色背景窗口中的所有内容。记住变量WHITE在前面被定义为三个 RGB 值的列表。

# Clear the screen and set the screen background

screen.fill(WHITE)

这应该在发出任何绘图命令之前完成。在程序绘制图形后清除屏幕会导致用户只看到一个空白屏幕。

第一次创建窗口时,它的背景是黑色的。清空屏幕仍然很重要,因为有几件事可能会阻止这个窗口开始清空。一个程序不应该假设它有一个空白的画布来绘制。

翻转屏幕

非常重要!画完后必须翻转显示屏。计算机不会在你绘制图形时显示它们,因为这会导致屏幕闪烁。这将等待显示屏幕,直到程序完成绘制。下面的命令将图形“翻转”到屏幕上。

不包含这个命令将意味着程序只是显示一个空白屏幕。翻转后的任何绘图代码都不会显示。

# Go ahead and``update

pygame.display.flip()

打开一个空白窗口

让我们把我们讨论过的所有内容纳入一个完整的程序。这段代码可以作为 Pygame 程序的基础模板。它会打开一个空白窗口,等待用户按下关闭按钮。

在网站上,如果你点击“示例”按钮,你可以选择“图形示例”,然后你会发现这个文件为 pygame_base_template.py。

"""

Pygame base template for opening a window

Sample Python/Pygame Programs

Simpson College Computer Science

http://programarcadegames.com/

http://simpson.edu/computer-science/

Explanation video:http://youtu.be/vRB_983kUMc

"""

import pygame

# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

GREEN = (0, 255, 0)

RED = (255, 0, 0)

pygame.init()

# Set the width and height of the screen [width, height]

size = (700, 500)

screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

# --- Main event loop

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# --- Game logic should go here

# --- Drawing code should go here

# First, clear the screen to white. Don’t put other drawing commands

# above this, or they will be erased with this command.

screen.fill(WHITE)

# ---``Go

pygame.display.flip()

# --- Limit to 60 frames per second

clock.tick(60)

# Close the window and quit.

# If you forget this line, the program will ’hang’

# on exit if running from IDLE.

pygame.quit()

图纸介绍

A978-1-4842-1790-0_6_Figh_HTML.jpg

这里有一个你可以画的东西的列表: http://www.pygame.org/docs/ref/draw.html

程序可以画出矩形、多边形、圆形、椭圆形、弧形和直线。我们还将介绍如何用图形显示文本。位图图形,如图像,包含在第十二章的中。如果您决定查看 pygame 参考,您可能会看到如下函数定义:

pygame.draw.rect(Surface, color, Rect, width=0): return Rect

混淆的一个常见原因是行中写着width=0的部分。这意味着如果你不提供宽度,它将默认为零。因此,这个函数调用:

pygame.draw.rect(screen, RED, [55, 50, 20, 25])

与此函数调用相同:

pygame.draw.rect(screen, RED, [55, 50, 20, 25], 0)

: return Rect告诉你这个函数返回一个矩形,就是传入的那个矩形。你可以忽略这部分。

尝试复制该行并将width = 0放在括号中是行不通的。

# This fails and the error the computer gives you is

# really hard to understand.

pygame.draw.rect(screen, RED, [55, 50, 20, 25], width=0)

画线

下面的代码示例显示了如何在屏幕上画线。它将在屏幕上绘制一条 5 像素宽的从(0,0)到(100,100)的绿线。记住GREEN是一个变量,它在前面被定义为三个 RGB 值的列表。

# Draw on the screen a green line from (0, 0) to (100, 100)

# that is 5 pixels wide.

pygame.draw.line(screen, GREEN, [0, 0], [100, 100], 5)

使用前面示例中的基本模板,并添加代码来画线。阅读注释,找出将代码放在哪里的确切位置。尝试用不同的粗细、颜色和位置绘制线条。画几条线。

绘制带有环和偏移的线

程序可以一遍又一遍地重复事情。下一个代码示例使用循环反复绘制一条线。程序可以使用这种技术绘制多条线,甚至画出整辆车。

将画线命令放入循环中会导致在屏幕上绘制多条线。但是有个问题。如果每条线都有相同的起点和终点坐标,那么每条线都将绘制在另一条线的顶部。看起来就像只画了一条线。

为了解决这个问题,有必要在每次循环中偏移坐标。所以第一次循环时,变量y_offset为零。下面代码中的线是从(0,10)画到(100,110)的。下一次通过循环y_offset增加 10。这将导致绘制的下一条线具有新的坐标(0,20)和(100,120)。每次循环都是这样,将每一行的坐标下移 10 个像素。

# Draw on the screen several lines from (0, 10) to (100, 110)

# 5 pixels wide using a while loop

y_offset = 0

while y_offset < 100:

pygame.draw.line(screen,RED,[0,10+y_offset],[100,110+y_offset],5)

y_offset = y_offset + 10

同样的代码可以用一个for循环更容易地完成:

# Draw on the screen several lines from (0,10) to (100,110)

# 5 pixels wide using a for loop

for y_offset in range(0, 100, 10):

pygame.draw.line(screen,RED,[0,10+y_offset],[100,110+y_offset],5)

运行此代码并尝试对偏移量使用不同的更改。尝试使用不同的值创建偏移。尝试不同的值,直到它的工作原理变得清晰。

例如,这是一个使用正弦和余弦创建更复杂的偏移集的循环,并生成下图所示的图像。

for i in range(200):

radians_x = i / 20

radians_y = i / 6

x = int(75 * math.sin(radians_x)) + 200

y = int(75 * math.cos(radians_y)) + 200

pygame.draw.line(screen, BLACK, [x,y], [x+5,y], 5)

A978-1-4842-1790-0_6_Figi_HTML.jpg

复杂偏移

可以在一个for循环中绘制多个元素,如下图所示,这个代码绘制了多个 X。

for x_offset in range(30, 300, 30):

pygame.draw.line(screen,BLACK,[x_offset,100],[x_offset-10,90],2)

pygame.draw.``line

A978-1-4842-1790-0_6_Figj_HTML.jpg

多个 X

画一个矩形

当绘制矩形时,计算机需要矩形左上角(原点)的坐标,以及高度和宽度。

该图显示了一个矩形(和一个椭圆,将在后面解释),其原点在(20,20),宽度为 250,高度为 100。当指定一个矩形时,计算机需要一个由这四个数字按(x,y,宽度,高度)顺序排列的列表。

A978-1-4842-1790-0_6_Figk_HTML.jpg

画一个椭圆

下一个代码示例绘制这个矩形。列表中的前两个数字将左上角定义为(20,20)。接下来的两个数字首先指定 250 像素的宽度,然后指定 100 像素的高度。

末尾的 2 指定 2 个像素的线宽。数字越大,矩形周围的线条越粗。如果该数字为 0,则矩形周围不会有边框。而是用指定的颜色填充。

# Draw a rectangle

pygame.draw.rect(screen,BLACK,[20,20,250,100],2)

画一个椭圆

椭圆的绘制就像矩形一样。指定矩形的边界,计算机在这些边界内绘制一个椭圆。

使用椭圆时最常见的错误是认为起点指定了椭圆的中心。现实中,起点什么都不画。它是包含椭圆的矩形的左上角。

回头看这幅图,你可以看到一个 250 像素宽,100 像素高的椭圆。包含它的 250x100 矩形的左上角位于(20,20)。请注意,在(20,20)处实际上没有绘制任何内容。将两者绘制在彼此之上,可以更容易地看到椭圆是如何指定的。

# Draw an ellipse, using a rectangle as the outside boundaries

pygame.draw.ellipse(screen, BLACK, [20,20,250,100], 2)

画弧线

如果一个程序只需要画椭圆的一部分怎么办?这可以通过arc命令来完成。该命令类似于ellipse命令,但它包括要绘制的弧的开始和结束角度。角度以弧度为单位。见下图。

A978-1-4842-1790-0_6_Figl_HTML.jpg

下面的代码示例绘制了四个圆弧,显示圆的四个不同象限。每个象限都用不同的颜色绘制,以便更容易看到弧段。这段代码的结果如上图所示。

# Draw an arc as part of an ellipse. Use radians to determine what

# angle to draw.

pygame.draw.arc(screen, GREEN, [100,100,250,200],  PI/2,     PI, 2)

pygame.draw.arc(screen, BLACK, [100,100,250,200],     0,   PI/2, 2)

pygame.draw.arc(screen, RED,   [100,100,250,200],3*PI/2,   2*PI, 2)

pygame.draw.arc(screen, BLUE,  [100,100,250,200],    PI, 3*PI/2, 2)

绘制多边形

下一行代码绘制一个多边形。三角形由(100,100) (0,200)和(200,200)三个点定义。可以根据需要列出任意多的点。请注意这些点是如何列出的。每个点是一个由两个数字组成的列表,这些点本身嵌套在另一个包含所有这些点的列表中。该代码绘制了下图中可以看到的内容。

A978-1-4842-1790-0_6_Figm_HTML.jpg

多边形

# This draws a triangle using the polygon command

pygame.draw.polygon(screen, BLACK, [[100,100], [0,200], [200,200]], 5)

绘图文本

文本稍微复杂一些。有三件事需要做。首先,程序创建一个变量来保存将要使用的字体的信息,比如字体和字体大小。

第二,程序创建文本的图像。一种想法是,该程序用所需的字母雕刻出一个“图章”,可以蘸上墨水印在纸上。

第三件要做的事情是程序告诉这个文本图像应该在屏幕上的什么地方被标记(或者“blit”)。

这里有一个例子:

# Select the``font

font = pygame.font.SysFont(’Calibri’, 25, True, False)

# Render the text. "True" means anti-aliased text.

# Black is the color. The variable BLACK was defined

# above as a list of [0, 0, 0]

# Note: This line creates an image of the letters,

# but does not put it on the screen yet.

text = font.render("My text", True, BLACK)

# Put the image of the text on the screen at 250x250

screen.blit(text, [250, 250])

想把分数打印到屏幕上?这就有点复杂了。这不起作用:

text = font.render("Score: ", score, True, BLACK)

为什么呢?一个程序不能像print语句一样只是给font.render添加额外的项。只能向命令发送一个字符串;因此,score 的实际值需要追加到“Score:”字符串中。但这也不管用:

text = font.render("Score: " + score, True, BLACK)

如果 score 是一个整型变量,计算机不知道如何把它加到一个字符串上。你,程序员,必须把乐谱转换成字符串。然后像这样把字符串加在一起:

text = font.render("Score: " + str(score), True, BLACK)

现在你知道如何打印分数了。如果你想打印一个定时器,那就需要打印格式,这将在后面的章节中讨论。查看timer.py示例的在线部分示例代码:

ProgramArcadeGames.com/python_examples/f.php?file=timer.py

完整的节目列表

这是本章讨论的程序的完整列表。该程序以及其他程序可以从以下网站下载:

ProgramArcadeGam es。com/ index。php?章节=示例 _ 代码

A978-1-4842-1790-0_6_Fign_HTML.jpg

示例程序的结果

"""

Simple graphics demo

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

# Import a``library

import pygame

# Initialize the game engine

pygame.init()

# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

BLUE = (0, 0, 255)

GREEN = (0, 255, 0)

RED = (255, 0, 0)

PI = 3.141592653

# Set the height and width of the screen

size = (400, 500)

screen = pygame.display.set_mode(size)

pygame.display.set_caption("Professor Craven’s Cool Game")

# Loop until the user clicks the close button.

done = False

clock = pygame.time.Clock()

# Loop as long as done == False

while not done:

for event in pygame.event.get():  # User did something

if event.type == pygame.QUIT:  # If user clicked close

done = True  # Flag that we are done so we exit this loop

# All drawing code happens after the for loop and but

# inside the main while not done loop.

# Clear the``screen

screen.fill(WHITE)

# Draw on the screen a line from (0,0) to (100,100)

# 5 pixels wide.

pygame.draw.line(screen, GREEN, [0, 0], [100, 100], 5)

# Draw on the screen several lines from (0,10) to (100,110)

# 5 pixels wide using a loop

for y_offset in range(0, 100, 10):

pygame.draw.line(screen, RED, [0, 10 + y_offset], [100, 110 + y_offset], 5)

# Draw a rectangle

pygame.draw.rect(screen, BLACK, [20, 20, 250, 100], 2)

# Draw an ellipse, using a rectangle as the outside boundaries

pygame.draw.ellipse(screen, BLACK, [20, 20, 250, 100], 2)

# Draw an arc as part of an ellipse.

# Use radians to determine what angle to draw.

pygame.draw.arc(screen, BLACK, [20, 220, 250, 200], 0, PI / 2, 2)

pygame.draw.arc(screen, GREEN, [20, 220, 250, 200], PI / 2, PI, 2)

pygame.draw.arc(screen, BLUE, [20, 220, 250, 200], PI, 3 * PI / 2, 2)

pygame.draw.arc(screen, RED, [20, 220, 250, 200], 3 * PI / 2, 2 * PI, 2)

# This draws a triangle using the polygon command

pygame.draw.polygon(screen, BLACK, [[100, 100], [0, 200], [200, 200]], 5)

# Select the font to use, size, bold, italics

font = pygame.font.SysFont(’Calibri’, 25, True, False)

# Render the text. "True" means anti-aliased text.

# Black is the color. This creates an image of the

# letters, but does not put it on the screen

text = font.render("My text", True, BLACK)

# Put the image of the text on the screen at 250x250

screen.blit(text, [250, 250])

# Go ahead and update the screen with what we’ve drawn.

# This MUST happen after all the other drawing commands.

pygame.display.flip()

#``This

# Leave this out and we will use all CPU we can.

clock.tick(60)

# Be IDLE friendly

pygame.quit()

回顾

多项选择测验

If a box is drawn starting at (x,y) coordinate (0,0), where will it be on the screen? Upper left   Lower left   Upper right   Lower right   Center   f.It won’t display   If the screen width and height are both 400 pixels, and a rectangle is drawn starting at (0,400), where will it display? Upper left   Lower left   Upper right   Lower right   Center   It won’t display     In computer graphics, as x and y coordinates increase in value, a point will move: Down and to the right   Up and to the right   Down and to the left   Up and to the left   Nowhere     What color would be defined by (0, 0, 0)? Black   Red   Green   Blue   White     What color would be defined by (0, 255, 0)? Black   Red   Green   Blue   White     What color would be defined by (255, 255, 255)? Black   Red   Green   Blue   White     What code will open up a window 400 pixels high and 800 pixels wide? size = [800, 400] screen = pygame.display.set_mode(size)   size = [400, 800] screen = pygame.display.set_mode(size)   size = 800,400 screen = pygame.display.set_mode(size)   size = 400,800 screen = pygame.display.set_mode(size)   screen = pygame.display.open_window(800, 400)   screen = pygame.display.open_window(400, 800)     What is the main program loop? It processes user input, updates objects, and draws the screen in each frame of the game.   It runs once for the entire game.   It loops once for each life that the player has.   It loops once for each level of the game.     Where does this code go? clock = pygame.time.Clock() The code is placed after the main program loop.   The code is placed inside the main program loop.   This code is placed before the main program loop.     Where does this code go, and what does it do? clock.tick(20) The code is place after the main program loop and pauses 20 milliseconds.   The code is place after the main program loop and limits the game to 20 frames per second.   The code is placed inside the main program loop and limits the game to 20 frames per second.   This code is placed before the main program loop and limits the game to 20 frames per second.   The code is placed inside the main program loop and pauses 20 milliseconds.     Changing this code from 20 to 30 will cause what to happen? clock.tick(20) Nothing.   The game will run faster.   The game will run slower.     What does this code do? pygame.display.flip() Nothing.   Clears the screen.   Displays everything that has been drawn so far.   Flips the screen from left to right.   Flips the screen from top to bottom.     What code will draw a line from x, y coordinates (0, 0) to (100, 100)? pygame.draw.line(screen, GREEN, [0,0,100,100], 5)   pygame.draw.line(screen, GREEN, 0, 0, 100, 100, 5)   pygame.draw.line(screen, GREEN, [0, 0], [100, 100], 5)   pygame.draw.line(GREEN, screen, 0, 0, 100, 100, 5)   pygame.draw.line(5, GREEN, [0, 0], [100, 100], screen)     What will this code draw? offset = 0 while offset < 100:     pygame.draw.line(screen, RED, [50+offset,20], [50+offset,60], 5)     offset = offset + 10 Ten vertical lines, 10 pixels apart, with a starting x coordinate of 50 and an ending coordinate of 140.   Ten horizontal lines, 10 pixels part, with a starting y coordinate of 50 and an ending coordinate of 150.   Ten vertical lines, 5 pixels apart, with a starting x coordinate of 50 and an ending coordinate of 100.   Ten vertical lines, 10 pixels apart, with a starting x coordinate of 10 and an ending coordinate of 110.   Ten vertical lines, 5 pixels apart, with a starting x coordinate of 10 and an ending coordinate of 150.   Ten lines, all drawn on top of each other.     How wide will this ellipse be? pygame.draw.ellipse(screen, BLACK, [0, 0, 100, 100], 2) 100 pixels   99 pixels   101 pixels   50 pixels   49 pixels     Where will the center of this ellipse be? pygame.draw.ellipse(screen, BLACK, [1, 1, 3, 3], 1) (1, 1)   (3, 3)   (2, 2)

简答工作表

Explain how the computer coordinate system differs from the standard Cartesian coordinate system. There are two main differences. List both.   Before a Python Pygame program can use any functions like pygame.display.set_mode(), what two lines of code must occur first?   Explain how WHITE = (255, 255, 255) represents a color.   When do we use variable names for colors in all uppercase, and when do we use variable names for colors in all lowercase? (This applies to all variables, not just colors.)   What does the pygame.display.set_mode() function do?   What does this for event in pygame.event.get() loop do?   What is pygame.time.Clock used for?   For this line of code: pygame.draw.line(screen, GREEN, [0, 0], [100, 100], 5)

  • screen是做什么的?
  • [0, 0]是做什么的?
  • [100, 100]是做什么的?
  • 5是做什么的?

What is the best way to repeat something over and over in a drawing?   When drawing a rectangle, what happens if the specified line width is zero?   Describe the ellipse drawn in the code below.

  • 原点坐标的 x,y 是什么?
  • 原点坐标说明了什么?圆心?
  • 椭圆的长度和宽度是多少?

pygame.draw.ellipse(screen, BLACK, [20, 20, 250, 100], 2)   When drawing an arc, what additional information is needed over drawing an ellipse?   Describe, in general, what are the three steps needed when printing text to the screen using graphics?   When drawing text, the first line of the three lines needed to draw text should actually be outside the main program loop. It should only run once at the start of the program. Why is this? You may need to ask.   What are the coordinates of the polygon that the code below draws? pygame.draw.polygon(screen, BLACK, [[50,100],[0,200],[200,200],[100,50]], 5)   What does pygame.display.flip() do?   What does pygame.quit() do?   Look up online how the pygame.draw.circle works. Get it working and paste a working sample here. I only need the one line of code that draws the circle, but make sure it is working by trying it out in a full working program.

锻炼

查看附录中本章附带的练习“创建图片”。

七、回到循环

A978-1-4842-1790-0_7_Figa_HTML.jpg

游戏包含许多复杂的循环。这是一个学习如何成为循环专家的挑战章节。如果你能理解这一章中的问题,在它结束时,你可以证明自己是一个循环专家。

如果成为循环专家不是你的目标,至少确保你能写出前八个问题的答案。那会给你足够的知识来继续这本书。(此外,作为一个循环专家从来没有给任何人带来过约会。除了土拨鼠日电影里的那个人。)

网上有答案的视频讲解,答案代码动画。只需点击“步骤”按钮,看看代码是如何运行的。

打印语句结束字符

默认情况下,print语句在输出内容的末尾放一个回车。正如我们在第一章中所解释的,回车符是一个将下一行输出移动到下一行的字符。大多数时候这是我们想要的。有时候不是。如果我们想在同一行上继续打印,我们可以改变最后打印的默认字符。这是我们更改结束字符之前的一个示例:

print("Pink")

print("Octopus")

…它将打印出:

Pink

Octopus

但是如果我们希望代码输出在同一行上打印,可以通过使用一个新选项来设置end字符来实现。例如:

print("Pink", end="")

print("Octopus")

这将打印:

PinkOctopus

我们也可以使用空格,而不是将其设置为空字符串:

print("Pink", end=" ")

print("Octopus")

这将打印:

Pink Octopus

这是另一个使用变量的例子:

i = 0

print(i, end=" ")

i = 1

print(i, end=" ")

这将打印:

0 1

高级循环问题

Write code that will print 10 asterisks (*) like the following: * * * * * * * * * * Have this code print using a for loop, and print each asterisk individually, rather than just printing 10 asterisks with 1 print statement. This can be done in 2 lines of code: a for loop and a print statement. When you have figured it out, or given up, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/problem_1.php   Write code that will print the following: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This is just like the prior problem but also printing 5 and 20 stars. Copy and paste from the prior problem, adjusting the for loop as needed. When you have figured it out, or given up, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/problem_2.php   Use two for loops, one of them nested inside the other, to print the following 10x10 rectangle: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * To start, take a look at Problem 1. The code in Problem 1 generates 1 line of asterisks. It needs to be repeated 10 times. Work on this problem for at least 10 minutes before looking at the answer. When you have figured it out, or given up, here it is: ProgramArcadeGames.com/chapters/06_back_to_looping/10x10box.php   Use two for loops, one of them nested, to print the following 5x10 rectangle: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This is a lot like the prior problem. Experiment with the ranges on the loops to find exactly what the numbers passed to the range function control. When you have figured it out, or given up, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/problem_4.php   Use two for loops, one of them nested, to print the following 20x5 rectangle: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Again, like Problem 3 and Problem 4, but with different range values. When you have figured it out, or given up, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/problem_5.php   Write code that will print the following: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 Use two nested loops. Print the first line with a loop, and not with: print("0 1 2 3 4 5 6 7 8 9") Tip: First, create a loop that prints the first line. Then enclose it in another loop that repeats the line 10 times. Use either i or j variables for what the program prints. This example and the next one help reinforce what those index variables are doing. Work on this problem for at least 10 minutes before looking at the answer. The process of spending 10 minutes working on the answer is far more important than the answer itself. ProgramArcadeGames.com/chapters/06_back_to_looping/number_square_answer.php   Adjust the prior program to print: 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 Answer: ProgramArcadeGames.com/chapters/06_back_to_looping/problem_7.php   Write code that will print the following: 0 0 1 0 1 2 0 1 2 3 0 1 2 3 4 0 1 2 3 4 5 0 1 2 3 4 5 6 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 9 Tip: This is just Problem 6, but the inside loop no longer loops a fixed number of times. Don’t use range(10), but adjust that range amount. After working at least 10 minutes on the problem, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/top_right_triangle.php Make sure you can write out the code for this and the prior problems. Yes, this practice is work, but it will pay off later and you’ll save time in the long run.   Write code that will print the following: 0 1 2 3 4 5 6 7 8 9   0 1 2 3 4 5 6 7 8     0 1 2 3 4 5 6 7       0 1 2 3 4 5 6         0 1 2 3 4 5           0 1 2 3 4             0 1 2 3               0 1 2                 0 1                   0 This one is difficult. Tip: Two loops are needed inside the outer loop that controls each row. First, a loop prints spaces, then a loop prints the numbers. Loop both these for each row. To start with, try writing just one inside loop that prints: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 0 1 2 3 4 5 0 1 2 3 4 0 1 2 3 0 1 2 0 1 0 Then once that is working, add a loop after the outside loop starts and before the already existing inside loop. Use this new loop to print enough spaces to right justify the other loops. After working at least 10 minutes on the problem, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/bottom_left_triangle.php   Write code that will print the following (Getting the alignment is hard, at least get the numbers): 1   2   3   4   5   6   7   8   9 2   4   6   8  10  12  14  16  18 3   6   9  12  15  18  21  24  27 4   8  12  16  20  24  28  32  36 5  10  15  20  25  30  35  40  45 6  12  18  24  30  36  42  48  54 7  14  21  28  35  42  49  56  63 8  16  24  32  40  48  56  64  72 9  18  27  36  45  54  63  72  81 Tip: Start by adjusting the code in Problem 1 to print: 0  0  0  0  0  0  0  0  0  0 0  1  2  3  4  5  6  7  8  9 0  2  4  6  8  10  12  14  16  18 0  3  6  9  12  15  18  21  24  27 0  4  8  12  16  20  24  28  32  36 0  5  10  15  20  25  30  35  40  45 0  6  12  18  24  30  36  42  48  54 0  7  14  21  28  35  42  49  56  63 0  8  16  24  32  40  48  56  64  72 0  9  18  27  36  45  54  63  72  81 Then adjust the code to print: 1  2  3  4  5  6  7  8  9 2  4  6  8  10  12  14  16  18 3  6  9  12  15  18  21  24  27 4  8  12  16  20  24  28  32  36 5  10  15  20  25  30  35  40  45 6  12  18  24  30  36  42  48  54 7  14  21  28  35  42  49  56  63 8  16  24  32  40  48  56  64  72 9  18  27  36  45  54  63  72  81 Finally, use an if to print spaces if the number being printed is less than 10. After working at least 10 minutes on the problem, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/multiplication_table.php   Write code that will print the following:                   1                 1 2 1               1 2 3 2 1             1 2 3 4 3 2 1           1 2 3 4 5 4 3 2 1         1 2 3 4 5 6 5 4 3 2 1       1 2 3 4 5 6 7 6 5 4 3 2 1     1 2 3 4 5 6 7 8 7 6 5 4 3 2 1   1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 Tip: first write code to print: 1 1 2 1 2 3 1 2 3 4 1 2 3 4 5 1 2 3 4 5 6 1 2 3 4 5 6 7 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 Then write code to print: 1 1 2 1 1 2 3 2 1 1 2 3 4 3 2 1 1 2 3 4 5 4 3 2 1 1 2 3 4 5 6 5 4 3 2 1 1 2 3 4 5 6 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 Then finish by adding spaces to print the final answer. After working at least 10 minutes on the problem, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/top_triangle.php   Write code that will print the following:                   1                 1 2 1               1 2 3 2 1             1 2 3 4 3 2 1           1 2 3 4 5 4 3 2 1         1 2 3 4 5 6 5 4 3 2 1       1 2 3 4 5 6 7 6 5 4 3 2 1     1 2 3 4 5 6 7 8 7 6 5 4 3 2 1   1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1     1 2 3 4 5 6 7 8       1 2 3 4 5 6 7         1 2 3 4 5 6           1 2 3 4 5             1 2 3 4               1 2 3                 1 2                   1 This can be done by combining Problems 11 and 9. After working at least 10 minutes on the problem, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/three_quarters.php   Write code that will print the following:                   1                 1 2 1               1 2 3 2 1             1 2 3 4 3 2 1           1 2 3 4 5 4 3 2 1         1 2 3 4 5 6 5 4 3 2 1       1 2 3 4 5 6 7 6 5 4 3 2 1     1 2 3 4 5 6 7 8 7 6 5 4 3 2 1   1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1     1 2 3 4 5 6 7 8 7 6 5 4 3 2 1       1 2 3 4 5 6 7 6 5 4 3 2 1         1 2 3 4 5 6 5 4 3 2 1           1 2 3 4 5 4 3 2 1             1 2 3 4 3 2 1               1 2 3 2 1                 1 2 1                   1 After working at least 10 minutes on the problem, here is the answer: ProgramArcadeGames.com/chapters/06_back_to_looping/full_diamond.php

回顾

多项选择测验

What does this code display? for i in range(3):         print("*") * * *   ***   0 1 2   012     What does this code display? for i in range(3):         print(i, end="") * * *   ***   0 1 2   012     What does this code display? for i in range(3):         print("*", end="") for j in range(3):         print("*", end="")

  • 012012

*** ***   *** *** ***   ****** (6 asterisks)   ********* (9 asterisks)     What does this code display? for i in range(3):         for j in range(3):                 print("*", end="")

  • 012012

*** ***   *** *** ***   ****** (6 asterisks)   ********* (9 asterisks)     What does this code display? for i in range(3):         for j in range(3):                 print("*", end="")                 print() (9 asterisks in a vertical line.)   *** *** ***   ********* (9 asterisks)     What does this code display? for i in range(3):         for j in range(3):                 print("*", end="")         print() (9 asterisks in a vertical line.)   *** *** ***   ********* (9 asterisks)     What does this code display? for i in range(3):         for j in range(3):                 print("*", end="") print() (9 asterisks in a vertical line.)   *** *** ***   ********* (9 asterisks)     What does this code display? for i in range(3):     for j in range(3):         print(i, end="")     print() 000 111 222   012 012 012   012 345 678

简答工作表

对于前两个问题中的每一个,写出您对代码将会输出什么的最佳猜测。清楚地将此标记为您的猜测。然后运行代码并查看输出。写下你的猜测是否正确。如果不是,请简要描述不同之处及其原因。

预测代码将做什么对于编写程序和弄清楚为什么程序不按预期方式运行是很重要的。

What does this program print out? (Remember: TWO answers. Your guess and the actual result.) x = 0 while x < 10:     print(x)     x = x + 2   What does this program print out? x = 1 while x < 64:     print(x)     x = x * 2   Why is the and x >= 0 not needed? x = 0 while x < 10 and x >= 0:     print(x)     x = x + 2   What does this program print out? (0 pts) Explain. x = 5 while x >= 0:     print(x)     if x == "1":         print("Blast off!")     x = x - 1   Fix the following code so it doesn’t repeat forever, and keeps asking the user until he or she enters a number greater than zero: x = float(input("Enter a number greater than zero: ")) while x <= 0:     print("Too small. Enter a number greater than zero: ")   Fix the following code: x = 10 while x < 0:     print(x)     x - 1 print("Blast-off")   What is wrong with this code? It runs but it has unnecessary code. Find all the unneeded code. Also, answer why it is not needed. i = 0 for i in range(10):     print(i)     i += 1   Explain why the values printed for x are so different. # Sample 1 x = 0 for i in range(10):     x += 1 for j in range(10):     x += 1 print(x) # Sample 2 x = 0 for i in range(10):     x += 1     for j in range(10):         x += 1 print(x)

锻炼

查看附录中本章附带的练习“Loopy Lab”。

八、列表简介

到目前为止,这本书已经展示了四种类型的数据:

  • 字符串(字符串是“字符串”的缩写,正常人认为它是文本。)
  • 整数
  • 浮点
  • 布尔代数学体系的

Python 可以用type函数显示一个值是什么类型的数据。

这个type函数对本书中的其他编程没有用,但是演示到目前为止介绍的数据类型是很有用的。在交互式 shell 中键入以下内容。(不要新建一个窗口,把这个作为程序打进去;没用的。)

type(3)

type(3.145)

type("Hi there")

type(True)

>>> type(3)

<class ’int’>

>>> type(3.145)

<class ’float’>

>>> type("Hi there")

<class ’str’>

>>> type(True)

<class ’bool’>

也可以对变量使用type函数来查看变量中的数据类型。

x = 3

type(x)

要收集的硬币不止一枚?使用列表!

本章介绍的两种新的数据类型是列表和元组。列表类似于另一种叫做数组的数据结构。列表可以调整大小,但数组不能。数据结构的课程会教你细节,但这超出了本书的范围。尝试在交互式 Python shell 中运行以下命令,并查看显示的内容:

type(  (2, 3, 4, 5) )

type(  [2, 3, 4, 5] )

使用列表

你已经创建了购物清单、待办事项清单、遗愿清单,但是你如何在电脑上创建一个清单呢?

A978-1-4842-1790-0_8_Figa_HTML.jpg

甚至计算机也使用列表

使用 IDLE 的命令行尝试这些示例。要创建列表并将其打印出来,请尝试以下方法:

>>> x = [1,2]

>>> print(x)

[1, 2]

要打印列表中的单个元素:

>>> print(x[0])

1

这个带有项目位置的数字称为索引。请注意,列表位置从零开始。所以一个有 10 个元素的列表或数组在 spot [10]中没有元素。只有斑点[0]到[9]。创建一个包含 10 个条目的列表,却没有第 10 个条目,这可能会非常令人困惑,但是大多数计算机语言都是从 0 开始计数,而不是从 1 开始计数。

将列表想象成一个盛放数字的冰块托盘,如下图所示。这些值存储在每个托盘点内,写在托盘侧面的是从零开始的数字,用于标识每个点的位置。

不要混淆指数和价值!

请记住,在处理数字列表时,需要考虑两组数字:位置和值。位置,也称为索引,是指值所在的位置。该值是存储在该位置的实际数字。当使用列表或数组时,一定要考虑是否需要位置或值。

给定位置很容易得到值,但是给定值很难得到位置。第十六章致力于回答如何找到特定值的位置。

A978-1-4842-1790-0_8_Figb_HTML.jpg

列表就像冰块托盘

程序可以给列表中的单个元素赋值。在下面的例子中,位置 0(不是 1)的第一个点被指定为数字 22。

>>> x[0] = 22

>>> print(x)

[22, 2]

此外,程序可以创建一个元组。这种数据类型的工作方式类似于列表,但有两个不同之处。首先,它是用圆括号而不是方括号创建的。第二,元组一旦创建就不可能改变。见下文:

>>> x = (1, 2)

>>> print(x)

(1, 2)

>>> print(x[0])

1

>>> x[0] = 22

Traceback (most recent call last):

File "<pyshell#18>", line 1, in <module>

x[0] = 22

TypeError: ’tuple’ object does not support item assignment

>>>

从上面代码的输出可以看出,我们不能给元组中的一个项赋值。为什么我们想要这个限制?首先,如果计算机知道值不会改变,它可以运行得更快。第二,一些我们不想改变的列表,比如红色的 RGB 颜色列表。红色不会改变;因此,不可变元组是更好的选择。

遍历列表

如果程序需要遍历列表中的每一项,比如打印出来,有两种类型的for循环可以做到这一点。

第一种在循环中迭代每一项的方法是使用 for-each 循环。这种类型的循环接受一组项,并对每个项循环一次代码。它将获取该项的一个副本,并将其存储在一个变量中进行处理。

命令的格式:for项目 _ 变量in列表 _ 名称:

以下是一些例子:

my_list = [101, 20, 10, 50, 60]

for item in my_list:

print(item)

101

20

10

50

60

程序也可以在列表中存储字符串:

my_list = ["Spoon", "Fork", "Knife"]

for item in my_list:

print(item)

Spoon

Knife

Fork

列表甚至可以包含其他列表。这将遍历主列表中的每一项,但不遍历子列表中的每一项。

my_list = [ [2,3], [4,3], [6,7] ]

for item in my_list:

print(item)

[2,3]

[4,3]

[6,7]

另一种遍历列表的方法是使用索引变量并直接访问列表,而不是通过每个条目的副本。为了使用索引变量,程序从 0 开始计数,直到列表的长度。如果有 10 个元素,循环必须从 0 到 9,总共 10 个元素。

列表的长度可以通过使用len函数得到。将它与range函数结合起来,允许程序遍历整个列表。

my_list = [101, 20, 10, 50, 60]

for i in range(len(my_list)):

print(my_list[i])

101

20

10

50

60

这种方法更复杂,但也更强大。因为我们直接使用列表元素,而不是副本,所以列表可以被修改。for-each 循环不允许修改原始列表。

添加到列表

通过使用append命令,可以向列表(而不是元组)添加新项目。例如:

my_list = [2, 4, 5, 6]

print(my_list)

my_list.append(9)

print(my_list)

[2, 4, 5, 6]

[2, 4, 5, 6, 9]

附注:如果追加时的性能是一个问题,那么理解一个列表是如何实现的就非常重要了。例如,如果一个列表被实现为数组数据类型,那么将一个条目添加到列表中就很像在一个装满鸡蛋的纸盒中添加一个新鸡蛋。一个新的鸡蛋纸盒必须有 13 个点。然后把 12 个鸡蛋挪过去。然后加入第 13 个鸡蛋。最后旧的鸡蛋纸盒被回收。因为这可能发生在函数的幕后,程序员可能会忘记这一点,让计算机做所有的工作。简单地告诉计算机制作一个有足够斑点的鸡蛋盒会更有效率。幸运的是,Python 没有将 list 实现为数组数据类型。但重要的是要注意你下学期的数据结构课,并了解所有这些是如何工作的。

要从头开始创建列表,需要创建一个空白列表,然后使用 append 函数。本示例根据用户输入创建一个列表:

my_list = [] # Empty list

for i in range(5):

userInput = input( "Enter an integer: ")

userInput = int( userInput )

my_list.append(userInput)

print(my_list)

Enter an integer: 4

[4]

Enter an integer: 5

[4, 5]

Enter an integer: 3

[4, 5, 3]

Enter an integer: 1

[4, 5, 3, 1]

Enter an integer: 8

[4, 5, 3, 1, 8]

如果一个程序需要创建一个具有相同值的特定长度的数组,一个简单的技巧是使用下面的代码:

# Create an array with 100 zeros.

my_list = [0] * 100

汇总或修改列表

创建数组的累计是一种常见的操作。这是如何做到的:

# Copy of the array to sum

my_list = [5,76,8,5,3,3,56,5,23]

# Initial sum should be zero

list_total = 0

# Loop from 0 up to the number of elements

# in the array:

for i in range(len(my_list)):

# Add element 0, next 1, then 2, etc.

list_total += my_list[i]

# Print the result

print(list_total)

同样的事情可以通过使用for循环迭代数组来完成,而不是通过一个范围来计数:

# Copy of the array to sum

my_list = [5, 76, 8, 5, 3, 3, 56, 5, 23]

# Initial sum should be zero

list_total = 0

# Loop through array, copying each item in the array into

# the variable named item.

for item in my_list:

# Add each item

list_total += item

# Print the result

print(list_total)

数组中的数字也可以通过使用for循环来改变:

# Copy of the array to modify

my_list = [5, 76, 8, 5, 3, 3, 56, 5, 23]

# Loop from 0 up to the number of elements

# in the array:

for i in range(len(my_list)):

# Modify the element by doubling it

my_list[i] = my_list[i] * 2

# Print the result

print(my_list)

然而,第 2 版不能将数组中的值加倍。为什么?因为一个item是数组中一个元素的副本。下面的代码是副本的两倍,而不是原始的数组元素。

# Copy of the array to modify

my_list = [5, 76, 8, 5, 3, 3, 56, 5, 23]

# Loop through each element in myArray

for item in my_list:

# This doubles item, but does not change the array

# because item is a copy of a single element.

item = item * 2

# Print the result

print(my_list)

分割字符串

字符串实际上是字符列表。它们可以像列表一样被处理,每个字母是一个单独的条目。使用两个版本的x运行以下代码:

x = "This is a sample string"

#x = "0123456789"

print("x=", x)

# Accessing a single character

print("x[0]=", x[0])

print("x[1]=", x[1])

# Accessing from the right side

print("x[-1]=", x[-1])

# Access 0-5

print("x[:6]=", x[:6])

# Access 6

print("x[6:]=", x[6:])

# Access 6-8

print("x[6:9]=", x[6:9])

Python 中的字符串可以和一些数学运算符一起使用。尝试下面的代码,看看 Python 做了什么:

a = "Hi"

b = "There"

c = "!"

print(a + b)

print(a + b + c)

print(3 * a)

print(a * 3)

print((a * 2) + (b * 2))

有可能得到一个字符串的长度。也可以用任何类型的数组来实现这一点。

a = "Hi There"

print(len(a))

b = [3, 4, 5, 6, 76, 4, 3, 3]

print(len(b))

因为字符串是一个数组,所以程序可以像数组一样遍历每个字符元素:

for character in "This is a test.":

print(character)

练习:从以下代码开始:

months = "JanFebMarAprMayJunJulAugSepOctNovDec"

n = int(input("Enter a month number: "))

打印用户输入的月份编号的三个月缩写。(计算字符串中的起始位置,然后使用我们刚刚学习的信息打印出正确的子字符串。)

秘密代码

这段代码分别打印出字符串中的每个字母:

plain_text = "This is a test. ABC abc"

for c in plain_text:

print(c, end=" ")

计算机实际上并不在内存中存储字符串的字母;电脑储存一系列数字。每个数字代表一个字母。计算机用来将数字转换成字母的系统称为 Unicode。编码的全称是通用字符集转换格式 8 位,通常缩写为UTF-8

Unicode 图表包含使用数字 0-127 的西方字母表。每个西文字母代表一个字节的内存。其他字母,如西里尔字母,可以用多个字节来表示每个字母。Unicode 图表的部分副本如下:

| 价值 | 性格;角色;字母 | 价值 | 性格;角色;字母 | 价值 | 性格;角色;字母 | 价值 | 性格;角色;字母 | | --- | --- | --- | --- | --- | --- | --- | --- | | Forty | ( | Sixty-one | = | Eighty-two | 稀有 | One hundred and three | g | | Forty-one | ) | Sixty-two | > | Eighty-three | S | One hundred and four | h | | forty-two | * | Sixty-three | ? | Eighty-four | T | One hundred and five | 我 | | Forty-three | + | Sixty-four | @ | eighty-five | U | One hundred and six | j | | forty-four | , | Sixty-five | A | Eighty-six | V | One hundred and seven | k | | Forty-five | - | Sixty-six | B | Eighty-seven | W | One hundred and eight | l | | Forty-six | 。 | Sixty-seven | C | Eighty-eight | X | One hundred and nine | m | | Forty-seven | / | sixty-eight | D | eighty-nine | Y | One hundred and ten | n | | Forty-eight | Zero | sixty-nine | E | Ninety | Z | One hundred and eleven | o | | forty-nine | one | Seventy | F | Ninety-one | [ | One hundred and twelve | p | | Fifty | Two | Seventy-one | G | Ninety-two | \ | One hundred and thirteen | q | | Fifty-one | three | seventy-two | H | Ninety-three | ] | One hundred and fourteen | r | | fifty-two | four | Seventy-three | 我 | Ninety-four | ^ | One hundred and fifteen | s | | Fifty-three | five | Seventy-four | J | Ninety-five | _ | One hundred and sixteen | t | | Fifty-four | six | Seventy-five | K | Ninety-six | ` | One hundred and seventeen | u | | Fifty-five | seven | Seventy-six | L | Ninety-seven | a | One hundred and eighteen | v | | fifty-six | eight | Seventy-seven | M | Ninety-eight | b | One hundred and nineteen | w | | Fifty-seven | nine | seventy-eight | 普通 | Ninety-nine | c | One hundred and twenty | x | | Fifty-eight | : | Seventy-nine | O | One hundred | d | One hundred and twenty-one | y | | Fifty-nine | ; | Eighty | P | One hundred and one | e | One hundred and twenty-two | z | | Sixty | < | Eighty-one | Q | One hundred and two | f |   |   |

有关 ASCII(与西方字母表的 Unicode 具有相同的值)的更多信息,请参见: http://en.wikipedia.org/wiki/ASCII

要看解释 Unicode 之美的视频,请看这里: http://hackaday.com/2013/09/27/utf-8-the-most-elegant-hack

下一组代码使用 UTF-8 将前面示例中的每个字母转换为它的序数值:

plain_text = "This is a test. ABC abc"

for c in plain_text:

print(ord(c), end=" ")

下一个程序获取每个 UTF-8 值并加 1。然后,它打印新的 UTF-8 值,然后将该值转换回字母。

plain_text = "This is a test. ABC abc"

for c in plain_text:

x = ord(c)

x = x + 1

c2 = chr(x)

print(c2, end="")

下一个代码清单获取每个 UTF-8 值并加 1,然后将该值转换回字母。

A978-1-4842-1790-0_8_Figc_HTML.jpg

# Sample Python/Pygame Programs

#http://programarcadegames.com/

# Explanation video:http://youtu.be/sxFIxD8Gd3A

plain_text = "This is a test. ABC abc"

encrypted_text = ""

for c in plain_text:

x = ord(c)

x = x + 1

c2 = chr(x)

encrypted_text = encrypted_text + c2

print(encrypted_text)

最后,最后一段代码获取每个 UTF-8 值并从中减去 1,然后将该值转换回字母。通过将前一个程序的输出提供给这个程序,它可以作为前一个例子编码的文本的解码器。

A978-1-4842-1790-0_8_Figd_HTML.jpg

# Sample Python/Pygame Programs

#http://programarcadegames.com/

# Explanation video:http://youtu.be/sxFIxD8Gd3A

encrypted_text = "Uijt!jt!b!uftu/!BCD!bcd"

plain_text = ""

for c in encrypted_text:

x = ord(c)

x = x - 1

c2 = chr(x)

plain_text = plain_text + c2

print(plain_text)

关联数组

Python 并不局限于使用数字作为数组索引。也可以使用关联数组。关联数组的工作方式如下:

# Create an empty associative array

# (Note the curly braces.)

x = {}

# Add some stuff to it

x["fred"] = 2

x["scooby"] = 8

x["wilma"] = 1

# Fetch and print an item

print(x["fred"])

对于这本书来说,你并不真的需要关联数组,但是我认为有必要指出这是可能的。

回顾

多项选择测验

What code will print out the first element? x = [1, 2, 3, 4, 5] print(x[0])   print(x[1])   print(x(0))   print(x(1))   print(x)   print([1])     What code will change the first element? x = [1, 2, 3, 4, 5] x[0] = 100   x = 100   x[1] = 100   [1] = 100   x(1) = 100     What code will print each element of my_list? my_list = [101, 20, 10, 50, 60] for item in my_list:     print(item)   for item in my_list: print(my_list)   for item in range(my_list): print(item)   for my_list in item: print(item)     What code will add a new element to this list? my_list = [5, 6, 7] my_list[8]   my_list(8)   my_list.add(8)   my_list.append(8)   my_list.add[8]     What code will sum all the elements in this list? my_array = [5, 76, 8, 5, 3, 3, 56, 5, 23] array_total = 0 for i in range(len(my_array)):     array_total += my_array[i]   array_total = 0 for i in range(my_array):     array_total += my_array[i]   array_total = 0 for i in range(len(my_array)):     array_total += i   i = 0 for i in range(len(my_array)):     array_total += my_array   i = 0 for i in my_array:     array_total += my_array(i)     What does this code print? my_array = [2, 4, 6] for i in range(3):         print(my_array) 0 1 2   1 2 3   [2, 4, 6] [2, 4, 6] [2, 4, 6]   246   [2, 4, 6]   2 4 6     What does this code print? my_array = [2, 4, 6] for i in range(3):         print(my_array[0])

  • 0``1
  • 2``2
  • [2, 4, 6]``[2, 4, 6]
  • 246
  • [2, 4, 6]
  • 2``4

What does this code print? my_array = [2, 4, 6] for i in range(3):         print(i)

  • 0``1
  • 2``2
  • [2, 4, 6]``[2, 4, 6]
  • 246
  • [2, 4, 6]
  • 2``4

简答工作表

在下面的问题中,如果一个错误阻止了一个例子的运行,一定要在结果中提到它。还有,要精确。如果程序打印[1],不会说它打印 1。

List the four types of data we’ve covered, and give an example of each:   What does this code print out? For this and the following problems, make sure you understand WHY it prints what it does. You don’t have to explain it, but if you don’t understand why, make sure to ask. Otherwise you are wasting your time doing these. my_list = [5, 2, 6, 8, 101] print(my_list[1]) print(my_list[4]) print(my_list[5])   What does this code print out? my_list=[5, 2, 6, 8, 101] for my_item in my_list:     print(my_item)   What does this code print out? my_list1 = [5, 2, 6, 8, 101] my_list2 = (5, 2, 6, 8, 101) my_list1[3] = 10 print(my_list1) my_list2[2] = 10 print(my_list2)   What does this code print out? my_list = [3 * 5] print(my_list) my_list = [3] * 5 print(my_list)   What does this code print out? my_list = [5] for i in range(5):         my_list.append(i) print(my_list)   What does this code print out? print(len("Hi")) print(len("Hi there.")) print(len("Hi") + len("there.")) print(len("2")) print(len(2))   What does this code print out? print("Simpson" + "College") print("Simpson" + "College"[1]) print( ("Simpson" + "College")[1] )   What does this code print out? word = "Simpson" for letter in word:     print(letter)   What does this code print out? word = "Simpson" for i in range(3):     word += "College" print(word)   What does this code print out? word = "Hi" * 3 print(word)   What does this code print out? my_text = "The quick brown fox jumped over the lazy dogs." print("The 3rd spot is: " + my_text[3]) print("The -1 spot is: " + my_text[-1])   What does this code print out? s = "0123456789" print(s[1]) print(s[:3]) print(s[3:])   Write a loop that will take in a list of five numbers from the user, adding each to an array. Then print the array. Try doing this without looking at the book.   Write a program that take an array like the following, and print the average. Use the len function, don’t just use 15, because that won’t work if the list size changes. (There is a sum function I haven’t told you about. Don’t use that. Sum the numbers individually as shown in the chapter.) my_list = [3,12,3,5,3,4,6,8,5,3,5,6,3,2,4]

锻炼

查看附录中本章附带的“冒险”练习。

九、动画介绍

为了开始我们的第一个动画,让我们从《??》第六章的基本 pygame 程序开始,它打开了一个空白屏幕。pygame_base_template.py的来源可以在这里找到: ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

我们将编写一个程序,在黑色背景的屏幕上弹出一个白色的矩形。随意挑选你自己的颜色;只是要确保背景颜色不同于矩形的颜色!

A978-1-4842-1790-0_9_Figa_HTML.jpg

第一步:从基础模板开始,将背景色从白色翻转为黑色。这段代码应该在第 46 行左右。

screen.fill(BLACK)

接下来,画出我们打算制作动画的矩形。一个简单的矩形就足够了。这段代码应该放在清空屏幕之后,翻转屏幕之前。

pygame.draw.rect(screen, WHITE, [50, 50, 50, 50])

每次通过循环,矩形将在(x,y)位置精确地(50,50)绘制。这由列表中的前两个 50 控制。在这些数字改变之前,正方形不会移动。

该矩形宽 50 像素,高 50 像素。尺寸由列表中的最后两个数字控制。我们也可以称这个矩形为正方形,因为它有相同的宽度和高度。我将坚持称它为矩形,因为所有的正方形也是矩形,并且取决于所使用的显示器和分辨率,像素并不总是正方形。如果你真的对这个主题感兴趣,可以查一下像素长宽比。

我们如何不断改变位置而不是停留在(50,50)?当然是用变量啦!下面的代码是实现这一目标的第一步:

rect_x = 50

pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])

要向右移动矩形,x可以每帧增加一。这段代码很接近,但并没有完全做到:

rect_x = 50

pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])

rect_x += 1

上面代码的问题是rect_x每次通过循环都被重置回 50。要解决这个问题,将rect_x的初始化移到循环之外。下一段代码将成功地将矩形向右滑动。

# Starting x position of the rectangle

# Note how this is outside the main while loop.

rect_x = 50

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get(): # User did something

if event.type == pygame.QUIT: # If user clicked close

done = True # Flag that we are done so we exit this loop

# Set the screen background

screen.fill(BLACK)

pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])

rect_x += 1

要更快地移动盒子,不要将rect_x增加 1,而是增加 5:

rect_x += 5

我们可以扩展这段代码,增加 x 和 y,使正方形同时向下和向右移动:

# Starting position of the rectangle

rect_x = 50

rect_y = 50

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# Set the screen background

screen.fill(BLACK)

# Draw the rectangle

pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

# Move the rectangle starting point

rect_x += 5

rect_y += 5

盒子移动的方向和速度可以存储在一个矢量中。这使得移动物体的方向和速度很容易被改变。下一段代码展示了如何使用变量来存储(5,5)的 x 和 y 变化。

# Starting position of the rectangle

rect_x = 50

rect_y = 50

# Speed and direction of rectangle

rect_change_x = 5

rect_change_y = 5

# -------- Main Program Loop -----------

while done == False:

for event in pygame.event.get(): # User did something

if event.type == pygame.QUIT: # If user clicked close

done = True # Flag that we are done so we exit this loop

# Set the screen background

screen.fill(BLACK)

# Draw the rectangle

pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

# Move the rectangle starting point

rect_x += rect_change_x

rect_y += rect_change_y

一旦盒子碰到屏幕的边缘,它就会继续前进。没有什么能让矩形从屏幕边缘反弹回来。为了反转方向,使矩形向右移动,一旦矩形到达屏幕的底部,rect_change_y需要从 5 变为-5。当rect_y大于屏幕高度时,矩形在底部。下面的代码可以进行检查并反转方向:

# Bounce the rectangle if needed

if rect_y > 450:

rect_change_y = rect_change_y * -1

A978-1-4842-1790-0_9_Figb_HTML.jpg

基于 y 坐标的矩形定位

为什么对照 450 查rect_y?如果屏幕是 500 像素高,那么检查 500 将是一个合乎逻辑的第一猜测。但是记住矩形是从矩形的左上角开始绘制的。如果矩形从 500 开始画,它会从 500 画到 550,在反弹之前完全离开屏幕。

考虑到矩形是 50 像素高,正确的反弹位置是:\(500-50=450\)

下面的代码将从 700x400 窗口的所有四个边反弹矩形:

# Bounce the rectangle if needed

if rect_y > 450 or rect_y < 0:

rect_change_y = rect_change_y * -1

if rect_x > 650 or rect_x < 0:

rect_change_x = rect_change_x * -1

对比矩形更复杂的形状感兴趣?基于rect_xrect_y可以使用几个绘图命令。下面的代码在白色矩形内绘制了一个红色矩形。红色矩形在 x、y 方向上从白色矩形的左上角偏移 10 个像素。它在两个维度上都小了 20 个像素,导致红色矩形周围有 10 个白色像素。见上图。

A978-1-4842-1790-0_9_Figc_HTML.jpg

中间有红色方块的白色矩形

# Draw a red rectangle inside the white one

pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

pygame.draw.rect(screen, RED, [rect_x + 10, rect_y + 10,30, 30])

动画雪

只制作一个物品的动画还不够吗?需要更多动画?能够一次制作数百个物体的动画怎么样?让我们扩展上面的代码,学习如何制作许多物体的动画。

代码解释

要启动这个程序,从打开一个空白屏幕的基本 pygame 模板开始。同样,pygame_base_template.py的来源可以在这里找到:

ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

通过使用随机数,可以为星星、雪或雨等事物创建 x,y 位置。尝试这个的最简单的方法是使用一个for循环在随机的 x,y 位置画圆。在主while循环中尝试下面的代码。

for i in range(50):

x = random.randrange(0, 400)

y = random.randrange(0, 400)

pygame.draw.circle(screen, WHITE, [x, y], 2)

试试看;这个程序有一个奇怪的问题!每秒二十次,每次通过循环,它在新的随机位置绘制雪。尝试调整雪花数量,看看它如何改变图像。

显然,我们需要随机放置雪花,并让它们保持在同一个位置。我们不想每秒钟生成 20 次新位置。我们需要保留一份他们在哪里的名单。该程序可以使用 python 列表来完成这项工作。这应该在主循环之前完成,否则程序会每 1/20 秒向列表中添加 50 个新雪花。

for i in range(50):

x = random.randrange(0, 400)

y = random.randrange(0, 400)

snow_list.append([x, y])

一旦雪花位置被添加,它们可以像普通列表一样被访问。以下代码将打印第一个位置的 x 和 y 坐标,存储在零位置:

print(snow_list[0])

如果我们只需要 x 或 y 坐标呢?我们在列表中有列表。主列表上有所有的坐标。在这个列表中,每个坐标都是一个 x 坐标(位置 0)和 y 坐标(位置 1)的列表。例如,这里有三个坐标:

[[34, 10],

[10, 50],

[20, 18]]

要打印位置 0 处的 y 坐标,首先选择坐标 0,然后选择位置 1 处的 y 值。代码将类似于:

print(snow_list[0][1])

要打印第 21 个坐标(位置 20)的 x 值,首先选择坐标 20,然后选择位置 0 的 x 值:

print(snow_list[20][0])

在主while循环中,程序可能会使用一个for循环来绘制雪列表中的每个项目。记住,len(snow_list)将返回雪花列表中元素的个数。

# Process each snow flake in the list

for i in range(len(snow_list)):

#``Draw

pygame.draw.circle(screen, WHITE, snow_list[i], 2)

记住,有两种类型的for循环。可以使用另一种类型的循环,它看起来像:

# Process A COPY of each snow flake’s location in the list

for xy_coord in snow_list:

# Draw the snow flake

pygame.draw.circle(screen, WHITE, xy_coord, 2)

然而,因为我们计划修改雪花的位置,所以我们不能使用这种类型的for循环,因为我们将修改雪花位置的副本的位置,而不是实际雪花的位置。

如果程序要让数组中的所有对象向下移动,比如雪,那么扩展上面创建的for循环将导致 y 坐标增加:

# Process each snow flake in the list

for i in range(len(snow_list)):

# Draw the snow flake

pygame.draw.circle(screen, WHITE, snow_list[i], 2)

# Move the snow flake down one pixel

snow_list[i][1] += 1

这使得雪向下移动,但是一旦离开屏幕,就没有新的东西出现了。通过添加下面的代码,雪将重置到屏幕顶部的一个随机位置:

# If the snow flake has moved off the bottom of the screen

if snow_list[i][1] > 400:

# Reset it just above the top

y = random.randrange(-50, -10)

snow_list[i][1] = y

# Give it a new x position

x = random.randrange(0, 400)

snow_list[i][0] = x

还可以向列表中添加内容,并为屏幕上的每个项目提供不同的大小、形状、颜色、速度和方向。然而,这变得很复杂,因为需要在列表中保存多种类型的数据。现在我们将保持它的简单,但是一旦我们在第十二章中学习了“类”,管理多个对象的不同属性将变得容易。

完整的节目列表

"""

Animating multiple objects using a list.

Sample Python/Pygame Programs

http://programarcadegames.com/

Explanation video:http://youtu.be/Gkhz3FuhGoI

"""

# Import a library of functions called ’pygame’

import pygame

import random

# Initialize the game engine

pygame.init()

BLACK = [0, 0, 0]

WHITE = [255, 255, 255]

# Set the height and width of the screen

SIZE = [400, 400]

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption("Snow Animation")

# Create an empty array

snow_list = []

# Loop 50 times and add a snow flake in a random x,y position

for i in range(50):

x = random.randrange(0, 400)

y = random.randrange(0, 400)

snow_list.append([x, y])

clock = pygame.time.Clock()

# Loop until the user clicks the close button.

done = False

while not done:

for event in pygame.event.get():   # User did something

if event.type == pygame.QUIT:  # If user clicked close

done = True   # Flag that we are done so we exit this loop

# Set the screen background

screen.fill(BLACK)

# Process each snow flake in the list

for i in range(len(snow_list)):

# Draw the snow flake

pygame.draw.circle(screen, WHITE, snow_list[i], 2)

# Move the snow flake down one pixel

snow_list[i][1] += 1

# If the snow flake has moved off the bottom of the screen

if snow_list[i][1] > 400:

# Reset it just above the top

y = random.randrange(-50, -10)

snow_list[i][1] = y

# Give it a new x position

x = random.randrange(0, 400)

snow_list[i][0] = x

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

clock.tick(20)

pygame.quit()

这个例子显示了每片雪花向同一个方向移动。如果每个项目都需要单独制作动画,并有自己的方向,该怎么办?如果你的游戏需要这个,请看第十三章关于如何使用职业。附录中的“类和图形”练习指导你如何制作数百个不同的动画项目,每个项目都有自己的方向。

三维动画

从一个 2D 环境扩展到一个包含游戏物理的 3D 环境并不像看起来那么难。虽然这超出了本书的范围,但还是值得看看它是如何做到的。

有一个免费的 3D 程序叫做 Blender,它有一个“游戏引擎”,允许程序员创建 3D 游戏。游戏中的 3D 对象可以附加 Python 代码来控制它们在游戏中的动作。

A978-1-4842-1790-0_9_Figd_HTML.jpg

基于搅拌机的游戏

看上图。这显示了一个绿色托盘,里面有几个对象。蓝色对象由 Python 脚本控制,该脚本使它在托盘周围移动,撞到其他对象。如下所示的脚本具有许多与 2D 程序相同的特性。有一个主循环,有一个 x,y 位置的列表,还有控制向量的变量。

主程序循环由 Blender 控制。清单中显示的 python 代码由 Blender 为游戏呈现的每个“帧”调用。这就是 Python 代码没有显示主程序循环的原因。然而,它确实存在。

蓝色对象的位置以 x,y,z 格式保存。可以通过使用blue_object.position变量来访问和更改它。数组位置 0 保存 x,位置 1 保存 y,位置 2 保存 z 位置。

这个 Blender 示例使用关联数组位置,而不是在这个转换器的 2D 示例中使用的change_xchange_y变量:

blue_object["x_change"]

blue_object["y_change"]

if语句检查蓝色物体是否已经到达屏幕边界,方向是否需要反转。与 2D 奥运会中使用的像素不同,物体的位置可以是浮点数类型。要将项目定位在 5 和 6 之间,允许将其位置设置为 5.5。

# Import Blender Game Engine

import bge

# Get a reference to the blue object

cont = bge.logic.getCurrentController()

blue_object = cont.owner

# Print the x,y coordinates where the blue object is

print(blue_object.position[0], blue_object.position[1] )

# Change x,y coordinates according to x_change and

# y_change. x_change and y_change are game properties

# associated with the blue object.

blue_object.position[0] += blue_object["x_change"]

blue_object.position[1] += blue_object["y_change"]

# Check to see of the object has gone to the edge.

# If so reverse direction. Do so with all 4 edges.

if blue_object.position[0] > 6 and blue_object["x_change"] > 0:

blue_object["x_change"] *= -1

if blue_object.position[0] < -6 and blue_object["x_change"] < 0:

blue_object["x_change"] *= -1

if blue_object.position[1] > 6 and blue_object["y_change"] > 0:

blue_object["y_change"] *= -1

if blue_object. position[1] < -6 and blue_object["y_change"] < 0:

blue_object["y_change"] *= -1

Blender 可从: http://www.blender.org/ 下载

完整的 blender 示例文件可在: ProgramArcadeGames.com/chapters/08_intro_to_animation/simple_block_move.blend 获得。

回顾

多项选择测验

In the bouncing rectangle program, if rect_change_x is positive and rect_change_y is negative, which way will the rectangle travel? Up   Up and right   Right   Down and right   Down   Down and left   Left   Up and left     In the bouncing rectangle program, if rect_change_x is zero and rect_change_y is positive, which way will the rectangle travel? Up   Up and right   Right   Down and right   Down   Down and left   Left   Up and left     This code is supposed to draw a white rectangle. But when the program is run, no rectangle shows up. Why? import pygame # Define some colors BLACK    = (   0,   0,   0) WHITE    = ( 255, 255, 255) pygame.init() # Set the height and width of the screen size = [700, 500] screen = pygame.display.set_mode(size) # Loop until the user clicks the close button. done = False # Used to manage how fast the screen updates clock = pygame.time.Clock() # -------- Main Program Loop ----------- while not done:     for event in pygame.event.get():         if event.type == pygame.QUIT:             done = True     # Set the screen background     screen.fill(BLACK)     # Draw the rectangle     pygame.draw.rect(screen, WHITE, [50, 50, 50, 50])     # Limit to 20 frames per second     clock.tick(20) # Be IDLE friendly. If you forget this line, the program will ’hang’ # on exit. pygame.quit() The rectangle dimensions are offscreen.   There is no flip command.   The rectangle is the same color as the background.   The rectangle is drawn outside the main program loop.   The rectangle is too small to see.   The rectangle should be drawn earlier in the code.     This code is supposed to draw a white rectangle. But when the program is run, no rectangle shows up. Curiously, when the user hits the close button, the rectangle briefly appears before the program closes. Why? import pygame # Define some colors BLACK    = (   0,   0,   0) WHITE    = ( 255, 255, 255) pygame.init() # Set the height and width of the screen size = [700, 500] screen = pygame.display.set_mode(size) # Loop until the user clicks the close button. done = False # Used to manage how fast the screen updates clock = pygame.time.Clock() # -------- Main Program Loop ----------- while not done:     for event in pygame.event.get():         if event.type == pygame.QUIT:             done = True     # Set the screen background     screen.fill(BLACK)     # Draw the rectangle     pygame.draw.rect(screen, WHITE, [50, 50, 50, 50])     # Limit to 20 frames per second     clock.tick(20) # Go ahead and update the screen with what we’ve drawn. pygame.display.flip() # Be IDLE friendly. If you forget this line, the program will ’hang’ # on exit. pygame.quit() The rectangle dimensions are offscreen.   The flip is unindented and doesn’t show until after the program ends.   The rectangle is the same color as the background.   The rectangle is drawn outside the main program loop.   The rectangle is too small to see.   The flip should be done before the rectangle is drawn.     This version of “The Bouncing Rectangle” doesn’t work. The rectangle won’t move. Why? import pygame # Define some colors BLACK    = (   0,   0,   0) WHITE    = ( 255, 255, 255) pygame.init() # Set the height and width of the screen size = [700, 500] screen = pygame.display.set_mode(size) #Loop until the user clicks the close button. done = False # Used to manage how fast the screen updates clock = pygame.time.Clock() # Speed and direction of rectangle rect_change_x = 5 rect_change_y = 5 # -------- Main Program Loop ----------- while not done:     for event in pygame.event.get():         if event.type == pygame.QUIT:             done=True     # Starting position of the rectangle     rect_x = 50     rect_y = 50     # Move the rectangle starting point     rect_x += rect_change_x     rect_y += rect_change_y     # Bounce the ball if needed     if rect_y > 450 or rect_y < 0:         rect_change_y = rect_change_y * -1     if rect_x > 650 or rect_x < 0:         rect_change_x = rect_change_x * -1     # Set the screen background     screen.fill(BLACK)     # Draw the rectangle     pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])     # Limit to 20 frames per second     clock.tick(20)     # Go ahead and update the screen with what we’ve drawn.     pygame.display.flip() # Be IDLE friendly. If you forget this line, the program will ’hang’ # on exit. pygame.quit () pygame.draw.rect doesn’t change where the rectangle is drawn based on the variables.   rect_x and rect_y are reset to 50 each time through the loop.   The 50,50 in the draw command also needs to be changed to rect_x,rect_y   The lines to adjust rect_x and rect_y need to be outside the while loop.     What is the correct code to make the rectangle bounce of the left and right sides of the screen? if rect_x > 450 or rect_x < 0: rect_x = rect_x * -1   if rect_x > 450 or rect_x < 0: rect_change_x = rect_change_x * -1   if rect_y > 450 or rect_y < 0: rect_y = rect_y * -1   if rect_y > 450 or rect_y < 0: rect_change_y = rect_change_y * -1     Why does this code not work for drawing stars? for i in range(50):     x = random.randrange(0,400)     y = random.randrange(0,400)     pygame.draw.circle(screen, WHITE, [x, y], 2) The stars are drawn offscreen.   The variable i should be used when drawing stars.   The stars get brand-new locations each time a frame is drawn.   The stars are drawn too small to be seen.   The x and y coordinates are reversed.

简答工作表

Why does using this code in the main loop not work to move the rectangle? rect_x = 50 pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50]) rect_x += 1   The example code to bounce a rectangle used a total of four variables. What did each variable represent?   If the screen is 400 pixels tall, and the shape is 20 pixels high, at what point should the code check to see if the shape is in contact with the bottom of the screen.   Explain what is wrong with the following code (explain it, don’t just correct the code): if rect_y > 450 or rect_y < 0:     rect_y = rect_y * -1   A student is animating a stick figure. He creates separate variables for tracking the position of the head, torso, legs, and arms. When the figure moves to the right he adds one to each of the variables. Explain an easier way to do this that only requires one pair of x, y variables.   When drawing a starry background, explain why it doesn’t work to put code like this in the main program loop: for i in range(50):     x = random.randrange(0, 400)     y = random.randrange(0, 400)     pygame.draw.circle(screen, WHITE, [x, y], 2)   Explain how to animate dozens of items at the same time.   If you have a list of coordinates like the following, what code would be required to print out the array location that holds the number 33? stars = [[ 3,  4],           [33, 94],           [ 0,  0]]   This code example causes snow to fall: # Process each snow flake in the list for i in range(len(snow_list)):     # Get the x and y from the lies     x = snow_list[i][0]     y = snow_list[i][1]     # Draw the snow flake     pygame.draw.circle(screen, WHITE, [x, y], 2)     # Move the snow flake down one pixel     snow_list[i][1] += 1 So does the example below. Explain why this example works as well. # Process each snow flake in the list for i in range(len(snow_list)):     # Draw the snow flake     pygame.draw.circle(screen, WHITE, snow_list[i], 2)     # Move the snow flake down one pixel     snow_list[i][1] += 1   Take a look at the radar_sweep.py program. You can find this example under the “graphics examples” subsection on the examples page. The radar_sweep.py is near the end of that list. Explain how this program animates the sweep to go in a circle.

锻炼

查看附录中本章附带的练习“动画”。

十、函数

使用函数有两个原因。首先,它们使代码更容易阅读和理解。其次,它们允许代码被多次使用。

想象一组绘制如下所示的树的代码。为此,程序员执行以下命令:

pygame.draw.rect(screen, BROWN, [60, 400, 30, 45])

pygame.draw.polygon(screen, GREEN, [[150, 400], [75, 250], [0, 400]])

pygame.draw.polygon(screen, GREEN, [[140, 350], [75, 230], [10, 350]])

A978-1-4842-1790-0_10_Figa_HTML.jpg

简单树

这三行代码看起来并不像是在画一棵树!如果我们有多棵树或者复杂的物体,就很难理解正在画的是什么。

通过定义一个函数,我们可以使程序更容易阅读。要定义一个函数,首先使用def命令。在def命令之后是函数名。在这种情况下,我们称之为draw_tree。我们对函数名使用与变量名相同的规则。

函数名后面是一组括号和一个冒号。该函数的所有命令都将在内部缩进。请参见下面的示例:

def draw_tree():

pygame.draw.rect(screen, BROWN, [60, 400, 30, 45])

pygame.draw.polygon(screen, GREEN, [[150, 400], [75, 250], [0, 400]])

pygame.draw.polygon(screen, GREEN, [[140, 350], [75, 230], [10, 350]])

就其本身而言,这段代码不会导致树的绘制。它会告诉计算机如何做draw_tree。您必须调用函数来实际运行函数中的代码,并绘制树:

draw_tree()

有了定义要绘制的不同事物的整个函数库,最终的程序可能看起来像这样:

draw_tree()

draw_house()

draw_car()

draw_killer_bunny()

记住draw_tree有三行代码。这些命令中的每一个,像draw_house,都有多行代码。通过使用函数,我们可以重复命令,而不必重复其中包含的所有代码,从而使程序变得更小。

函数名非常重要。如果函数名是描述性的,那么即使非程序员也应该能够阅读一组代码并了解发生了什么。函数名遵循与变量名相同的规则,应该以小写字母开头。

函数参数

函数可以带参数。很有可能,你已经在数学课上用过参数了。在下面的球体体积公式中,函数 v 有一个参数:r,它代表半径。这个函数可以用来确定任何球体的体积,不管它的半径是多少。这些参数用于根据传递给函数的参数改变结果,从而增加函数的灵活性。

 $$ v(r)=\frac{4}{3}\pi {r}³. $$

例如,我们名为draw_tree()的函数在一个特定的地方绘制树。但是该函数可以更改为接受一个指定在何处绘制树的参数。例如draw_tree(screen, 0, 230)将在(0,230)的(x,y)位置绘制树。

调整树的函数可能如下所示:

def draw_tree(screen, x, y):

pygame.draw.rect(screen, BROWN, [60+x, 170+y, 30, 45])

pygame.draw.polygon(screen, GREEN, [[150+x,170+y],[75+x,20+y], [x,170+y]])

pygame.draw.polygon(screen, GREEN, [[140+x,120+y], [75+x,y], [10+x,120+y]])

这将允许我们在任何我们喜欢的地方画多棵树:

draw_tree(screen, 0, 230)

draw_tree(screen, 200, 230)

draw_tree(screen, 400, 230)

这里有一个不同的函数,可以不使用图形运行。这个函数将计算并打印出一个球体的体积:

def volume_sphere(radius):

pi = 3.141592653589

volume = (4 / 3) * pi * radius ** 3

print("The volume is", volume)

参数是在调用函数时赋值的,而不是在定义函数时。

函数的名字是volume_sphere。进入函数的数据将存储在一个名为radius的新变量中。生成的体积被打印到屏幕上。radius变量在这里没有得到一个值。新程序员经常会感到困惑,因为在定义函数时,参数变量没有赋值,所以看起来不合法。当函数被调用时,参数被赋予一个值。

要调用此函数,请使用:

volume_sphere(22)

函数中的radius变量被创建并用值 22 初始化。一旦执行到达对该函数的调用,就运行该函数的代码。

如果我们需要传入多个值呢?可以向函数传递多个参数,每个参数用逗号分隔:

def volume_cylinder(radius, height):

pi = 3.141592653589

volume = pi * radius ** 2 * height

print("The volume is", volume)

该函数可以通过以下方式调用:

volume_cylinder(12, 3)

参数是按顺序做的,所以radius会得到 12,height会得到 3 的值。

返回和捕获值

不幸的是,这些示例函数是有限的。为什么?如果一个人想用volume_cylinder函数来计算六瓶装啤酒的体积,那是行不通的。它只打印出一个圆柱体的体积。不可能将一个气缸体积的函数结果用在一个等式中,然后乘以 6 来得到六个气缸的体积。

返回值

这可以通过使用一个return语句来解决。例如:

# Add two numbers and return the results

def sum_two_numbers(a, b):

result = a + b

return result

Return 不是函数,不使用括号。不要做return(result)

这只让我们走了一半。因为如果我们现在调用这个函数,不会发生什么。数字相加。他们会回到我们身边。但我们对结果无动于衷。

# This doesn’t do much, because we don’t capture the result

sum_two_numbers(22, 15)

捕获返回值

我们需要捕捉结果。我们通过设置一个变量等于函数返回值来实现:

# Store the function’s result into a variable

my_result = sum_two_numbers(22, 15)

print(my_result)

现在结果没有丢失。它存储在my_result中,我们可以打印或使用其他方式。

改进 volume_cylinder 示例

def volume_cylinder(radius, height):

pi = 3.141592653589

volume = pi * radius ** 2 * height

return volume

因为有了return,这个函数可以在以后作为方程式的一部分来计算六个装的体积,如下所示:

six_pack_volume = volume_cylinder(2.5, 5) * 6

volume_cylinder返回的值进入等式并乘以 6。

打印值的函数和返回值的函数有很大的区别。看看下面的代码,并尝试一下。

# Function that prints the result

def sum_print(a, b):

result = a + b

print(result)

# Function that returns the results

def sum_return(a, b):

result = a + b

return result

# This prints the sum of 4+4

sum_print(4, 4)

# This does not

sum_return(4, 4)

# This will not set x1 to the sum

# It actually gets a value of ’None’

x1 = sum_print(4, 4)

# This will

x2 = sum_return(4, 4)

第一次使用函数时,看到这样的代码并不罕见:

def calculate_average(a, b):

""" Calculate an average of two numbers """

result = (a * b) / 2

return result

# Pretend you have some code here

x = 45

y = 56

# Wait, how do I print the result of this?

calculate_average(x, y)

我们如何打印calculate_average的结果?程序不能打印出result,因为这个变量只存在于函数内部。相反,使用变量来获取结果:

def calculate_average(a, b):

""" Calculate an average of two numbers """

result = (a * b) / 2

return result

# Pretend you have some code here

x = 45

y = 56

average = calculate_average(x, y)

print(average)

记录函数

Python 中的函数通常有一个注释作为函数的第一条语句。这个注释用三个双引号分隔,称为 docstring。一个函数可能看起来像:

def volume_cylinder(radius, height):

"""Returns volume of a cylinder given radius, height."""

pi = 3.141592653589

volume = pi * radius ** 2 * height

return volume

在函数中使用文档字符串的好处在于,可以使用 http://sphinx-doc.org/ 这样的工具将注释提取出来并放到记录代码的网站上。大多数语言都有类似的工具,可以帮助您轻松编写代码。当您开始处理更大的程序时,这可以节省大量时间。

变量作用域

函数的使用引入了作用域的概念。作用域是指代码中变量处于活动状态并且可以被访问的地方。例如,看看下面的代码:

# Define a simple function that sets

# x equal to 22

def f():

x = 22

# Call the function

f()

# This fails, x only exists in f()

print(x)

最后一行会产生一个错误,因为 x 只存在于f()函数内部。这个变量是在调用f()时创建的,一旦f()结束,它所使用的内存就会被释放。

这就是事情变得复杂的地方。

一个更令人困惑的规则是访问在f()函数之外创建的变量。在下面的代码中,x是在f()函数之前创建的,因此可以从f()函数内部读取。

# Create the x variable and set to 44

x = 44

# Define a simple function that prints x

def f():

print(x)

# Call the function

f()

只有在函数不改变值的情况下,才可以在函数内部读取在函数之前创建的变量。这段代码与上面的代码非常相似,将会失败。计算机会声称它不知道x是什么。

# Create the x variable and set to 44

x = 44

# Define a simple function that prints x

def f():

x += 1

print(x)

# Call the function

f()

与 Python 相比,其他语言在变量和范围的创建方面有更复杂的规则。因为 Python 简单明了,所以它是一种很好的入门语言。

传递副本

函数通过创建原始函数的副本来传递它们的值。例如:

# Define a simple function that prints x

def f(x):

x += 1

print(x)

# Set y

y = 10

# Call the function

f(y)

# Print y to see if it changed

print(y)

即使f()函数增加了传递给它的值,y的值也不会改变。函数中作为参数列出的每个变量都是一个全新的变量。该变量的值是从调用它的地方复制的。

这在前面的例子中相当简单。令人困惑的是,调用函数的代码和函数本身是否有同名的变量。下面的代码与前面的清单相同,但是它没有使用y,而是使用了x

# Define a simple function that prints x

def f(x):

x += 1

print(x)

# Set x

x = 10

# Call the function

f(x)

# Print x to see if it changed

print(x)

输出与使用y的程序相同。尽管函数和周围的代码都使用x作为变量名,但实际上有两个不同的变量。一个变量x存在于函数内部,另一个变量x存在于函数外部。

函数调用函数

一个函数调用另一个函数是完全可能的。例如,假设定义了如下函数:

def arm_out(which_arm, palm_up_or_down):

# code would go here

def hand_grab(hand, arm):

# code goes here

然后可以创建另一个函数来调用其他函数:

def macarena():

arm_out("right", "down")

arm_out("left", "down")

arm_out("right", "up")

arm_out("left", "up")

hand_grab("right", "left arm")

hand_grab("left", "right arm")

# etc

主要函数和全局

全局变量纯粹是邪恶的。

随着程序变得越来越大,保持代码的组织性和函数性是很重要的。Python 允许我们在“缩进级别 0”编写代码这描述了我们到目前为止编写的大部分代码。我们的代码排在左边,不包含在函数中。

这种哲学就像把你的衣服堆在地板中间,或者把你所有的工具堆在工作台上。只有当你没有太多东西的时候,它才起作用。即使你没有太多的东西,它仍然是凌乱的。

你所有的代码和变量都应该放在函数中。这将使你的代码有条理。当你需要追踪程序中的错误时,它也会有所帮助。在“缩进级别 0”创建的变量称为全局变量。全局变量是一件非常糟糕的事情。为什么呢?因为任何地方的任何一段代码都可以改变它们的值。如果你有一个 50,000 行的程序,每一行代码都可以改变全局变量。相反,如果您将变量保存在函数中,那么只有函数中的代码可以更改变量。因此,如果变量中有一个意外值,您只需要查看函数中大约 50 行代码。否则你必须检查整个程序中的每一行代码!

用 Python 编写程序的一个更好的方法是遵循以下模式:

def main():

print("Hello world.")

main()

在这种情况下,我通常在缩进级别 0 运行的所有代码都放在了main函数中。文件的最后一行调用main

但是等等!我们还需要解决另一个问题。在第十五章中,我们将讨论如何把我们的程序分成多个文件。我们可以使用import命令从我们创建的其他模块中引入函数。如果我们在这个模块上使用import命令,它会自动开始运行main函数。我们不想那样。我们希望导入它的程序控制函数何时被调用。

为了解决这个问题,我们可以让我们的程序检查一个由 Python 自动定义的全局变量。(我知道,我刚才说全局变量不好,对吧?)那个变量叫做__name__,前后有两个下划线。我们可以检查它,看看这段代码是否正在被导入或运行。如果代码正在运行,Python 会自动将该变量的值设置为__main__。通过使用if语句,我们将只在代码运行时调用main函数。否则代码将只定义main函数。导入它的代码可以在需要时调用该函数。

所有 Python 代码都应该这样运行:

def main():

print("Hello world.")

if __name__ == "__main__":

main()

我喜欢将 Python 作为第一语言的原因之一是,除非你需要,否则不需要使用这种复杂性。其他语言,比如 Java,不管你的程序有多小,都需要它。

为了使本书更简单,我们没有使用这种模式展示我们的例子。但是在这本书之后,你的程序可能会变得足够复杂,如果你不“把你所有的衣服都扔进一堆”,生活会变得更容易。

如果你对编程非常感兴趣,试着从现在开始用这种方式编写你的程序。虽然开始时可能会有一点挑战,但它会使以后编写程序变得更容易。这也是了解如何正确管理数据及其范围的好方法。

这里有一个例子,展示了如何使用这个模式来做基本的 pygame 模板:programarcadegames.com/python_examples/f.php?file=pygame_base_template_proper.py

不需要使用这个模板。如果你把衣服堆在地板中间,我一会儿没问题。我很高兴你穿着衣服。(对于爱整洁的人来说,当我们读到 Python 类这一章时,我们可以更清楚地理解这个程序。)

简短的例子

对于下面的每一个例子,想想会打印出什么。检查一下你是否正确。如果你没有猜对,花时间去理解为什么。

# Example 1

def a():

print("A")

def b():

print("B")

def c():

print("C")

a()

# Example 2

def a():

b()

print("A")

def b():

c()

print("B")

def c():

print("C")

a()

# Example 3

def a():

print("A")

b()

def b():

print("B")

c()

def c():

print("C")

a()

# Example 4

def a():

print("A start")

b()

print("A end")

def b():

print("B start")

c()

print("B end")

def c():

print("C start and end")

a()

# Example 5

def a(x):

print("A start, x =",x)

b(x + 1)

print("A end, x =",x)

def b(x):

print("B start, x =",x)

c(x + 1)

print("B end, x =",x)

def c(x):

print("C start and end, x =",x)

a(5)

# Example 6

def a(x):

x = x + 1

x = 3

a(x)

print(x)

# Example 7

def a(x):

x = x + 1

return x

x = 3

a(x)

print(x)

# Example 8

def a(x):

x = x + 1

return x

x = 3

x = a(x)

print(x)

# Example 9

def a(x, y):

x = x + 1

y = y + 1

print(x, y)

x = 10

y = 20

a(y, x)

# Example 10

def a(x, y):

x = x + 1

y = y + 1

return x

return y

x = 10

y = 20

z = a(x, y)

print(z)

# Example 11

def a(x, y):

x = x + 1

y = y + 1

return x, y

x = 10

y = 20

z = a(x, y)

print(z)

# Example 12

def a(x, y):

x = x + 1

y = y + 1

return x, y

x = 10

y = 20

x2, y2 = a(x, y) # Most computer languages don’t support this

print(x2)

print(y2)

# Example 13

def a(my_data):

print("function a, my_data =  ", my_data)

my_data = 20

print("function a, my_data =  ", my_data)

my_data = 10

print("global scope, my_data =", my_data)

a(my_data)

print("global scope, my_data =", my_data)

# Example 14

def a(my_list):

print("function a, list =  ", my_list)

my_list = [10, 20, 30]

print("function a, list =  ", my_list)

my_list = [5, 2, 4]

print("global scope, list =", my_list)

a(my_list)

print("global scope, list =", my_list)

# Example 15

# New concept!

# Covered in more detail in Chapter``13

def a(my_list):

print("function a, list =  ", my_list)

my_list[0] = 1000

print("function a, list =  ", my_list)

my_list = [5, 2, 4]

print("global scope, list =", my_list)

a(my_list)

print("global scope, list =", my_list)

泥球游戏示例

"""

This is a sample text-only game that demonstrates the use of functions.

The game is called "Mudball" and the players take turns lobbing mudballs

at each other until someone gets hit.

"""

import math

import random

def print_instructions():

""" This function prints the instructions. """

# You can use the triple-quote string in a print statement to

# print multiple lines.

print("""

Welcome to Mudball! The idea is to hit the other player with a mudball.

Enter your angle (in degrees) and the amount of PSI to charge your gun

with.

""")

def calculate_distance(psi, angle_in_degrees):

""" Calculate the distance the mudball flies. """

angle_in_radians = math.radians(angle_in_degrees)

distance = .5 * psi ** 2 * math.sin(angle_in_radians) * math.cos(angle_in_radians)

return distance

def get_user_input(name):

""" Get the user input for psi and angle. Return as a list of two

numbers. """

# Later on in the ’exceptions’ chapter, we will learn how to modify

# this code to not crash the game if the user types in something that

# isn’t a valid number.

psi = float(input(name + " charge the gun with how many psi? "))

angle = float(input(name + " move the gun at what angle? "))

return psi, angle

def get_player_names():

""" Get a list of names from the players. """

print("Enter player names. Enter as many players as you like.")

done = False

players = []

while not done:

player = input("Enter player (hit enter to quit): ")

if len(player) > 0:

players.append(player)

else:

done = True

print()

return players

def process_player_turn(player_name, distance_apart):

""" The code runs the turn for each player.

If it returns False, keep going with the game.

If it returns True, someone has won, so stop. """

psi, angle = get_user_input(player_name)

distance_mudball = calculate_distance(psi, angle)

difference = distance_mudball - distance_apart

# By looking ahead to the chapter on print formatting, these

# lines could be made to print the numbers is a nice formatted

# manner.

if difference > 1:

print("You went", difference, "yards too far!")

elif difference < -1:

print("You were", difference * -1, "yards too short!")

else:

print("Hit!", player_name, "wins!")

return True

print()

return False

def main():

""" Main program. """

# Get the game started.

print_instructions()

player_names = get_player_names()

distance_apart = random.randrange(50, 150)

# Keep looking until someone wins

done = False

while not done:

# Loop for each player

for player_name in player_names:

# Process their turn

done = process_player_turn(player_name, distance_apart)

# If someone won, ’break’ out of this loop and end the game.

if done:

break

if __name__ == "__main__":

main()

回顾

多项选择测验

What does this code print? def f():     print("f") def g():     print("g") print("a") f   a   g   f g a   a f g     What does this code print? def f():     print("f") def g():     print("g") g() print("a") f() f   a   g   g a f   f g a   a f g     What does this code print? def f(a):     print(a) a = 5 f(a + 1) 0   5   Nothing   6   a+1   a     What does this code print? def f(a, b):     print(a)     print(b) a = 1 b = 2 f(b, a) 1 2   2 1   a b   b a     What does this code print? def f(a):     return a + 1 b = f(5) print(b) 5   6   a+1   b   a     What is wrong with this code? def sum_list(list):     sum = 0     for item in list:         sum + = item         return sum list=[45, 2, 10, -5, 100] print(sum_list(list)) The return statement should print the sum instead.   The return statement should be unindented one tab stop.   The return statement should be unindented two tab stops.   The variable i should be used instead of item.   The sum_list function may not be placed inside the print statement.     What will this print? a = 3 def f():     print(a) f() f   3   Nothing   An error, a is undefined.     What will this print? a = 3 def f():         a = a + 1     print(a) f() f   4   Nothing   An error, a is undefined.     What will this print? def f(a):     return a + 1 print( f( f(5) ) ) 5   6   7   An error.     What will this print? def foo():     x = 3     print("foo has been called") x = 10 print("x=", x) foo() print("x=", x) x= 10 foo has been called x= 10   x= 3 foo has been called x= 10   x= 10 foo has been called x= 3   An error.

简答工作表

对于下面的代码,写一个关于它将输出什么的预测。然后运行代码并声明您的预测是否准确。如果你的预测不正确,确保你明白为什么。

Block 1 (Remember, guess AND actual.) for i in range(5):     print(i + 1)   Block 2 for i in range(5):     print(i)     i = i + 1   Block 3 x = 0 for i in range(5):     x += 1 print(x)   Block 4 x = 0 for i in range(5):     for j in range(5):         x += 1 print(x)   Block 5 for i in range(5):     for j in range(5):         print(i, j)   Block 6 for i in range(5):     for j in range(5):         print("*", end="")         print()   Block 7 for i in range(5):     for j in range(5):         print("*", end="")     print()   Block 8 for i in range(5):     for j in range(5):         print("*", end="") print()   Block 9 # This is supposed to sum a list of numbers # What is the mistake here? my_list = [5, 8, 10, 4, 5] i = 0 for i in my_list:     i = i + my_list[i] print(i)   Block 10 for i in range(5):     x = 0     for j in range(5):         x += 1 print(x)   Block 11 import random play_again = "y" while play_again == "y":     for i in range(5):         print(random.randrange(2), end="")     print()     play_again = input("Play again? ") print("Bye!")   Block 12 def f1(x):     print(x) y = 3 f1(y)   Block 13 def f2(x):     x = x + 1     print(x) y = 3 f2(y) print(y)   Block 14 def f3(x):     x = x + 1     print(x) x = 3 f3(x) print(x)   Block 15 def f4(x):     z = x + 1     print(z) x = 3 f4(x) print(z)   Block 16 def foo(x):     x = x + 1     print("x=", x) x = 10 print("x=", x) foo(x) print("x=", x)   Block 17 def f():     print("f start")     g()     h()     print("f end") def g():     print("g start")     h()     print("g end") def h():     print("h") f()   Block 18 def foo():     x = 3     print("foo has been called") x = 10 print("x=", x) foo() print("x=", x)   Block 19 (This demonstrates a new concept that won’t be fully explained until Chapter 13.) def a(x):     print("a", x)     x = x + 1     print("a", x) x = 1 print("main", x) a(x) print("main", x) def b(y):     print("b", y[1])     y[1] = y[1] + 1     print("b", y[1]) y=[123, 5] print("main", y[1]) b(y) print("main", y[1]) def c(y):     print("c", y[1])     y = [101, 102]     print("c", y[1]) y = [123, 5] print("main", y[1]) c(y) print("main", y[1])

校正码

下一节涉及到查找代码中的错误。如果你找不到错误,可以查看在线视频,寻找答案和错误的解释。

Correct the following code: (Don’t let it print out the word “None.”) def sum(a, b, c):     print(a + b + c) print(sum(10, 11, 12))   Correct the following code: (x should increase by one, but it doesn’t.) def increase(x):     return x + 1 x = 10 print("X is", x, " I will now increase x." ) increase(x) print("X is now", x)   Correct the following code: def print_hello:     print("Hello") print_hello()   Correct the following code: def count_to_ten():     for i in range[10]:         print(i) count_to_ten()   Correct the following code: def sum_list(list):     for i in list:         sum = i         return sum list = [45, 2, 10, -5, 100] print(sum_list(list))   Correct the following code: (This almost reverses the string. What is wrong?) def reverse(text):     result = ""     text_length = len(text)     for i in range(text_length):         result = result + text[i * -1]     return result text = "Programming is the coolest thing ever." print(reverse(text))   Correct the following code: def get_user_choice():     while True:         command = input("Command: ")         if command = f or command = m or command = s or command = d or command = q:             return command         print("Hey, that’s not a command. Here are your options:" )         print("f - Full speed ahead")         print("m - Moderate speed")         print("s - Status")         print("d - Drink")         print("q - Quit") user_command = get_user_choice() print("You entered:", user_command)

对于此部分,编写满足以下各项的代码:

Write a function that prints out “Hello World.”   Write code that will call the function in the prior problem.   Write a function that prints out “Hello Bob” and will take a parameter to let the caller specify the name. Do not put an input statement inside the function! Use a parameter.   Write code that will call the function in the prior problem.   Write a function that will take two numbers as parameters (not as input from the user) and print their product (i.e., multiply them).   Write code that will call the prior function.   Write a function that takes in two parameters. The first parameter will be a string named phrase. The second parameter will be a number named count. Print phrase to the screen count times. (e.g., the function takes in "Hello" and 5 then prints "Hello" five times.)   Write code to call the previous function.   Write code for a function that takes in a number and returns the square of that number. (I’m not asking for the square root but the number squared.) Note, this function should RETURN the answer, not print it out.   Write code to call the function above and print the output.   Write a function that takes three numbers as parameters and returns the centrifugal force. The formula for centrifugal force is:  $$ F=m\frac{v²}{r} $$ F is force, m is mass, r is radius, and v is angular velocity.   Write code to call the function above and display the result.   Write a function that takes a list of numbers as a parameter and prints out each number individually using a for loop.

锻炼

查看附录,了解本章附带的练习“函数”。

十一、控制器和屏幕

我们如何使用键盘、鼠标或游戏控制器来移动物体?

A978-1-4842-1790-0_11_Figa_HTML.jpg

到目前为止,我们已经展示了如何在屏幕上制作动画,但没有展示如何与它们交互。我们如何使用鼠标、键盘或游戏控制器来控制屏幕上的动作?谢天谢地,这很容易。

首先,有一个可以在屏幕上移动的物体是必要的。最好的方法是用一个函数获取 x 和 y 坐标,然后在那个位置绘制一个对象。所以回到第十章!让我们来看看如何编写一个函数来绘制一个对象。

所有的 pygame 绘制函数都需要一个screen参数来让 Pygame 知道在哪个窗口上绘制。我们需要将它传递给我们创建的在屏幕上绘制对象的任何函数。

该函数还需要知道在屏幕上的何处绘制对象。这个函数需要一个 x 和 y,我们将这个位置作为参数传递给这个函数。下面是定义一个函数的示例代码,该函数在被调用时将绘制一个雪人:

def draw_snowman(screen, x, y):

# Draw a circle for the head

pygame.draw.ellipse(screen, WHITE, [35+x, 0+y, 25, 25])

# Draw the middle snowman circle

pygame.draw.ellipse(screen, WHITE, [23+x, 20+y, 50, 50])

# Draw the bottom snowman circle

pygame.draw.ellipse(screen, WHITE, [0+x, 65+y, 100, 100])

然后在主程序循环中,可以绘制多个雪人,如下图所示。

A978-1-4842-1790-0_11_Figb_HTML.jpg

由函数绘制的雪人

# Snowman in upper left

draw_snowman(screen, 10, 10)

# Snowman in upper right

draw_snowman(screen, 300, 10)

# Snowman in lower left

draw_snowman(screen, 10, 300)

完整的工作示例可从以下网址在线获得:

ProgramArcadeGames.com/python_examples/f.php?file=functions_and_graphics.py

很有可能,从先前的实验室中,你已经有了一个可以画出很酷的东西的代码。但是你怎么把它变成一个函数呢?让我们举一个画简笔画的代码的例子:

# Head

pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

# Legs

pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)

pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

# Body

pygame.draw.line(screen, RED, [100,100], [100,90], 2)

# Arms

pygame.draw.line(screen, RED, [100,90], [104,100], 2)

pygame.draw.line(screen, RED, [100,90], [96,100], 2)

A978-1-4842-1790-0_11_Figc_HTML.jpg

人物线条画

通过添加一个函数def并缩进其下的代码,可以很容易地将这段代码放入函数中。我们需要引入该函数绘制简笔画所需的所有数据。我们需要screen变量来告诉函数在哪个窗口上绘制,以及在哪里绘制简笔画的 x 和 y 坐标。

但是我们不能在程序循环中间定义函数!这段代码应该从程序的主要部分删除。函数声明应该在程序的开始。我们需要将代码移到顶部。见图有助于形象化。

def draw_stick_figure(screen,x,y):

# Head

pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

# Legs

pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)

pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

# Body

pygame.draw.line(screen, RED, [100,100], [100,90], 2)

# Arms

pygame.draw.line(screen, RED, [100,90], [104,100], 2)

pygame.draw.line(screen, RED, [100,90], [96,100], 2)

A978-1-4842-1790-0_11_Figd_HTML.jpg

制作一个功能并把它放在正确的位置

现在,这段代码接受一个 x 和 y 坐标。不幸的是,它实际上对它们没有任何作用。您可以指定任何想要的坐标;简笔画总是画在同一个精确的位置。不是很有用。下一个代码示例将 x 和 y 坐标添加到我们之前的代码中。

def draw_stick_figure(screen, x, y):

# Head

pygame.draw.ellipse(screen, BLACK,[96+x,83+y,10,10],0)

# Legs

pygame.draw.line(screen, BLACK, [100+x,100+y], [105+x,110+y], 2)

pygame.draw.line(screen, BLACK, [100+x,100+y], [95+x,110+y], 2)

# Body

pygame.draw.line(screen, RED, [100+x,100+y], [100+x,90+y], 2)

# Arms

pygame.draw.line(screen, RED, [100+x,90+y], [104+x,100+y], 2)

pygame.draw.line(screen, RED, [100+x,90+y], [96+x,100+y], 2)

但问题是,图形已经画出了离原点一定的距离。它假设原点为(0,0 ),并向下绘制简笔画超过大约 100 个像素。看下一张图以及简笔画是如何不在传入的(0,0)坐标处绘制的。

A978-1-4842-1790-0_11_Fige_HTML.jpg

人物线条画

通过在函数中添加 x 和 y,我们将棒图的原点移动该量。例如,如果我们调用:

draw_stick_figure(screen, 50, 50)

代码没有将简笔画放在(50,50)处。它将原点下移超过 50 个像素。因为我们的简笔画已经在大约(100,100)处被绘制,随着原点的移动,该图形大约是(150,150)。我们如何解决这个问题,使图形实际绘制在函数调用请求的地方?

A978-1-4842-1790-0_11_Figf_HTML.jpg

寻找最小的 X 和 Y 值

如上图所示,求最小的 x 值和最小的 y 值。然后从函数中的每个 x 和 y 中减去这个值。不要弄乱高度和宽度值。这里有一个例子,我们减去了最小的 x 和 y 值:

def draw_stick_figure(screen, x, y):

# Head

pygame.draw.ellipse(screen, BLACK,[96-95+x,83-83+y,10,10],0)

# Legs

pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [105-95+x,110-83+y], 2)

pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [95-95+x,110-83+y], 2)

# Body

pygame.draw.line(screen, RED, [100-95+x,100-83+y], [100-95+x,90-83+y], 2)

# Arms

pygame.draw.line(screen, RED, [100-95+x,90-83+y], [104-95+x,100-83+y], 2)

pygame.draw.line(screen, RED, [100-95+x,90-83+y], [96-95+x,100-83+y], 2)

或者,为了使程序更简单,自己做减法:

def draw_stick_figure(screen, x, y):

# Head

pygame.draw.ellipse(screen, BLACK, [1+x,y,10,10], 0)

# Legs

pygame.draw.line(screen, BLACK,[5+x,17+y], [10+x,27+y], 2)

pygame.draw.line(screen, BLACK, [5+x,17+y], [x,27+y], 2)

# Body

pygame.draw.line(screen, RED, [5+x,17+y], [5+x,7+y], 2)

# Arms

pygame.draw.line(screen, RED, [5+x,7+y], [9+x,17+y], 2)

pygame.draw.line(screen, RED, [5+x,7+y], [1+x,17+y], 2)

老鼠

太好了,现在我们知道如何写一个函数来画一个物体在特定的坐标。我们怎么得到那些坐标?最容易操作的是鼠标。获得坐标只需要一行代码:

pos = pygame.mouse.get_pos()

诀窍是坐标作为列表返回,或者更具体地说,作为不可修改的元组返回。x 和 y 值都存储在同一个变量中。所以如果我们做一个print(pos)我们会得到下图所示的结果。

A978-1-4842-1790-0_11_Figg_HTML.jpg

协调

变量pos是一个由两个数字组成的元组。x 坐标在数组的位置 0,y 坐标在位置 1。这些可以很容易地取出并传递给绘制该项目的函数:

# Game logic

pos = pygame.mouse.get_pos()

x = pos[0]

y = pos[1]

# Drawing section

draw_stick_figure(screen, x, y)

获取鼠标应该进入主程序循环的游戏逻辑部分。函数调用应该在主程序循环的绘图部分进行。

唯一的问题是鼠标指针画在简笔画的正上方,很难看到,如下图所示。

A978-1-4842-1790-0_11_Figh_HTML.jpg

鼠标光标位于顶部的简笔画

在主程序循环之前,可以使用下面的代码隐藏鼠标:

# Hide the mouse cursor

pygame.mouse.set_visible(False)

完整的工作示例可在此处找到:

ProgramArcadeGames.com/python_examples/f.php?file=move_mouse.py

键盘

用键盘控制有点复杂。我们不能只从鼠标中抓取 x 和 y。键盘没有给出 x 和 y,我们需要:

  • 为我们的起始位置创建一个初始 x 和 y。
  • 设置按下箭头键时每帧的像素速度。(按键)
  • 释放箭头键时,将速度重置为零。(击键)
  • 根据速度调整每帧的 x 和 y。

这看起来很复杂,但这就像我们之前做的弹跳矩形一样,只是速度是由键盘控制的。

首先,在主循环开始之前设置位置和速度:

# Speed in pixels per frame

x_speed = 0

y_speed = 0

# Current position

x_coord = 10

y_coord = 10

在程序的主while循环中,我们需要向事件处理循环中添加一些项目。除了寻找一个pygame.QUIT事件,程序还需要寻找键盘事件。用户每次按键都会生成一个事件。

当一个键被按下时,产生一个pygame.KEYDOWN事件。当用户放开某个键时,会生成一个pygame.KEYUP事件。当用户按下一个键时,速度向量被设置为每帧 3 或-3 像素。当用户放开一个键时,速度向量被重置为零。最后通过矢量调整对象的坐标,然后绘制对象。请参见下面的代码示例:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# User pressed down on a key

elif event.type == pygame.KEYDOWN:

# Figure out if it was an arrow key. If so

# adjust speed.

if event.key == pygame.K_LEFT:

x_speed = -3

elif event.key == pygame.K_RIGHT:

x_speed = 3

elif event.key == pygame.K_UP:

y_speed = -3

elif event.key == pygame.K_DOWN:

y_speed = 3

# User let up on a key

elif event.type == pygame.KEYUP:

# If it is an arrow key, reset vector back to zero

if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:

x_speed = 0

elif event.key == pygame.K_UP or event.key == pygame.K_DOWN:

y_speed = 0

# Move the object according to the speed vector.

x_coord += x_speed

y_coord += y_speed

# Draw the stick figure

draw_stick_figure(screen, x_coord, y_coord)

有关完整示例,请参见:

ProgramArcadeGames.com/python_examples/f.php?file=move_keyboard.py

注意,这个例子并没有阻止角色离开屏幕边缘。为此,在游戏逻辑部分,需要一组if语句来检查x_coordy_coord的值。如果它们在屏幕的边界之外,则将坐标重置为边缘。具体的代码留给读者作为练习。

下表显示了 Pygame 中可以使用的键码的完整列表:

| Pygame Code(游戏代码) | 美国信息交换标准代码 | 普通名词 | | --- | --- | --- | | k _ 退格键 | \b | 退格键 | | k _ 返回 | \r | 返回 | | K_TAB | \t | 标签 | | K_ESCAPE | [ | 逃跑 | | k _ 空间 |   | 空间 | | k _ 逗号 | , | 逗号符号 | | k 减 | - | 负的 | | k _ 周期 | 。 | 句点斜线 | | k _ 斜线 | / | 向前 | | k0 | Zero | Zero | | k1 | one | one | | k2 | Two | Two | | k3 | three | three | | k4 | four | four | | K5 | five | five | | K6 | six | six | | k7 | seven | seven | | k8 | eight | eight | | K9 | nine | nine | | k _ 分号 | ; | 分号符号 | | k _ 等于 | = | 等号 | | k _ 左括号 | [ | 左边的 | | K_RIGHTBRACKET | ] | 正确 | | k _ 反斜杠 | \ | 反斜杠括号 | | k _ 反引号 | ` | 坟墓 | | K_a | a | a | | K_b | b | b | | K_c | c | c | | K_d | d | d | | K_e | e | e | | K_f | f | f | | K_g | g | g | | K_h | h | h | | K_i | 我 | 我 | | kj | j | j | | K_k | k | k | | K_l | l | l | | K_m | m | m | | K_n | n | n | | k0 | o | o | | K_p | p | p | | K_q | q | q | | K_r | r | r | | K_s | s | s | | K_t | t | t | | K_u | u | u | | K_v | v | v | | K_w | w | w | | K_x | x | x | | K_y | y | y | | kz | z | z | | k _ 删除 | 删除 |   | | K_KP0 | 小键盘 | Zero | | K_KP1 | 小键盘 | one | | K_KP2 | 小键盘 | Two | | K_KP3 | 小键盘 | three | | K_KP4 | 小键盘 | four | | K_KP5 | 小键盘 | five | | K_KP6 | 小键盘 | six | | K_KP7 | 小键盘 | seven | | K_KP8 | 小键盘 | eight | | K_KP9 | 小键盘 | 9 期 | | kkp 周期 | 。 | 键盘划分 | | K _ KP _ 除 | / | 键盘乘法 | | kkp 乘 | * | 键盘减号 | | K _ KP _ 减 | - | 键盘加号 | | S7-1200 可编程控制器 | + | 键盘输入 | | K_KP_ENTER | \r | 键盘等于 | | KP 等于 | = | 小键盘 | | K_UP | 起来 | 箭 | | K_DOWN | 向下 | 箭 | | 爵士 | 正确 | 箭 | | k _ 左 | 左边的 | 箭 | | k _ 插入 | 插入 |   | | K_HOME | 家 |   | | K_END | 目标 |   | | K_PAGEUP | 页,面,张,版 | 起来 | | K_PAGEDOWN | 页,面,张,版 | 向下 | | K_F1 | 子一代 |   | | K_F2 | 第二子代 |   | | K_F3 | 第三子代 |   | | F4 k | 法乐四联症 |   | | K_F5 | F5 |   | | F6 k | F6 |   | | F7 k | F7 |   | | F8 k | F8 |   | | K_F9 | F9 |   | | F10 k | F10 |   | | K_F11 | F11 |   | | K_F12 | F12 |   | | K_NUMLOCK | 键盘上的数字锁定键 |   | | K_CAPSLOCK | 帽锁 |   | | K_RSHIFT | 正确 | 变化 | | 克 _LSHIFT | 左边的 | 变化 | | S7-1200 可编程控制器 | 正确 | 计算机的 ctrl 按键 | | S7-1200 可编程控制器 | 左边的 | 计算机的 ctrl 按键 | | K_RALT | 正确 | 中高音 | | k _ lalt(消歧义) | 左边的 | 中高音 |

游戏控制器

游戏控制器需要一套不同的代码,但想法仍然很简单。

首先,检查计算机是否有操纵杆,并在使用前进行初始化。这应该只做一次。在主程序循环之前完成:

# Current position

x_coord = 10

y_coord = 10

# Count the joysticks the computer has

joystick_count = pygame.joystick.get_count()

if joystick_count == 0:

# No joysticks!

print("Error, I didn’t find any joysticks.")

else:

# Use joystick #0 and initialize it

my_joystick = pygame.joystick.Joystick(0)

my_joystick.init()

操纵杆将返回两个浮点值。如果操纵杆完全居中,它将返回(0,0)。如果操纵杆向左完全抬起,它将返回(-1,-1)。如果操纵杆向下向右,它将返回(1,1)。如果操纵杆介于两者之间,数值会相应地缩放。从下图开始查看控制器图像,了解其工作原理。

A978-1-4842-1790-0_11_Figi_HTML.jpg

居中(0,0)

A978-1-4842-1790-0_11_Figj_HTML.jpg

左上(-1,-1)

A978-1-4842-1790-0_11_Figk_HTML.jpg

向上(0,-1)

A978-1-4842-1790-0_11_Figl_HTML.jpg

向右上方(1,-1)

A978-1-4842-1790-0_11_Figm_HTML.jpg

右(1,0)

A978-1-4842-1790-0_11_Fign_HTML.jpg

右下(1,1)

A978-1-4842-1790-0_11_Figo_HTML.jpg

向下(0,1)

A978-1-4842-1790-0_11_Figp_HTML.jpg

左下方(-1,1)

A978-1-4842-1790-0_11_Figq_HTML.jpg

向左(-1,0)

在主程序循环中,操纵杆返回的值可能会根据对象应该移动的距离而增加。在下面的代码中,在一个方向上完全移动操纵杆会使其每帧移动 10 个像素,因为操纵杆值被乘以 10。

# This goes in the main program loop!

# As long as there is a joystick

if joystick_count != 0:

# This gets the position of the axis on the game controller

# It returns a number between -1.0 and +1.0

horiz_axis_pos = my_joystick.get_axis(0)

vert_axis_pos = my_joystick.get_axis(1)

# Move x according to the axis. We multiply by 10 to speed up the movement.

# Convert to an integer because we can’t draw at pixel 3.5, just 3 or 4.

x_coord = x_coord + int(horiz_axis_pos * 10)

y_coord = y_coord + int(vert_axis_pos * 10)

# Clear the screen

screen.fill(WHITE)

# Draw the item at the proper coordinates

draw_stick_figure(screen, x_coord, y_coord)

有关完整示例,请参见

ProgramArcadeGames.com/python_examples/f.php?file=move_game_controller.py

控制器有很多操纵杆、按钮,甚至帽子开关。下面是一个示例程序和屏幕截图,它将所有内容打印到屏幕上,显示每个游戏控制器正在做什么。注意,游戏控制器必须在程序启动前插上电源,否则程序无法检测到它们。

A978-1-4842-1790-0_11_Figr_HTML.jpg

操纵杆调用程序

"""

Sample Python/Pygame Programs

http://programarcadegames.com/

Show everything we can pull off the joystick

"""

import pygame

# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

class TextPrint(object):

"""

This is a simple class that will help us print to the screen

It has nothing to do with the joysticks, just outputting the

information.

"""

def __init__(self):

""" Constructor """

self.reset()

self.x_pos = 10

self.y_pos = 10

self.font = pygame.font.Font(None, 20)

def print(self, my_screen, text_string):

""" Draw text onto the screen. """

text_bitmap = self.font.render(text_string, True, BLACK)

my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])

self.y_pos += self.line_height

def reset(self):

""" Reset text to the top of the screen. """

self.x_pos = 10

self.y_pos = 10

self.line_height = 15

def indent(self):

""" Indent the next line of text """

self.x_pos += 10

def unindent(self):

""" Unindent the next line of text """

self.x_pos -= 10

pygame.init()

# Set the width and height of the screen [width,height]

size = [500, 700]

screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# Initialize the joysticks

pygame.joystick.init()

# Get ready to print

textPrint = TextPrint()

# -------- Main Program Loop -----------

while not done:

# EVENT PROCESSING STEP

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN

# JOYBUTTONUP JOYHATMOTION

if event.type == pygame.JOYBUTTONDOWN:

print("Joystick button pressed.")

if event.type == pygame.JOYBUTTONUP:

print("Joystick button released.")

# DRAWING STEP

# First, clear the screen to white. Don’t put other drawing commands

# above this, or they will be erased with this command.

screen.fill(WHITE)

textPrint.reset()

# Get count of joysticks

joystick_count = pygame.joystick.get_count()

textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))

textPrint.indent()

# For each joystick:

for i in range(joystick_count):

joystick = pygame.joystick.Joystick(i)

joystick.init()

textPrint.print(screen, "Joystick {}".format(i))

textPrint.indent()

# Get the name from the OS for the controller/joystick

name = joystick.get_name()

textPrint.print(screen, "Joystick name: {}".format(name))

# Usually axis run in pairs, up/down for one, and left/right for

# the other.

axes = joystick.get_numaxes()

textPrint.print(screen, "Number of axes: {}".format(axes))

textPrint.indent()

for i in range(axes):

axis = joystick.get_axis(i)

textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))

textPrint.unindent()

buttons = joystick.get_numbuttons()

textPrint.print(screen, "Number of buttons: {}".format(buttons))

textPrint.indent()

for i in range(buttons):

button = joystick.get_button(i)

textPrint.print(screen, "Button {:>2} value: {}".format(i, button))

textPrint.unindent()

# Hat switch. All or nothing for direction, not like joysticks.

# Value comes back in an array.

hats = joystick.get_numhats()

textPrint.print(screen, "Number of hats: {}".format(hats))

textPrint.indent()

for i in range(hats):

hat = joystick.get_hat(i)

textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))

textPrint.unindent()

textPrint.unindent()

# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Limit to 60 frames per second

clock.tick(60)

pygame.quit()

回顾

多项选择测验

What code will draw a circle at the specified x and y locations? def draw_circle(screen, x, y): pygame.draw.ellipse(screen, WHITE, [x, y, 25, 25])   def draw_circle(screen,x,y): pygame.draw.ellipse(screen, WHITE, [x, y, 25 + x, 25 + y])   def draw_circle(screen, x, y): pygame.draw.ellipse(screen, WHITE, [0, 0, 25 + x, 25 + y])     The following code draws an “X.” What would the code look like if it was moved from the main program loop to a function, with the ability to specify how the coordinates of X appear? pygame.draw.line(screen, RED, [80, 80], [100, 100], 2) pygame.draw.line(screen, RED, [80, 100], [100, 80], 2) def draw_x(screen, x, y):     pygame.draw.line(screen, RED, [80, 80], [100, 100], 2)     pygame.draw.line(screen, RED, [80, 100], [100, 80], 2)   def draw_x(screen, x, y):     pygame.draw.line(screen, RED, [80+x, 80+y], [100, 100], 2)     pygame.draw.line(screen, RED, [80+x, 100+y], [100, 80], 2)   def draw_x(screen, x, y):     pygame.draw.line(screen, RED, [x, y], [20+x, 20+y], 2)     pygame.draw.line(screen, RED, [x, 20+y], [20+x, y], 2)   def draw_x(screen, x, y):     pygame.draw.line(screen, RED, [x, y], [20, 20], 2)     pygame.draw.line(screen, RED, [x, 20+y], [20, 0], 2)   def draw_x(screen, x, y):     pygame.draw.line(screen, RED, [80+x, 80+y], [100+x, 100+y], 2)     pygame.draw.line(screen, RED, [80+x, 100+y], [100+x, 80+y], 2)     code will get the x and y position of the mouse? pos = pygame.mouse.get_pos() x = pos[0] y = pos[1]   pos = pygame.mouse.get_pos() x = pos[x] y = pos[y]   pos = pygame.mouse.get_pos() x = pos(x) y = pos(y)   x = pygame.mouse.get_pos(x) y = pygame.mouse.get_pos(y)   x = pygame.mouse.get_pos(0) y = pygame.mouse.get_pos(1)     In the keyboard example, if x_speed and y_speed were both set to 3, then: The object would be set to location (3, 3).   The object would move down and to the right at 3 pixels per frame.   The object would move down and to the right at 3 pixels per second.   The object would move up and to the right at 3 pixels per second.   The object would move up and to the left 3 pixels per frame.     The call axes = joystick.get_numaxes() will return how many axes for a game controller? 2   4   One for each analog joystick on the game controller.   Two for each analog joystick on the game controller.   One for each button on the game controller.     Depending on the button state, what value will the variable button be assigned using this code? button = joystick.get_button( 0 ) 0 or 1   On or Off   Up or Down   True or False     What is the difference between a hat on a game controller and a joystick? Nothing, they are just different names for the same thing.   A hat can be moved in small amounts; an analog joystick is all or nothing.   An analog joystick can be moved in small amounts; a hat is all or nothing.     What axis values will be returned when the joystick is moved up and to the left? (-1, -1)   (1, 1)   (0, 0)     What axis values will be returned when the joystick is centered? (-1, -1)   (1, 1)   (0, 0)     What code would move an object based on the position of the joystick on the game controller? horiz_axis_pos = my_joystick.get_axis(0) vert_axis_pos = my_joystick.get_axis(1)   x_coord = int(x_coord + horiz_axis_pos * 10) y_coord = int(y_coord + vert_axis_pos * 10)   x_coord = my_joystick.get_axis(0) y_coord = my_joystick.get_axis(1)   x_coord = my_joystick.get_axis(0)*10 y_coord = my_joystick.get_axis(1)*10

简答工作表

What’s wrong with this code that uses a function to draw a stick figure? Assume the colors are already defined and the rest of the program is OK. What is wrong with the code in the function? def draw_stick_figure(screen, x, y):     # Head     pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)     # Legs     pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)     pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)     # Body     pygame.draw.line(screen, RED, [100,100], [100,90], 2)     # Arms     pygame.draw.line(screen, RED, [100,90], [104,100], 2)     pygame.draw.line(screen, RED, [100,90], [96,100], 2)   Show how to only grab the x coordinate of where the mouse is.   Why is it important to keep the event processing loop together and only have one of them? It is more than organization; there will be subtle hard-to-detect errors. What are they and why will they happen without the event processing loop together? (Review “The Event Processing Loop” in Chapter 5 if needed.)   When we created a bouncing rectangle, we multiplied the speed times -1 when the rectangle hit the edge of the screen. Explain why that technique won’t work for moving an object with the keyboard.   Why does movement with the keyboard or game controller need to have a starting x, y location, but the mouse doesn’t?   What values will a game controller return if it is held all the way down and to the right?

锻炼

请查看本章附带的练习“功能和用户控制”的附录。

十二、位图图形和声音

为了超越绘制圆形和矩形所提供的简单形状,我们的程序需要处理位图图形的能力。位图图形可以是从绘图程序创建和保存的照片或图像。

但是图形是不够的。游戏也需要声音!这一章展示了如何在你的游戏中加入图像和声音。

将程序存储在文件夹中

到目前为止,我们制作的程序只涉及一个文件。现在我们包括图像和声音,有更多的文件是我们程序的一部分。很容易将这些文件与我们正在制作的其他程序混在一起。保持一切整洁和独立的方法是将这些程序放入各自的文件夹中。在开始任何这样的项目之前,点击“新建文件夹”按钮,并使用该新文件夹作为存放所有新文件的地方,如下图所示。

A978-1-4842-1790-0_12_Figa_HTML.jpg

创建新文件夹

设置背景图像

需要为你的游戏设置背景图片?找到如下图所示的图像。如果你在网络浏览器上在线浏览,你通常可以右键点击一张图片,然后把它保存到电脑上。将图像保存到我们刚刚为游戏创建的文件夹中。

确保不使用有版权的图片!使用反向图像搜索可以很容易地再次确认你没有复制它。

A978-1-4842-1790-0_12_Figb_HTML.jpg

背景图像

游戏中使用的任何位图图像都应该已经根据它在屏幕上的显示方式进行了调整。不要从高分辨率相机拍摄 5000x5000 像素的图像,然后试图将其加载到只有 800x600 的窗口中。在 Python 程序中使用图像之前,使用图形程序(甚至 MS Paint 也可以)并调整图像大小/裁剪图像。

加载图像是一个简单的过程,只需要一行代码。在那一行代码中发生了很多事情,所以对这一行的解释将分为三个部分。我们的load命令的第一个版本将加载一个名为saturn_family1.jpg的文件。该文件必须位于 Python 程序所在的同一目录中,否则计算机将找不到它:

pygame.image.load("saturn_family1.jpg")

该代码可以加载图像,但是我们没有办法引用和显示该图像!我们需要一个与load()命令返回的值相等的变量集。在 load 命令的下一个版本中,我们创建了一个名为background_image的新变量。第二版见下文:

background_image = pygame.image.load("saturn_family1.jpg")

最后,需要将图像转换成 pygame 更容易处理的格式。为此,我们将.convert()添加到命令中来调用 convert 函数。函数.convert()Image类中的一个方法。我们将在第十三章中详细讨论类、对象和方法。

所有的图片都应该使用类似下面的代码来加载。只需根据需要更改变量名和文件名。

background_image = pygame.image.load("saturn_family1.jpg").convert()

加载图像应该在主程序循环之前完成。虽然有可能在主程序循环中加载它,但这将导致程序每秒钟从磁盘获取图像 20 次左右。这完全没有必要。只需要在程序启动时做一次。

使用blit命令显示图像。这将图像位传送到屏幕上。在第六章中,我们已经在游戏窗口显示文本时使用过这个命令。

blit命令是screen变量中的一个方法,所以我们需要通过screen.blit来启动我们的命令。接下来,我们需要将图像传递给 blit,以及在哪里 blit 它。这个命令应该在循环内部执行,这样图像就可以在每一帧中绘制出来。见下文:

screen.blit(background_image, [0, 0])

这段代码从(0,0)开始将保存在background_image中的图像传送到屏幕上。

移动图像

现在我们想加载一个图像并在屏幕上移动它。我们将从一艘简单的橙色宇宙飞船开始。你可以从 http://kenney.nl/ 那里得到这个和许多其他伟大的资产。见下图。这艘船的图片可以从这本书的网站上下载,或者你也可以在。你喜欢的白色或黑色背景的 png。不要用. jpg。

A978-1-4842-1790-0_12_Figc_HTML.jpg

玩家形象

为了加载图像,我们需要使用与背景图像相同的命令。在这种情况下,我假设文件保存为player.png

player_image = pygame.image.load("player.png").convert()

在主程序循环中,检索鼠标坐标,并将其传递给另一个blit函数作为绘制图像的坐标:

# Get the current mouse position. This returns the position

# as a list of two numbers.

player_position = pygame.mouse.get_pos()

x = player_position[0]

y = player_position[1]

# Copy image to screen:

screen.blit(player_image, [x, y])

这说明了一个问题。该图像是一艘带有纯黑背景的宇宙飞船。所以当图像被绘制时,程序显示下图。

A978-1-4842-1790-0_12_Figd_HTML.jpg

这张图片的纯黑背景很明显

我们只要飞船,不要长方形背景!但是我们可以加载的所有图像都是矩形,那么我们如何只显示图像中我们想要的部分呢?解决这个问题的方法是告诉程序让一种颜色透明而不显示。这可以在装载后立即完成。下面的代码使黑色(假设黑色已经被定义为一个变量)透明:

player_image.set_colorkey(BLACK)

这适用于大多数以结尾的文件。这对于大多数人来说并不太好。jpg 文件。jpeg 图像格式非常适合保存照片,但作为使图像变小的算法的一部分,它确实会微妙地改变图像。图像输入。gif 和。png 也是压缩的,但是这些格式中使用的算法不会改变图像。格式。bmp 根本没有压缩,它会产生巨大的文件。因为。jpg 格式改变了格式,这意味着不是所有的背景颜色都会完全一样。在下一张图中,飞船被保存为白色背景的 jpeg 格式。船周围的白色并不完全是(255,255,255),而是非常接近。

A978-1-4842-1790-0_12_Fige_HTML.jpg

JPEG 压缩伪像

如果你要挑选一个透明的图像,选择. gif 或. png。这些是图形艺术类型图像的最佳格式。照片应该是. jpg。请记住,仅仅通过将文件扩展名重命名为. png 是不可能将. jpg 更改为另一种格式的。即使您将其命名为其他格式,它仍然是. jpg。它需要在图形程序中进行转换,以将其转换为不同的格式。但是一旦转换成. jpg 格式,它就被修改了,转换成. png 格式不会修复这些修改。

这里有三个很好的地方可以找到在您的程序中使用的免费图像:

声音

在这一节中,我们将在点击鼠标按钮时播放激光声音。这个声音最初来自 Kenney.nl,你可以在这里下载并保存声音:ProgramArcadeGames.com/python_examples/en/laser5.ogg

像图像一样,声音在使用前必须加载。这应该在主程序循环之前进行一次。以下命令加载一个声音文件,并创建一个名为click_sound的变量来引用它:

click_sound = pygame.mixer.Sound("laser5.ogg")

我们可以使用以下命令来播放声音:

click_sound.play()

但是我们把这个命令放在哪里呢?如果我们把它放在主程序循环中,它将每秒播放 20 次左右。真的很烦。我们需要一个触发器。一些动作发生,然后我们播放声音。例如,当用户用下面的代码点击鼠标按钮时,可以播放这个声音:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

elif event.type == pygame.MOUSEBUTTONDOWN:

click_sound.play()

未压缩的声音文件通常以.wav结尾。这些文件比其他格式的文件大,因为没有对它们运行算法来使它们变小。还有曾经流行的.mp3格式,尽管这种格式有专利,可能使它不适合某些应用。另一种可以免费使用的格式是以.ogg结尾的 OGG Vorbis 格式。

Pygame 并不播放互联网上能找到的所有.wav文件。如果你有一个不工作的文件,你可以试着用程序 http://sourceforge.net/projects/audacity 把它转换成以.ogg结尾的 ogg-vorbis 类型的声音文件。这种文件格式对于 pygame 来说很小也很可靠。

如果你想在你的节目中播放背景音乐,请查看在线示例部分:

ProgramArcadeGames.com/en/python_examples/f.php?file=background_music.py

请注意,您不能将受版权保护的音乐与您的程序一起再分发。即使你制作了一个以受版权保护的音乐为背景的视频,YouTube 和类似的视频网站也会标记你侵犯版权。

找到在您的程序中使用的免费声音的好地方:

完整列表

"""

Sample Python/Pygame Programs

http://programarcadegames.com/

Explanation video:http://youtu.be/4YqIKncMJNs

Explanation video:http://youtu.be/ONAK8VZIcI4

Explanation video:http://youtu.be/_6c4o41BIms

"""

import pygame

# Define some colors

WHITE = (255, 255, 255)

BLACK = (0, 0, 0)

# Call this function so the Pygame library can initialize itself

pygame.init()

# Create an 800x600 sized screen

screen = pygame.display.set_mode([800, 600])

# This sets the name of the window

pygame.display.set_caption(’CMSC 150 is cool’)

clock = pygame.time.Clock()

# Before the loop, load the sounds:

click_sound = pygame.mixer.Sound("laser5.ogg")

# Set positions of graphics

background_position = [0, 0]

# Load and set up graphics.

background_image = pygame.image.load("saturn_family1.jpg").convert()

player_image = pygame.image.load("playerShip1_orange.png").convert()

player_image.set_colorkey(BLACK)

done = False

while not done:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

elif event.type == pygame.MOUSEBUTTONDOWN:

click_sound.play()

# Copy image to screen:

screen.blit(background_image, background_position)

# Get the current mouse position. This returns the position

# as a list of two numbers.

player_position = pygame.mouse.get_pos()

x = player_position[0]

y = player_position[1]

# Copy image to screen:

screen.blit(player_image, [x, y])

pygame.display.flip()

clock.tick(60)

pygame.quit()

回顾

多项选择测验

Should the following line go inside, or outside of the main program loop? background_image = pygame.image.load("saturn_family1.jpg").convert() Outside the loop, because it isn’t a good idea to load the image from the disk 20 times per second.   Inside the loop, because the background image needs to be redrawn every frame.     In the following code, what does the [0, 0] do? screen.blit(background_image, [0, 0]) Default dimensions of the bitmap.   Specifies the x and y of the top left coordinate of where to start drawing the bitmap on the screen.   Draw the bitmap in the center of the screen.     Should the following line go inside or outside of the main program loop? screen.blit(background_image, [0, 0]) Outside the loop, because it isn’t a good idea to load the image from the disk 20 times per second.   Inside the loop, because the background image needs to be redrawn every frame.     Given this line of code, what code will get the x value of the current mouse position? player_position = pygame.mouse.get_pos() x = player_position[x]   x = player_position[0]   x = player_position.x   x[0] = player_position     What types of image file formats are loss-less (i.e., they do not change the image)? Choose the best answer. png, jpg, gif   png, gif   png   jpg   gif   jpg, gif     What does this code do? player_image.set_colorkey(WHITE) Makes the bitmap background white.   Sets all the white pixels to be transparent instead.   Sets the next color to be drawn to white.   Clears the screen to a white color.   Draws the player image in white.     What is wrong with section of code? for event in pygame.event.get():     if event.type == pygame.QUIT:         done=True     if event.type == pygame.MOUSEBUTTONDOWN:         click_sound = pygame.mixer.Sound("click.wav")         click_sound.play() Pygame doesn’t support .wav files.   The colorkey hasn’t been set for click_sound yet.   Sounds should be loaded at the start of the program, not in the main program loop.   Sounds should not be played in a main program loop.

简答工作表

This is about the time that many people learning to program run into problems with Windows hiding file extensions. Briefly explain how to make Windows show file extensions. If you don’t remember, go back to Chapter 1 to see the details.   For the following file extensions:

  • 。使用 jpeg 文件交换格式存储的编码图像文件扩展名
  • 。声音资源文件
  • 。可交换的图像格式
  • 。巴布亚新几内亚
  • 。格式
  • 。位图文件的扩展名
  • . mp3

…match the extension to the category it best fits:

  • 照片
  • 图形艺术
  • 未压缩图像
  • 歌曲和音效
  • 未压缩的声音

Should an image be loaded inside the main program loop or before it? Should the program blit the image in the main program loop or before it?   How can a person change an image from one format to another? For example, how do you change a .jpg to a .gif? Why does changing the file extension not really work? (Ask if you can’t figure it out.)   Explain why an image that was originally saved as a .jpg doesn’t work with setting a background color even after it is converted to a .png.   Briefly explain how to play background music in a game and how to automatically start playing a new song when the current song ends.

锻炼

查看附录中本章附带的练习“位图图形和用户控制”。

十三、类简介

类和对象是非常强大的编程工具。它们使编程更容易。事实上,您已经熟悉了类和对象的概念。类是对象的分类。比如人或者形象。对象是一个类的特定实例。就像玛丽是一个人的实例。

对象有属性,比如一个人的名字、身高和年龄。对象也有方法。方法定义了一个对象能做什么:比如跑、跳或坐。

为什么要学习课程?

冒险游戏中的每个角色都需要数据:名字、位置、实力;他们举起他们的手臂;他们朝哪个方向前进;等等。再加上那些角色做事。他们跑,跳,打,和说话。

如果没有类,我们存储这些数据的 Python 代码可能看起来像这样:

name = "Link"

sex = "Male"

max_hit_points = 50

current_hit_points = 50

为了处理这个字符,我们需要将数据传递给一个函数:

def display_character(name, sex, max_hit_points, current_hit_points):

print(name, sex, max_hit_points, current_hit_points)

现在想象一下,创建一个程序,为我们游戏中的每个角色、怪物和物品设置一组这样的变量。然后我们需要创建处理这些项目的函数。我们现在已经陷入了数据的泥潭。突然间,这听起来一点也不好玩了。

但是等等,更糟的是!随着我们游戏的扩展,我们可能需要添加新的领域来描述我们的角色。在这种情况下,我们添加了max_speed:

name = "Link"

sex = "Male"

max_hit_points = 50

current_hit_points = 50

max_speed = 10

def display_character(name, sex, max_hit_points, current_hit_points, max_speed):

print(name, sex, max_hit_points, current_hit_points)

在上面的例子中,只有一个函数。但是在一个大型的视频游戏中,我们可能有数百个函数来处理主角。添加一个新的字段来帮助描述一个角色有什么和能做什么,这将要求我们遍历这些函数中的每一个,并将其添加到参数列表中。那将是大量的工作。也许我们需要给不同类型的角色添加max_speed,比如怪物。需要有更好的方法。不知何故,我们的程序需要将这些数据字段打包,以便于管理。

定义和创建简单的类

管理多个数据属性的更好方法是定义一个包含所有信息的结构。然后我们可以给这些信息起一个名字,比如字符或地址。在 Python 和任何其他现代语言中,通过使用一个类可以很容易地做到这一点。

例如,我们可以定义一个代表游戏中角色的类:

class Character():

""" This is a class that represents the main character in a game. """

def __init__(self):

""" This is a method that sets up the variables in the object. """

self.name = "Link"

self.sex = "Male"

self.max_hit_points = 50

self.current_hit_points = 50

self.max_speed = 10

self.armor_amount = 8

这是另一个例子。我们定义一个类来保存地址的所有字段:

class Address():

""" Hold all the fields for a mailing address. """

def __init__(self):

""" Set up the address fields. """

self.name = ""

self.line1 = ""

self.line2 = ""

self.city = ""

self.state = ""

self.zip = ""

在上面的代码中,Address是类名。类中的变量,如namecity,称为属性或字段。(注意声明一个类和声明一个函数的异同。)

与函数和变量不同,类名应该以大写字母开头。虽然可以用小写字母开始一个类,但这不被认为是好的做法。

被称为构造函数的特殊函数中的def __init__(self):在创建类时自动运行。我们稍后会详细讨论构造函数。

这个self.有点像代词 my。在课堂上,我们谈论我的名字、我的城市等等。我们不想在Address的类定义之外使用self.,来引用一个Address字段。为什么?因为就像代词“我的”一样,当由不同的人说时,它的意思是完全不同的人!

为了更好地可视化类以及它们之间的关系,程序员经常制作图表。Address类的图表如下图所示。查看类名是如何显示在顶部的,下面列出了每个属性的名称。每个属性的右边是数据类型,如字符串或整数。

A978-1-4842-1790-0_13_Figa_HTML.jpg

类图

类代码定义了一个类,但它实际上并没有创建一个类的实例。代码告诉计算机一个地址有哪些字段,初始默认值是什么。虽然我们还没有地址。我们可以定义一个类而不创建它,就像我们可以定义一个函数而不调用它一样。要创建一个类并设置字段,请查看下面的示例:

# Create an address

home_address = Address()

# Set the fields in the address

home_address.name = "John Smith"

home_address.line1 = "701 N. C Street"

home_address.line2 = "Carver Science Building"

home_address.city = "Indianola"

home_address.state = "IA"

home_address.zip = "50125"

在第 2 行用Address()创建了一个 address 类的实例。注意类名Address的用法,后跟括号。变量名可以是遵循正常命名规则的任何名称。

要设置类中的字段,程序必须使用点运算符。该运算符是位于home_address和字段名之间的句点。查看最后 6 行如何使用点运算符来设置每个字段值。

使用类时一个非常常见的错误是忘记指定要使用的类的实例。如果只创建了一个地址,很自然地认为计算机会知道使用你所说的那个地址。然而,事实并非如此。请参见下面的示例:

class Address():

def __init__(self):

self.name = ""

self.line1 = ""

self.line2 = ""

self.city = ""

self.state = ""

self.zip = ""

# Create an address

my_address = Address()

# Alert! This does not set the address’s name!

name = "Dr. Craven"

# This doesn’t set the name for the address either

Address.name = "Dr. Craven"

# This does work:

my_address.name = "Dr. Craven"

可以创建第二个地址,并且可以使用来自两个实例的字段。请参见下面的示例(为便于阅读,添加了行号):

001 class Address():

002     def __init__(self):

003         self.name = ""

004         self.line1 = ""

005         self.line2 = ""

006         self.city = ""

007         self.state = ""

008         self.zip = ""

009

010 # Create an address

011 home_address = Address()

012

013 # Set the fields in the address

014 home_address.name = "John Smith"

015 home_address.line1 = "701 N. C Street"

016 home_address.line2 = "Carver Science Building"

017 home_address.city = "Indianola"

018 home_address.state = "IA"

019 home_address.zip = "50125"

020

021 # Create another address

022 vacation_home_address = Address()

023

024 # Set the fields in the address

025 vacation_home_address.name = "John Smith"

026 vacation_home_address.line1 = "1122 Main Street"

027 vacation_home_address.line2 = ""

028 vacation_home_address.city = "Panama City Beach"

029 vacation_home_address.state = "FL"

030 vacation_home_address.zip = "32407"

031

032 print("The client’s main home is in " + home_address.city)

033 print("His vacation home is in " + vacation_home_address.city)

第 11 行创建了第一个Address实例;第 22 行创建了第二个实例。变量home_address指向第一个实例,vacation_home_address指向第二个实例。

第 25–30 行设置了这个新类实例中的字段。第 32 行打印家庭地址的城市,因为home_address出现在点运算符之前。第 33 行打印假期地址,因为vacation_home_address出现在点运算符之前。

在示例中,Address被称为类,因为它为数据对象定义了一个新的分类。变量home_addressvacation_home_address引用对象,因为它们引用了类Address的实际实例。一个对象的简单定义是它是一个类的实例。像鲍勃和南希是人类类的实例。

通过使用www.pythontutor.com,我们可以可视化代码的执行(见下文)。有三个变量在起作用。一个指向Address的类定义。另外两个变量指向不同的地址对象和它们的数据。

A978-1-4842-1790-0_13_Figb_HTML.jpg

两个地址

将大量数据字段放入一个类中,可以很容易地将数据传入和传出一个函数。在下面的代码中,该函数接受一个地址作为参数,并将其打印在屏幕上。没有必要为地址的每个字段传递参数。

# Print an address to the screen

def print_address(address):

print(address.name)

# If there is a line1 in the address, print it

if len(address.line1) > 0:

print(address.line1)

# If there is a line2 in the address, print it

if len(address.line2) > 0:

print( address.line2 )

print(address.city + ", " + address.state + " " + address.zip)

print_address(home_address)

print()

print_address(vacation_home_address)

向类中添加方法

除了属性,类还可以有方法。方法是存在于类内部的函数。扩展前面回顾问题 1 中的Dog类的例子,下面的代码增加了一个狗叫的方法。

class Dog():

def __init__(self):

self.age = 0

self.name = ""

self.weight = 0

def bark(self):

print("Woof")

方法定义包含在上面的第 7–8 行中。类中的方法定义看起来几乎和函数定义一模一样。最大的不同是在第 7 行增加了一个参数self。类中任何方法的第一个参数必须是self。即使函数不使用该参数,它也是必需的。

以下是为类创建方法时要记住的重要事项:

  • 应该先列出属性,后列出方法。
  • 任何方法的第一个参数都必须是self
  • 方法定义恰好缩进一个制表位。

调用方法的方式类似于引用对象的属性。请参见下面的示例代码。

001 my_dog = Dog()

002

003 my_dog.name = "Spot"

004 my_dog.weight = 20

005 my_dog.age = 3

006

007 my_dog.bark()

第 1 行创建了狗。第 3–5 行设置对象的属性。第 7 行调用了bark函数。注意,即使bark函数有一个参数self,调用也不会传入任何东西。这是因为第一个参数被假定为对 dog 对象本身的引用。在幕后,Python 发出了一个看起来像这样的调用:

# Example, not actually legal

Dog.bark(my_dog)

如果bark函数需要引用任何属性,那么它就使用self引用变量。例如,我们可以改变Dog类,这样当狗叫的时候,它也能打印出狗的名字。在下面的代码中,使用点运算符和self引用来访问 name 属性。

def bark(self):

print("Woof says", self.name)

属性是形容词,方法是动词。该类的绘图如下图所示。

A978-1-4842-1790-0_13_Figc_HTML.jpg

狗类

示例:球类

这个示例代码可以在 Python/pygame 中用来画一个球。将所有参数包含在一个类中会使数据管理更容易。下图显示了Ball类的图表。

A978-1-4842-1790-0_13_Figd_HTML.jpg

球类

class Ball():

def __init__(self):

# --- Class Attributes ---

# Ball position

self.x = 0

self.y = 0

# Ball’s vector

self.change_x = 0

self.change_y = 0

# Ball size

self.size = 10

# Ball color

self.color = [255,255,255]

# --- Class Methods ---

def move(self):

self.x += self.change_x

self.y += self.change_y

def draw(self, screen):

pygame.draw.circle(screen, self.color, [self.x, self.y], self.size )

下面是在主程序循环之前创建一个球并设置其属性的代码:

theBall = Ball()

theBall.x = 100

theBall.y = 100

theBall.change_x = 2

theBall.change_y = 1

theBall.color = [255,0,0]

这段代码将进入主循环来移动和绘制球:

theBall.move()

theBall.draw(screen)

参考

这就是我们区分真正的程序员和想要成为程序员的地方。理解类引用。看一下下面的代码:

class Person():

def __init__(self):

self.name = ""

self.money = 0

bob = Person()

bob.name = "Bob"

bob.money = 100

nancy = Person()

nancy.name = "Nancy"

print(bob.name, "has", bob.money, "dollars.")

print(nancy.name, "has", nancy.money, "dollars.")

上面的代码创建了 Person()类的两个实例,使用www.pythontutor.com我们可以在下图中可视化这两个类。

A978-1-4842-1790-0_13_Fige_HTML.jpg

两个人

上面的代码没有什么新意。但是下面的代码可以:

class Person():

def __init__(self):

self.name = ""

self.money = 0

bob = Person()

bob.name = "Bob"

bob.money = 100

nancy = bob

nancy.name = "Nancy"

print(bob.name, "has", bob.money, "dollars.")

print(nancy.name, "has", nancy.money, "dollars.")

看到第 10 行和nancy = bob的区别了吗?

处理对象时一个常见的误解是假设变量bob是 Person 对象。事实并非如此。变量bob是对 Person 对象的引用。也就是说,它存储的是对象所在的内存地址,而不是对象本身。

如果bob实际上是对象,那么第 9 行可以创建对象的副本,这样就会有两个对象存在。程序的输出将显示 Bob 和 Nancy 都有 100 美元。但是当运行时,程序输出以下内容:

Nancy has 100 dollars.

Nancy has 100 dollars.

bob存储的是对对象的引用。除了引用,我们还可以称之为地址、指针或句柄。引用是计算机内存中存储对象的地址。这个地址是一个十六进制数,如果打印出来,可能看起来像 0x1e504。运行第 9 行时,复制的是地址,而不是地址指向的整个对象。见下图。

A978-1-4842-1790-0_13_Figf_HTML.jpg

类别引用

我们也可以在www.pythontutor.com中运行它,看看这两个变量是如何指向同一个对象的。

A978-1-4842-1790-0_13_Figg_HTML.jpg

一个人,两个指针

功能和参考

请看下面的代码示例。第 1 行创建了一个接受一个数字作为参数的函数。变量money是包含传入数据副本的变量。将该数字加 100 不会改变存储在第 11 行bob.money中的数字。因此,第 14 行的 print 语句打印出 100 而不是 200。

def give_money1(money):

money += 100

class Person():

def __init__(self):

self.name = ""

self.money = 0

bob = Person()

bob.name = "Bob"

bob.money = 100

give_money1(bob.money)

print(bob.money)

http://www.pythontutor.com/visualize.html#mode=display上运行,我们看到money变量有两个实例。一个是对give_money1函数的复制和本地。

A978-1-4842-1790-0_13_Figh_HTML.jpg

函数引用

看看下面的附加代码。这段代码确实导致bob.money增加,并且打印语句打印 200。

def give_money2(person):

person.money += 100

give_money2(bob)

print(bob.money)

这是为什么呢?因为person包含的是对象的内存地址的副本,而不是实际的对象本身。你可以把它想象成一个银行账号。该函数有一个银行帐号的副本,而不是整个银行帐户的副本。因此,使用银行账号的副本存入 100 美元会导致 Bob 的银行账户余额上升。

A978-1-4842-1790-0_13_Figi_HTML.jpg

函数引用

数组以同样的方式工作。接受一个数组(列表)作为参数并修改该数组中的值的函数将修改调用代码创建的同一数组。复制的是数组的地址,而不是整个数组。

复习问题

Create a class called Cat. Give it attributes for name, color, and weight. Give it a method called meow.   Create an instance of the cat class, set the attributes, and call the meow method.   Create a class called Monster. Give it an attribute for name and an integer attribute for health. Create a method called decrease_health that takes in a parameter amount and decreases the health by that much. Inside that method, print that the animal died if its health goes below zero.

构造器

下面列出的Dog我们班有一个可怕的问题。当我们创建一只狗时,默认情况下,这只狗没有名字。狗应该有名字!我们不应该允许狗出生后就不给它取名字。然而,下面的代码允许这种情况发生,那只狗永远不会有名字。

class Dog()

def __init__(self):

self.name = ""

my_dog = Dog()

Python 不希望这种情况发生。这就是 Python 类有一个特殊函数的原因,每当创建该类的实例时都会调用这个函数。通过添加一个称为构造函数的函数,程序员可以添加每次创建类的实例时自动运行的代码。请参见下面的示例构造函数代码:

class Dog():

def __init__(self):

""" Constructor. Called when creating an object of this type. """

self.name = ""

print("A new dog is born!")

# This creates the dog

my_dog = Dog()

构造函数从第 2 行开始。必须取名__init__init前有两个下划线,后有两个下划线。一个常见的错误是只使用一个。

构造函数必须接受self作为第一个参数,就像类中的其他方法一样。当程序运行时,它将打印:

A new dog is born!

当在第 8 行创建一个Dog对象时,自动调用__init__函数,并将消息打印到屏幕上。

避免这个错误

将一个方法的所有内容放入一个定义中。不要定义两次。例如:

# Wrong:

class Dog():

def __init__(self):

self.age = 0

self.name = ""

self.weight = 0

def __init__(self):

print("New dog!")

计算机会忽略第一个__init__并使用最后一个定义。请改为这样做:

# Correct:

class Dog():

def __init__(self):

self.age = 0

self.name = ""

self.weight = 0

print("New dog!")

构造函数可用于初始化和设置对象的数据。上面的示例Dog类仍然允许在创建 dog 对象后将name属性留空。我们如何防止这种情况发生?许多对象在创建时就需要有正确的值。可以使用构造函数来实现这一点。参见下面的代码:

001 class Dog():

002

003     def __init__(self, new_name):

004         """ Constructor. """

005         self.name = new_name

006

007 # This creates the dog

008 my_dog = Dog("Spot")

009

010 # Print the name to verify it was set

011 print(my_dog.name)

012

013 # This line will give an error because

014 # a name is not passed in.

015 herDog = Dog()

在第 3 行,构造函数现在有了一个名为new_name的额外参数。该参数的值用于设置第 8 行的Dog类中的name属性。不再可能创建一个没有名字的狗类。第 15 行的代码尝试这样做。这将导致 Python 错误,并且无法运行。一个常见的错误是将__init__函数的参数命名为与属性相同的名称,并假设这些值会自动同步。这种情况不会发生。

复习问题

Should class names begin with an upper or lowercase letter?   Should method names begin with an upper or lowercase letter?   Should attribute names begin with an upper or lowercase letter?   Which should be listed first in a class, attributes or methods?   What are other names for a reference?   What is another name for an instance variable?   What is the name for an instance of a class?   Create a class called Star that will print out “A star is born!” every time it is created.   Create a class called Monster with attributes for health and a name. Add a constructor to the class that sets the health and name of the object with data passed in as parameters.

遗产

使用类和对象的另一个强大特性是利用继承的能力。创建一个类并继承父类的所有属性和方法是可能的。

例如,一个程序可能会创建一个名为Boat的类,它拥有代表游戏中一艘船所需的所有属性:

class Boat():

def __init__(self):

self.tonnage = 0

self.name = ""

self.isDocked = True

def dock(self):

if self.isDocked:

print("You are already docked.")

else:

self.isDocked = True

print("Docking")

def undock(self):

if not self.isDocked:

print("You aren’t docked.")

else:

self.isDocked = False

print("Undocking")

要测试我们的代码:

b = Boat()

b.dock()

b.undock()

b.undock()

b.dock()

b.dock()

产出:

You are already docked.

Undocking

You aren’t docked.

Docking

You are already docked.

我们的项目也需要一艘潜艇。我们的潜艇能做一艘船能做的一切,另外我们需要一个指挥submerge。没有继承,我们有两个选择。

  • 一,给我们的船添加submerge()命令。这不是一个好主意,因为我们不想给人一种我们的船通常在水下的印象。
  • 第二,我们可以创建一个Boat类的副本,并将其命名为Submarine。在这个类中,我们将添加submerge()命令。这一开始很容易,但是如果我们改变了Boat类,事情就变得更难了。程序员需要记住,我们不仅需要修改Boat类,还要对Submarine类做同样的修改。保持这些代码的同步既费时又容易出错。

幸运的是,有一个更好的方法。我们的程序可以创建继承父类所有属性和方法的子类。然后,子类可以添加符合其需求的字段和方法。例如:

class Submarine(Boat):

def submerge(self):

print("Submerge!")

第 1 行是重要的部分。仅仅通过在类声明期间将Boat放在括号之间,我们就已经自动选择了Boat类中的每个属性和方法。如果我们更新Boat,那么子类Submarine将自动获得这些更新。传承就是这么简单!

下一个代码示例如下图所示。

A978-1-4842-1790-0_13_Figj_HTML.jpg

类图

001 class Person():

002     def __init__(self):

003         self.name = ""

004

005 class Employee(Person):

006     def __init__(self):

007         # Call the parent/super class constructor first

008         super().__init__()

009

010         # Now set up our variables

011         self.job_title = ""

012

013 class Customer(Person):

014     def __init__(self):

015         super().__init__()

016         self.email = ""

017

018 john_smith = Person()

019 john_smith.name = "John Smith"

020

021 jane_employee = Employee()

022 jane_employee.name = "Jane Employee"

023 jane_employee.job_title = "Web Developer"

024

025 bob_customer = Customer()

026 bob_customer.name = "Bob Customer"

027 bob_customer.email = "send_me@spam.com"

通过将Person放在第 5 行和第 13 行的括号之间,程序员告诉计算机PersonEmployeeCustomer的父类。这允许程序在第 19 和 22 行设置name属性。

方法也是继承的。父类拥有的任何方法,子类也会拥有。但是如果我们在子类和父类中都有一个方法呢?

我们有两个选择。我们可以用关键字super()运行它们。使用super()后跟一个点操作符,最后是一个方法名,允许您调用该方法的父版本。

上面的代码显示了使用super的第一个选项,其中我们不仅运行子构造函数,还运行父构造函数。

如果您正在为子方法编写方法,并且想要调用父方法,通常它将是子方法中的第一条语句。请注意上面的例子。

所有的构造函数都应该调用父构造函数,因为那样你就会有一个没有父构造函数的子构造函数,这很可悲。事实上,有些语言强制执行这个规则,但是 Python 没有。

第二种选择?方法可以被子类重写以提供不同的功能。以下示例显示了这两个选项。Employee.report覆盖了Person.report,因为它从不调用和运行父报告方法。Customer报告确实调用了父类,Customer中的report方法增加了Person功能。

class Person():

def __init__(self):

self.name = ""

def report(self):

# Basic report

print("Report for", self.name)

class Employee(Person):

def __init__(self):

# Call the parent/super class constructor first

super().__init__()

# Now set up our variables

self.job_title = ""

def report(self):

# Here we override report and just do this:

print("Employee report for", self.name)

class Customer(Person):

def __init__(self):

super().__init__()

self.email = ""

def report(self):

# Run the parent report:

super().report()

# Now add our own stuff to the end so we do both

print("Customer e-mail:", self.email)

john_smith = Person()

john_smith.name = "John Smith"

jane_employee = Employee()

jane_employee.name = "Jane Employee"

jane_employee.job_title = "Web Developer"

bob_customer = Customer()

bob_customer.name = "Bob Customer"

bob_customer.email = "send_me@spam.com"

john_smith.report()

jane_employee.report()

bob_customer.report()

Is-A 和 Has-A 关系

类有两种主要类型的关系。他们是“是一个”和“有一个”的关系。

父类应该总是子类的更一般、更抽象的版本。这种类型的父子关系称为 is 关系。例如,一个父类动物可以有一个子类狗。Dog 类可以有一个子类 Poodle。另一个例子,海豚是哺乳动物。反之亦然:哺乳动物不一定是海豚。所以海豚类永远不会成为哺乳动物类的父母。同样,教室桌子不应该是教室椅子的父代,因为椅子不是桌子。

另一种关系是有关系。这些关系通过类属性在代码中实现。一只狗有一个名字,因此 dog 类有一个 name 属性。同样,一个人可以有一只狗,这可以通过让 person 类有一个 dog 属性来实现。Person 类不会从 Dog 派生,因为那会是一种侮辱。

查看前面的代码示例,我们可以看到:

  • 员工是一个人。
  • 客户是一个人。
  • 人有名字。
  • 员工有职称。
  • 客户有一封电子邮件。

静态变量与实例变量

静态变量和实例变量之间的区别令人困惑。谢天谢地,现在没有必要完全理解这种区别。但是如果你坚持编程,它将是。因此我们在这里简单介绍一下。

在我出版这本书的最初几年,Python 还有一些奇怪的地方让我感到困惑。所以你可能会看到我弄错的旧视频和例子。

实例变量是我们到目前为止使用的类变量的类型。该类的每个实例都有自己的值。例如,在一个挤满人的房间里,每个人都有自己的年龄。有些年龄可能是相同的,但我们仍然需要单独跟踪每个年龄。

有了实例变量,就不能和一屋子人只说“年龄”了。我们需要具体说明我们谈论的是谁的年龄。此外,如果房间里没有人,那么在没有人的情况下提及年龄是没有意义的。

对于静态变量,类的每个实例的值都是相同的。即使没有实例,静态变量仍然有一个值。例如,我们可能有一个静态变量来表示现有的Human类的数量。没有人类?值为零,但仍然存在。

在下面的例子中,ClassA创建了一个实例变量。ClassB创建一个静态变量。

001 # Example of an instance variable

002 class ClassA():

003     def __init__(self):

004         self.y = 3

005

006 # Example of a static variable

007 class ClassB():

008     x = 7

009

010 # Create class instances

011 a = ClassA()

012 b = ClassB()

013

014 # Two ways to print the static variable.

015 # The second way is the proper way to do it.

016 print(b.x)

017 print(ClassB.x)

018

019 # One way to print an instance variable.

020 # The second generates an error, because we don’t know what instance

021 # to reference.

022 print(a.y)

023 print(ClassA.y)

在上面的例子中,第 16 行和第 17 行打印出静态变量。第 17 行是这样做的正确方式。与以前不同,我们可以在使用静态变量时引用类名,而不是指向特定实例的变量。因为我们使用的是类名,所以通过查看第 17 行,我们可以立即看出我们使用的是静态变量。第 16 行可以是实例或静态变量。这种混乱使得第 17 行成为更好的选择。

第 22 行打印出实例变量,就像我们在前面的例子中所做的一样。第 23 行将产生一个错误,因为y的每个实例都是不同的(毕竟它是一个实例变量),并且我们没有告诉计算机我们在谈论的是ClassA的哪个实例。

实例变量隐藏静态变量

这是我不喜欢 Python 的一个特性。静态变量和实例变量可以同名。请看下面的例子:

# Class with a static variable

class ClassB():

x = 7

# Create a class instance

b = ClassB()

# This prints 7

print(b.x)

# This also prints 7

print(ClassB.x)

# Set x to a new value using the class name

ClassB.x = 8

# This also prints 8

print(b.x)

# This prints 8

print(ClassB.x)

# Set x to a new value using the instance.

# Wait! Actually, it doesn’t set x to a new value!

# It creates a brand new variable, x. This x

# is an instance variable. The static variable is

# also called x. But they are two different

# variables. This is super-confusing and is bad

# practice.

b.x = 9

# This prints 9

print(b.x)

# This prints 8\. NOT 9!!!

print(ClassB.x)

允许实例变量隐藏静态变量让我困惑了很多年!

回顾

多项选择测验

Select the best class definition for an alien: class Alien(): def __init__(self): self.name = "" self.height = 7.2 self.weight = 156   class alien(): def __init__(self): self.name = "" self.height = 7.2 self.weight = 156   class alien.name = "" class alien.height = 7.2 class alien.weight = 156   class alien( def __init__(self): self.name = "" self.height = 7.2 self.weight = 156 )     What does this code do? d1 = Dog() d2 = Dog() Creates two objects, of type Dog.   Creates two classes, of type Dog.   Creates one object, of type Dog.     What does this code do? d1 = Dog() d2 = d1 Creates two objects, of type Dog.   Creates two classes, of type Dog.   Creates one object, of type Dog.     What is wrong with the following code: class Book():     def open(self):         print("You opened the book")     def __init__(self):         self.pages = 347 There should be a self. in front of pages.   Book should not be capitalized.   The __init__ with attributes should be listed first.   open should be capitalized.     What is wrong with the following code: class Ball():     def __init__(self):         self.x = 0         self.y = 0         self.change_x = 0         self.change_y = 0     x += change_x     y += change_y The ball should not be at location 0, 0   The variables should be set equal to "".   The code to add to x and y must be in a method.   The code to set the variables to zero should be inside a method.   All classes must have at least one method   self should be in between the parentheses.     What is wrong with the following code: class Ball():     def __init__(self):         self.x = 0         self.y = 0 Ball.x = 50 Ball.y = 100 Lines 3 and 4 should not have self. in front.   Ball. does not refer to an instance of the class.   Lines 3 and 5 should be used to set x and y to 50 and 100.   Ball. on lines 6 and 7 should be lowercase.     What is wrong with the following code: class Ball():     def __init__(self):         self.x = 0         self.y = 0         b = Ball()         b.x = 50         b.y = 100 Lines 6–8 should be in a method.   Lines 6–8 should not be indented.   Lines 7 and 8 should have self. instead of b.   Line 6 should have self in between the parentheses.     What will this print? class Ball():     def __init__(self):         self.x = 0         self.y = 0 b1 = Ball() b2 = b1 b1.x = 40 b2.x = 50 b1.x += 5 b2.x += 5 print(b1.x, b2.x) 40 40   60 60   45 55   55 55   40 50     What will this print? class Account():     def __init__(self):         self.money = 0     def deposit(self, amount):         self.money += amount account = Account() money = 100 account.deposit(50) print(money, account.money) 150 150   100 50   100 100   50 100   50 50   100 150     What is wrong with the following: class Dog():     def __init__(self, new_name):         """ Constructor.         Called when creating an object of this type """         name = new_name         print("A new dog is born!") # This creates the dog my_dog = Dog("Rover") On line 6, there should be a self. in front of new_name   On line 6, there should be a self. in front of name   Line 10 has 1 parameter, yet in line 2 we can see __init__ takes two parameters.   Lines 9 and 10 should be indented.   Lines 6 to 7 should not be indented.

简答工作表

第一部分:

What is the difference between a class and an object?   What is the difference between a function and a method?   Write code to create an instance of this class and set its attributes: class Dog():     def __init__(self):         self.age = 0         self.name = ""         self.weight = 0   Write code to create two different instances of this class and set attributes for both objects: class Person():     def __init__(self):         self.name = ""         self.cell_phone = ""         self.email = ""   For the code below, write a class that has the appropriate class name and attributes that will allow the code to work. my_bird = Bird() my_bird.color = "green" my_bird.name = "Sunny" my_bird.breed = "Sun Conure"   Define a class that would represent a character in a simple 2D game. Include attributes for the position, name, and strength.   The following code runs, but it is not correct. What did the programmer do wrong? class Person():     def __init__(self):         self.name = ""         self.money = 0 nancy = Person() name = "Nancy" money = 100   Take a look at the code. It does not run. What is the error that prevents it from running? class Person():     def __init__(self):         self.name = ""         self.money = 0 bob = Person() print(bob.name, "has", money, "dollars.")   Even with that error fixed, the program will not print out: Bob has 0 dollars. Instead it just prints out: has 0 dollars. Why is this the case?   Take pairs of the following items, and list some of the “has-a” relationships, and the “is-a” relationships between them.

  • 往来账户
  • 抵押账户
  • 顾客
  • 撤退
  • 银行存款
  • severely subnormal 智力严重逊常
  • 交易
  • 地址
  • 存款

In Python, how is an “is-a” relationship implemented? Give an example.   In Python, how is a “has-a” relationship implemented? Give an example.   How does this change if an object is allowed more than one item of a given type? (Ask if you aren’t sure.)

第二部分:

要回答接下来的四个问题,创建一个程序。在那个程序中会有所有四个问题的答案。确保程序运行,然后从程序中复制/粘贴来回答下面的每个问题。

你应该有一个以三个类定义开始的程序,前三个问题各有一个。然后你应该有代码来创建每个类的实例,这将是最后一个问题的答案。

Write code that defines a class named Animal:

  • 为动物名称添加一个属性。
  • 为打印“Munch munch”的Animal添加一个eat()方法
  • 打印“Grrr 表示[动物名称]”的Animalmake_noise()方法
  • 为打印“一只动物出生了”的Animal类添加一个构造函数

A class named Cat:

  • 使Animal成为父级。
  • 打印“喵说[动物名]”的Catmake_noise()方法
  • 打印“一只猫出生了”的Cat的构造函数
  • 修改构造函数,使它也调用父构造函数。

A class named Dog:

  • 使Animal成为父级。
  • 一个Dogmake_noise()方法,打印“树皮说【动物名称】”
  • 打印“一只狗出生了”的Dog的构造函数
  • 修改构造函数,使它也调用父构造函数。

A main program with:

  • 创建一只猫、两只狗和一只动物的代码。
  • 设定每种动物的名称。
  • 为每种动物调用eat()make_noise()的代码。(这个别忘了!)

锻炼

查看附录中本章附带的练习“类和图形”。

十四、精灵简介

我们的游戏需要支持处理碰撞的物体。球从球拍上弹开,激光束击中外星人,或者我们最喜欢的角色收集硬币。所有这些例子都需要碰撞检测。

pygame 库支持精灵。精灵是一个二维图像,是更大的图形场景的一部分。通常,精灵是场景中的某种对象,可以与汽车、青蛙或小水管工进行交互。

A978-1-4842-1790-0_14_Figa_HTML.jpg

最初,视频游戏控制台内置了对小精灵的硬件支持。现在不再需要这种专门的硬件支持,但我们仍然使用术语 sprite。

基本精灵和碰撞

让我们逐步完成一个使用精灵的示例程序。这个例子展示了如何创建一个黑色方块的屏幕,并使用一个由鼠标控制的红色方块来收集它们,如下图所示。该计划保持分数多少块已经收集。此示例的代码可以在以下位置找到:

ProgramArcadeGames.com/python_examples/f.php?file=sprite_collect_blocks.py

A978-1-4842-1790-0_14_Figb_HTML.jpg

精灵游戏示例

我们程序的前几行像我们做过的其他游戏一样开始(为了清楚起见添加了行号):

001 import pygame

002 import random

003

004 # Define some colors

005 BLACK = (  0,   0,   0)

006 WHITE = (255, 255, 255)

007 RED   = (255,   0,   0)

pygame 库是为第 1 行的 sprite 支持而导入的。随机库被导入用于第 2 行上的块的随机放置。颜色的定义在第 5-7 行是标准的;这个例子中没有什么新的东西。

009 class Block(pygame.sprite.Sprite):

010     """

011     This class represents the ball.

012     It derives from the "Sprite" class in pygame.

013     """

第 9 行开始定义Block类。注意,在第 9 行,这个类是Sprite类的子类。pygame.sprite.指定了库和包,这将在第十五章中讨论。Sprite类的所有默认功能现在都将成为Block类的一部分。

015     def __init__(self, color, width, height):

016         """ Constructor. Pass in the color of the block,

017         and its x and y position. """

018

019         # Call the parent class (Sprite) constructor

020         super().__init__()

第 15 行的Block类的构造函数像任何其他构造函数一样为self接受一个参数。它还接受定义对象颜色、高度和宽度的参数。

调用Sprite中的父类构造函数来允许精灵初始化是很重要的。这是在第 20 行完成的。

022         # Create an image of the block, and fill it with a color.

023         # This could also be an image loaded from the disk.

024         self.image = pygame.Surface([width, height])

025         self.image.fill(color)

第 24 行和第 25 行创建了最终将出现在屏幕上的图像。第 24 行创建了一个空白图像。第 25 行用黑色填充它。如果程序需要黑色方块以外的东西,这些是要修改的代码行。

例如,看看下面的代码:

def __init__(self, color, width, height):

"""

Ellipse Constructor. Pass in the color of the ellipse,

and its size

"""

# Call the parent class (Sprite) constructor

super().__init__()

# Set the background color and set it to be transparent

self.image = pygame.Surface([width, height])

self.image.fill(WHITE)

self.image.set_colorkey(WHITE)

# Draw the ellipse

pygame.draw.ellipse(self.image, color, [0, 0, width, height])

如果上面的代码被替换,那么一切都将是省略号的形式。第 29 行绘制椭圆,第 26 行将白色变成透明色,这样背景就显示出来了。这与第十二章中使用的使图像的白色背景透明的概念相同。

def __init__(self):

""" Graphic Sprite Constructor. """

# Call the parent class (Sprite) constructor

super().__init__()

# Load the image

self.image = pygame.image.load("player.png").convert()

# Set our transparent color

self.image.set_colorkey(WHITE)

如果需要位图图形,替换上面的代码行将加载一个图形(第 22 行)并将白色设置为透明背景色(第 25 行)。在这种情况下,sprite 的尺寸将自动设置为图形尺寸,不再需要传递它们。看看第 15 行为什么不再有这些参数。

不管我们有什么样的精灵,在构造函数中还需要一行重要的代码:

027         # Fetch the rectangle object that has the dimensions of the image

028         # image.

029         # Update the position of this object by setting the values

030         # of rect.x and rect.y

031         self.rect = self.image.get_rect()

属性rect是一个变量,它是 pygame 提供的Rect类的一个实例。矩形代表精灵的尺寸。这个 rectangle 类具有可以设置的 x 和 y 属性。Pygame 将在 x 和 y 属性所在的地方绘制精灵。所以要移动这个精灵,程序员需要设置mySpriteRef.rect.xmySpriteRef.rect.y,其中mySpriteRef是指向精灵的变量。

我们已经完成了Block类。是时候进入初始化代码了。

033 # Initialize pygame

034 pygame.init()

035

036 # Set the height and width of the screen

037 screen_width = 700

038 screen_height = 400

039 screen = pygame.display.set_mode([screen_width, screen_height])

上面的代码初始化 pygame 并为游戏创建一个窗口。这里没有任何来自其他 pygame 程序的新内容。

041 # This is a list of ’sprites.’ Each block in the program is

042 # added to this list. The list is managed by a class called ’Group.’

043 block_list = pygame.sprite.Group()

044

045 # This is a list of every sprite.

046 # All blocks and the player block as well.

047 all_sprites_list = pygame.sprite.Group()

和精灵一起工作的一个主要优势是能够和他们一起分组工作。如果精灵在一个组中,我们可以用一个命令来绘制和移动所有的精灵。我们还可以检查整个组的精灵碰撞。

上面的代码创建了两个列表。变量all_sprites_list将包含游戏中的每一个精灵。这个列表将被用来绘制所有的精灵。变量block_list保存玩家可以碰撞的每个物体。在这个例子中,它将包括游戏中除玩家之外的所有对象。我们不希望玩家出现在这个列表中,因为当我们检查玩家是否与block_list中的物体发生碰撞时,pygame 会继续,如果玩家出现在列表中,它会返回碰撞结果。

049 for i in range(50):

050     # This represents a block

051     block = Block(BLACK, 20, 15)

052

053     # Set a random location for the block

054     block.rect.x = random.randrange(screen_width)

055     block.rect.y = random.randrange(screen_height)

056

057     # Add the block to the list of objects

058     block_list.add(block)

059     all_sprites_list.add(block)

从第 49 行开始的循环向屏幕添加了 50 个黑色精灵块。第 51 行创建一个新块,设置颜色、宽度和高度。第 54 行和第 55 行设置了这个对象出现的坐标。第 58 行将方块添加到玩家可以碰撞的方块列表中。第 59 行将其添加到所有块的列表中。这应该与您在练习 13 中写的代码非常相似。

061 # Create a RED player block

062 player = Block(RED, 20, 15)

063 all_sprites_list.add(player)

第 61–63 行为我们的游戏设置了玩家。第 62 行创建了一个红色块,最终将作为播放器。该块被添加到第 63 行的all_sprites_list中,因此它可以被绘制,但不能被绘制到block_list中。

065 # Loop until the user clicks the close button.

066 done = False

067

068 # Used to manage how fast the screen updates

069 clock = pygame.time.Clock()

070

071 score = 0

072

073 # -------- Main Program Loop -----------

074 while not done:

075     for event in pygame.event.get():

076         if event.type == pygame.QUIT:

077             done = True

078

079     # Clear the screen

080     screen.fill(WHITE)

上面的代码是一个标准的程序循环,在第六章中首次介绍。第 71 行将我们的score变量初始化为 0。

082     # Get the current mouse position. This``returns

083     # as a list of two numbers.

084     pos = pygame.mouse.get_pos()

085

086     # Fetch the x and y out of the list,

087        # just like we’d fetch letters out of a string.

088     # Set the player object to the mouse location

089     player.rect.x = pos[0]

090     player.rect.y = pos[1]

第 84 行获取鼠标位置,类似于之前讨论的其他 pygame 程序。重要的新部分包含在第 89–90 行,其中包含精灵的矩形被移动到新的位置。请记住,这个 rect 是在第 31 行创建的,如果没有那一行,这段代码将无法运行。

092     # See if the player block has collided with anything.

093     blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)

这行代码获取由player引用的 sprite,并对照block_list中的所有 sprite 进行检查。代码返回一个重叠的精灵列表。如果没有重叠的精灵,它将返回一个空列表。布尔True将从列表中删除碰撞的精灵。如果设置为False,精灵将不会被移除。

095     # Check the list of collisions.

096     for block in blocks_hit_list:

097         score += 1

098         print(score)

这为在第 93 行创建的碰撞列表中的每个精灵循环。如果列表中有精灵,增加每次碰撞的分数。然后将分数打印到屏幕上。请注意,第 98 行的print不会将乐谱打印到主窗口和精灵窗口,而是打印到控制台窗口。弄清楚如何让分数显示在主窗口上是练习“收集精灵”的一部分

100     # Draw all the spites

101     all_sprites_list.draw(screen)

all_sprites_list所属的Group类有一个叫做draw的方法。该方法遍历列表中的每个 sprite,并调用该 sprite 的draw方法。这意味着只需一行代码,一个程序就可以让all_sprites_list中的每个精灵都被绘制出来。

103     # Go ahead and update the screen with what we’ve drawn.

104     pygame.display.flip()

105

106     # Limit to 60 frames per second

107     clock.tick(60)

108

109 pygame.quit()

当主循环完成时,第 103–109 行翻转屏幕并调用quit方法。

移动精灵

在到目前为止的例子中,只有玩家精灵移动。一个程序怎么能让所有的精灵都动起来?这很容易做到;只需要两步。

第一步是向Block类添加一个新方法。这种新方法叫做update。当对整个列表调用update时,将自动调用更新功能。

把这个放进雪碧里:

def update(self):

""" Called each frame. """

# Move block down one pixel

self.rect.y += 1

把这个放到主程序循环中:

# Call the update() method for all blocks in``t

block_list.update()

代码并不完美,因为块会从屏幕上掉下来,不再出现。这段代码将改进update函数,使块重新出现在顶部。

def update(self):

# Move the block down one pixel

self.rect.y += 1

if self.rect.y > screen_height:

self.rect.y = random.randrange(-100, -10)

self.rect.x = random.randrange(0, screen_width)

如果程序应该重置收集到屏幕顶部的块,可以使用以下代码更改 sprite:

def reset_pos(self):

""" Reset position to the top of the screen, at a random x location.

Called by update() or the main program loop if there is a collision.

"""

self.rect.y = random.randrange(-300, -20)

self.rect.x = random.randrange(0, screen_width)

def update(self):

""" Called each frame. """

# Move block down one pixel

self.rect.y += 1

# If block is too far down, reset to top of screen.

if self.rect.y > 410:

self.reset_pos()

当碰撞发生时,程序可能会调用reset_pos函数,而不是销毁块,块将移动到屏幕顶部准备收集。

# See if the player block has collided with anything.

blocks_hit_list = pygame.sprite.spritecollide(player, block_list, False)

# Check the list of collisions.

for block in blocks_hit_list:

score += 1

print(score)

# Reset block to the top of the screen to fall again.

block.reset_pos()

此示例的完整代码如下:

ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites.py

如果你想看弹跳精灵的代码,请看这里:

ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites_bounce.py

如果你想让它们转圈:

ProgramArcadeGames.com/python_examples/f.php?file=sprite_circle_movement.py

游戏课

回到第十章我们介绍了函数。在本章的最后,我们讨论了使用main函数的选项。随着程序变得越来越大,这种技术有助于我们避免因需要整理大量代码而带来的问题。我们的程序还没有那么大。然而,我知道有些人喜欢从一开始就组织好事情。

对于那些人来说,这里有另一种组织代码的可选技术。(如果您不在这个阵营中,您可以跳过这一部分,稍后当您的程序变得太大时再回来。)观看视频,了解该程序的工作原理。

ProgramArcadeGames.com/python_examples/f.php?file=game_class_example.py

其他示例

这里有几个你可以用精灵做什么的例子。其中一些还包括一个解释代码如何工作的链接视频。

拍摄东西

拍摄东西

A978-1-4842-1790-0_14_Figc_HTML.jpg

对射击游戏感兴趣吗?类似经典的太空入侵者?此示例显示了如何创建代表项目符号的精灵:

ProgramArcadeGames.com/python_examples/f.php?file=bullets.py

墙壁

你在寻找更多的冒险游戏吗?你不想让你的玩家到处乱逛吧?这显示了如何添加阻止玩家移动的墙:

ProgramArcadeGames.com/python_examples/f.php?file=move_with_walls_example.py

A978-1-4842-1790-0_14_Figd_HTML.jpg

跟着我们能碰到的墙走

等等?一个房间还不够冒险吗?你想让你的玩家在屏幕间移动吗?我们能做到!请看这个例子,玩家可能在一个多房间的迷宫中奔跑:

ProgramArcadeGames.com/python_examples/f.php?file=maze_runner.py

A978-1-4842-1790-0_14_Fige_HTML.jpg

多房间迷宫

平台

有兴趣打造一个平台化的,比如大金刚?我们需要使用与我们的墙壁示例相同的想法,但是增加一些重力:

ProgramArcadeGames.com/python_examples/f.php?file=platform_jumper.py

A978-1-4842-1790-0_14_Figf_HTML.jpg

在平台上跳跃

好的平台玩家可以左右移动。这是一个侧面滚动平台:

ProgramArcadeGames.com/python_examples/f.php?file=platform_scroller.py

A978-1-4842-1790-0_14_Figg_HTML.jpg

侧面滚动平台

甚至更酷的平台游戏也有移动的平台!看看这个例子是如何做到的:

ProgramArcadeGames.com/python_examples/f.php?file=platform_moving.py

A978-1-4842-1790-0_14_Figh_HTML.jpg

移动平台

蛇/蜈蚣

我偶尔会碰到一些读者想制作一个蛇或者蜈蚣类型的游戏。你有一条可以控制的多节蛇。这需要将每个段保存在一个列表中。虽然它需要学习两个新的命令,但这个游戏背后的概念并不难。

控制一条蛇或蜈蚣绕着屏幕走:

ProgramArcadeGames.com/python_examples/f.php?file=snake.py

A978-1-4842-1790-0_14_Figi_HTML.jpg

使用精灵工作表

这是一个广泛的例子,使用 sprite 表来提供平台游戏背后的图形。它支持多层次和移动平台。游戏被分成多个文件。ProgramArcadeGames.com/python_examples/en/sprite_sheets

A978-1-4842-1790-0_14_Figj_HTML.jpg

雪碧薄板平台车

回顾

多项选择测验

What is a Sprite? A graphic image that he computer can easily track, draw on the screen, and detect collisions with.   A very bright color that seems to glow.   A function that draws images to the screen.   A sprite is to Tinkerbell as a human is to Bob.     Which option best describes how a programmer use sprites in his or her program? Derive a new class from pygame.sprite.Sprite, and then create instances of those sprites and add them to sprite groups.   Create instances of pygame.sprite.Sprite and add them to sprite groups.   Use functions to draw images directly to the screen   Use bitmaps and blit images to the screen.     What is the standard way to draw sprites in a program? Add a sprite to a group. Then call .draw(screen) on the group.   Call the sprite’s .draw(screen) method.   Call the sprite’s .update(screen) method.   Call the sprite’s .blit(screen) method.     How does a program move a sprite pointed to by mysprite? Set new mysprite.rect.x and mysprite.rect.y values.   Set new mysprite.x and mysprite.y values.   Call mysprite.draw(x,y) with the desired x and y values.   Call mysprite.move(x,y) with the desired x and y values.     How does a sprite move itself? Create an update() method. Change self.rect.x and self.rect.y values.   Create an update() method. Change rect.x and rect.y values.   Create a move() method. Change self.x and self.y values.     If a programmer creates his/her own constructor for a sprite, what must be the first line of that constructor? super().__init__()   self.image = pygame.Surface([width, height])   self.image.set_colorkey(white)     If a programmer wants to create a transparent background for a sprite, what type of image should be avoided? jpg   png   gif     What does the True do in this line of code? sprites_hit_list = pygame.sprite.spritecollide(sprite, sprite_list, True) Removes sprite if any sprite in sprite_list is overlapping.   Creates an explosion effect when the sprites collide.   Creates a sound effect when the sprites collide.   Removes any sprite in sprite_list that is overlapping sprite.     What is special about a sprite’s update() function? It is called automatically each time through the game loop.   It is called automatically when the code calls update() on any list that sprite is in.   There is no special significance to that function.     What is the proper command to add a sprite to an instance of pygame.sprite.Group() pointed to by a sprite_list? sprite_list.append(my_sprite)   sprite_list.add(my_sprite)   sprite_list.insert(my_sprite)     If the screen is 600 wide and 400 tall, where will this sprite be moved? mysprite.rect.x = 600 mysprite.rect.y = 400 A978-1-4842-1790-0_14_Figk_HTML.jpg   A978-1-4842-1790-0_14_Figl_HTML.jpg   A978-1-4842-1790-0_14_Figm_HTML.jpg   A978-1-4842-1790-0_14_Fign_HTML.jpg

锻炼

查看附录中本章附带的练习“收集精灵”。

十五、库和模块

A978-1-4842-1790-0_15_Figa_HTML.jpg

库是函数和类的代码集合。通常,这些库是由其他人编写并引入项目中的,这样程序员就不必重新发明轮子。在 Python 中,用来描述代码库的术语是模块。

通过使用import pygameimport random,目前创建的程序已经使用了模块。一个库可以由多个可以导入的模块组成。通常一个库只有一个模块,所以这些词有时可以互换使用。

模块通常被组织成具有相似功能的组。在这个类中,程序已经使用了来自math模块、random模块和pygame库的函数。可以对模块进行组织,以便单个模块包含其他模块。例如,pygame模块包含pygame.drawpygame.imagepygame.mouse的子模块。

除非程序要求,否则模块不会被加载。这样可以节省时间和计算机内存。本章介绍了如何创建模块以及如何导入和使用该模块。

为什么要创建库?

程序员创建自己的库有三个主要原因:

It breaks the code into smaller, easier to use parts.   It allows multiple people to work on a program at the same time.   The code written can be easily shared with other programmers.

本书中已经创建的一些程序已经开始变得相当长。通过将一个大程序分成几个小程序,管理代码就更容易了。例如,在前一章的 sprite 示例中,程序员可以将 sprite 类移动到一个单独的文件中。在一个复杂的程序中,每个精灵可能包含在它自己的文件中。

如果多个程序员在同一个项目上工作,如果所有的代码都在一个文件中,这几乎是不可能的。然而,通过将程序分成多个部分,它变得更容易。一个程序员可以开发一个Orc sprite 类。另一个程序员可以处理Goblin sprite 类。由于精灵在不同的文件中,程序员不会遇到冲突。

现代程序员很少从头开始构建程序。通常,程序是由共享相同功能的其他程序的部分构建而成的。如果一个程序员编写了可以处理抵押申请表格的代码,那么这些代码最好是放在一个库中。然后,需要管理该银行的抵押申请表单的任何其他程序都可以调用该库。

创建您自己的模块/库文件:

在这个例子中,我们将把一个短程序分成多个文件。这里我们在一个名为test.py的文件中有一个函数以及对该函数的调用:

# Foo function

def foo():

print("foo!")

# Foo call

foo()

是的,这个程序在一个文件中不会太长。但是如果函数和主程序代码都很长,那就不一样了。如果我们有几个函数,每个函数都有 100 行长,那么管理这么大的文件将会非常耗时。但是在这个例子中,为了清楚起见,我们将保持代码简短。

我们可以将foo函数移出这个文件。那么这个文件将只剩下主程序代码。(在这个例子中,除了学习如何做之外,没有理由将它们分开。)

为此,创建一个新文件并将foo函数复制到其中。用名称my_functions.py保存新文件。文件必须保存在与test.py相同的目录下。

# Foo function

def foo():

print("foo!")

# Foo call that doesn’t work

foo()

不幸的是,事情没有这么简单。文件test.py不知道去查看my_functions.py文件并导入它。我们必须添加命令来导入它:

# Import the my_functions.py file

import my_functions

# Foo call that still doesn’t work

foo()

那还是不行。我们遗漏了什么?就像我们导入 pygame 的时候,要把包名放在函数前面一样。像这样:

# Import the my_functions.py file

import my_functions

# Foo call that does work

my_functions.foo()

这是因为my_functions.被加到了函数调用的前面。

命名空间

一个程序可能需要使用两个库文件。如果这些库有同名的函数会怎么样?如果有两个名为print_report的函数——一个打印成绩,一个打印账户报表,会怎么样?例如:

def print_report():

print("Student Grade Report:" )

def print_report():

print("Financial Report:" )

如何让程序指定调用哪个函数?嗯,那很简单。您可以指定名称空间。命名空间是出现在下面代码中的函数名之前的作品:

import student_functions

import financial_functions

student_functions.print_report()

financial_functions.print_report()

因此,现在我们可以看到为什么这可能是必要的。但是如果没有名字冲突呢?每次都键入一个名称空间可能会令人厌烦。您可以通过将库导入本地名称空间来解决这个问题。本地命名空间是一个函数、变量和类的列表,您不必在前面加上命名空间。回到foo的例子,让我们删除原来的导入并用一种新的导入类型替换它:

# import foo

from my_functions import *

foo()

即使没有在函数调用前添加my_functions.,这也是可行的。星号是一个通配符,将从my_functions导入所有函数。如果需要,程序员可以通过指定函数名来导入单独的函数。

第三方库

使用 Python 时,可以使用 Python 内置的许多库。看看这里提供的所有库:

http://docs.python.org/3/py-modindex.html

可以下载并安装其他库。有处理网络、复数、数据库等的库。

  • Pygame:用于创建游戏的库。http://www.pygame.org/docs/
  • wxPython:创建 GUI 程序,包括窗口、菜单等等。http://www.wxpython.org/
  • pydot:生成复杂的有向和无向图。http://code.google.com/p/pydot/
  • NumPy:用于处理矩阵的复杂库。http://numpy.scipy.org/

Python 库的精彩列表和安装程序的链接可以在这里找到:http://www.lfd.uci.edu/~gohlke/pythonlibs/

浏览可用的库列表可以帮助您集思广益,找出可以创建的程序类型。大多数编程都涉及到组装大型部件,而不是从头开始编写所有东西。

回顾

多项选择测验

What is a library? A collection of functions and/or classes that can be imported into a project.   A store where you can buy code from other developers.   Any code that has not been written by a developer.   A .pyc file.     Why would a person create a library? It provides an easy way for developers to share code between projects and other developers.   It makes the code run faster.   It makes the code smaller.   Libraries are something to be avoided by developers.     Why does the following code not work? The first file: def foo():     print("foo!") And the second file: import my_functions foo() The program should read my_functions.foo()   The program should read import my_functions.py   The program should read import foo   The program should read from foo import my_functions     What is the proper code to import the math library into the local namespace? from math import *   import math   import local   from math import local   import math into local     What does the asterisk represent in the following line of code: from my_functions import * Wildcard. Import every function in the my_functions module.   It represents the local namespace.   My God, it’s full of stars!

简答工作表

What is a Python library?   What are some of the reasons why a programmer would want to create his/her own library file?   There are two ways to import library files in Python. Give an example of each.   How do calls to functions and classes differ depending on how the library is imported?   Can library files import other library files?   What is a namespace?

锻炼

查看附录中本章附带的练习“移动精灵”。

十六、搜索

A978-1-4842-1790-0_16_Figa_HTML.jpg

搜索是计算机一直在做的一项重要且非常常见的操作。每当有人按 ctrl-f 键进行“查找”时,当用户使用“键入”键快速选择一个项目时,或者当 web 服务器提取有关客户的信息以显示带有客户订单的定制网页时,都会使用搜索。

有很多方法可以搜索数据。谷歌将一整个价值数十亿美元的公司建立在这个事实上。本章介绍两种最简单的搜索方法:线性搜索和二分搜索法。

从文件中读取

在讨论如何搜索之前,我们需要学习如何从文件中读取数据。从文件中读入数据集比每次手工输入要有趣得多。

假设我们需要创建一个程序,让我们能够快速找到一个超级恶棍的名字。首先,我们的程序需要一个超级反派的数据库。要下载该数据集,请下载并保存该文件:

http://ProgramArcadeGames.com/chapters/16_searching/super_villains.txt

这些是 nine.frenchboys.net 网站随机生成的名字。保存这个文件,并记住你把它保存到哪个目录。

在与super_villains.txt相同的目录下,创建、保存并运行以下 python 程序:

file = open("super_villains.txt")

for line in file:

print(line)

这段代码中只有一个新命令open。因为是类似print的内置函数,所以不需要import。关于这个函数的全部细节可以在 https://docs.python.org/3/library/functions.html#open 中找到,但是在这一点上,这个命令的文档太专业了,甚至不值得一看。

上面的程序有两个问题,但是它提供了一个简单的读取文件的例子。第 1 行打开一个文件,并准备好读取它。文件的名称在引号之间。新变量file是一个对象,表示正在读取的文件。第 3 行展示了如何使用普通的for循环来逐行读取文件。把file想象成一个行列表,当程序在循环中运行时,新的变量line将被设置到每一行。

尝试运行该程序。它的一个问题是文本以双倍行距打印。原因是从文件中取出并存储在变量line中的每一行都包含回车作为字符串的一部分。还记得第一章中介绍的回车和换行吗?print语句添加了另一个回车,结果是双倍行距的输出。

第二个问题是文件打开了,但没有关闭。这个问题不像双倍行距问题那样明显,但是很重要。Windows 操作系统一次只能打开这么多文件。一个文件通常一次只能由一个程序打开。让文件保持打开状态会限制其他程序对该文件的操作,并会占用系统资源。有必要关闭该文件,让 Windows 知道该程序不再使用该文件。在这种情况下,这并不太重要,因为一旦任何程序完成运行,Windows 将自动关闭任何打开的文件。但是因为这样编程是一个坏习惯,所以让我们更新代码:

file = open("super_villains.txt")

for line in file:

line = line.strip()

print(line)

file.close()

上面的清单效果更好。它有两个新的补充。第 4 行是对每个String类中内置的strip方法的调用。这个函数返回一个新字符串,没有原始字符串的尾随空格和回车。方法不会改变原始字符串,而是创建一个新字符串。这行代码不起作用:

line.strip()

如果程序员希望原始变量引用新字符串,她必须将它赋给新返回的字符串,如第 4 行所示。

第二次添加在第 7 行。这将关闭文件,这样操作系统就不必在程序结束后再去清理打开的文件。

读入数组

将文件的内容读入一个数组是很有用的,这样程序就可以在以后对它进行处理。这可以在 python 中用以下代码轻松完成:

# Read in a file from disk and put it in an array.

file = open("super_villains.txt")

name_list = []

for line in file:

line = line.strip()

name_list.append(line)

file.close()

这结合了如何读取文件的新模式,以及先前学习的如何创建空数组并在新数据进入时追加到该数组的模式,这在第八章的中有所介绍。为了验证文件是否被正确读入数组,程序员可以打印数组的长度:

print( "There were",len(name_list),"names in the file.")

或者程序员可以带来数组的全部内容:

for name in name_list:

print(name)

继续进行不同的搜索之前,请确保您可以读取该文件。

线性搜索

如果一个程序在一个数组中有一组数据,它如何找到一个特定的元素呢?有两种方法可以做到这一点。第一种方法是使用线性搜索。这从第一个元素开始,并不断比较元素,直到找到所需的元素(或用尽元素。)

线性搜索算法

# --- Linear search

key = "Morgiana the Shrew"

i = 0

while i < len(name_list) and name_list[i] != key:

i += 1

if i < len(name_list):

print( "The name is at position", i)

else:

print( "The name was not in the list." )

线性搜索相当简单。第 2 行设置了一个增量变量,该变量将准确跟踪程序下一次需要检查的列表位置。需要检查的第一个元素是零,所以i设置为零。

下一行有点复杂。计算机需要保持循环,直到两件事情之一发生。它找到了元素,或者用完了元素。第一个比较看我们正在检查的当前元素是否小于列表的长度。如果是这样,我们可以继续循环。第二个比较查看名称列表中的当前元素是否等于我们正在搜索的名称。

这种查看程序是否已经用完元素的检查必须首先发生。否则,程序将检查不存在的元素,这将导致错误。

如果在第 3 行中满足继续搜索的条件,第 4 行只是移动到下一个元素。

在循环结束时,程序检查第 6 行是否到达了列表的末尾。记住,n 个元素的列表编号为 0 到 n-1。因此,如果i等于列表的长度,则已经到达末尾。如果少了,我们就找到了元素。

线性搜索的变体

线性搜索的变体可用于创建几种常见的算法。例如,假设我们有一份外星人的名单。我们可能要检查这群外星人,看看是否有一个外星人是绿色的。还是所有的外星人都是绿色的?哪些外星人是绿色的?

首先,我们需要定义我们的外星人:

class Alien:

""" Class that defines an alien"""

def __init__(self, color, weight):

""" Constructor. Set name and color"""

self.color = color

self.weight = weight

然后,我们需要创建一个函数来检查并查看它是否具有我们正在寻找的属性。在这种情况下,是绿色的吗?我们将假设颜色是一个文本字符串,我们将把它转换成大写以消除大小写的敏感性。

def has_property(my_alien):

""" Check to see if an item has a property.

In this case, is the alien green? """

if my_alien.color.upper() == "GREEN":

return True

else:

return False

至少有一个项目有属性吗?

至少有一个外星人是绿色的吗?我们可以查一下。这项检查背后的基本算法是:

def check_if_one_item_has_property_v1(my_list):

""" Return true if at least one item has a

property. """

i = 0

while i < len(my_list) and not has_property(my_list[i]):

i += 1

if i < len(my_list):

# Found an item with the property

return True

else:

# There is no item with the property

return False

这也可以用一个for循环来完成。在这种情况下,一旦找到项目,循环将通过使用return提前退出。代码更短,但不是每个程序员都喜欢它。一些程序员认为循环不应该过早地以returnbreak语句结束。这完全取决于个人偏好——或者买单人的个人偏好。

def check_if_one_item_has_property_v2(my_list):

""" Return true if at least one item has a

property. Works the same as v1, but less code. """

for item in my_list:

if has_property(item):

return True

return False

所有项目都有属性吗?

外星人都是绿色的吗?这段代码与前面的例子非常相似。找出不同之处,看看你是否能找出变化背后的原因。

def check_if_all_items_have_property(my_list):

""" Return true if at ALL items have a property. """

for item in my_list:

if not has_property(item):

return False

return True

创建包含所有匹配属性的项目的列表

如果你想要一份绿色外星人的名单呢?这是我们之前的代码和将项目添加到列表中的代码的组合,我们在第七章的中学到了这些。

def get_matching_items(list):

""" Build a brand new list that holds all the items

that match our property. """

matching_list = []

for item in list:

if has_property(item):

matching_list.append(item)

return matching_list

你如何在测试中运行所有这些?上面的代码可以与下面的代码结合起来运行:

alien_list = []

alien_list.append(Alien("Green", 42))

alien_list.append(Alien("Red", 40))

alien_list.append(Alien("Blue", 41))

alien_list.append(Alien("Purple", 40))

result = check_if_one_item_has_property_v1(alien_list)

print("Result of test check_if_one_item_has_property_v1:", result)

result = check_if_one_item_has_property_v2(alien_list)

print("Result of test check_if_one_item_has_property_v2:", result)

result = check_if_all_items_have_property(alien_list)

print("Result of test check_if_all_items_have_property:", result)

result = get_matching_items(alien_list)

print("Number of items returned from test get_matching_items:", len(result))

有关完整的工作示例,请参见:

programarcadegames.com/python_examples/show_file.php?file=property_check_examples.py

这些常见的算法可以作为一个更大问题的解决方案的一部分,比如查找客户列表中所有无效的地址。

二进位检索

使用二分搜索法可以更快地搜索列表。二分搜索法的过程可以用经典的数字猜谜游戏“猜一个 1 到 100 之间的数字”来描述。为了更容易理解这个过程,让我们将这个游戏修改为“猜一个 1 到 128 之间的数字”数字范围包括 1 和 128,这意味着 1 和 128 都是可能的。

如果一个人使用线性搜索作为猜测秘密数字的方法,这个游戏将会相当漫长和无聊。

Guess a number 1 to 128: 1

Too low.

Guess a number 1 to 128: 2

Too low.

Guess a number 1 to 128: 3

Too low.

....

Guess a number 1 to 128: 93

Too low.

Guess a number 1 to 128: 94

Correct!

大多数人会用二分搜索法来查找号码。下面是一个用二分搜索法玩游戏的例子:

Guess a number 1 to 128: 64

Too low.

Guess a number 1 to 128: 96

Too high.

Guess a number 1 to 128: 80

Too low.

Guess a number 1 to 128: 88

Too low.

Guess a number 1 to 128: 92

Too low.

Guess a number 1 to 128: 94

Correct!

在每一轮数字猜谜游戏中,猜谜者都能够通过猜高或猜低来消除一半的问题空间。

在二分搜索法中,需要跟踪答案可能在列表中的上界和下界。计算机或猜数字的人选择这些元素的中点。重温示例:

下限为 1,上限为 128,中点为 $$ \frac{1+128}{2}=64.5 $$

Guess a number 1 to 128: 64

Too low.

下限 65,上限 128,中点 $$ \frac{65+128}{2}=96.5 $$

Guess a number 1 to 128: 96

Too high.

下限为 65,上限为 95,中点为 $$ \frac{65+95}{2}=80 $$

Guess a number 1 to 128: 80

Too low.

下限为 81,上限为 95,中点为 $$ \frac{81+95}{2}=88 $$

Guess a number 1 to 128: 88

Too low.

下限为 89,上限为 95,中点为 $$ \frac{89+95}{2}=92 $$

Guess a number 1 to 128: 92

Too low.

下限为 93,上限为 95,中点为 $$ \frac{93+95}{2}=94 $$

Guess a number 1 to 128: 94

Correct!

二分搜索法需要更少的猜测。最坏的情况是,它可以在 7 次猜测中猜出 1 到 128 之间的一个数字。再猜一次就把限制提高到 256。九次猜测可以得到一个 1 到 512 之间的数字。一个人只要猜 32 次,就能得到 1 到 42 亿之间的数字。

要计算给定一定次数的猜测,列表可以有多大,公式如下:n=x g 其中 n 是列表的大小,g 是猜测的次数。例如:

27

28

29

232

如果你知道问题的大小,我们可以使用 log 函数计算出猜测的次数。具体来说,就是以 2 为基数的对数。如果不指定底数,大多数人会假设你指的是底数为 e ≈ 2.71828 的自然对数,这不是我们想要的。例如,使用以 2 为底的对数来计算猜测次数:

log2

log2

数学够了!代码在哪里?执行二分搜索法的代码比线性搜索更复杂:

# --- Binary search

key = "Morgiana the Shrew";

lower_bound = 0

upper_bound = len(name_list)-1

found = False

# Loop until we find the item, or our upper/lower bounds meet

while lower_bound <= upper_bound and not found:

# Find the middle position

middle_pos = (lower_bound + upper_bound) // 2

# Figure out if we:

# move up the lower bound, or

# move down the upper bound, or

# we found what we are looking for

if name_list[middle_pos] < key:

lower_bound = middle_pos + 1

elif name_list[middle_pos] > key:

upper_bound = middle_pos - 1

else:

found = True

if found:

print( "The name is at position", middle_pos)

else:

print( "The name was not in the list." )

因为列表从元素 0 开始,所以第 3 行将下限设置为零。第 4 行将上限设置为列表长度减 1。因此,对于 100 个元素的列表,下限将是 0,上限是 99。

第 5 行的布尔变量将用于让 while 循环知道已经找到了元素。

第 6 行检查是否找到了元素,或者我们是否用完了元素。如果我们用完了所有的元素,下界将会等于上界。

第 7 行找到中间位置。有可能得到 64.5 左右的中间位置。不可能查找位置 64.5。(尽管 J. K .罗琳相当聪明地提出了平台 $$ 9\raisebox{1ex}{}!\left/ !\raisebox{-1ex}{}\right. $$ ,但这在这里行不通。最好的处理方式是使用第六章中首次引入的//操作符。这类似于/操作符,但只会返回整数结果。例如,11 // 2会给出 5 作为答案,而不是 5.5。

从第 8 行开始,程序检查猜测值是高、低还是正确。如果猜测值较低,则下限向上移动,刚好超过猜测值。如果猜测值太高,上限就移到猜测值之下。如果已经找到答案,则将found设置为True以结束搜索。

对于一个包含 100 个元素的列表,人们可以合理地猜测,平均来说,使用线性搜索,程序在找到元素之前必须检查其中的 50 个。对于二分搜索法,平均来说你仍然需要猜 7 次。在高级算法课程中,你可以找到精确的公式。在本课程中,假设平均情况和最坏情况相同。

回顾

多项选择测验

Before reading from a file, a program must: Open it with the open command.   Initialize it with the init command.   Reset the file with a reset command.     In order to read in each line of the file a program should: Use a for loop   Use the read_all_lines command.   Access each line in an array.     What will happen if a program fails to close a file after reading it? The file will be marked as busy and will be inaccessible until the program ends.   The file will not be able to be used until the computer restarts.   The programmer will get a call from his mother reminding him that he forgot to close his that file, again.   Nothing, it doesn’t really matter if you don’t close the file.     What processing usually needs to be done on a line after it has been read in? The carriage return and/or line feed need to be stripped of the end of the line.   The line needs to be converted to uppercase.   The line needs to be converted from a string of integers to a string of letters.   The dilithium crystals must be recalibrated before use.     What is wrong with this linear search? i = 0 while my_list[i] != key and i < len(my_list): i += 1 The loop needs to check to see if we ran out of list items before checking to see if the item is equal to the key.   The first check should be == not !=.   The second check should be <= not <.   The second check should be > not <.     After this code runs, what is the proper way to tell if the item was found or not? i = 0 while i < len(my_list) and my_list[i] != key: i += 1 if i == len(my_list):   if my_list[i] != key:   if my_list[i] == key:   if i > len(my_list):     A binary search starts looking: In the middle of the list.   At the beginning of the list.   At the end of the list.   At a random spot in the list.     Using a binary search, a list of 128 elements takes at most how many times through the search loop? 7   64   128   1     In a binary search, how do you know if an element is not in the list and the search should stop? The lower bound is equal or greater than the upper bound.   After every item has been checked.   When the key is not equal to the middle element.   When i is greater than or equal to the list length.     If the key is less than the middle element, the search should: Move the upper bound down to the middle element.   Move the lower bound up to the middle element.

简答工作表线性搜索审核

假设一个程序使用线性搜索,回答下列问题:

If a list has n elements, in the best case how many elements would the computer need to check before it found the desired element?   If a list has n elements, in the worst case how many elements would the computer need to check before it found the desired element? (Remember, give your answer in terms of n.)   If a list has n elements, how many elements need to be checked to determine that the desired element does not exist in the list?   If a list has n elements, what would the average number of elements be that the computer would need to check before it found the desired element?   Take the example linear search code and put it in a function called linear_search. Take in the list along with the desired element as parameters. Return the position of the element, or -1 if it was not found. Once you’ve written the function, try it out with the following code to see if it works: # --- Put your definition for linear_search right below: # --- Now if the function works, all these tests should pass: my_list = [4, 3, 2, 1, 5, 7, 6] r = linear_search(my_list, 3) if r == 1:     print("Test A passed") else:     print("Test A failed") r = linear_search(my_list, 2) if r == 2:     print("Test B passed") else:     print("Test B failed") r = linear_search(my_list, 10) if r == -1:     print("Test C passed") else:     print("Test C failed")

二分搜索法评论

假设一个程序使用二分搜索法,并且搜索列表是有序的,请回答以下问题:

If a list has n elements, in the best case how many elements would the computer need to check before it found the desired element?   If a list has n elements, in the worst case how many elements would the computer need to check before it found the desired element?   If a list has n elements, how many elements need to be checked to determine that the desired element does not exist in the list?   If a list has n elements, what would the average number of elements be that the computer would need to check before it found the desired element?   Take the example binary search code and put it in a function named binary_search. Take in the list along with the desired element as parameters. Return the position of the element, or -1 if it was not found. Once you’ve written the function, try it out with the following code to see if it works: # --- Put your definition for binary_search right below: # --- Now if the function works, all these tests should pass: my_list = [0, 3, 5, 12, 18, 50, 70, 78] r = binary_search(my_list, 3) if r == 1:     print("Test A passed") else:     print("Test A failed") r = binary_search(my_list, 2) if r == 2:     print("Test B passed") else:     print("Test B failed") r = binary_search(my_list, 10) if r == -1:     print("Test C passed") else:     print("Test C failed")

挑战问题

Does the following function correctly detect whether a list contains at least one positive element? Write code to try it out. Explain why it does work or why it does not work. Come up with working code. def detect_positive(list):     for element in list:         if element > 0:             return True         else:             return False

锻炼

查看附录中本章附带的“拼写检查”练习。

十七、数组支持的网格

像扫雷、井字游戏和许多类型的冒险游戏都将游戏数据保存在数字网格中。例如,井字游戏棋盘:

|   | O | O | |   | X |   | | X |   |   |

…可以使用数字网格来表示空白点、O 和 X,如下所示:

| Zero | Two | Two | | Zero | one | Zero | | one | Zero | Zero |

这个数字网格也可以称为二维数组或矩阵。(最后,我们开始学习矩阵。)表格中的数字值代表每个电路板位置应显示的内容。在前面的例子中,0 代表一个没有人打球的位置,1 代表一个 X,2 代表一个 o。

A978-1-4842-1790-0_17_Figa_HTML.jpg

扫雷游戏,显示支持网格的数字

上图是经典扫雷游戏中的一个例子。这个例子被修改为在左边显示经典显示,在右边显示棋盘上的数字网格。

数字10代表一个地雷,数字0代表一个没有被点击的空间,数字9代表一个被清除的空间。数字 1 到 8 代表周围 8 个方块内有多少个地雷,只有当用户点击方块时才会填充。

扫雷实际上可以有两个网格:一个用于常规显示,一个完全独立的数字网格将跟踪用户是否在板上放置了标记地雷位置的旗帜。

经典冒险游戏地图是使用平铺地图编辑器创建的。这些是巨大的网格,每个位置只是一个代表那里地形类型的数字。地形可以是泥土、道路、小径、绿草、棕草等等。下图所示的类似于 Tiled Qt 的程序允许开发人员轻松地制作这些地图并将网格写入磁盘。

A978-1-4842-1790-0_17_Figb_HTML.jpg

使用 Qt 图块创建冒险地图

冒险游戏也使用多个数字网格,就像扫雷有一个地雷网格和一个单独的旗帜网格。冒险游戏中的一个格子或层代表你可以行走的地形;另一个是你不能在上面行走的东西,比如墙和树;一层是可以瞬间杀死你的东西,比如熔岩或无底洞;一个是可以拿起来到处移动的物体;还有一层是用来放置怪物的。

像这样的地图可以加载到 Python 程序中,但不幸的是,如何管理的完整描述超出了本书的范围。像 https://github.com/bitcraft/PyTMX 这样的项目提供了加载这些地图所需的一些代码。

应用

说够了,我们来写点代码吧。这个例子将创建一个网格,如果我们显示一个白色或绿色块,这个网格将被触发。我们可以通过点击来改变网格值并使其变成绿色。这是像扫雷,战舰,连接四等基于网格的游戏的第一步。

转到示例代码页,下载基本模板文件:ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

从空白模板文件开始,尝试按照这里的说明重新创建这个程序。最后的程序在这一章的末尾,但是不要跳到前面去复制它!如果你那样做,你将什么也学不到。任何人都可以复制和粘贴代码,但如果你能重新创建这个程序,你就拥有了人们愿意为之付费的技能。如果你只会复制粘贴,那你就在这里浪费时间了。

绘制网格

Adjust the program’s window size to 255×255 pixels.   Create variables named width, height, and margin. Set the width and height to 20. This will represent how large each grid location is. Set the margin to 5. This represents the margin between each grid location and the edges of the screen. Create these variables before the main program loop.   Draw a white box in the upper left corner. Draw the box drawn using the height and width variables created earlier. (Feel free to adjust the colors.) When you get done your program’s window should look like the next figure.

A978-1-4842-1790-0_17_Figc_HTML.jpg

Step 3   Use a for loop to draw 10 boxes in a row. Use column for the variable name in the for loop. The output will look like one long box until we add in the margin between boxes. See the next figure.

A978-1-4842-1790-0_17_Figd_HTML.jpg

Step 4   Adjust the drawing of the rectangle to add in the margin variable. Now there should be gaps between the rectangles. See the next figure.

A978-1-4842-1790-0_17_Fige_HTML.jpg

Step 5   Add the margin before drawing the rectangles, in addition to between each rectangle. This should keep the box from appearing right next to the window edge. See the next figure.

A978-1-4842-1790-0_17_Figf_HTML.jpg

Step 6   Add another for loop that also will loop for each row. Call the variable in this for loop row. Now we should have a full grid of boxes. See the next figure.

A978-1-4842-1790-0_17_Figg_HTML.jpg

Step 7

普及网格

现在我们需要创建一个二维数组。不幸的是,在 Python 中创建二维数组不像在其他一些计算机语言中那么容易。有一些可以为 Python 下载的库使它变得容易,但是对于这个例子,它们将不会被使用。

To create a two-dimensional array and set an example, use the code below: # --- Create grid of numbers # Create an empty list grid = [] # Loop for each row for row in range(10):     # For each row, create a list that will     # represent an entire row     grid.append([])     # Loop for each column     for column in range(10):         # Add a the number zero to the current row         grid[row].append(0) A much shorter example is below, but this example uses some odd parts of Python that I don’t bother to explain in this book: grid = [[0 for x in range(10)] for y in range(10)] Use one of these two examples and place the code to create our array ahead of your main program loop.   Set an example location in the array to 1. Two-dimensional arrays are usually represented addressed by first their row and then the column. This is called a row-major storage. Most languages use row-major storage, with the exception of Fortran and MATLAB. Fortran and MATLAB use column-major storage. # Set row 1, column 5 to one grid[1][5] = 1 Place this code somewhere ahead of your main program loop.   Select the color of the rectangle based on the value of a variable named color. Do this by first finding the line of code where the rectangle is drawn. Ahead of it, create a variable named color and set it equal to white. Then replace the white color in the rectangle declaration with the colorvariable.   Select the color based on the value in the grid. After setting color to white, place an if statement that looks at the value in grid[row][column] and changes the color to green if the grid value is equal to 1. There should now be one green square. See the following figure.

A978-1-4842-1790-0_17_Figh_HTML.jpg

Step 11   Print “click” to the screen if the user clicks the mouse button. See bitmapped_graphics.py for example code of how to detect a mouse click.   Print the mouse coordinates when the user clicks the mouse. See move_mouse.py for an example on getting the position of the mouse. See the next figure.

A978-1-4842-1790-0_17_Figi_HTML.jpg

Step 13   Convert the mouse coordinates into grid coordinates. Print those instead. Remember to use the width and height of each grid location combined with the margin. It will be necessary to convert the final value to an integer. This can be done by using int or by using the integer division operator // instead of the normal division operator /. See the next figure.

A978-1-4842-1790-0_17_Figj_HTML.jpg

Step 14   Set the grid location at the row/column clicked to 1. See the next figure.

A978-1-4842-1790-0_17_Figk_HTML.jpg

Step 15

最终计划

"""

Example program to show using an array to back a grid on-``s

Sample Python/Pygame Programs

http://programarcadegames.com/

Explanation video:http://youtu.be/mdTeqiWyFnc

"""

import pygame

# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

GREEN = (0, 255, 0)

RED = (255, 0, 0)

# This sets the WIDTH and HEIGHT of each grid location

WIDTH = 20

HEIGHT = 20

# This sets the margin between each cell

MARGIN = 5

# Create a 2 dimensional array. A two dimensional

# array is simply a list of lists.

grid = []

for row in range(10):

# Add an empty array that will hold each cell

# in this row

grid.append([])

for column in range(10):

grid[row].append(0)  # Append a cell

# Set row 1, cell 5 to one. (Remember rows and

# column numbers start at zero.)

grid[1][5] = 1

# Initialize pygame

pygame.init()

# Set the HEIGHT and WIDTH of the screen

WINDOW_SIZE = [255, 255]

screen = pygame.display.set_mode(WINDOW_SIZE)

# Set title of screen

pygame.display.set_caption("Array Backed Grid")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen``upd

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():  # User did something

if event.type == pygame.QUIT:  # If user clicked close

done = True  # Flag that we are done so we exit this loop

elif event.type == pygame.MOUSEBUTTONDOWN:

# User clicks the mouse. Get the position

pos = pygame.mouse.get_pos()

# Change the x/y screen coordinates to grid coordinates

column = pos[0] // (WIDTH + MARGIN)

row = pos[1] // (HEIGHT + MARGIN)

# Set that location to zero

grid[row][column] = 1

print("Click ", pos, "Grid coordinates: ", row, column)

# Set the screen background

screen.fill(BLACK)

# Draw the grid

for row in range(10):

for column in range(10):

color = WHITE

if grid[row][column] == 1:

color = GREEN

pygame.draw.rect(screen,

color,

[(MARGIN + WIDTH) * column + MARGIN,

(MARGIN + HEIGHT) * row + MARGIN,

WIDTH,

HEIGHT])

# Limit to 60 frames per``secon

clock.tick(60)

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Be IDLE friendly. If you forget this line, the program will ’hang’

# on exit.

pygame.quit()

开始做你自己的视频游戏吧!

回顾

多项选择测验

In computer science, a grid of numbers is called a: Two-dimensional array   Bingo board   One-dimensional array   Two-dimensional board     To print the value of the top left corner of a 10x10 two-dimensional array, the current code would be: print(my_array[0][0])   print(my_array[1][1])   print(my_array[0,0])   print(my_array[1,1])     To store a 10 into an x, y position on the grid of (0, 5), what is the correct code? my_array[5][0] = 10   my_array[0][5] = 10   [0][5] = 10   my_array = 10   my_array[10] = (0,5)   print( my_arrayp[1,1] )     To process an entire two-dimensional array, a program needs: Two nested for loops: one for each row, one for each element in the row.   Two sequential for loops: one for each row, one for each element in the row.   One for loop to process every element.   A function for each element in the grid.   Two nested classes: one for each row, one for each element.     In the chapter example, how does the program find which grid location was clicked on with the mouse? Divide coordinates by the size of each grid location (including the margin).   Subtract the margin, divide by grid size.   Subtract the grid size.   Divide the grid size by the x and y coordinates.

简答工作表

Start with the final program. Modify it so that rather than just changing the block the user clicks on, it also changes the blocks of the squares next to the user’s click. If the user clicks on an edge, make sure the program doesn’t crash and still handles the click appropriately.   Write a celebrity-finding function. Start with a function check_celebrity that takes an n by n matrix named grid as a parameter. The grid location grid[i][j] = 1 if person i knows person j and grid[i][j] = 0 otherwise. (Assume that grid[i][i] = 1 for every i, since every person knows him/herself.) A celebrity is a person who is known by everyone and does not know anyone besides him/herself. Write a function that given the matrix grid, prints all the celebrities. For example, in the following grid person 2 is a celebrity:      0  1  2  3   -------------- 0 |  1  1  1  0 1 |  0  1  1  0 2 |  0  0  1  0 3 |  1  0  1  1 In the next example, no one is a celebrity:      0  1  2  3  4   ---------------- 0 |  1  1  1  0  1 1 |  0  1  1  0  1 2 |  0  0  1  0  0 3 |  1  0  1  1  1 4 |  1  0  0  1  1 Remember: A matrix can be represented as a list of lists, where each sublist is a row of the matrix. For example, the first matrix can be represented as: grid = [ [1, 1, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [1, 0, 1, 1] ] Or you can use multiple lines to define the grid: grid = [ [1, 1, 1, 0],          [0, 1, 1, 0],          [0, 0, 1, 0],          [1, 0, 1, 1] ] You can test your function with code like the following test cases: print("Test 1, Should show #2 is a celebrity.") grid = [ [1, 1, 1, 0],          [0, 1, 1, 0],          [0, 0, 1, 0],          [1, 0, 1, 1] ] check_celebrity(grid) print("Test 2, Should show no one is a celebrity.") grid = [ [1, 1, 1, 0, 1],          [0, 1, 1, 0, 1],          [0, 0, 1, 0, 0],          [1, 0, 0, 1, 1],          [1, 0, 0, 1, 1] ] check_celebrity(grid) print("Test 3, Should show #2 is a celebrity.") grid = [ [1, 1, 1, 0, 1],          [0, 1, 1, 0, 1],          [0, 0, 1, 0, 0],          [0, 0, 1, 0, 1],          [1, 0, 1, 1, 1] ] check_celebrity(grid) print("Test 4, Should show no one is a celebrity.") grid = [ [1, 1, 1, 0, 1],          [0, 1, 1, 0, 1],          [1, 0, 1, 0, 0],          [0, 0, 1, 0, 1],          [1, 0, 1, 1, 1] ] check_celebrity(grid)

十八、排序

二进制搜索只对有序列表有效。那么程序如何按顺序得到一个列表呢?当用户点击一个列标题或者需要排序的东西时,程序如何排序一个条目列表?

有几种算法可以做到这一点。两种最简单的排序算法是选择排序和插入排序。也存在其他排序算法,比如 shell、merge、heap 和快速排序。

了解这些种类如何工作的最好方法是观察它们。要查看常用的排序算法,请访问这个优秀的网站:

http://www.sorting-algorithms.com

每一种都有优点和缺点。有些人会很快对列表进行排序,如果列表几乎是按顺序排列的话。如果列表是完全随机的,有些人会快速排序。其他列表排序很快,但占用更多内存。理解排序是如何工作的对于为你的程序选择合适的排序是很重要的。

交换值

在学习排序之前,我们需要学习如何在两个变量之间交换值。这是很多排序算法中的常见操作。假设一个程序有一个如下所示的列表:

my_list = [15,57,14,33,72,79,26,56,42,40]

开发者想要交换位置 0 和 2,它们分别包含数字 15 和 14。见下图。

A978-1-4842-1790-0_18_Figa_HTML.jpg

交换数组中的值

第一次尝试编写这段代码可能如下所示:

my_list[0] = my_list[2]

my_list[2] = my_list[0]

A978-1-4842-1790-0_18_Figb_HTML.jpg

交换数组值的尝试不正确

请看上图,了解一下会发生什么。这显然行不通。第一次赋值list[0] = list[2]导致位置 0 中的值 15 被位置 2 中的值 14 覆盖,并且不可恢复地丢失。带有list[2] = list[0]的下一行只是将 14 复制回单元格 2,它已经有了一个 14。

要解决这个问题,应该分三步来交换数组中的值。有必要创建一个临时变量来保存交换操作期间的值。见下图。进行交换的代码如下所示:

temp = my_list[0]

my_list[0] = my_list[2]

my_list[2] = temp

第一行将位置 0 的值复制到temp变量中。这允许代码用位置 2 的值覆盖位置 0,而不会丢失数据。最后一行获取位置 0 的旧值,当前保存在temp变量中,并将其放在位置 2。

A978-1-4842-1790-0_18_Figc_HTML.jpg

交换数组值的正确方法

选择排序

选择从查看元素 0 开始。然后,代码 next 从元素 1 到 n-1 扫描列表的其余部分,以找到最小的数字。最小的数字被交换到元素 0 中。然后代码移动到元素 1,然后是元素 2,依此类推。从图形上看,排序如下图所示。

A978-1-4842-1790-0_18_Figd_HTML.jpg

选择排序

选择排序的代码包含两个嵌套循环。外部循环跟踪代码希望将最小值交换到的当前位置。内部循环从当前位置开始,向右扫描寻找最小值。当它找到最小值时,交换就发生了。

def selection_sort(my_list):

""" Sort a list using the selection sort """

# Loop through the entire array

for cur_pos in range(len(my_list)):

# Find the position that has the smallest number

# Start with the current position

min_pos = cur_pos

# Scan left to right (end of the list)

for scan_pos in range(cur_pos + 1, len(my_list)):

# Is this position smallest?

if my_list[scan_pos] < my_list[min_pos]:

# It is, mark this position as the smallest

min_pos = scan_pos

# Swap the two values

temp = my_list[min_pos]

my_list[min_pos] = my_list[cur_pos]

my_list[cur_pos] = temp

外部循环将总是运行 n 次。内部循环将运行 $$ \frac{n}{2} $$ 次。无论列表是否有序,都会出现这种情况。通过在排序结束时代码进行交换之前检查min_poscur_pos是否相等,可以提高循环的效率。如果那些变量相等,就没有必要做那三行。

为了测试上面的选择排序代码,可以使用下面的代码。第一个函数将打印出列表。下一段代码将创建一个随机数列表,打印出来,排序,然后再打印一次。第 3 行的print语句将数字右对齐,使数字列更容易阅读。格式化打印报表将在第二十一章的中介绍。

# Before this code, paste the selection sort and import random

def print_list(my_list):

for item in my_list:

print("{:3}".format(item), end="")

print()

# Create a list of random numbers

my_list = []

for i in range(10):

my_list.append(random.randrange(100))

# Try out the sort

print_list(my_list)

selection_sort(my_list)

print_list(my_list)

在以下位置查看选择排序的动画:

http://www.sorting-algorithms.com/selection-sort

要获得真正独特的选择排序可视化效果,请在 YouTube 上搜索“选择排序舞蹈”或使用以下链接:

http://youtu.be/Ns4TPTC8whw

插入排序

在外部循环的工作方式上,插入排序类似于选择排序。插入排序从数组的左侧开始,一直到右侧。不同的是,插入排序并不选择最小的元素并将其放入适当的位置;插入排序选择已排序元素右侧的下一个元素。然后,它向上滑动每个较大的元素,直到到达要插入的正确位置。从图形上看,它类似于下图。

A978-1-4842-1790-0_18_Fige_HTML.jpg

插入排序

插入排序将列表分为两部分:排序的一半和未排序的一半。在每一轮外部循环中,算法将抓取下一个未排序的元素,并将其插入到列表中。

在下面的代码中,key_pos标记了列表中已排序和未排序部分的边界。该算法使用变量scan_pos扫描到key_pos的左侧。注意,在插入短消息中,scan_pos向下到左边,而不是向上到右边。大于key_value的每个单元格位置都上移(向右)一个位置。

当循环找到一个小于key_value的位置时,它停止并将key_value放在它的左边。

带有插入排序的外部循环将运行 n 次。如果循环被随机打乱,内部循环将平均运行 $$ \frac{n}{2} $$ 次。如果循环已经接近一个已排序的循环,那么内部循环不会运行太多,排序时间更接近 n。

def insertion_sort(my_list):

""" Sort a list using the insertion sort """

# Start at the second element (pos 1).

# Use this element to insert into the

# list.

for key_pos in range(1, len(my_list)):

# Get the value of the element to insert

key_value = my_list[key_pos]

# Scan from right to the left (start of list)

scan_pos = key_pos - 1

# Loop each element, moving them up until

# we reach the position the

while (scan_pos >= 0) and (my_list[scan_pos] > key_value):

my_list[scan_pos + 1] = my_list[scan_pos]

scan_pos = scan_pos - 1

# Everything’s been moved out of the way, insert

# the key into the correct location

my_list[scan_pos + 1] = key_value

在以下位置查看插入排序的动画:

http://www.sorting-algorithms.com/insertion-sort

如果你想了解另一种舞蹈,可以在 YouTube 上搜索“插入排序舞蹈”或者使用以下链接:

http://youtu.be/ROalU379l3U

回顾

多项选择测验

How many lines of code are normally used to swap two values? 3   2   4   5     What is key in writing code to properly swap two values? Using the swap operator.   Make sure you use the == operator rather than the = operator.   Using a variable to temporarily hold one of the values while swapping.     In the selection sort, what does the outside loop do? Selects the next element that we will be placing the smallest remaining value into.   Finds the smallest value in the list.   Counts the number of items in the list.     In the selection sort, what does the inside loop do? Selects the next element that we will be placing the smallest remaining value into.   Finds the smallest value in the list.   Counts the number of items in the list.     In the insertion sort, what does the outside loop do? Slides an element into a sorted position.   Selects the next element to be slid into a sorted position.   Finds the smallest value in the list.     In the insertion sort, what does the inside loop do? Slides an element into a sorted position.   Selects the next element to be slid into a sorted position.   Finds the smallest value in the list.     If the selection sort and insertion sort run in n2 time, what is n? The number of lines of code.   The number of elements to sort.   The time it takes to sort in milliseconds.   The number of lines of code.   The size of each element.     If the selection sort and insertion sort run in n2 time, what does that mean if I have a problem size of 100 (n = 100) and increase it by 10 times to n = 1000? The 1,000 elements will take about 1,000 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 100 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 10 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 4 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 2 times longer to sort than a list of 100 elements.     What type of list does the insertion sort work particularly well on? A list that already close to being in order.   A list that is in reverse order.   A randomly sorted list.

简答工作表

Write code to swap the values 25 and 40. my_list = [55, 41, 52, 68, 45, 27, 40, 25, 37, 26]   Write code to swap the values 2 and 27. my_list = [27, 32, 18,  2, 11, 57, 14, 38, 19, 91]   Why does the following code not work? my_list = [70, 32, 98, 88, 92, 36, 81, 83, 87, 66] temp = list[0] my_list[1] = list[0] my_list[0] = temp   Show how the following numbers can be sorted using the selection sort. Show the numbers after each iteration of the outer loop, similar to what is shown earlier in the chapter where we showed how the numbers move around. I am not looking for a copy of the code to do the sort. 97   74    8   98   47   62   12   11    0   60   Show how the following numbers can be sorted using the selection sort: 74   92   18   47   40   58    0   36   29   25   Show how the following numbers can be sorted using the insertion sort. Note: The 0 will not be immediately sorted into place. If you think it should, go back and review how the insertion sort works again. 74   92   18   47   40   58    0   36   29   25   Show how the following numbers can be sorted using the insertion sort: 37   11   14   50   24    7   17   88   99    9   Explain what min_pos does in the selection sort.   Explain what cur_pos does in the selection sort.   Explain what scan_pos does in the selection sort.   Explain what key_pos and key_value are in the insertion sort.   Explain scan_pos in the insertion sort.   Look at the example sort program in the examples section here: http://ProgramArcadeGames.com/python_examples/f.php?file=sorting_examples.py Modify the sorts to print the number of times the inside loop is run and the number of times the outside loop is run. Modify the program to work with a list of 100. Paste the code you used here. Run the program and list the numbers you got here. (Don’t forget this part!)

十九、异常

当你的程序出错时,你想让用户看不到红色的 Python 错误信息吗?您想让您的程序不挂起吗?如果是这样,那么你需要异常。

异常用于处理代码执行过程中可能出现的异常情况。异常通常用于文件和网络操作。这使得代码能够优雅地处理磁盘空间不足、网络错误或权限错误。

词汇

处理异常时会用到几个术语和短语。以下是最常见的:

  • 异常:这个术语可能有两种意思。首先,导致异常程序流的条件。或者它可以用于引用表示数据条件的对象。每个异常都有一个保存相关信息的对象。
  • 异常处理:处理正常程序流程异常的过程。
  • Catch 块或异常块:处理异常情况的代码被称为捕获异常。
  • 抛出或抛出:当检测到程序流的异常情况时,创建一个异常对象的实例。然后将它抛出或引发给将捕获它的代码。
  • 未处理的异常或未捕获的异常:抛出但从未捕获的异常。这通常会导致错误和程序结束或崩溃。
  • Try 块:一组可能抛出异常的代码。

大多数编程语言都使用抛出和捕捉这两个术语。不幸的是 Python 没有。Python 使用 raise 和 exception。我们在这里介绍抛出/捕捉词汇,因为它们是行业中最流行的术语。

异常处理

处理异常的代码很简单。请参见下面的示例:

# Divide by zero

try:

x = 5 / 0

except:

print("Error dividing by zero")

第 2 行是try语句。它下面的每一行都是 try 块的一部分。在try块下面可能没有不以except语句开始的未缩进代码。try语句定义了代码试图执行的一段代码。

如果在代码处理过程中出现任何异常,执行将立即跳转到 catch 块。该代码块缩进在第 4 行的except语句下。这段代码负责处理错误。

程序可以使用异常来捕捉从文本到数字转换过程中出现的错误。例如:

# Invalid number conversion

try:

x = int("fred")

except:

print("Error converting fred to a number")

第 3 行将抛出一个异常,因为"fred"不能被转换成整数。第 5 行的代码将打印出一条错误消息。

下面是这个例子的扩展版本。它对用户的输入进行错误检查,以确保输入的是整数。如果用户不输入整数,程序会一直要求输入。代码使用异常处理来捕获可能发生在第 5 行的转换错误。如果用户输入的不是整数,当在第 5 行转换为数字时会抛出异常。如果第 5 行出现异常,第 6 行将number_entered设置为True的代码将不会运行。

number_entered = False

while not number_entered:

number_string = input("Enter an integer: ")

try:

n = int(number_string)

number_entered = True

except:

print("Error, invalid integer")

文件在操作过程中特别容易出错。磁盘可能会被填满,用户可能会删除正在写入的文件,文件可能会被移动,或者 USB 驱动器可能会在操作过程中被拔出。使用异常处理也可以很容易地捕获这些类型的错误。

# Error opening file

try:

my_file = open("myfile.txt")

except:

print("Error opening file")

可以捕获多种类型的错误,并以不同的方式进行处理。向用户提供比简单的“发生了错误”更准确的错误信息会很有用

在下面的代码中,不同类型的错误可能发生在try块中。通过将IOError放在except之后,该代码将只处理关于输入和输出(IO)的错误。同样,由于ValueError,下一个except模块仅处理转换值的误差,下一个模块覆盖除以零的误差。最后的异常处理发生在最后两行。由于该except块不包括特定类型的错误,所以它将处理上述先前的except块未涵盖的任何错误。总括except必须总是最后。

# Multiple errors

try:

my_file = open("myfile.txt")

my_line = my_file.readline()

my_int = int(s.strip())

my_calculated_value = 101 / my_int

except IOError:

print("I/O error")

except ValueError:

print("Could not convert data to an integer.")

except ZeroDivisionError:

print("Division by zero error")

except:

print("Unexpected error")

可从以下网址获得内置异常列表: http://docs.python.org/library/exceptions.html

示例:保存高分

这显示了如何在游戏之间保存高分。分数存储在一个名为high_score.txt的文件中。

"""

Show how to use exceptions to save a high score for a game.

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

def get_high_score():

# Default high score

high_score = 0

# Try to read the high score from a file

try:

high_score_file = open("high_score.txt", "r")

high_score = int(high_score_file.read())

high_score_file.close()

print("The high score is", high_score)

except IOError:

# Error reading file, no high score

print("There is no high score yet.")

except ValueError:

# There’s a file there, but we don’t understand the number.

print("I’m confused. Starting with no high score.")

return high_score

def save_high_score(new_high_score):

try:

# Write the file to disk

high_score_file = open("high_score.txt", "w")

high_score_file.write(str(new_high_score))

high_score_file.close()

except IOError:

# Hm, can’t write it.

print("Unable to save the high score.")

def main():

""" Main program is here. """

# Get the high score

high_score = get_high_score()

# Get the score from the current game

current_score = 0

try:

# Ask the user for his/her score

current_score = int(input("What is your score? "))

except ValueError:

# Error, can’t turn what they typed into a number

print("I don’t understand what you typed.")

# See if we have a new high score

if current_score > high_score:

# We do! Save to disk

print("Yea! New high score!")

save_high_score(current_score)

else:

print("Better luck next time.")

# Call the main function, start up the game

if __name__ == "__main__":

main()

异常对象

关于错误的更多信息可以从异常对象中提取。在使用as关键字捕获错误时,可以检索这个对象。例如:

try:

x = 5 / 0

except ZeroDivisionError as e:

print(e)

e变量指向可以打印出来的关于异常的更多信息。使用异常对象可以做更多的事情,但不幸的是这超出了本章的范围。有关异常对象的详细信息,请查看 Python 在线文档。

异常生成

使用raise命令可能会产生异常。例如:

# Generating exceptions

def get_input():

user_input = input("Enter something: ")

if len(user_input) == 0:

raise IOError("User entered nothing")

getInput()

尝试使用上面的代码,并为引发的IOError添加异常处理。

也可以创建自定义异常,但这也超出了本书的范围。好奇的读者可以访问以下网站了解更多信息:

http://docs.python.org/tutorial/errors.html#raising-exceptions

正确的异常使用

if语句可以轻松处理条件时,不应该使用异常。正常代码在运行快乐路径场景时不应该引发异常。构造良好的 try/catch 代码很容易理解,但是涉及许多异常和跳转到不同处理程序的代码可能是调试的噩梦。(有一次,我被分配去调试读取 XML 文档的代码。它为所读取的文件的每一行生成了许多异常。它慢得令人难以置信,而且容易出错。该代码在读取文件的正常过程中应该从未生成过任何异常)。

回顾

多项选择测验

What is an exception? Something that results in abnormal program flow.   An if statement that is False.   Code that handles an unexpected condition in the program.   Paramore thinks you are the only one.     What is a catch or exception block? Something computers play in the back yard.   Code that may cause an error that needs to be handled.   Code that handles an unexpected condition in the program.   A block with 22 lines in it.     What is a try block? Something that results in abnormal program flow.   An if statement that is False.   Code that handles an unexpected condition in the program.   There is no try block, only do or not do blocks.   Code that may cause an error that needs to be handled.     What will print for x? try:         x = 5/0         y = 10 except:         print("Error") print(x) 5/0   Infinity   5   0 because the error has been caught.   x will not print, there is an error.     What does the keyword raise do? Checks for errors.   Brings code back to life after it has been executed.   Generates a new exception that will be handled by a try block.   Generates a new exception that will be handled by an except block.     What will print for y? try:         x = 5/0         y = 10 except:         print("Error") print(y) 10   Infinity   5   10 because the error has been caught.   y will not print, there is an error.     What is e? try:         x = 5 / 0 except ZeroDivisionError as e:         print(e) 5   0   An object that stores data about the error.   A class that stores data about the error.   A library for exception handling.

简答工作表

Define the following terms in your own words. Don’t just copy/paste from the book:

  • 异常
  • 异常处理
  • 尝试阻止
  • 捕捉块
  • 未处理异常情况

Show how to modify the following code so that an error is printed if the number conversion is not successful. Modify this code; don’t just copy the example from the text. No need to ask again if the conversion is unsuccessful. user_input_string = input("Enter a number:") user_value = int(user_input_string)   What will the following code output? Predict, and then run the code to see if you are correct. Write your prediction here and if you are right. If you aren’t, make sure you understand why. (Make sure to write both the prediction, and the actual results. If the program raises an error, list that fact for this and the next problem as well.) x = 5 y = 0 print("A") try:     print("B")     a = x / y     print("C") except:     print("D") print("E") print(a)   What will the following code output? Predict, and then run the code to see if you are correct. Write your prediction here and if you are right. If you aren’t, make sure you understand why. x = 5 y = 10 print("A") try:     print("B")     a = x / y     print("C") except:     print("D") print("E") print(a)

二十、递归

A child couldn't sleep. Her mother told her the story of a little frog. The little frog couldn't sleep. The mother frog told her the story of a bear. The bear couldn't sleep. The mother bear told her the story of a little weasel ... The little weasel fell asleep. .... the bear is asleep; ... the little frog is asleep; .... the child is asleep. (Source: http://everything2.com/title/recursion )

递归是根据自身定义的对象或过程。像阶乘和斐波纳契数列这样的数学模式是递归的。可以包含其他文档的文档(这些文档本身可以包含其他文档)是递归的。分形图像甚至某些生物过程在工作方式上都是递归的。

递归用在哪里?

文档,比如网页,自然是递归的。例如,下图显示了一个 web 文档。

A978-1-4842-1790-0_20_Figa_HTML.jpg

网页

该 web 文档可以包含在一个框中,这有助于页面布局,如下图所示。

A978-1-4842-1790-0_20_Figb_HTML.jpg

带有表格的网页

这是递归的。每个框可以包含一个网页,网页可以有一个框,框可以包含另一个网页,如图所示。

A978-1-4842-1790-0_20_Figc_HTML.jpg

递归网页

递归函数通常与高级搜索和排序算法一起使用。我们将在这里展示一些,如果你决定学习数据结构,你将会看到更多。

即使一个人没有成为程序员,理解递归系统的概念也是很重要的。如果业务上需要递归表结构、文档或其他东西,知道如何事先向程序员说明这一点是很重要的。

例如,一个人可能指定一个关于食谱的 web 程序需要支持配料和方向的能力。熟悉递归的人可能会说,每种成分本身都可能是其他成分的配方(这可能是配方。)第二个系统要强大得多。

递归是如何编码的?

在前面的章节中,我们已经使用了调用其他函数的函数。例如:

def f():

g()

print("f")

def g():

print("g")

f()

函数也有可能调用自己。一个调用自身的函数使用了一个叫做递归的概念。例如:

def f():

print("Hello")

f()

f()

上面的例子将打印Hello,然后再次调用f()函数。这将导致另一个Hello被打印出来,并再次调用f()函数。这种情况会一直持续下去,直到计算机耗尽所谓的堆栈空间。发生这种情况时,Python 将输出一个很长的错误,以下列内容结束:

RuntimeError: maximum recursion depth exceeded

电脑在告诉你,程序员,你在兔子洞里走得太远了。

控制递归深度

为了成功地使用递归,需要有一种方法来防止函数无休止地一遍又一遍地调用自己。下面的例子计算了它被调用的次数,并使用一个if语句在函数调用自己 10 次后退出。

def f(level):

# Pring the level we are at

print("Recursion call, level",level)

# If we haven’t reached level ten…

if level < 10:

# Call this function again

# and add one to the level

f(level+1)

# Start the recursive calls at level 1

f(1)

Recursion call, level 1

Recursion call, level 2

Recursion call, level 3

Recursion call, level 4

Recursion call, level 5

Recursion call, level 6

Recursion call, level 7

Recursion call, level 8

Recursion call, level 9

Recursion call, level 10

递归阶乘计算

任何可以递归完成的代码都可以不使用递归来完成。一些程序员觉得递归代码更容易理解。

计算一个数的阶乘是使用递归的典型例子。阶乘在概率和统计中很有用。例如:

 $$ 10!=10\cdot 9\cdot 8\cdot 7\cdot 6\cdot 5\cdot 4\cdot 3\cdot 2\cdot 1 $$

递归地,这可以描述为:

 $$ n!=\left{\begin{array}{cc}\hfill 1\hfill & \hfill if\kern0.5em n=0,\hfill \ {}\hfill n\cdot \left(n-1\right)!\hfill & \hfill if\kern0.5em n>0.\hfill \end{array}\right. $$

下面是计算 n 的两个示例函数!。第一个是非递归的;第二个是递归的。

# This program calculates a factorial

# WITHOUT using recursion

def factorial_nonrecursive(n):

answer = 1

for i in range(2, n + 1):

answer = answer * i

return answer

# This program calculates a factorial

# WITH recursion

def factorial_recursive(n):

if n <= 1:

return 1

else:

return n * factorial_recursive(n - 1)

这些函数本身什么也不做。下面是一个例子,我们把它放在一起。这个例子还在函数中添加了一些print语句,这样我们就可以看到发生了什么。

# This program calculates a factorial

# WITHOUT using recursion

def factorial_nonrecursive(n):

answer = 1

for i in range(2, n + 1):

print(i, "*", answer, "=", i * answer)

answer = answer * i

return answer

print("I can calculate a factorial!")

user_input = input("Enter a number:")

n = int(user_input)

answer = factorial_nonrecursive(n)

print(answer)

# This program calculates a factorial

# WITH recursion

def factorial_recursive(n):

if n == 1:

return n

else:

x = factorial_recursive(n - 1)

print( n, "*", x, "=", n * x )

return n * x

print("I can calculate a factorial!")

user_input = input("Enter a number:")

n = int(user_input)

answer = factorial_recursive(n)

print(answer)

I can calculate a factorial!

Enter a number:7

2 * 1 = 2

3 * 2 = 6

4 * 6 = 24

5 * 24 = 120

6 * 120 = 720

7 * 720 = 5040

5040

I can calculate a factorial!

Enter a number:7

2 * 1 = 2

3 * 2 = 6

4 * 6 = 24

5 * 24 = 120

6 * 120 = 720

7 * 720 = 5040

5040

递归矩形

递归非常适合处理本身是递归的结构化文档。例如,web 文档可以有一个分成行和列的表格来帮助布局。一行可能是页眉,另一行是正文,最后是页脚。表格单元格内可能是另一个表格。其中还可以存在另一个表。

另一个例子是电子邮件。可以将他人的电子邮件附加到您自己的电子邮件中。但是该电子邮件可能附有另一封电子邮件,以此类推。

我们能在我们的 pygame 程序中直观地看到递归吗?没错。图中显示了一个示例程序,该程序绘制了一个矩形,并递归地保持在矩形内绘制矩形。每个矩形比父矩形小 20%。看代码。密切注意recursive_draw函数中的递归调用。

A978-1-4842-1790-0_20_Figd_HTML.jpg

递归矩形

"""

Recursively draw rectangles.

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

import pygame

# Colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

def recursive_draw(x, y, width, height):

""" Recursive rectangle function. """

pygame.draw.rect(screen, BLACK,

[x, y, width, height],

1)

# Is the rectangle wide enough to draw again?

if(width > 14):

# Scale down

x += width * .1

y += height * .1

width *= .8

height *= .8

# Recursively draw again

recursive_draw(x, y, width, height)

pygame.init()

# Set the height and width of the screen

size = [700, 500]

screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# Set the screen background

screen.fill(WHITE)

# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT

recursive_draw(0, 0, 700, 500)

# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Limit to 60 frames per second

clock.tick(60)

# Be IDLE friendly. If you forget this line, the program will ’hang’

# on exit.

pygame.quit()

分形

分形是递归定义的。这是一个非常简单的分形,展示了它是如何根据递归的深度而变化的。

A978-1-4842-1790-0_20_Fige_HTML.jpg

递归分形等级 0

A978-1-4842-1790-0_20_Figf_HTML.jpg

递归分形级别 1

A978-1-4842-1790-0_20_Figg_HTML.jpg

递归分形层次 2

A978-1-4842-1790-0_20_Figh_HTML.jpg

递归分形等级 3

"""

Sample fractal using recursion.

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

import pygame

# Define some colors

black = (0, 0, 0)

white = (255, 255, 255)

green = (0, 255, 0)

red = (255, 0, 0)

def recursive_draw(x, y, width, height, count):

# Draw the rectangle

# pygame.draw.rect(screen,black,[x,y,width,height],1)

pygame.draw.line(screen,

black,

[x + width*.25, height // 2 + y],

[x + width*.75, height // 2 + y],

3)

pygame.draw.line(screen,

black,

[x + width * .25, (height * .5) // 2 + y],

[x + width * .25,  (height * 1.5) // 2 + y],

3)

pygame.draw.line(screen,

black,

[x + width * .75, (height * .5) // 2 + y],

[x + width * .75, (height * 1.5) // 2 + y],

3)

if count > 0:

count -= 1

# Top left

recursive_draw(x, y, width // 2, height // 2, count)

# Top right

recursive_draw(x + width // 2, y, width // 2, height // 2, count)

# Bottom left

recursive_draw(x, y + width // 2, width // 2, height // 2, count)

# Bottom right

recursive_draw(x + width // 2, y + width // 2, width // 2, height // 2, count)

pygame.init()

# Set the height and width of the screen

size = [700, 700]

screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# Set the screen background

screen.fill(white)

# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT

fractal_level = 3

recursive_draw(0, 0, 700, 700, fractal_level)

# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Limit to 20 frames per second

clock.tick(20)

# Be IDLE friendly. If you forget this line, the program will ’hang’

# on exit.

pygame.quit()

递归二分搜索法

递归也可以用来执行二分搜索法。下面是第十六章中的一个非递归二分搜索法:

def binary_search_nonrecursive(search_list, key):

lower_bound = 0

upper_bound = len(search_list) - 1

found = False

while lower_bound < upper_bound and found == False:

middle_pos = (lower_bound + upper_bound) // 2

if search_list[middle_pos] < key:

lower_bound = middle_pos + 1

elif list[middle_pos] > key:

upper_bound = middle_pos

else:

found = True

if found:

print( "The name is at position",middle_pos)

else:

print( "The name was not in the list." )

binary_search_nonrecursive(name_list,"Morgiana the Shrew")

同样的二分搜索法以递归的方式写道:

def binary_search_recursive(search_list, key, lower_bound, upper_bound):

middle_pos = (lower_bound + upper_bound) // 2

if search_list[middle_pos] < key:

binary_search_recursive(search_list,

key,

middle_pos + 1,

upper_bound)

elif search_list[middle_pos] > key:

binary_search_recursive(search_list,

key,

lower_bound,

middle_pos )

else:

print("Found at position", middle_pos)

lower_bound = 0

upper_bound = len(name_list) - 1

binary_search_recursive(name_list,

"Morgiana the Shrew",

lower_bound,

upper_bound)

回顾

简答工作表

“To understand recursion, one must first understand recursion.” Explain the joke.   Two mirrors face each other. Explain how their reflections demonstrate the property of recursion.   Explain how Multi-Level Marketing uses recursion.   Explain how the sweep function in the classic minesweeper game could be done with recursion.   Explain how finding your way out of a maze could be done with recursion.   Use the Chrome browser and create your own screenshot at: http://juliamap.googlelabs.com Use your mouse and mouse wheel to zoom into an interesting part of the fractal.   Write a recursive function f(n) that takes in a value n and returns the value for f, given the definition below.

 $$ {f}_n=\left{\begin{array}{c}\hfill 6\hfill \ {}\hfill \frac{1}{2}{f}_{n-1}+4\hfill \end{array}\kern1.5em \begin{array}{c}\hfill if\kern0.5em n=1,\hfill \ {}\hfill if\kern0.5em n>1.\hfill \end{array}\right. $$

Then write a for loop that prints out the answers for values of n from 1 to 10. It should look like: n= 1, a= 6 n= 2, a= 7.0 n= 3, a= 7.5 n= 4, a= 7.75 n= 5, a= 7.875 n= 6, a= 7.9375 n= 7, a= 7.96875 n= 8, a= 7.984375 n= 9, a= 7.9921875 n= 10, a= 7.99609375 The function should not have a print statement inside it, nor a loop. The for loop that is written should be outside the function and call the function to get the results and print them. Write recursive code that will print out the first 10 terms of the sequence below.

 $$ {f}_n=\left{\begin{array}{c}\hfill \begin{array}{l}1\ {}1\end{array}\hfill \ {}\hfill f\left(n-1\right)+f\left(n-2\right)\hfill \end{array}\kern1.5em \begin{array}{c}\hfill \begin{array}{l} if\kern0.5em n=1\ {} if\kern0.5em n=2\end{array}\hfill \ {}\hfill if\kern0.5em n>2\hfill \end{array}\right. $$

二十一、格式化

这里有一个快速表格,用于在设置文本格式时参考。有关文本格式如何工作的详细解释,请继续阅读。

| 数字 | 格式 | 输出 | 描述 | | --- | --- | --- | --- | | `3.1415926` | `{:.2f}` | `3.14` | 两位小数 | | `3.1415926` | `{:+.2f}` | `+3.14` | 带符号的 2 位小数 | | `-1` | `{:+.2f}` | `-1.00` | 带符号的 2 位小数 | | `3.1415926` | `{:.0f}` | `3` | 无小数位(将四舍五入) | | `5` | `{:0>2d}` | `05` | 左边用零填充 | | `1000000` | `{:,}` | `1,000,000` | 带逗号分隔符的数字格式 | | `0.25` | `{:.2%}` | `25.00%` | 格式化百分比 | | `1000000000` | `{:.2e}` | `1.00e+09` | 指数符号 | | `11` | `{:>10d}` | `11` | 右对齐 | | `11` | `{:<10d}` | `11` | 左对齐 | | `11` | `{:¹⁰d}` | `11` | 居中对齐 |

十进制数字

尝试运行下面的程序,打印出几个随机数。

import random

for i in range(10):

x = random.randrange(20)

print(x)

输出是左对齐的,数字看起来很糟糕:

16

13

2

0

10

3

18

1

14

5

我们可以使用字符串格式,通过右对齐使数字列表看起来更好。第一步是在管柱上使用format命令。见下文:

import random

for i in range(10):

x = random.randrange(20)

print("{}".format(x) )

这使我们的程序更接近正确的数字,但我们还没有完全实现。看字符串如何以.format(x)结尾。所有字符串实际上都是名为String的类的实例。该类有可以调用的方法。其中之一就是format

format函数不会打印出花括号{},而是用x中的值替换它们。输出(如下)看起来就像我们之前看到的一样。

7

15

4

12

3

8

7

15

12

8

为了右对齐,我们添加了更多关于如何格式化花括号之间的数字的信息:

import random

for i in range(10):

x = random.randrange(20)

print("{:2}".format(x) )

输出:

7

15

4

12

3

8

7

15

12

8

这样更好;我们有右对齐的数字!但是它是如何工作的呢?我们添加的:2不太直观。

下面是分解情况:{ }告诉计算机我们要格式化一个数字。在花括号内的:之后将是格式化信息。在这种情况下,我们给它一个2来指定两个字符的字段宽度。字段宽度值告诉计算机尝试将数字放入两个字符宽的字段中。默认情况下,它会尝试右对齐数字和左对齐文本。

更好的是,程序不再需要调用str( )来将数字转换成字符串。不考虑字符串转换。

如果你有很多数字呢?让我们制造更大的随机数:

import random

for i in range(10):

x = random.randrange(100000)

print("{:6}".format(x) )

这给出了右对齐的输出,但看起来仍然不太好:

18394

72242

97508

21583

11508

76064

88756

77413

7930

81095

逗号在哪里?这个列表在每三个数字之间用分隔符会更好看。请看下一个示例,了解它们是如何添加到中的:

import random

for i in range(10):

x = random.randrange(100000)

print("{:6,}".format(x) )

输出:

65,732

30,248

13,802

17,177

3,584

7,598

21,672

82,900

72,838

48,557

我们在字段宽度说明符后添加了一个逗号,现在我们的数字有了逗号。逗号必须在字段宽度说明符之后,而不是之前。计算字段宽度时包括逗号。例如,1,024的字段宽度是 5,而不是 4。

我们可以打印多个值,并将这些值与文本结合起来。运行下面的代码。

x = 5

y = 66

z = 777

print("A - ’{}’ B - ’{}’ C - ’{}’".format(x, y, z))

程序会用数字代替花括号,并打印出字符串中的所有其他文本:

A - ’5’ B - ’66’ C - ’777’

如果有三组花括号,计算机将期望在format命令中列出三个值。给定的第一个值将替换第一个大括号。

有时,我们可能希望将相同的值打印两次。或者以不同于输入到format函数的顺序显示它们。

x = 5

y = 66

z = 777

print("C - ’{2}’ A - ’{0}’ B - ’{1}’ C again - ’{2}’".format(x, y, z))

请注意,通过在花括号中放置一个数字,我们可以指定哪个参数传递给了我们想要打印出来的format函数。参数从 0 开始编号,所以x被认为是参数 0。

我们仍然可以在冒号后指定格式信息。例如:

x = 5

y = 66

z = 777

print("C - ’{2:4}’ A - ’{0:4}’ B - ’{1:4}’ C again - ’{2:4}’".format(x, y, z))

我们可以看到上面的代码将显示字段宽度为 4 的右对齐值:

C - ’ 777’ A - ’   5’ B - ’  66’ C again - ’ 777’

用线串

让我们看看如何格式化字符串。

下面的列表看起来很可怕。

my_fruit = ["Apples","Oranges","Grapes","Pears"]

my_calories = [4, 300, 70, 30]

for i in range(4):

print(my_fruit[i], "are", my_calories[i], "calories.")

输出:

Apples are 4 calories.

Oranges are 300 calories.

Grapes are 70 calories.

Pears are 30 calories.

现在使用format命令尝试一下。请注意我们如何将附加文本和多个值放入同一行。

my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]

my_calories = [4, 300, 70, 30]

for i in range(4):

print("{:7} are {:3} calories.".format(my_fruit[i],my_calories[i]) )

输出:

Apples  are   4 calories.

Oranges are 300 calories.

Grapes  are  70 calories.

Pears   are  30 calories.

这很酷,而且看起来是我们想要的样子。但是如果我们不希望数字右对齐,文本左对齐呢?我们可以使用<>字符,如下例所示:

my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]

my_calories = [4, 300, 70, 30]

for i in range(4):

print("{:>7} are {:<3} calories.".format(my_fruit[i],my_calories[i]) )

输出:

Apples are 4   calories.

Oranges are 300 calories.

Grapes are 70  calories.

Pears are 30  calories.

前导零

这会产生不正确的输出:

for hours in range(1,13):

for minutes in range(0,60):

print("Time {}:{}".format(hours, minutes))

不太好的输出:

Time 8:56

Time 8:57

Time 8:58

Time 8:59

Time 9:0

Time 9:1

Time 9:2

我们需要使用前导零来显示时钟中的数字。不要为字段宽度指定一个2,而是使用02。这将用零而不是空格填充字段。

for hours in range(1,13):

for minutes in range(0,60):

print("Time {:02}:{:02}".format(hours, minutes))

输出:

Time 08:56

Time 08:57

Time 08:58

Time 08:59

Time 09:00

Time 09:01

Time 09:02

浮点数

我们还可以控制浮点输出。检查以下代码及其输出:

x = 0.1

y = 123.456789

print("{:.1}  {:.1}".format(x,y) )

print("{:.2}  {:.2}".format(x,y) )

print("{:.3}  {:.3}".format(x,y) )

print("{:.4}  {:.4}".format(x,y) )

print("{:.5}  {:.5}".format(x,y) )

print("{:.6}  {:.6}".format(x,y) )

print()

print("{:.1f}  {:.1f}".format(x,y) )

print("{:.2f}  {:.2f}".format(x,y) )

print("{:.3f}  {:.3f}".format(x,y) )

print("{:.4f}  {:.4f}".format(x,y) )

print("{:.5f}  {:.5f}".format(x,y) )

print("{:.6f}  {:.6f}".format(x,y) )

下面是这段代码的输出:

0.1  1e+02

0.1  1.2e+02

0.1  1.23e+02

0.1  123.5

0.1  123.46

0.1  123.457

0.1  123.5

0.10  123.46

0.100  123.457

0.1000  123.4568

0.10000  123. 45679

0.100000  123.456789

.2的格式表示以两位精度显示数字。不幸的是,这意味着如果我们显示有三个有效数字的数字123,而不是四舍五入,我们得到的是科学记数法中的数字:1.2e+02

.2f(注意f的格式)表示显示小数点后两位数的数字。因此数字1将显示为1.00,数字1.5555将显示为1.56

程序也可以指定字段宽度字符:

x = 0.1

y = 123.456789

print("’{:10.1}’  ’{:10.1}’".format(x,y) )

print("’{:10.2}’  ’{:10.2}’".format(x,y) )

print("’{:10.3}’  ’{:10.3}’".format(x,y) )

print("’{:10.4}’  ’{:10.4}’".format(x,y) )

print("’{:10.5}’  ’{:10.5}’".format(x,y) )

print("’{:10.6}’  ’{:10.6}’".format(x,y) )

print()

print("’{:10.1f}’  ’{:10.1f}’".format(x,y) )

print("’{:10.2f}’  ’{:10.2f}’".format(x,y) )

print("’{:10.3f}’  ’{:10.3f}’".format(x,y) )

print("’{:10.4f}’  ’{:10.4f}’".format(x,y) )

print("’{:10.5f}’  ’{:10.5f}’".format(x,y) )

print("’{:10.6f}’  ’{:10.6f}’".format(x,y) )

格式10.2f不是指小数点前 10 位和小数点后 2 位。这意味着总字段宽度为 10。所以小数点前会有 7 位数,小数点后多算 1 位数,后面有 2 位数。

’       0.1’  ’     1e+02’

’       0.1’  ’   1.2e+02’

’       0.1’  ’  1.23e+02’

’       0.1’  ’     123.5’

’       0.1’  ’    123.46’

’       0.1’  ’   123.457’

’       0.1’  ’     123.5’

’      0.10’  ’    123.46’

’     0.100’  ’   123.457’

’    0.1000’  ’  123.4568’

’   0.10000’  ’ 123.45679’

’  0.100000’  ’123. 456789’

印刷美元和美分

如果你想打印一个浮点数,你可以使用一个f。见下文:

cost1  = 3.07

tax1   = cost1 * 0.06

total1 = cost1 + tax1

print("Cost:  ${0:5.2f}".format(cost1) )

print("Tax:    {0:5.2f}".format(tax1) )

print("------------")

print("Total: ${0:5.2f}".format(total1) )

记住!很容易认为%5.2f意味着 5 个数字,一个小数,后面跟着 2 个数字。但事实并非如此。这意味着字段总宽度为 8,包括小数和后面的两位数字。以下是输出结果:

Cost:  $ 3.07

Tax:     0.18

------------

Total: $ 3.25

危险!上面的代码有一个错误,这个错误在处理金融交易时非常常见。你能发现它吗?尝试使用下面的扩展代码示例来发现它:

cost1  = 3.07

tax1   = cost1 * 0.06

total1 = cost1 + tax1

print("Cost:  ${0:5.2f}".format(cost1) )

print("Tax:    {0:5.2f}".format(tax1) )

print("------------")

print("Total: ${0:5.2f}".format(total1) )

cost2  = 5.07

tax2   = cost2 * 0.06

total2 = cost2 + tax2

print()

print("Cost:  ${0:5.2f}".format(cost2) )

print("Tax:    {0:5.2f}".format(tax2) )

print("------------")

print("Total: ${0:5.2f}".format(total2) )

print()

grand_total = total1 + total2

print("Grand total: ${0:5.2f}".format(grand_total) )

以下是输出结果:

Cost:  $ 3.07

Tax:     0.18

------------

Total: $ 3.25

Cost:  $ 5.07

Tax:     0.30

------------

Total: $ 5.37

Grand total: $ 8.63

发现错误?你必须小心舍入误差!看看那个例子;看起来总数应该是$ 8.62但不是。

打印格式不改变数字,只改变输出的内容!如果我们更改打印格式,在小数点后包含三位数字,错误的原因将变得更加明显:

Cost:  $3.070

Tax:    0.184

------------

Total: $3.254

Cost:  $5.070

Tax:    0.304

------------

Total: $5.374

Grand total: $8.628

同样,显示格式不会改变数字。使用round命令改变数值并真正取整。见下文:

cost1 = 3.07

tax1 = round(cost1 * 0.06, 2)

total1 = cost1 + tax1

print("Cost:  ${0:5.2f}".format(cost1) )

print("Tax:    {0:5.2f}".format(tax1) )

print("------------")

print("Total: ${0:5.2f}".format(total1) )

cost2 = 5.07

tax2 = round(cost2 * 0.06,2)

total2 = cost2 + tax2

print()

print("Cost:  ${0:5.2f}".format(cost2) )

print("Tax:    {0:5.2f}".format(tax2) )

print("------------")

print("Total: ${0:5.2f}".format(total2) )

print()

grand_total = total1 + total2

print("Grand total: ${0:5.2f}".format(grand_total) )

输出:

Cost:  $ 3. 07

Tax:     0.18

------------

Total: $ 3.25

Cost:  $ 5.07

Tax:     0.30

------------

Total: $ 5.37

Grand total: $ 8.62

round 命令控制我们四舍五入到小数点后多少位。它返回舍入值,但不改变原始值。见下文:

x = 1234.5678

print(round(x, 2))

print(round(x, 1))

print(round(x, 0))

print(round(x, -1))

print(round(x, -2))

参见下文,了解将round()函数值(如-2)输入小数点后的数字如何影响输出:

1234.57

1234.6

1235.0

1230.0

1200.0

在 Pygame 中使用

我们不仅仅需要为print语句格式化字符串。示例 timer.py 使用字符串格式并将生成的文本直接传送到屏幕上,以创建一个屏幕计时器:

# Use python string formatting to format in leading zeros

output_string = "Time: {0:02}:{1:02}".format(minutes,seconds)

# Blit to the screen

text = font.render(output_string, True, BLACK)

screen.blit(text, [250, 250])

回顾

简答工作表

Take the following program: score = 41237 highscore = 1023407 print("Score:      " + str(score) ) print("High score: " + str(highscore) ) Which right now outputs: Score:      41237 High score: 1023407 Use print formatting so that the output instead looks like: Score:          41,237 High score:  1,023,407 Make sure the print formatting works for any integer from zero to nine million.   Create a program that loops from 1 to 20 and lists the decimal equivalent of their inverse. Use print formatting to exactly match the following output: 1/1  = 1.0 1/2  = 0.5 1/3  = 0.333 1/4  = 0.25 1/5  = 0.2 1/6  = 0.167 1/7  = 0.143 1/8  = 0.125 1/9  = 0.111 1/10 = 0.1 1/11 = 0.0909 1/12 = 0.0833 1/13 = 0.0769 1/14 = 0.0714 1/15 = 0.0667 1/16 = 0.0625 1/17 = 0.0588 1/18 = 0.0556 1/19 = 0.0526 1/20 = 0.05   Write a recursive function that will calculate the Fibonacci series, and use output formatting. Your result should look like: 1 -         0 2 -         1 3 -         1 4 -         2 5 -         3 6 -         5 7 -         8 8 -        13 9 -        21 10 -        34 11 -        55 12 -        89 13 -       144 14 -       233 15 -       377 16 -       610 17 -       987 18 -     1,597 19 -     2,584 20 -     4,181 21 -     6,765 22 -    10,946 23 -    17,711 24 -    28,657 25 -    46,368 26 -    75,025 27 -   121,393 28 -   196,418 29 -   317,811 30 -   514,229 31 -   832,040 32 - 1,346,269 33 - 2,178,309 34 - 3,524,578 35 - 5,702,887   Why does the problem above run so slow? How could it be made to run faster?

二十二、练习

| 练习 1:自定义计算器 | | 练习 2:创建一个测验 | | 练习 3:骆驼 | | 练习 4:创建一幅图片 | | 练习 5:多圈实验室 | | 练习 6:冒险! | | 练习 7:动画 | | 练习 8:函数 | | 练习 9:功能和用户控制 | | 练习 10:位图图形、声音效果和音乐 | | 练习 11:类和图形 | | 练习 12:收集雪碧 | | 练习 13:移动精灵 | | 练习 14:拼写检查 | | 练习 15:最终练习 |

练习 1:自定义计算器

A978-1-4842-1790-0_22_Figa_HTML.jpg

在本练习中,我们将创建三个自定义计算器程序。为了帮助创建这些练习,请查看第二章中的代码。特别是,本章末尾的示例程序为本练习所需的代码提供了一个很好的模板。

确保你能写出简单的程序,就像这个练习中布置的那样。既能凭记忆做,也能写在纸上。这些程序遵循一种非常常见的计算模式:

Take in data   Perform calculations   Output data

程序从数据库、3D 模型、游戏控制器、键盘和互联网等来源获取数据。它们执行计算并输出结果。有时我们甚至每秒钟循环数千次。

将计算与数据输出分开是一个好主意。虽然可以在 print 语句中进行计算,但最好是先进行计算,将其存储在一个变量中,然后再输出。这样计算和输出就不会混在一起。

编写程序时,使用空行来分隔代码的逻辑分组是一个好主意。例如,在输入语句、计算和输出语句之间放置一个空行。此外,在程序中添加注释来标记这些部分。

在本练习中,您将创建三个简短的程序:

程序

创建一个程序,要求用户输入华氏温度,然后打印出摄氏温度。在互联网上搜索正确的计算方法。查看第二章的每加仑英里数示例,了解应该做些什么。

样品运行:

Enter temperature in Fahrenheit: 32

The temperature in Celsius: 0.0

样品运行:

Enter temperature in Fahrenheit: 72

The temperature in Celsius: 22.2222222222

这个程序的数字格式不会很好。没关系。但是如果它困扰着你,向前看第二十一章,看看如何让你的输出看起来很棒!

程序

创建一个新的程序,要求用户提供计算梯形面积所需的信息,然后打印出面积。梯形的面积公式为:

 $$ A=\frac{1}{2}\left({x}_1+{x}_2\right)h $$

样品运行:

Area of a trapezoid

Enter the height of the trapezoid: 5

Enter the length of the bottom base: 10

Enter the length of the top base: 7

The area is: 42.5

程序

创建您自己的原始问题,并让用户插入变量。如果你不想要任何原创的东西,从下面的列表中选择一个等式:

| 圆的面积 | ![ $$ A=\pi {r}² $$ ](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_22_Chapter_TeX2GIF_IEq1.gif) | | 椭圆的面积 | ![ $$ A=\pi {r}_1{r}_2 $$ ](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_22_Chapter_TeX2GIF_IEq2.gif) | | 等边三角形的面积 | ![ $$ A=\frac{h²\sqrt{3}}{3} $$ ](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_22_Chapter_TeX2GIF_IEq3.gif) | | 圆锥体的体积 | ![ $$ V=\frac{\pi {r}²h}{3} $$ ](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_22_Chapter_TeX2GIF_IEq4.gif) | | 球体的体积 | ![ $$ V=\frac{4\pi {r}³}{3} $$ ](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_22_Chapter_TeX2GIF_IEq5.gif) | | 任意三角形的面积 | ![$$ A=\frac{1}{2} ab\kern0.5em \sin C $$](https://gitee.com/OpenDocCN/vkdoc-python-zh/raw/master/docs/prog-arcade-game/img/A978-1-4842-1790-0_22_Chapter_TeX2GIF_IEq6.gif) |

完成后,检查以确保变量名以小写字母开头,并且在代码的逻辑分组之间使用空行。(在这种情况下,在输入、计算和输出之间。)

练习 2:创建一个测验

A978-1-4842-1790-0_22_Figb_HTML.jpg

现在是你自己写测验的机会了。使用这些测验来筛选求职者,淘汰潜在的伴侣,或者只是有机会坐在桌子的另一边做测验,而不是参加测验。

本练习应用了第四章中关于使用if语句的材料。这也需要一点第二章的内容,因为程序必须计算一个百分比。

描述

这是您的测验需要具备的功能列表:

Create your own quiz with five or more questions. You can ask questions that require:

  • 一个数字作为答案(例如,1+1 是什么?)
  • 正文(例如哈利波特姓什么?)
  • 选择(这些选项中哪些是正确的?a,B,还是 C?)

If you have the user enter non-numeric answers, think and cover the different ways a user could enter a correct answer. For example, if the answer is “a,” would “A” also be acceptable? See Chapter 4 for a reminder on how to do this.   Let the user know if they get the question correct. Print a message depending on the user’s answer.   You need to keep track of how many questions they get correct.   At the end of the program print the percentage of questions the user gets right.

创建程序时,请记住以下几点:

Variable names should start with a lowercase letter. Uppercase letters work, but it is not considered proper. (Right, you didn’t realize that programming was going to be like English Tea Time, did you?)   To create a running total of the number correct, create a variable to store this score. Set it to zero. With an if statement, add one to the variable each time the user gets a correct answer. (How do you know if they got it correct? Remember that if you are printing out “correct” then you have already done that part. Just add a line there to add one to the number correct.) If you don’t remember how to add one to a variable, go back and review Chapter 2.   Treat true/false questions like multiple choice questions; just compare to “True” or “False.” Don’t try to do if a: we’ll implement if statements like that later on in the class, but this isn’t the place.   Calculate the percentage by using a formula at the end of the game. Don’t just add 20% for each question the user gets correct. If you add 20% each time, then you have to change the program 5 places if you add a 6th question. With a formula, you only need 1 change.   To print a blank line so that all the questions don’t run into each other, use the following code: print()   Remember the program can print multiple items on one line. This can be useful when printing the user’s score at the end. print("The value in x is", x)   Separate out your code by using blank lines to group sections together. For example, put a blank line between the code for each question.   Sometimes it makes sense to reuse variables. Rather than having a different variable to hold the user’s answer for each question, you could reuse the same one.   Use descriptive variable names. x is a terrible variable name. Instead use something like number_correct.

示例运行

这是我的程序中的一个例子:

Quiz time!

How many books are there in the Harry Potter series? 7

Correct!

What is 3*(2-1)? 3

Correct!

What is 3*2-1? 5

Correct!

Who sings Black Horse and the Cherry Tree?

1\. Kelly Clarkson

2\. K.T. Tunstall

3\. Hillary Duff

4\. Bon Jovi

? 2

Correct!

Who is on the front of a one dollar bill

1\. George Washington

2\. Abraham Lincoln

3\. John Adams

4\. Thomas Jefferson

? 2

No.

Congratulations, you got 4 answers right.

That is a score of 80.0 percent.

练习 3:骆驼

骆驼游戏的描述

A978-1-4842-1790-0_22_Figc_HTML.jpg

骆驼的想法最初来自健康用户组,并于 1979 年发表在更基本的电脑游戏中。

这个想法是在被追赶的时候骑着你的骆驼穿越沙漠。你需要管理你的口渴,骆驼有多累,你领先土著多远。

这是我在 Apple //e 上编程的第一批游戏之一,游戏很灵活。我知道有人创造了这个游戏的星球大战主题版本,你需要骑着万帕穿越霍斯。很容易将沙尘暴和其他随机事件添加到游戏中,使其更加有趣。

骆驼试跑

这是一个游戏的运行示例:

Welcome to Camel!

You have stolen a camel to make your way across the great Mobi desert.

The natives want their camel back and are chasing you down! Survive your

desert trek and outrun the natives.

A. Drink from your canteen.

B. Ahead moderate speed.

C. Ahead full speed.

D. Stop and rest.

E. Status check.

Q. Quit.

Your choice? C

You traveled 12 miles.

A. Drink from your canteen.

B. Ahead moderate speed.

C. Ahead full speed.

D. Stop and rest.

E. Status check.

Q. Quit.

Your choice? C

You traveled 17 miles.

A. Drink from your canteen.

B. Ahead moderate speed.

C. Ahead full speed.

D. Stop and rest.

E. Status check.

Q. Quit.

Your choice? e

Miles traveled:  29

Drinks in canteen:  3

The natives are 31 miles behind you.

A. Drink from your canteen.

B. Ahead moderate speed.

C. Ahead full speed.

D. Stop and rest.

E. Status check.

Q. Quit.

Your choice? b

You traveled 6 miles.

...and so on until...

A. Drink from your canteen.

B. Ahead moderate speed.

C. Ahead full speed.

D. Stop and rest.

E. Status check.

Q. Quit.

Your choice? C

You traveled 12 miles.

The natives are getting close!

A. Drink from your canteen.

B. Ahead moderate speed.

C. Ahead full speed.

D. Stop and rest.

E. Status check.

Q. Quit.

Your choice? C

You traveled 11 miles.

The natives are getting close!

You made it across the desert! You won!

节目指南

下面是完成这个练习的步骤。请随意修改和增加练习内容。与朋友和家人一起尝试游戏。

Create a new program and print the instructions to the screen. Do this with multiple print statements. Don’t use one print statement and multiple \n characters to jam everything on one line. Welcome to Camel! You have stolen a camel to make your way across the great Mobi desert. The natives want their camel back and are chasing you down! Survive your desert trek and out run the natives.   Create a Boolean variable called done and set to False.   Create a while loop that will keep looping while done is False.   Inside the loop, print out the following: A. Drink from your canteen. B. Ahead moderate speed. C. Ahead full speed. D. Stop for the night. E. Status check. Q. Quit.   Ask the user for their choice. Make sure to add a space before the quote so the user input doesn’t run into your text.   If the user’s choice is Q, then set done to True. By doing something like user_choice.upper() instead of just user_choice in your if statement you can make it case insensitive.   Test and make sure that you can quit out of the game.   Before your main program loop, create variables for miles traveled, thirst, and camel tiredness. Set these to zero.   Create a variable for the distance the natives have traveled and set it to -20. (Twenty miles back.)   Create and set an initial number of drinks in the canteen.   Add an elif in your main program loop and see if the user is asking for status. If so, print out something like this: Miles traveled:  0 Drinks in canteen:  3 The natives are 10 miles behind you.   Add an elif in your main program loop and handle if the user wants to stop for the night. If the user does, reset the camel’s tiredness to zero. Print that the camel is happy, and move the natives up a random amount from 7 to 14 or so.   Add an elif in your main program loop and handle if the user wants to go ahead full speed. If the user does, go forward a random amount between 10 and 20 inclusive. Print how many miles the user traveled. Add 1 to thirst. Add a random 1 to 3 to camel tiredness. Move the natives up 7 to 14 miles.   Add an elifin your main program loop and handle if the user wants to go ahead moderate speed. If the user does, go forward a random amount between 5 and 12 inclusive. Print how many miles the user traveled. Add 1 to thirst. Add 1 to camel tiredness. Move the natives up 7 to 14 miles.   Add an elif in your main program loop and handle if the user wants to go ahead drink from the canteen. If the user does, make sure there are drinks in the canteen. If there are, subtract one drink and set the player’s thirst to zero. Otherwise print an error.   In the loop, print “You are thirsty.” if the user’s thirst is above 4.   Print “You died of thirst!” if the user’s thirst is above 6. Set done to true. Make sure you create your code so that the program doesn’t print both “You are thirsty” and “You died of thirst!” Use elif as appropriate.   Print “Your camel is getting tired.” if the camel’s tiredness is above 5.   Print “Your camel is dead.” if the camel’s tiredness is above 8. Like the prior steps, print one or the other. It is a good idea to include a check with the done variable so that you don’t print that your camel is getting tired after you died of thirst.   If the natives have caught up, print that they caught the player and end the game.   Else if the natives are less than 15 miles behind, print “The natives are getting close!”   If the user has traveled 200 miles across the desert, print that they won and end the game. Make sure they aren’t dead before declaring them a winner.   Add a one-in-twenty chance of finding an oasis. Print that the user found it, refill the canteen, reset player thirst, and rest the camel.   Play the game and tune the numbers so it is challenging but not impossible. Fix any bugs you find.

暗示

  • 请记住,在程序中的代码逻辑分组之间放置空行是一个好主意。例如,在指令之后和每个用户命令之间有一个空行。
  • 使用while not done:而不是while done == False:被认为是更好的样式
  • 以防止错误的消息组合,如打印“你渴死了。”和“你发现了一片绿洲!”在同一个转弯中,使用and操作符。如,if not done and thirst > 4:

练习 4:创建一幅图片

描述

A978-1-4842-1790-0_22_Figd_HTML.jpg

你的任务是:画一幅漂亮的画。这个练习的目标是练习使用函数,使用for循环,并介绍计算机图形学。

练习你所有的新技能:

  • 拥有多种颜色的图像。
  • 做出连贯的画面。不要只做形状随意的抽象艺术。那没有挑战性。
  • 尝试几种类型的图形功能(例如,圆形、矩形、线条等。).
  • 使用whilefor循环创建重复模式。不要在同一个位置重复画同样的东西 10 次。实际上,使用索引变量作为偏移量来替换您正在绘制的内容。请记住,您可以在一个循环中包含多个绘制命令,因此您可以绘制多个火车车厢。

对于要修改的模板程序,请查看以下示例程序:

ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

ProgramArcadeGames.com/python_examples/f.php?file=simple_graphics_demo.py

参见第六章了解模板的说明。有关 draw 模块的官方文档:

http://www.pygame.org/docs/ref/draw.html

要选择新颜色,请使用

http://www.colorpicker.com/

或者打开 Windows 画图程序,点击“编辑颜色”复制红色、绿色和蓝色的值。不要担心颜色的色调、饱和度或亮度。

请使用注释和空行,以便于跟踪您的计划。如果你有 5 条线画一个机器人,把它们组合在一起,上面和下面用空白的线。然后在顶部添加注释,告诉读者你在画什么。

A978-1-4842-1790-0_22_Figi_HTML.jpg

A978-1-4842-1790-0_22_Figh_HTML.jpg

A978-1-4842-1790-0_22_Figg_HTML.jpg

A978-1-4842-1790-0_22_Figf_HTML.jpg

A978-1-4842-1790-0_22_Fige_HTML.jpg

练习 5:多圈实验室

第一部分

编写一个 Python 程序,它将打印以下内容:

10

11 12

13 14 15

16 17 18 19

20 21 22 23 24

25 26 27 28 29 30

31 32 33 34 35 36 37

38 39 40 41 42 43 44 45

46 47 48 49 50 51 52 53 54

第一部分的提示

  • 使用两个for循环生成第一部分的输出,其中一个是嵌套的。
  • 创建一个单独的变量来存储将要打印的数字。不要使用你在for循环中创建的变量。这将是第三个变量,从 10 开始,每次增加 1。

第二部分

用 n 行小 o 创建一个大盒子,表示任何所需的大小 n。使用input语句允许用户输入 n 的值,然后打印大小合适的盒子。

E.g. n = 3

oooooo

o    o

oooooo

E.g. n = 8

oooooooooooooooo

o              o

o              o

o              o

o              o

o              o

o              o

oooooooooooooooo

第三部分

对于任意正整数 n,打印以下内容。使用input语句允许用户输入 n 的值,然后打印适当大小的框。

E.g. n = 3

1 3 5 5 3 1

3 5     5 3

5         5

5         5

3 5     5 3

1 3 5 5 3 1

E.g. n = 5

1 3 5 7 9 9 7 5 3 1

3 5 7 9     9 7 5 3

5 7 9         9 7 5

7 9             9 7

9                 9

9                 9

7 9             9 7

5 7 9         9 7 5

3 5 7 9     9 7 5 3

1 3 5 7 9 9 7 5 3 1

不要担心处理多位数的间距。如果你想向前看,第二十一章会谈到这一点,但这并不是必需的。

这部分练习很难。如果你对挑战不感兴趣,跳到第四部分。

第四部分

从 pygame 模板代码开始:

ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

使用嵌套的for循环绘制绿色小矩形。使图像看起来像下图。

A978-1-4842-1790-0_22_Figj_HTML.jpg

Pygame Grid(游戏网格)

不要通过画线来创建网格;使用由矩形创建的网格。

如果这太无聊,创建一个类似的网格的东西。可以更改所绘制形状的颜色、大小和类型。只需习惯使用嵌套的for循环来生成网格。

有时人们觉得需要在这个程序中给偏移量加一个零。提醒自己,在一个数字上加零有点傻。

练习 6:冒险!

冒险游戏的描述

A978-1-4842-1790-0_22_Figk_HTML.jpg

我玩过的第一个游戏是一个叫做巨大洞穴探险的文本冒险。你可以在网上玩这个游戏,了解一下文字冒险游戏是什么样的。可以说这类游戏中最著名的是 Zork 系列。

我自己创建的第一个“大型”程序是文本冒险。开始这样的冒险很容易。这也是练习使用列表的好方法。我们这个练习的游戏将包括一个房间列表,可以通过向北、向东、向南或向西导航。每个房间将是一个房间描述的列表,然后是每个方向的房间。请参见下面的示例运行部分:

样品运行

You are in a dusty castle room.

Passages lead to the north and south.

What direction? n

You are in the armory.

There is a room off to the south.

What direction? s

You are in a dusty castle room.

Passages lead to the north and south.

What direction? s

You are in a torch-lit hallway.

There are rooms to the east and west.

What direction? e

You are in a bedroom. A window overlooks the castle courtyard.

A hallway is to the west.

What direction? w

You are in a torch-lit hallway.

There are rooms to the east and west.

What direction? w

You are in the kitchen. It looks like a roast is being made for supper.

A hallway is to the east.

What direction? w

Can’t go that way.

You are in the kitchen. It looks like a roast is being made for supper.

A hallway is to the east.

What direction?

创建你的地牢

在你开始之前,勾画出你想要创建的地牢。它可能看起来像这样:

A978-1-4842-1790-0_22_Figl_HTML.jpg

接下来,从零开始给所有房间编号。

A978-1-4842-1790-0_22_Figm_HTML.jpg

用这张草图来找出所有房间是如何连接的。例如,房间 0 连接到北面的房间 3,房间 1 连接到东面,而南面和西面没有房间。

逐步说明

Create an empty array called room_list.   Create a variable called room. Set it equal to an array with five elements. For the first element, create a string with a description of your first room. The last four elements will be the number of the next room if the user goes north, east, south, or west. Look at your sketch to see what numbers to use. Use None if no room hooks up in that direction. (Do not put None in quotes. It is a special value that represents no value.)   Append this room to the room list.   Repeat the prior two steps for each room you want to create. Just reuse the room variable.   Create a variable called current_room. Set it to zero.   Print the room_list variable. Run the program. You should see a really long list of every room in your adventure. (If you are using an IDE like Wing, don’t leave it scrolled way off to the right.)   Adjust your print statement to only print the first room (element zero) in the list. Run the program and confirm you get output similar to: [’You are in a room. There is a passage to the north.’, 1, None, None, None]   Using current_room and room_list, print the current room the user is in. Since your first room is zero, the output should be the same as before.   Change the print statement so that you only print the description of the room, and not the rooms that hook up to it. Remember if you are printing a list in a list the index goes after the first index. Don’t do this: [current_room[0]], do [current_room][0] You are in a room. There is a passage to the north.   Create a variable called done and set it to False. Then put the printing of the room description in a while loop that repeats until done is set to True.   Before printing the description, add a code to print a blank line. This will make it visually separate each turn when playing the game.   After printing the room description, add a line of code that asks the user what direction they wish to go.   Add an if statement to see if the user wants to go north.   If the user wants to go north, create a variable called next_room and get it equal to room_list[current_room][1], which should be the number for what room is to the north.   Add another if statement to see if the next room is equal to None. If it is, print “You can’t go that way.” Otherwise set current_room equal to next_room.   Test your program. Can you go north to a new room?   Add elif statements to handle east, south, and west. Add an else statement to let the user know the program doesn’t understand what she typed.   Add several rooms, at least five. It may be necessary to draw out the rooms and room numbers to keep everything straight. Test out the game. You can use \n or triple quotes if you have a multiline room description.   Optional: Add a quit command. Make sure that the program works for upper and lower case directions. Have the program work if the user types in “north” or “n.”

花一点时间让这个游戏变得有趣。不要简单地创造一个“东房间”和一个“西房间”。那太无聊了。

还要花一点时间仔细检查拼写和语法。在没有文字处理器检查你的写作的情况下,小心是很重要的。

使用\n在你的描述中添加回车符,这样它们就不会在一行中全部打印出来。不要在\n周围加空格,否则空格会被打印出来。

我喜欢这个程序是因为它很容易扩展成一个完整的游戏。使用所有八个基本方向(包括“西北”),连同“上”和“下”是相当容易的。管理可以存在于房间中、被拾取和丢弃的物品的清单也是保存列表的问题。

将这个程序扩展成一个完整的游戏是最后练习的两个选项之一。

练习 7:动画

要求

修改之前的“创建图片”练习,或者开始一个新的练习。

动画图像。尝试以下一种或多种方法:

  • 在屏幕上移动项目。
  • 来回移动项目。
  • 向上/向下/斜向移动。
  • 转圈。
  • 让人挥动手臂。
  • 创建一个改变颜色的交通信号灯。

记住,天赋越多越好!享受这个练习,花点时间看看你能做什么。

练习 8:函数

把这个写成一个程序。整个东西应该可以直接穿过去。

这个程序有几个部分。以下是每个部分的描述:

Write a function called min3 that will take three numbers as parameters and return the smallest value. If more than one number tied for smallest, still return that smallest number. Use a proper if/elif/else chain. Once you’ve finished writing your function, copy/paste the following code and make sure that it runs against the function you created: print(min3(4, 7, 5)) print(min3(4, 5, 5)) print(min3(4, 4, 4)) print(min3(-2, -6, -100)) print(min3("Z", "B", "A")) You should get this result: 4 4 4 -100 A The function should return the value, not print the value. Also, while there is a min function built into Python, don’t use it. Please use if statements and practice creating it yourself. Leave the testing statements in the program so the instructor can check the program. If you also get None to print out, then chances are you are using print instead of return in your function.   Write a function called box that will output boxes given a height and width. Once you’ve finished writing your function, copy and paste the following code after it and make sure it works with the function you wrote: box(7,5)  # Print a box 7 high, 5 across print()   # Blank line box(3,2)  # Print a box 3 high, 2 across print()   # Blank line box(3,10) # Print a box 3 high, 10 across You should get the following results from the sample code: ***** ***** ***** ***** ***** ***** ***** ** ** ** ********** ********** ********** Go back and look at Chapter 7if you’ve forgotten how to do this.   Write a function called find that will take a list of numbers, my_list, along with one other number, key. Have it search the list for the value contained in key. Each time your function finds the key value, print the array position of the key. You will need to juggle three variables: one for the list, one for the key, and one for the position of where you are in the list. This code will look similar to the Chapter 8 code for iterating though a list using the range and len functions. Start with that code and modify the print to show each element and its position. Then instead of just printing each number, add an if statement to only print the ones we care about. Copy/paste this code to test it: my_list = [36, 31, 79, 96, 36, 91, 77, 33, 19, 3, 34, 12, 70, 12, 54, 98, 86, 11, 17, 17] find(my_list, 12) find(my_list, 91) find(my_list, 80) . . . check for this output: Found 12 at position 11 Found 12 at position 13 Found 91 at position 5 Use a for loop with an index variable and a range. Inside the loop use an if statement. The function can be written in about four lines of code.   Write one program that has the following:

  • 功能:
    • 编写一个名为create_list的函数,它接受一个列表大小并返回一个从 1 到 6 的随机数列表(即,调用create_list(5)应该返回 5 个从 1 到 6 的随机数。(记住,第八章有代码展示了如何做类似的事情,用用户输入的五个数字创建一个列表。这里,您需要创建随机数,而不是询问用户。)为了测试,对您编写的函数使用下面的代码:my_list = create_list(5) print(my_list),您应该得到五个随机元素的输出,看起来像:[2,5,1,6,3]
    • 编写一个名为count_list的函数,它接受一个列表和一个数字。让函数返回指定数字在列表中出现的次数。为了测试,对您编写的函数使用下面的代码:count = count_list([1,2,3,3,3,4,2,1],3) print(count),您应该得到类似于3的输出
    • 编写一个名为average_list的函数,返回传递给它的列表的平均值。为了测试,对您编写的函数使用下面的代码:avg = average_list([1,2,3]) print(avg),您应该得到类似于2的输出
  • 现在已经创建了这些函数,在一个主程序中使用它们,它将:
    • 创建一个包含 10,000 个从 1 到 6 的随机数的列表。这应该需要一行代码。使用您之前在练习中创建的函数。)
    • 打印从 1 到 6 的计数。(即打印 1 在 10000 次中出现的次数。然后对 2-6 做同样的事情。)
    • 打印所有 10,000 个随机数的平均值。

练习 9:用户控制

这个练习给你一个机会练习用一个函数画一个对象,并允许用户控制它。

创建一个包含以下内容的程序:

Create at least two different functions that draw an object to the screen. For example, draw_bird and draw_tree. Do not draw a stick figure; we did that one already. Create your own unique item. If you created your own object in the create-a-picture exercise feel free to adapt it to this exercise.   In Chapter 11, we talked about moving graphics with the keyboard, a game controller, and the mouse. Pick two of those and use them to control two different items on the screen.   In the case of the game controller and the keyboard, make sure to add checks so that your object does not move offscreen and get lost.

练习 10:位图图形和用户控件

创建一个基于图形的程序。你可以开始一个新的程序或继续先前的练习。

这是完成本练习的清单:

  • 确保这个程序是在它自己的目录中创建的。使用您已有的空目录,或创建一个新目录。
  • 至少包含一个在屏幕上绘制项目的功能。该函数应该获取指定在何处绘制项目的位置数据。(注意:您还需要传递一个对“屏幕”的引用另一个注意:这对于从文件中加载的图像来说是很难做到的。我建议只使用常规的绘图命令。)
  • 添加通过鼠标、键盘或游戏控制器控制项目的功能。
  • 包括某种位图图形。不要将位图图形作为“用函数绘图”的一部分在我们了解更多一点之前,这不会有什么效果。
  • 包括声音。当用户点击鼠标、击键、移动到某个位置等时,你可以发出声音。如果声音有问题,尝试使用 Audacity 程序加载声音,然后将其导出为。ogg 文件。
  • 如果您将此程序发送给某人,请确保发送了所有文件。很容易忘记添加图像和声音文件。

示例代码:

ProgramArcadeGames.com/index.php?chapter=example_code

您可以使用的声音和位图:

opengameart.org

可以使用之前练习中的代码,例如练习 5。

练习 11:类和图形

图形提供了一个使用类的绝佳机会。每个图形对象可以由一个对象表示。每种类型的图形对象都可以用一个类来表示。对象的位置、速度和颜色可以存储在属性中。

说明

Start a new program with: ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py   Right after the default colors are defined in the example program, create a class called Rectangle.

  • 添加xy属性,用于存储对象的位置。
  • 创建一个draw方法。让这个方法在xy中存储的位置创建一个绿色的 10×10 的矩形。不要忘记在变量前使用self.。该方法需要接受对screen的引用,以便pygame.draw.rect函数可以将矩形绘制到正确的屏幕上。

Before the program loop, create a variable called my_object and set it equal to a new instance of Rectangle.   Inside the main program loop, call my_object’s draw() method.   Checkpoint: Make sure your program works, and the output looks like this figure.

A978-1-4842-1790-0_22_Fign_HTML.jpg

Rectangle in top left corner   Right after the program creates the instance of Rectangle, set the x and y values to something new, like 100, 200. Run the program again to make sure it works and the rectangle moves to the new coordinates.   Add attributes to the class for height and width. Draw the rectangle using these new attributes. Run the program and make sure it works.   Get the object to move:

  • change_xchange_y添加属性。
  • 创建一个名为move()的新方法,根据change_xchange_y调整 x 和 y。(注意,move 方法不需要 screen 作为参数,因为它不会在屏幕上绘制任何东西。)
  • my_objectchange_xchange_y设置为数值,如 2 和 2。
  • 在主程序循环中调用move()方法。
  • 测试以确保对象移动。

Randomize the object

A978-1-4842-1790-0_22_Figo_HTML.jpg

  • 导入随机库
  • 将 x 位置设置为 0 到 700 之间的一个随机数。您可以在创建对象的循环中这样做,也可以在__init__方法中这样做。
  • 将 y 位置设置为 0 到 500 之间的随机数。
  • 将高度和宽度设置为 20 到 70 之间的任意数字。
  • change_xchange_y设置为-3 和 3 之间的随机数。
  • 测试并确保它看起来像这个图形。

Rectangle in random spot   Create and display a list of objects

A978-1-4842-1790-0_22_Figp_HTML.jpg

  • 在创建my_object的代码之前,创建一个空列表,名为my_list
  • 创建一个循环 10 次的for循环。
  • 将创建my_object的代码放入for循环中
  • my_object追加到my_list上。
  • 在主程序循环内,循环通过my_list的每一项。
  • 为列表中的每一项调用 draw 和 move 方法。
    • 确保代码调用了for循环拉出的元素的 draw 方法,不要只使用my_object.draw()。这是最常见的错误之一。
  • 测试一下,看看你的程序看起来是否像下图。

Ten rectangles   Use inheritance

A978-1-4842-1790-0_22_Figq_HTML.jpg

  • Rectangle类之后,创建一个名为Ellipse的新类。
  • Rectangle设置为Ellipse的父类。
  • 您不需要创建新的___init__;我们将只是继承父类的方法。
  • 创建一个绘制椭圆而不是矩形的新绘制方法。
  • 创建一个新的for循环,除了 10 个矩形之外,还将 10 个Ellipse实例添加到my_list中。(就用两个独立的for循环。)
  • 请确保不要创建新列表;只需将它们添加到同一个my_list
  • 因为矩形和椭圆都被添加到同一个列表中,所以只需要一个循环就可以遍历列表并绘制两者。不要犯有两个循环的错误,一个用于矩形,一个用于椭圆。事情不是这样的。
  • 测试一下,看看你的程序是否像下图这样。

Rectangles and ellipses   Make it more colorful

A978-1-4842-1790-0_22_Figr_HTML.jpg

  • 调整程序,让颜色成为Rectangle的一个属性。
  • 使用新颜色绘制矩形和椭圆。
  • for循环中,将形状设置为随机颜色。记住,颜色是由列表中的三个数字指定的,所以你需要一个由三个随机数组成的列表(r, g, b)
  • 测试一下,看看你的程序看起来是否像下图。

Colorful shapes   Try it with more than 10 items of each type. This next figure shows 1,000 shapes.   You are done! Turn in your program.

A978-1-4842-1790-0_22_Figs_HTML.jpg

Shapes gone crazy

练习 12:收集雪碧

本练习练习使用 pygame 精灵,如第十四章所述。

Make sure this program is created in its own directory.   Start with the following program: ProgramArcadeGames.com/python_examples/f.php?file=sprite_collect_blocks.py   Update the comments at the beginning to reflect that it is now your program, not mine.   Modify it so the player moves with the keyboard rather than the mouse. Take a look at the move_sprite_keyboard_smooth.py program also available on the example page: ProgramArcadeGames.com/python_examples/f.php?file=move_sprite_keyboard_smooth.py

  • 从这个文件中,您需要获取Player类,并将其移动到您自己的程序中。不要去掉Block类。在你的程序中你将同时拥有BlockPlayer两个职业。
  • 现在,你的玩家是Block的一个实例。您需要对它进行修改,以便创建一个Player的实例。注意,Player的构造函数与Block采用不同的参数。
  • 更新您的事件循环,使其像这个新示例一样响应键盘输入。
  • 删除用鼠标移动播放器的代码。
  • 让玩家变蓝。
  • 确保在主程序循环中有一个对all_sprites_list.update()的调用。这将调用每个 sprite 中的update()方法。
  • 测试并确保它现在工作。

Create both good sprites and bad sprites

  • 好雪碧
    • 你现在创建 50 个块,不是把它们添加到一个叫做block_list的列表中,而是把它们添加到一个叫做good_block_list的列表中。
    • 把积木变成绿色。
    • 在主循环中检查块冲突的地方,更新检查,使其使用good_block_list
  • 坏精灵不会在你添加坏方块或玩家之前重新创建列表。
    • 复制这段代码,并将另外 50 个块添加到名为bad_block_list的新列表中。
    • 确保你的程序只创建一个all_sprites_list。Don’t re-create the list right before you add bad blocks or the player.
    • 把积木涂成红色。
    • 复制代码并对照bad_block_list进行检查。降低分数而不是增加分数。
    • 测试并确保它正在工作。
  • 使用图形来表示好/坏精灵,如sprite_collect_graphic.py示例文件所示。不使用图形只是罚两分。

Rather than simply use print to display the score on the console, display the score on the graphics window. Go back to the end of Chapter 6 and look up the section on drawing text.   Add sound effects for when the user hits good blocks, or bad blocks. Here are a couple from OpenGameArt.org : ProgramArcadeGames.com/labs/sprite_collecting/good_block.wav ProgramArcadeGames.com/labs/sprite_collecting/bad_block.wav   Looking back at how we made a sound to begin with, we triggered the sound with a mouse click event. We won’t be doing that here. Find the code that you already have that detects when a collision occurs. Play the sound when you have a collision. Also, remember that when you load a sound, it needs to be done before the main loop, but after the pygame.init() command.   Add a check and make sure the player doesn’t slide off the end of the screen. This check should go in the update method of the Player class. There should be four if statement checks, one for each border. If the player’s x and y get outside the bounds of the screen, reset the player back inside the screen. Do not modify change_x or change_y. That doesn’t work for player-controlled objects. Don’t forget to check the right and bottom borders; they are easy to get wrong.   Download a wav or ogg file and have it play a sound if the user tries to slide off the screen. Here’s one sound you can use: ProgramArcadeGames.com/labs/sprite_collecting/bump.wav   Check to make sure the bump sound doesn’t continually play when the user is at the edge of the screen. If it does, the program is checking the edge incorrectly.   If you send your program to anyone, remember they will need all the files, not just the Python program.

练习 13:精灵移动

A978-1-4842-1790-0_22_Figt_HTML.jpg

这个练习使用 pygame 精灵,如第十四章中所述,并且将职业分成不同的文件,如第十五章中所述。

Make sure this program is created in its own directory.   Start with a copy of the program you wrote for Exercise 12: Sprite Collecting. Copy those files into the directory for this exercise.   Move the Block class into a new file. Many people get confused between the name of the library file, the name of the class, and the variable that points to the instance of the object. Library files should be all lowercase. I’d recommend calling your new file that you put the Block class into block_library.py.   Make sure your program runs like before. Adjust import statements as needed. Remember that you prepend the library name to the class, not the variable that points to the instance of the class. For example: my_dog = dog_library.Dog() and NOT dog_library.my_dog = Dog() because Dog is what is in the library, not my_dog.   Define a GoodBlock class in a new file, and inherit from your Block class. I’d recommend using the file name goodblock_library.py to keep with the pattern we set up before. Remember, define the class. Don’t create an instance of it. Your for loop that creates the instances does not move.   Add a new update method. (You probably don’t need a new __init__ method.) Make the good block randomly move up, down, left or right each update. (Change self.rect.x and self.rect.y randomly each time the update function is called. Not to a completely new number, but add a random number from -3 to 3 or so. Remember that random.randrange(-3,3) does not generate a random number from -3 to 3.)   Change your for loop so that it creates instances of the GoodBlock class and not your old regular Block class.   Call update on the list of all the sprites you have. Do this in the main program loop so the blocks keep moving, not just once at the start of the program.   Test and make sure it works.   It is ok if the sprites move off the screen, but a common mistake results in the sprites all moving up and to the left off the screen. If that happens, go back four steps and check your random range.   Double-check, and make sure GoodBlock inherits from the Block class. If done correctly, GoodBlock won’t need an __init__ method because it will get this method from Block.   Create a BadBlock class in a new file and inherit from the Block class.   Make an update function and have the bad block sprites move down the screen, similar to what was done in Chapter 14. Extra kudos if you make a bouncing rectangle.   Test, and make sure it works.   Double-check to make sure each class is in its own file.   If you have extra time, you can look at the sprite examples section on the web site and see how to get sprites to bounce or move in circles.

练习 14:拼写检查

本练习展示了如何创建拼写检查器。要准备练习,请访问:

ProgramArcadeGames.com/index.php?chapter=examples list

。。。并下载下面列出的文件。这些文件也在“搜索和排序示例”部分。

  • AliceInWonderLand.txt——《爱丽丝梦游仙境》正文
  • AliceInWonderLand200.txt——《爱丽丝梦游仙境》第一章
  • dictionary.txt -一串单词

要求

用 Python 写一个检查《爱丽丝梦游仙境》第一章拼写的程序。首先使用线性搜索,然后使用二分搜索法。打印行号和字典中不存在的单词。

请仔细遵循以下步骤。如果你不知道如何完成一个步骤,在进入下一步之前问一下。

要完成的步骤:

If you work off the BitBucket template, skip ahead to step 6.   Find or create a directory for your project.   Download the dictionary to the directory.   Download first 200 lines of Alice In Wonderland to your directory.   Start a Python file for your project.   It is necessary to split apart the words in the story so that they may be checked individually. It is also necessary to remove extra punctuation and white space. Unfortunately, there is not any good way of doing this with what the book has covered so far. The code to do this is short, but a full explanation is beyond the scope of this class. Include the following function in your program. Remember, function definitions should go at the top of your program just after the imports. We’ll call this function in a later step. import re # This function takes in a line of text and returns # a list of words in the line. def split_line(line):     return re.findall(’[A-Za-z]+(?:\’[A-Za-z]+)?’,line) This code uses a regular expression to split the text apart. Regular expressions are very powerful and relatively easy to learn. To learn more about regular expressions, see: http://en.wikipedia.org/wiki/Regular_expression   Read the file dictionary.txt into an array. Go back to the chapter on Searching, or see the searching_example.py for example code on how to do this. This does not have anything to do with the import command, libraries, or modules. Don’t call the dictionary word_list or something generic because that will be confusing. Call it dictionary_list or something similar.   Close the file.   Print --- Linear Search ---   Open the file AliceInWonderLand200.txt   We are not going to read the story into a list. Do not create a new list here like you did with the dictionary.   Start a for loop to iterate through each line.   Call the split_line function to split apart the line of text in the story that was just read in. Store the list that the function returns in a new variable named words. Remember, just calling the function won’t do anything useful. You need to assign a variable equal (words) to the result. If you’ve forgotten now to capture the return value from a function, flip back to the functions chapter to find it.   Start a nested for loop to iterate through each word in the words list. This should be inside the for loop that runs through each line in the file. (One loop for each line, another loop for each word in the line.)   Using a linear search, check the current word against the words in the dictionary. Check the chapter on searching or the searching_example.py for example code on how to do this. The linear search is just three lines long. When comparing to the word to the other words in the dictionary, convert the word to uppercase. In your while loop just use word.upper() instead of word for the key. This linear search will exist inside the for loop created in the prior step. We are looping through each word in the dictionary, looking for the current word in the line that we just read in.   If the word was not found, print the word. Don’t print anything if you do find the word; that would just be annoying.   Close the file.   Make sure the program runs successfully before moving onto the next step.   Create a new variable that will track the line number that you are on. Print this line number along with the misspelled from the prior step.   Make sure the program runs successfully before moving onto the next step.   Print --- Binary Search ---   The linear search takes quite a while to run. To temporarily disable it, it may be commented out by using three quotes before and after that block of code. Ask if you are unsure how to do this.   Repeat the same pattern of code as before, but this time use a binary search. Much of the code from the linear search may be copied, and it is only necessary to replace the lines of code that represent the linear search with the binary search.   Note the speed difference between the two searches.   Make sure the linear search is re-enabled, if it was disabled while working on the binary search.   Upload the final program or check in the final program.

示例运行

--- Linear Search ---

Line 3  possible misspelled word: Lewis

Line 3  possible misspelled word: Carroll

Line 46  possible misspelled word: labelled

Line 46  possible misspelled word: MARMALADE

Line 58  possible misspelled word: centre

Line 59  possible misspelled word: learnt

Line 69  possible misspelled word: Antipathies

Line 73  possible misspelled word: curtsey

Line 73  possible misspelled word: CURTSEYING

Line 79  possible misspelled word: Dinah’ll

Line 80  possible misspelled word: Dinah

Line 81  possible misspelled word: Dinah

Line 89  possible misspelled word: Dinah

Line 89  possible misspelled word: Dinah

Line 149  possible misspelled word: flavour

Line 150  possible misspelled word: toffee

Line 186  possible misspelled word: croquet

--- Binary Search ---

Line 3  possible misspelled word: Lewis

Line 3  possible misspelled word: Carroll

Line 46  possible misspelled word: labelled

Line 46  possible misspelled word: MARMALADE

Line 58  possible misspelled word: centre

Line 59  possible misspelled word: learnt

Line 69  possible misspelled word: Antipathies

Line 73  possible misspelled word: curtsey

Line 73  possible misspelled word: CURTSEYING

Line 79  possible misspelled word: Dinah’ll

Line 80  possible misspelled word: Dinah

Line 81  possible misspelled word: Dinah

Line 89  possible misspelled word: Dinah

Line 89  possible misspelled word: Dinah

Line 149  possible misspelled word: flavour

Line 150  possible misspelled word: toffee

Line 186  possible misspelled word: croquet

练习 15:最终练习

最后的练习有两个选项:“视频游戏选项”和“文本冒险选项”

视频游戏选项

就是这里!这是你发挥创造力的机会,真正展示你在自己的游戏中所能创造的东西。

这个最后的练习分为三个部分。每一部分都提高了你的游戏需要具备的能力。

第一部分的要求:

  • 打开一个屏幕。
  • 设置要在屏幕上绘制的项目。
  • 通过鼠标、键盘或游戏控制器提供一些基本的玩家动作。

提示:

  • 如果你的程序会涉及到互相碰撞的东西,从使用精灵开始。不要从使用绘图命令开始,并期望以后添加精灵。这是行不通的,你需要从头开始。这将是可悲的。
  • 如果你正在编写一个类似扫雷或连接四的程序,不要使用精灵。因为不需要碰撞检测,所以没有必要搞乱精灵。
  • 在“更长的游戏示例”下,我有两个程序展示了如何创建 pong 或 breakout 风格的游戏。不要只是把这些作为第一部分上交;在它真正合格之前,你需要增加很多。
  • OpenGameArt.org 有很多图片和声音你可以免费使用。
  • Kenney.nl 有许多形象和声音。

第二部分的要求:

对于最后的练习第二部分,你的游戏应该是功能性的。一个人应该能够坐下来玩几分钟游戏,让它感觉像一个真正的游戏。以下是您可能想要添加的一些内容:

  • 能够碰撞物体。
  • 如果发生不好的事情,玩家可能会输掉游戏。
  • 屏幕配乐。
  • 一些初始音效。
  • 屏幕上其他字符的移动。
  • 点击地雷或空点的能力。

第三部分的要求:

最后,为你的游戏添加最后的润色。以下是您可能想要添加的一些内容:

  • 多层次
  • 声音
  • 多重“生命”
  • 标题和说明屏幕
  • 背景音乐
  • 热追踪导弹
  • 隐藏的门
  • 扫雷游戏中的“清扫”动作或放置“旗帜”的能力

文本冒险选项

对电子游戏不感兴趣?继续你的工作从“冒险!”游戏。

第一部分的要求:

Rather than have each room be a list of [description, north, east, south, west], create a Room class. The class should have a constructor that takes in (description, north, east, south, west) and sets fields for the description and all of the directions. Get the program working with the new class.   Expand the game so that a person can travel up and down. Also expand it so the person can travel northwest, southwest, northeast, and southeast.   Create another class for Object. Give the object fields for name, description, and current room. For example, you might have a name of “key,” a description of “This is a rusty key that looks like it would fit in an old lock. It has not been used in a long time.” The current room number would be 3 if the key was in room 3. If the player is carrying the key then the current room for the object will be -1.   Create a list for objects, and add several objects to the list. The code for this will be very similar to the list of rooms that you created and added to the list of rooms.   After printing the description of the room, have the program search the entire list of objects, and print if the room of the object matches the room the player is in. For example, if current_room == current_object.room then print: “There is a key here.”   Test your game and make sure it works.

第二部分的要求:

Add the ability to pick up an object. If the user types get key then: Split the user input so you split out and just have a variable equal to “key.”   Search the list until you find an object that matches what the user is trying to pick up.   If the object isn’t found, or if the object isn’t in the current room, print an error.   If the object is found and it is in the current room, then set the object’s room number to -1.     Add the ability to drop an object.   Add a command for “inventory” that will print every object who’s room number is equal to -1.   Add the ability to use the objects. For example “use key” or “swing sword” or “feed bear.”

第三部分的要求:

将游戏再扩展一些。尝试以下一些想法:

Create a file format that allows you to load the rooms and objects from a file rather than write code for it.   Have monsters with hit points.   Split the code up into multiple files for better organization.   Remove globals using a main function as shown at the end of the chapter about functions.   Have objects with limited use. Like a bow that only has so many arrows.   Have creatures with limited health, and weapons that cause random damage and have a random chance to hit.

posted @ 2024-08-09 17:40  绝不原创的飞龙  阅读(8)  评论(0编辑  收藏  举报