第四章5:創(chuàng)建猜單詞游戲(Hangman)
星期五:創(chuàng)建猜單詞游戲(Hangman)
隨著前幾周的學(xué)習(xí),我們會發(fā)現(xiàn)這些項(xiàng)目代碼通常會變的越來越長。今天,我們將利用過去四個(gè)天學(xué)到的所有概念來構(gòu)建Hangman游戲。正如往常一樣,隨著項(xiàng)目代碼寫入,我們將引入新的概念。今天,我們的目標(biāo)是創(chuàng)建功能齊全的Hangman游戲,在這個(gè)游戲里,我們可以猜詞,減少生命值,并最后贏或輸?shù)粲螒?。在這個(gè)游戲中,我們不會創(chuàng)建圖象。在我們共同完成項(xiàng)目后,你可以根據(jù)自己的需求隨意添加圖形。
為了完成本課程,讓我們繼續(xù)從上一個(gè)筆記本文件“ Week_04”開始,并在下面添加一個(gè)標(biāo)記為“星期五項(xiàng)目:創(chuàng)建猜單詞游戲(Hangman)”的Markdown模塊。
最終設(shè)計(jì)
與往常一樣,我們希望在開始編碼之前先對最終設(shè)計(jì)進(jìn)行規(guī)劃布局。與上周不同的是,本周將不會基于圖形,因此我們將重點(diǎn)介紹運(yùn)行程序所需的邏輯和必要步驟。對我們來說幸運(yùn)的是,邏輯本質(zhì)上是玩游戲所需的步驟:
1.選擇一個(gè)要玩的單詞。
2.要求玩家輸入。
3.檢查是否猜對。
a.如果猜對,請?jiān)谶m當(dāng)?shù)奈恢蔑@示字母。
b.如果猜錯(cuò),喪失一條命。
4.繼續(xù)執(zhí)行步驟2和3,直到發(fā)生以下情況之一:
a.玩家猜詞正確。
b.玩家喪失全部生命。
這是主要的游戲玩法。在實(shí)際運(yùn)行游戲之前,我們還需要執(zhí)行其他幾個(gè)步驟,如聲明游戲變量;但是,在我們開始編碼之前需要布置游戲所需要的主要功能。知道這種結(jié)構(gòu)將使我們能夠條理清晰的創(chuàng)建程序。
以前的線符號介紹
正如我們在第一周添加線號一樣,我們也將介紹這個(gè)項(xiàng)目以及所有其他項(xiàng)目的線符號概念。由于需要編輯以前編寫的行,甚至需要在項(xiàng)目的中間添加代碼,我們這里將介紹線符號的概念。這些符號將通過三個(gè)空方塊來顯示,代表先前編寫的代碼。你可以在下面的例子中看到:
1|if num > 1: ???
3| # 新代碼將寫在這里
5| print(???
當(dāng)我們在先前編寫的代碼之間添加行時(shí),我將使用這三個(gè)正方形來表示哪一行應(yīng)在我們正在編寫的代碼之上和之下。這也意味著你應(yīng)該保持不變。當(dāng)我們需要覆蓋上一行時(shí),書中會明確的進(jìn)行說明。當(dāng)你看到這三個(gè)正方形時(shí),請務(wù)必要注意每行代碼的行號,因?yàn)檫@將幫助你了解是否錯(cuò)過了相關(guān)代碼行。
注意:單擊單元格的側(cè)面后,按“ L”打開線。
導(dǎo)入庫
我們將在一個(gè)單元中編寫該程序,該程序代碼大約有50行。第一步是導(dǎo)入一些我們需要用到的庫:
1| # 導(dǎo)入庫
2| from random import choice
3| from IPython.display import clear_output
代碼塊第二行將從random庫中導(dǎo)入一個(gè)名為“ choice”的函數(shù),該函數(shù)將從列表中隨機(jī)選擇一個(gè)元素。我們將使用這個(gè)函數(shù)來隨機(jī)選擇單詞。代碼塊第三行是導(dǎo)入Jupyter Notebook專用功能,目的是清除輸出。我們在使用循環(huán)時(shí),如果不清除輸出,則循環(huán)將不斷的相互疊加輸出。
聲明游戲變量
接下來,我們要了解運(yùn)行游戲所需的變量并聲明它們。如果你考慮“ Hangman”游戲以及我們需要跟蹤的內(nèi)容,則需要跟蹤玩家的生命,他們嘗試猜測的單詞,可供選擇的單詞列表以及游戲是否結(jié)束:
5| # 聲明游戲變量
6| words = [ "tree", "basket", "chair", "paper", "python" ]
7| word = choice(words) # 從單詞列表中隨機(jī)選擇一個(gè)單詞
8| guessed, lives, game_over = [ ], 7, False # 多個(gè)變量分配元素
代碼塊第七行聲明了一個(gè)名為word的變量,它將從單詞列表中隨機(jī)選擇一個(gè)。代碼塊 第八行一起聲明了三個(gè)變量。gussed變量將被賦予一個(gè)空列表的值,lives變量將被分配元素7,game_over變量將被聲明為布爾值False。
注意:在編寫代碼時(shí),請隨時(shí)用打印語句來檢查每個(gè)變量的值。這有助于了解我們的聲明是否為我們所需要的。
生成隱藏字
在游戲過程中,我們希望玩家能夠看到所猜單詞包含多少個(gè)字母。為此,我們可以創(chuàng)建一個(gè)字符串列表,其中每個(gè)字符串都是一個(gè)下劃線。列表中的元素?cái)?shù)量將設(shè)置為所選單詞的相同長度:
10| # 創(chuàng)建一個(gè)與單詞長度相同的且包含下劃線的列表
11| guesses = [ "_ " ] * len(word)
在第11行上,我們聲明了一個(gè)名為guesses的變量,該變量設(shè)置為包括下劃線的列表。通過將列表乘以單詞的長度,可以將列表內(nèi)的元素復(fù)制,得到與單詞長度一致的列表。
創(chuàng)建游戲循環(huán)
無論程序的大小,每個(gè)游戲都有一個(gè)主循環(huán)。我們的主循環(huán)將執(zhí)行我們在“最終設(shè)計(jì)”部分中定義的邏輯。讓我們采取一些小步驟,而非一次寫完全部代碼。第一步是可以接受玩家輸入并停止游戲:
13| # 創(chuàng)建游戲主循環(huán)
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
繼續(xù)并運(yùn)行這一代碼塊。如果鍵入“ quit”,game_over變?yōu)門rue(僅當(dāng)我們輸入“ quit”時(shí)才會發(fā)生),則程序停止循環(huán)。
注意:在繼續(xù)操作之前,請始終確保代碼塊運(yùn)行完畢。
輸出游戲信息
接下來,我們開始向玩家輸出相關(guān)游戲信息。讓我們以一種特定格式的語句輸出他們的生命值和試圖猜測的單詞:
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( ???
繼續(xù)并運(yùn)行這一代碼塊。根據(jù)所選擇的單詞,你將獲得不同的輸出。如果選擇的單詞是四個(gè)字母,我們將得到“猜單詞:_ _ _ _”和“生命值:7”的輸出。格式化字符不是什么新鮮事物,但是對于第16行的代碼你是否知道是用來實(shí)現(xiàn)什么功能的嗎?我們之所以能夠在第17行中輸出帶下劃線的字符串,正是因?yàn)槭褂昧薺oin方法。它作用是將我們希望猜測的列表中的所有項(xiàng)目以特定字符連接在一起。例如:
chars = ['h', 'e', 'l', 'l', 'o']
print('-'.join(chars))
運(yùn)行這一代碼,結(jié)果將輸出“ h-e-l-l-o”。這是一種將列表顯示為字符串的簡單方法。
檢查猜測結(jié)果
接下來,所要實(shí)現(xiàn)的功能是檢查并查看玩家的輸入是否正確。我們暫時(shí)不會更改任何字母,因?yàn)槲覀兪紫纫_保我們可以識別正確的猜測,并輸出他們正確猜出的字母或猜錯(cuò)將減少一個(gè)生命值:
24| game_over = True ???
25| elif ans in word: # 檢查字母是否在單詞中
26| print("You guessed correctly!")
27| else: # 否則,減少一個(gè)生命值
28| lives -= 1 # 等價(jià)于 lives = lives - 1
29| print("Incorrect, you lost a life.")
繼續(xù)并運(yùn)行這一代碼塊。如果你繼續(xù)猜錯(cuò),就會發(fā)現(xiàn)生命將降至零。在測試中,一定要輸入所猜單詞的正確字母和不正確字母,以便全面測試程序是否可行。
清空輸出
現(xiàn)在,我們對程序進(jìn)行了進(jìn)一步的學(xué)習(xí),可以看到程序循環(huán)不斷的在之前輸出的信息下方輸出信息。讓我們開始清除輸出:
20| ans = input( ???
22| clear_output( ) # 清除之前輸出的全部信息
24| if ans == 'quit': ???
繼續(xù)并運(yùn)行這一代碼塊。你將注意到之前的信息無論有多少都會被清除。這也是Jupyter Notebook的特殊功能。
創(chuàng)建生命值降低條件
下面的操作邏輯將是創(chuàng)建一種減少生命值的方法,以便于玩家的生命值降低到零:
31| print('Incorrect, ???
33| if lives <= 0:
34| print("You lost all your lives, you lost!")
35| game_over = True
繼續(xù)并運(yùn)行這一代碼塊。現(xiàn)在如果玩家失去了全部生命值,游戲?qū)V惯\(yùn)行并告訴玩家生命值已全部丟失,玩家已出局。記住,只有當(dāng)變量game_over為True時(shí),循環(huán)才會停止運(yùn)行。這也意味著我們曾經(jīng)設(shè)置的五次生命值已經(jīng)變?yōu)榱肆恪?/p>
處理正確猜詞
現(xiàn)在我們已經(jīng)能夠處理猜錯(cuò)的情況了,接下來我們還要有能力處理猜詞正確的情況。為了理解如何更改字母的顯示,我們首先需要記住輸出的結(jié)果是什么。我們的guesses列表將會變?yōu)橐粋€(gè)字符串并進(jìn)行輸出。這就意味著當(dāng)玩家猜詞正確,我們將改變在他們一貫位置上guesses列表中的元素。列表與我們代碼塊開始選擇的單詞的長度相同,所以每一個(gè)下標(biāo)都代表了一個(gè)字母的位置。如以單詞“sport”為例,第一個(gè)下標(biāo)在“_____ _ _ _ _”將代表“s _ _ _”。我們僅僅需要包含被猜測的字母列表中使用正確的下標(biāo)。要實(shí)現(xiàn)這一功能,我們可以通過一個(gè)for循環(huán)和追蹤索引做到這一點(diǎn):
28| print('You guessed correctly!') ???
30| # 創(chuàng)建循環(huán)以將下劃線更改為正確的字母
31| for i in range(len(word)):
32| if word[i] == ans[i]: # 比較索引的值
33| guesses[i] = ans[i]
34| else: ???
繼續(xù)并運(yùn)行這一代碼塊?,F(xiàn)在,當(dāng)猜測正確的字母時(shí),它將輸出更改。for循環(huán)正在循環(huán)到單詞的長度,并且我們使用變量“ i”來進(jìn)行跟蹤索引。然后,我們檢查每個(gè)字符是否等于猜出的字母。如果是,則將項(xiàng)目從下劃線更改為該索引下的字母。為更清楚的理解,請查看表4-5中有關(guān)該過程的示例。讓我們在單詞中使用“ pop”,在“ p”中使用為猜測。
表4-5跟蹤索引上的值來檢查是否猜對
| ans值 | i值 | 列表中第i個(gè)索引的值 | 條件值 | 改變后猜測的值 |
|---|---|---|---|---|
| ‘p’ | 0 | ‘p’ | True | [‘p’, ‘_’, ‘-’] |
| ‘p’ | 1 | ‘o’ | False | [‘p’, ‘_’, ‘-’] |
| ‘p’ | 2 | ‘p’ | True | [‘p’, ‘_’, ‘p’] |
創(chuàng)建一個(gè)取勝條件
完成該項(xiàng)目的最后步驟中的一個(gè)條件就是建立獲勝條件。為了獲勝,玩家需要猜測所選隨機(jī)詞中的所有字母。我們已經(jīng)跟蹤他們正確猜出的單詞,所以我們只需要檢查一下這些隨機(jī)詞:
40| game_over = True ???
41| elif word == "".join(guesses):
42| print("Congratulations, you guessed it correctly!")
43| game_over = True
繼續(xù)并運(yùn)行這一代碼塊?,F(xiàn)在,如果玩家猜對了所有字母,便可以取得獲勝。我們使用與之前相同的join方法,將列表轉(zhuǎn)換為字符串,因此,如果列表中仍有下劃線,則連接的字符串將不等于隨機(jī)詞。然后,我們打印出一個(gè)祝賀語句,并將我們的game_over變量更改為True來結(jié)束循環(huán)。
輸出猜測的字母
盡管我們的游戲現(xiàn)在已經(jīng)完成,并且我們可以跟據(jù)實(shí)際情況來判定玩家的輸贏,但我們應(yīng)該再給它添加一個(gè)關(guān)鍵功能:處理以前猜到的字母。每當(dāng)玩家猜到前一個(gè)字母,他們不應(yīng)該為此受罰,但他們也應(yīng)該能夠看以前的猜測。在該項(xiàng)目的開始,我們創(chuàng)建了一個(gè)變量guessed,到現(xiàn)在為止我們還沒有使用過這一變量。該變量一直為空列表,因此 到目前為止,讓我們實(shí)現(xiàn)它。在我們添加到列表之前,請確保我們可以打印出正確的信息:
16| hidden_word = ???
17| print("Your guessed letters: {}".format(guessed) )
18| print("Word to guess ???
繼續(xù)并運(yùn)行這一代碼塊。在我們輸出信息的頂部,打印出猜字母的完整列表。最好將其保留在列表中。即使您猜到了,它仍然會顯示一個(gè)空列表,因?yàn)槲覀冞€沒有為它添加功能呢。
增加玩家猜測的字母
現(xiàn)在,我們添加功能來將玩家猜測的字母添加到我們的變量guessed列表中:
37| print("Incorrect, ???
39| if ans not in guessed:
40| guessed.append(ans) # 增加元素到guessed列表
42| if lives <= 0: ???
繼續(xù)并運(yùn)行這一代碼塊?,F(xiàn)在guesses列表將隨著玩家玩游戲而更新。
處理以前的猜測
最后一項(xiàng)業(yè)務(wù)是確保當(dāng)他們再次猜出同一字母時(shí),程序并沒減少玩家的生命值,而是提醒他們被猜到了。我們需要重寫整個(gè)條件語句,以檢查字母是否在整個(gè)詞語中:
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: ???
繼續(xù)并運(yùn)行這一代碼塊。我們必須更改第28行的elif語句,因?yàn)槲覀冞€需要檢查該字母是否尚未添加到猜測列表中。在第35行,我們添加了第二個(gè)elif語句,該語句將檢查字母是否特別在變量guessed列表中清單。請記住,一旦運(yùn)行一個(gè)if/elif語句,那么它下面的語句將不會運(yùn)行。如果這些條件都不是真的,那意味著他們還沒猜到字母,它不在隨機(jī)詞語中。到這里,游戲現(xiàn)已全部完成,并具有非常完整的功能。
寫在本周最后的話
恭喜你,完成此項(xiàng)目!由于項(xiàng)目大小,完整代碼不會寫在這里。相反,你可能會在以下位置找到完整的代碼版本,本書的資源文件位于Github上。您可以在書的最前面找到相應(yīng)的鏈接,每周的所有資源文件都位于該鏈接內(nèi)。查找具體該項(xiàng)目的代碼,只需打開或下載“ Week_04.ipynb”文件。如果遇錯(cuò)誤,請確保將你的代碼與該文件中的代碼交叉引用,并且查看你可能出現(xiàn)的問題。所有未來項(xiàng)目的最終代碼輸出也可以在同一位置找到,因此請務(wù)必在此頁面添加書簽。
多么美好的一天!我們能夠使用循環(huán)的概念以及列表來創(chuàng)建一個(gè)有趣的游戲。嘗試添加自己的代碼,或?qū)⑵渲貥?gòu),以更進(jìn)一步了解,什么可能會或可能不會。
一周總結(jié)
當(dāng)然,這是其中較長的一周,每天都充滿了大量的信息。請自己務(wù)必花一些時(shí)間對這些概念進(jìn)行練習(xí)或通過完成每天的練習(xí)來實(shí)踐這些概念。我們介紹了為什么列表在Python中如此重要以及如何在我們的程序中使用它們。還介紹了Python提供的兩個(gè)循環(huán):for循環(huán)和while循環(huán)。使用循環(huán),我們可以根據(jù)需要多次重新運(yùn)行代碼,或?qū)ο窳斜磉@樣的數(shù)據(jù)集合進(jìn)行迭代。如果你對這些信息不知所措,請確保在剩余的部分,我們在所做的所有事情中都使用循環(huán)和列表。這會給你很多練習(xí)和重復(fù)。
歡迎點(diǎn)擊閱讀原文訪問 我的Python中文網(wǎng),學(xué)習(xí)更多Python教程。
