既然讨论的是文件,为什么要在这一章建立一个游戏呢?嗯,Hangman 游戏之所以有趣,原因在于它有一个庞大的词汇表,可以从这个词汇表中选择题目。要做到这一点,最容易的办法就是从文件中读取。我们仍然使用 PythonCard 来完成这个游戏,也想由此说明并非只能使用 Pygame 建立图形化游戏。
我不打算像介绍其他程序那样详细地解释这个程序。现在你应该已经会看代码了,相信你能自己搞清楚其中大部分代码的作用。我只会稍稍给你一点指导,帮助你顺利读懂代码。
Hangman GUI
我们的 Hangman 程序的主 GUI 是像下图这样的:
这里显示了上吊小人的各个部分,不过程序运行时,我们首先会隐藏小人的所有部分。玩家猜错一个字母时,就会显示这个小人的下一部分。如果整个小人都显示出来,玩家可以再猜一次,然后游戏结束!
玩家猜一个字母时,程序会查看这个字母是否在秘密词中。如果确实是秘密词中的字母,就把这个字母显示出来。在窗口下方,玩家可以看见到目前为止他猜过的所有字母。玩家什么时候都可以尝试猜词。
下面概括一下这个程序的工作原理。
开始时,程序完成以下工作:
从文件加载词汇表;
从每行的末尾去除换行符;
让小人的所有部分都不可见;
从词汇表随机选择一个词;
根据秘密词中的字母个数显示相同数目的横线。
玩家点击 Guess! 按钮时,程序做以下工作。
检查猜的是一个字母还是一个词。
如果是一个字母:
检查秘密词,查看是否包含这个字母。
如果玩家猜对了,用这个字母取代横线,显示这个字母出现在什么位置。
如果玩家猜错了,显示小人的另一部分。
把猜出的字母增加到 Previous guesses 显示区。
查看玩家是否已经猜出单词(猜出所有字母)。
如果是一个词:
检查玩家猜得正确与否。
如果正确,显示一个对话框,指出 You Won!(你赢了!)并开始新游戏。
查看玩家是不是已经没有机会了,如果是,显示一个对话框,指出 You Lost(你失败了),并显示这个秘密词到底是什么。
从词汇表得到单词
这一章讨论的是文件,所以下面来看程序中得到词汇表的部分。相应代码如下:
words.txt 文件只是一个文本文件,所以可以使用 readlines
读这个文件。为了从词汇表中选择一个词,我们使用了 random.choice
函数,如下:
self.currentword = random.choice(self.lines)
显示小人
要跟踪已经显示了小人的哪些部分,另外下一步要显示哪一部分,这有很多方法。我们决定使用一个循环,代码如下:
def wrong(self): self.pieces_shown += 1 for i in range(self.pieces_shown): self.pieces[i].setHidden(False) if self.pieces_shown == len(self.pieces): message = /"You lose. The word was /" + self.currentword QtGui.QMessageBox.warning(self,/"Hangman/",message) self.new_game
我们使用 self.pieces_shown
来跟踪上吊小人显示了多少部分。如果所有的部分都显示出来了,我们就使用一个对话框来告知玩家他输了。
检查猜到的字母
这个程序最难的一部分就是检查玩家猜到的字母,看它是否出现在秘密词中。这个工作之所以困难,是因为字母可能在一个单词中出现多次。例如,如果秘密词是 lever,玩家猜到了 e,就必须把第 2 个和第 4 个字母都显示出来,因为它们都是 e。
我们有几个函数来完成这项工作。find_letters
函数会查找某个字母在单词中出现的所有位置,并返回一个包含这些位置的列表。例如,对于字母 e 和单词 lever,这个函数会返回 [1, 3],因为字母 e 出现在这个字符串的索引 1 和索引 3 的位置上。(记住,索引从 0 开始。)代码如下:
replace_letters
函数从 find_letters
得到列表,用正确的字母替换这些位置上的横线。在我们的例子中(lever 中的字母 e),它会用 -e-e-
替换 -----
。这就向玩家显示出猜对的字母出现在单词的什么位置,其余仍然为横线。代码如下:
def replace_letters(string, locations, letter): new_string = /'/' for i in range (0, len(string)):if i in locations: new_string = new_string + letterelse: new_string = new_string + string[i] return new_string
玩家猜一个字母时,我们使用刚才定义的两个函数 find_letters
和 replace_letters
:
整个程序大约 95 行代码,另外我还加入了一些空行,让代码看起来更美观。代码清单 22-8 给出了整个程序,这里加了对各个不同部分做的一些解释。如果使用本书的安装程序,你的计算机上的 ExamplesHangman 文件夹中应该已经有这个代码了,另外也可以在网站上找到这个代码,包括 hangman.py、hangman.ui 和 words.txt。切记,正如我们在第 20 章提到的,如果你使用的是 Mac,则需要在 Qt Designer 中打开 hangman.ui,并勾选掉 menubar
对象的 nativeMenuBar
属性。
代码清单 22-8 完整的 hangman.py 程序
为了简化起见,我们的 Hangman 程序只使用了小写字母。我们提供的单词表只有小写字母,用户也必须将其猜测的内容以小写形式输入。
新游戏开始时,处的 dashes
函数使用横杠替换了字母。但它并没有替换标点符号,比如撇号。所以如果单词是 doesn/'t,玩家将会看到 -----/'-
。
建议你自己创建这个程序。可以用 Qt Designer 构建 GUI,即使看上去与这里的版本不完全一样也没有关系。不过一定要仔细查看代码,看看组件分别使用什么名字。代码中的名字必须与 .ui 文件中的名字一致。
尽可能自己键入代码。运行程序,看看结果怎么样。如果你想做些不同的尝试,那就放手去做!充分尝试,大胆试验,并享受其中的快乐。这正是编程最有意思也最有收获的地方,大多数知识都是通过这个途径获得的。
你学到了什么
在这一章,你学到了以下内容。
什么是文件。
如何打开和关闭文件。
打开文件的不同方式:读、写和追加。
写文件的不同方法:
write
或print >>
。如何使用
pickle
在文件中保存列表和对象(以及其他 Python 数据结构)。很多与文件夹(也称为目录)、文件位置和路径相关的内容。
我们还建立了一个 Hangman 游戏,这个游戏使用来自文件的数据得到一个词汇表。
测试题
1. Python 中用来处理文件的对象称为 ________。
2. 如何创建一个文件对象?
3. 文件对象和文件名之间有什么区别?
4. 完成文件读写时应该对文件做什么操作?
5. 如果用追加模式打开一个文件然后在文件中写入内容会怎样?
6. 如果用写模式打开一个文件然后在文件中写入内容会怎样?
7. 读过文件的一部分之后如何从文件起始位置开始读?
8. 将一个 Python 对象保存到文件中要使用哪个 pickle
函数?
9. 要“还原”一个对象 ( 从 pickle
文件得到对象 , 并放回到 Python 变量中 ),应当使用哪个 pickle
方法?
动手试一试
1. 建立程序造一些滑稽句子。每个句子应该至少有 4 部分,类似于:
The ____________ _______________ ______________ _________________
(形容词) ( 名词 ) ( 动词短语 )( 副词短语 )
例如:
这个程序会随机选择一个形容词、一个名词、一个动词短语和一个副词短语来创建句子。这些单词都存储在文件中,可以使用“记事本”创建这些单词。要完成这个程序,最简单的方法是为这 4 组单词分别创建一个文件,不过也可以采用你希望的任何方式创建文件。下面给出一些点子来启发一下你,不过,我相信你也能提出自己的想法。
形容词:crazed, silly, shy, goofy, angry, lazy, obstinate, purple
名词:monkey, elephant, cyclist, teacher, author, hockey player
动词短语:played a ukulele, danced a jig, combed his hair, flapped her ears
副词短语:on the table, at the grocery store, in the shower, after breakfast,with a broom
再给出一个示例输出:“The lazy author combed his hair with a broom”。
2. 编写一个程序,让用户输入名字、年龄、最喜欢的颜色和最喜欢的食物。程序要把所有这 4 项保存在一个文本文件中,每一项分别放在单独的一行上。
3. 完成第 2 题的任务,不过使用 pickle
将数据保存到一个文件。(提示:如果先把数据放在一个列表中就会很容易做到。)