Day20 创建猜单词游戏(Hangman)
随着前几周的学习,我们会发现这些项目代码通常会变的越来越长。今天,我们将利用过去四个天学到的所有概念来构建Hangman游戏。正如往常一样,随着项目代码写入,我们将引入新的概念。今天,我们的目标是创建功能齐全的Hangman游戏,在这个游戏里,我们可以猜词,减少生命值,并最后赢或输掉游戏。在这个游戏中,我们不会创建图象。在我们共同完成项目后,你可以根据自己的需求随意添加图形。
为了完成本课程,让我们继续从上一个笔记本文件“ Week_04”开始,并在下面添加一个标记为“星期五项目:创建猜单词游戏(Hangman)”的Markdown模块。
最终设计
与往常一样,我们希望在开始编码之前先对最终设计进行规划布局。与上周不同的是,本周将不会基于图形,因此我们将重点介绍运行程序所需的逻辑和必要步骤。对我们来说幸运的是,逻辑本质上是玩游戏所需的步骤:
1.选择一个要玩的单词。
2.要求玩家输入。
3.检查是否猜对。
a.如果猜对,请在适当的位置显示字母。
b.如果猜错,丧失一条命。
4.继续执行步骤2和3,直到发生以下情况之一:
a.玩家猜词正确。
b.玩家丧失全部生命。
这是主要的游戏玩法。在实际运行游戏之前,我们还需要执行其他几个步骤,如声明游戏变量;但是,在我们开始编码之前需要布置游戏所需要的主要功能。知道这种结构将使我们能够条理清晰的创建程序。
以前的线符号介绍
正如我们在第一周添加线号一样,我们也将介绍这个项目以及所有其他项目的线符号概念。由于需要编辑以前编写的行,甚至需要在项目的中间添加代码,我们这里将介绍线符号的概念。这些符号将通过三个空方块来显示,代表先前编写的代码。你可以在下面的例子中看到:
1|if num > 1: ▢▢▢
3| # 新代码将写在这里
5| print(▢▢▢
当我们在先前编写的代码之间添加行时,我将使用这三个正方形来表示哪一行应在我们正在编写的代码之上和之下。这也意味着你应该保持不变。当我们需要覆盖上一行时,书中会明确的进行说明。当你看到这三个正方形时,请务必要注意每行代码的行号,因为这将帮助你了解是否错过了相关代码行。
注意:单击单元格的侧面后,按“ L”打开线。
导入库
我们将在一个单元中编写该程序,该程序代码大约有50行。第一步是导入一些我们需要用到的库:
1| # 导入库
2| from random import choice
3| from IPython.display import clear_output
代码块第二行将从random库中导入一个名为“ choice”的函数,该函数将从列表中随机选择一个元素。我们将使用这个函数来随机选择单词。代码块第三行是导入Jupyter Notebook专用功能,目的是清除输出。我们在使用循环时,如果不清除输出,则循环将不断的相互叠加输出。
声明游戏变量
接下来,我们要了解运行游戏所需的变量并声明它们。如果你考虑“ Hangman”游戏以及我们需要跟踪的内容,则需要跟踪玩家的生命,他们尝试猜测的单词,可供选择的单词列表以及游戏是否结束:
5| # 声明游戏变量
6| words = [ "tree", "basket", "chair", "paper", "python" ]
7| word = choice(words) # 从单词列表中随机选择一个单词
8| guessed, lives, game_over = [ ], 7, False # 多个变量分配元素
代码块第七行声明了一个名为word的变量,它将从单词列表中随机选择一个。代码块 第八行一起声明了三个变量。gussed变量将被赋予一个空列表的值,lives变量将被分配元素7,game_over变量将被声明为布尔值False。
注意:在编写代码时,请随时用打印语句来检查每个变量的值。这有助于了解我们的声明是否为我们所需要的。
生成隐藏字
在游戏过程中,我们希望玩家能够看到所猜单词包含多少个字母。为此,我们可以创建一个字符串列表,其中每个字符串都是一个下划线。列表中的元素数量将设置为所选单词的相同长度:
10| # 创建一个与单词长度相同的且包含下划线的列表
11| guesses = [ "_ " ] * len(word)
在第11行上,我们声明了一个名为guesses的变量,该变量设置为包括下划线的列表。通过将列表乘以单词的长度,可以将列表内的元素复制,得到与单词长度一致的列表。
创建游戏循环
无论程序的大小,每个游戏都有一个主循环。我们的主循环将执行我们在“最终设计”部分中定义的逻辑。让我们采取一些小步骤,而非一次写完全部代码。第一步是可以接受玩家输入并停止游戏:
13| # 创建游戏主循环
14| while not game_over:
15| ans = input("Type quit or guess a letter: ").lower( )
17| if ans == "quit":
18| print("Thanks for playing.")
19| game_over = True
继续并运行这一代码块。 如果键入“ quit”,game_over变为True(仅当我们输入“ quit”时才会发生),则程序停止循环。
注意:在继续操作之前,请始终确保代码块运行完毕。
输出游戏信息
接下来,我们开始向玩家输出相关游戏信息。让我们以一种特定格式的语句输出他们的生命值和试图猜测的单词:
14| while not game_over: ◻◻◻
15| # 输出游戏信息
16| hidden_word = "".join(guesses)
17| print( "Word to guess: {}".format(hidden_word) )
18| print( "Lives: {}".format(lives) )
20| ans = input( ◻◻◻
继续并运行这一代码块。 根据所选择的单词,你将获得不同的输出。 如果选择的单词是四个字母,我们将得到“猜单词: _ _ ”和“生命值:7”的输出。格式化字符不是什么新鲜事物,但是对于第16行的代码你是否知道是用来实现什么功能的吗?我们之所以能够在第17行中输出带下划线的字符串,正是因为使用了join方法。它作用是将我们希望猜测的列表中的所有项目以特定字符连接在一起。 例如:
chars = ['h', 'e', 'l', 'l', 'o']
print('-'.join(chars))
运行这一代码,结果将输出“ h-e-l-l-o”。 这是一种将列表显示为字符串的简单方法。
检查猜测结果
接下来,所要实现的功能是检查并查看玩家的输入是否正确。我们暂时不会更改任何字母,因为我们首先要确保我们可以识别正确的猜测,并输出他们正确猜出的字母或猜错将减少一个生命值:
24| game_over = True ◻◻◻
25| elif ans in word: # 检查字母是否在单词中
26| print("You guessed correctly!")
27| else: # 否则,减少一个生命值
28| lives -= 1 # 等价于 lives = lives - 1
29| print("Incorrect, you lost a life.")
继续并运行这一代码块。 如果你继续猜错,就会发现生命将降至零。 在测试中,一定要输入所猜单词的正确字母和不正确字母,以便全面测试程序是否可行。
清空输出
现在,我们对程序进行了进一步的学习,可以看到程序循环不断的在之前输出的信息下方输出信息。让我们开始清除输出:
20| ans = input( ◻◻◻
22| clear_output( ) # 清除之前输出的全部信息
24| if ans == 'quit': ◻◻◻
继续并运行这一代码块。你将注意到之前的信息无论有多少都会被清除。这也是Jupyter Notebook的特殊功能。
创建生命值降低条件
下面的操作逻辑将是创建一种减少生命值的方法,以便于玩家的生命值降低到零:
31| print('Incorrect, ◻◻◻
33| if lives <= 0:
34| print("You lost all your lives, you lost!")
35| game_over = True
继续并运行这一代码块。现在如果玩家失去了全部生命值,游戏将会停止运行并告诉玩家生命值已全部丢失,玩家已出局。记住,只有当变量game_over为True时,循环才会停止运行。这也意味着我们曾经设置的五次生命值已经变为了零。
处理正确猜词
现在我们已经能够处理猜错的情况了,接下来我们还要有能力处理猜词正确的情况。为了理解如何更改字母的显示,我们首先需要记住输出的结果是什么。我们的guesses列表将会变为一个字符串并进行输出。这就意味着当玩家猜词正确,我们将改变在他们一贯位置上guesses列表中的元素。列表与我们代码块开始选择的单词的长度相同,所以每一个下标都代表了一个字母的位置。如以单词“sport”为例,第一个下标在“_____ _ _ _ ”将代表“s _ _ ”。我们仅仅需要包含被猜测的字母列表中使用正确的下标。要实现这一功能,我们可以通过一个for循环和追踪索引做到这一点:
28| print('You guessed correctly!') ◻◻◻
30| # 创建循环以将下划线更改为正确的字母
31| for i in range(len(word)):
32| if word[i] == ans[i]: # 比较索引的值
33| guesses[i] = ans[i]
34| else: ◻◻◻
继续并运行这一代码块。 现在,当猜测正确的字母时,它将输出更改。 for循环正在循环到单词的长度,并且我们使用变量“ i”来进行跟踪索引。 然后,我们检查每个字符是否等于猜出的字母。如果是,则将项目从下划线更改为该索引下的字母。为更清楚的理解,请查看表4-5中有关该过程的示例。 让我们在单词中使用“ pop”,在“ p”中使用为猜测。
表4-5跟踪索引上的值来检查是否猜对
ans值 | i值 | 列表中第i个索引的值 | 条件值 | 改变后猜测的值 |
---|---|---|---|---|
‘p’ | 0 | ‘p’ | True | [‘p’, ‘_’, ‘-’] |
‘p’ | 1 | ‘o’ | False | [‘p’, ‘_’, ‘-’] |
‘p’ | 2 | ‘p’ | True | [‘p’, ‘_’, ‘p’] |
创建一个取胜条件
完成该项目的最后步骤中的一个条件就是建立获胜条件。 为了获胜,玩家需要猜测所选随机词中的所有字母。 我们已经跟踪他们正确猜出的单词,所以我们只需要检查一下这些随机词:
40| game_over = True ◻◻◻
41| elif word == "".join(guesses):
42| print("Congratulations, you guessed it correctly!")
43| game_over = True
继续并运行这一代码块。 现在,如果玩家猜对了所有字母,便可以取得获胜。 我们使用与之前相同的join方法,将列表转换为字符串,因此,如果列表中仍有下划线,则连接的字符串将不等于随机词。 然后,我们打印出一个祝贺语句,并将我们的game_over变量更改为True来结束循环。
输出猜测的字母
尽管我们的游戏现在已经完成,并且我们可以跟据实际情况来判定玩家的输赢,但我们应该再给它添加一个关键功能:处理以前猜到的字母。 每当玩家猜到前一个字母,他们不应该为此受罚,但他们也应该能够看以前的猜测。 在该项目的开始,我们创建了一个变量guessed,到现在为止我们还没有使用过这一变量。 该变量一直为空列表,因此 到目前为止,让我们实现它。 在我们添加到列表之前,请确保我们可以打印出正确的信息:
16| hidden_word = ◻◻◻
17| print("Your guessed letters: {}".format(guessed) )
18| print("Word to guess ◻◻◻
继续并运行这一代码块。 在我们输出信息的顶部,打印出猜字母的完整列表。 最好将其保留在列表中。即使您猜到了,它仍然会显示一个空列表,因为我们还没有为它添加功能呢。
增加玩家猜测的字母
现在,我们添加功能来将玩家猜测的字母添加到我们的变量guessed列表中:
37| print("Incorrect, ◻◻◻
39| if ans not in guessed:
40| guessed.append(ans) # 增加元素到guessed列表
42| if lives <= 0: ◻◻◻
继续并运行这一代码块。现在guesses列表将随着玩家玩游戏而更新。
处理以前的猜测
最后一项业务是确保当他们再次猜出同一字母时,程序并没减少玩家的生命值,而是提醒他们被猜到了。我们需要重写整个条件语句,以检查字母是否在整个词语中:
27| game_over = True ◻◻◻
28| elif ans in word and ans not in guessed:
29| print("You guessed correctly!") ◻◻◻
34| guesses[ i ] = ans ◻◻◻
35| elif ans in guessed:
36| print("You already guessed that. Try again.")
37| else: ◻◻◻
继续并运行这一代码块。 我们必须更改第28行的elif语句,因为我们还需要检查该字母是否尚未添加到猜测列表中。在第35行,我们添加了第二个elif语句,该语句将检查字母是否特别在变量guessed列表中清单。 请记住,一旦运行一个if/elif语句,那么它下面的语句将不会运行。 如果这些条件都不是真的,那意味着他们还没猜到字母,它不在随机词语中。 到这里,游戏现已全部完成,并具有非常完整的功能。
写在本周最后的话
恭喜你,完成此项目! 由于项目大小,完整代码不会写在这里。 相反,你可能会在以下位置找到完整的代码版本,本书的资源文件位于Github上。 您可以在书的最前面找到相应的链接,每周的所有资源文件都位于该链接内。 查找具体该项目的代码,只需打开或下载“ Week_04.ipynb”文件。 如果遇错误,请确保将你的代码与该文件中的代码交叉引用,并且查看你可能出现的问题。 所有未来项目的最终代码输出也可以在同一位置找到,因此请务必在此页面添加书签。
多么美好的一天! 我们能够使用循环的概念以及列表来创建一个有趣的游戏。 尝试添加自己的代码,或将其重构,以更进一步了解,什么可能会或可能不会。
一周总结
当然,这是其中较长的一周,每天都充满了大量的信息。 请自己务必花一些时间对这些概念进行练习或通过完成每天的练习来实践这些概念。 我们介绍了为什么列表在Python中如此重要以及如何在我们的程序中使用它们。 还介绍了Python提供的两个循环:for循环和while循环。 使用循环,我们可以根据需要多次重新运行代码,或对像列表这样的数据集合进行迭代。 如果你对这些信息不知所措,请确保在剩余的部分,我们在所做的所有事情中都使用循环和列表。 这会给你很多练习和重复。
挑战题解答
即使我们正在寻找一个特定的答案,这仍是一个棘手的问题。如果你的第一个动作是转身问:“这是什么问题?” 或“我为什么要撤离这座城市?”,那么你回答正确。 我们首先要问这个问题的原因,因为不同的问题需要不同的解决方案。 如果你开始编写需要开车的疏散计划,那么问题是所有街道都被水淹没了。 建议人们开车离开城市并不是很明智。有时候,问题的答案本身就是问题。 可以从中得出的另一个教训是,你应该始终退后一步,仔细思考每个问题。永远不要以为你立即知道解决方案; 提问是最先要做的。
一周挑战
为了测试你的技巧,尝试这些挑战:
1.金字塔:使用一个for循环建造一个由x字母组成的金字塔。它应该是模块化的,这样,如果你循环到5或50,它仍然会创建均匀间隔的行。
小提示:通过行数乘以字符串“X"。例如,如果循环到4的范围,则应产生以下结果:
x
xxx
xxxxx
xxxxxxx
2.输出名字:编写一个循环,该循环将遍历项目列表,并且仅输出字符串中包含字母的项目。以下面的列表为例,仅应输出“ John”和“ Amanda”:
names = ['John', ' ', 'Amanda', 5]
3.转换摄氏:给定一个以摄氏度为单位的温度列表,编写一个循环遍历该列表并输出转换为华氏温度的温度的循环。
小提示:转换公式为”F = (9/5) ∗ C + 32“。
输入:temps = [32, 12, 44, 29]
输出:[89.6, 53.6, 111.2, 84.2]