猜的成語 通過游戲編程學Python(番外篇)— 亂序成語、猜單詞
通過游戲編程學
通過游戲編程學(5)— 猜成語(下)
通過游戲編程學(4)— 猜成語(上)
通過游戲編程學(3)— 賭大小
文章目錄
前言
大家好,五一放假了,問哥決定更新勤快點,另一方面,也是因為上節課的猜成語小游戲引發了一些思考:我們怎么樣擴展我們已開發的項目,添加更多的功能和樂趣。就比如猜數字、賭大小這樣簡單的入門小游戲,一樣可以放在一個大的項目里,作為某個小的環節。比如,有朝一日我們開發了一款RPG,就可以給玩家角色去賭大小、猜數字的機會,獲取游戲中的金幣,進而有更多的交互項目等等。而且,當以后我們學了GUI圖形化編程后,這些不起眼的小游戲也都可以用另一種形式煥發新生。
說到GUI,迄今為止,我們一直在的終端下開發文字游戲,而且問哥計劃在介紹GUI編程之前還要繼續用文本介紹幾個經典小游戲,于是問哥覺得還是有必要稍微介紹一下終端的運行方法。
此外,為了下節課做準備,今天也會簡單介紹一下文本文件的讀取方法,正好也用得到。
如此一來,雖然是一節“番外篇”,并沒有新的游戲,但它卻含有不少干貨,也希望大家都能有所得。
一、知識點 自帶的編程環境讀取txt文本文件r字符串與f字符串列表生成式.()方法 二、猜成語小游戲的擴展 1. 玩法簡介
游戲的整體框架基于上節課的內容,但是在成語的提示上由提示詞改為亂序顯示成語的每個字,由玩家輸入正確的成語;修改猜錯單詞的機會為3次(6次實在太多了哈)。此外還加入了計分功能,在游戲結束后統計玩家一共猜對了多少個成語。
游戲截圖如下:
2. 游戲流程 3. 程序代碼
全部代碼:
附上我使用的200個成語的TXT文件下載鏈接,用于練習。
import random# 從TXT文本文件中讀取成語組成詞庫# f = open(r'C:\Coding\word.txt', 'r', encoding="utf-8")# WORD = f.readlines()# f.close()with open(r'C:\Coding\word.txt', 'r', encoding = 'utf-8') as f: WORD = f.readlines()# 列表生成式WORD = [i.strip() for i in WORD]def 猜成語(猜過啥, 詞長): # 確保玩家輸入正確字數,且不會重復輸入錯誤的詞,然后返回玩家猜的漢字 while True: print('猜猜看,這是什么成語呢?') print() 猜 = input() if len(猜) != 詞長: print(f'請輸入{詞長}個字的成語') print() elif 猜 in 猜過啥: print('這個詞你已經猜過了,換一個試試吧') print() else: return 猜def 再玩一次(): # 如果玩家輸入yes或y,則游戲從頭再來,否則游戲結束 print('還要繼續玩嗎?(yes-是,no-否)') return input().lower().startswith('y')def 亂序(原詞): # 把原有的詞序打亂,組成新的詞序 亂序 = list(原詞) random.shuffle(亂序) # 檢查亂序后的成語是否湊巧和原詞相同,如果相同則再次打亂 while ''.join(亂序) == 原詞: random.shuffle(亂序) # 返回亂序后的字組成的列表 return 亂序# 游戲從這里開始print('《 開 心 猜 成 語 2》\n')錯詞表 = []游戲結束 = False電腦選詞 = random.choice(WORD)亂序選詞 = 亂序(電腦選詞)# 定義一個新的變量用于記錄猜對的次數correct = 0while True: # 亂序顯示要猜的成語 for i in 亂序選詞: print(i, end=' ') print() # 請玩家猜一個四字成語 猜 = 猜成語(錯詞表, len(電腦選詞)) if 猜 == 電腦選詞: # 如果猜對了游戲結束 print('沒錯!就是“' + 電腦選詞 + '”!你贏了!') correct += 1 游戲結束 = True else: print('不對哦~\n') 錯詞表.append(猜) # 檢查玩家是否猜錯太多次 if len(錯詞表) == 3: # 顯示狀態(提示字, 電腦選詞) print('很遺憾,你已經猜錯' + str(len(錯詞表)) + '次,游戲結束。' + '正確的成語是“' + 電腦選詞 + '”。') 游戲結束 = True # 詢問玩家是否再玩一次 if 游戲結束: if 再玩一次(): 錯詞表 = [] 游戲結束 = False 電腦選詞 = random.choice(WORD) 亂序選詞 = 亂序(電腦選詞) else: breakprint(f'您本輪一共猜對了{correct}個成語,再接再厲!')
3. 代碼簡析
相比較上節課的程序而言,有以下主要變化:
使用open方法從txt文件里讀取成語建立詞庫(200個成語),而不用在里輸入;增加了一個新的自定義函數亂序(),接收一個參數,返回一個把成語打散漢字后的列表;原有的自定義函數猜成語() 增加了一個形參詞長,用來告訴函數比較玩家輸入的字符長度是否等于系統選中的詞(因為有可能是三字、五字成語或更長),如果不等則要求玩家重新輸入;增加了一個變量,每當玩家猜對成語后它的值增加1,當游戲結束后在屏幕上顯示出玩家總共猜對多少成語。相當于一個簡單的計分系統;將玩家猜錯的機會從6減成3——豐儉由君,當然你也可以改成任何數;刪掉不需要的自定義函數和變量。 三、知識點 1. 自帶的IDLE
如果不是經常需要編程,使用自帶的編譯器就完全足夠了。以問哥電腦系統安裝的.10.4版本為例,從官網下載好安裝包以后,在開始菜單就可以找到類似下圖的軟件:
打開它,就是自帶的命令行窗口了。(我們在或Unix/系統的命令行下敲擊回車,也可以進入控制臺環境。)
是解釋型語言,逐條運行語句,所以我們可以在控制臺里像一問一答一樣和交互,如:
同樣,我們也可以把所有語句都寫到文本文件里,后綴改成py猜的成語,也就是程序文件,交給編譯器運行。打開左上角的File菜單,新建New File,或打開Open已有的py文件都可以。
然后在新跳出的窗口里輸入代碼(或已有代碼),點擊菜單欄的Run運行程序,或者F5快捷鍵,就可以運行程序了。
程序運行過程中生成的所有的文本輸入輸出信息(如() / ())都會在控制臺輸出。
2. txt文本文件的讀取
在里常用的讀取txt文件的方法是open()方法,接收的必選參數有文件所在路徑,以及打開方式(本例中是’r’,read-only只讀)等,都是字符串格式。可選的參數有編碼方式,這里使用’utf-8’的編碼方式讀取txt文件。編碼方式就像一把鑰匙,如果鑰匙不對,程序可能會報錯或者讀取出一串亂碼。
open()方法返回一個文件類型的對象,我們將它賦值給變量f,然后在后面的程序里就可以操作它了。
f = open(r'C:\Coding\word.txt', 'r', encoding="utf-8")WORD = f.readlines()f.close()
注意:操作完該文件后,必須使用文件對象的()方法將其關閉,不然它一直會存在內存里。如果沒有正常關閉的話,文件將無法被修改。
()方法
文本文件類型的對象也有很多方法猜的成語,今天我們先認識和學習一下()方法,該方法就是逐段讀取txt文件的全部內容,以換行符為界,然后將每個段落的字符串作為元素保存在一個列表里返回。
觀察一下txt文件里的內容:
再比較一下常量WORD接收的()方法返回的列表的內容,會發現txt里每一段(這里就是每個成語)都是列表里的一個元素字符串,而且換行符\n也作為字符串的一部分讀取了進來。
with open() as …
問哥在示例中先給出了直接調用open()方法打開文件的語句,然后又將其注釋掉,是因為我更加推薦使用with open…as的語句來調用。在這條語句里open()的使用方法和實現的效果與直接調用相同,但是with open…as卻有個很重要的優點,就是它會生成一個局部作用域,接收txt文件對象的f變量變成了一個局部變量。它像語句,if語句,for語句一樣,后面跟上冒號“:”,然后代碼塊里的語句縮進。
with open(r'C:\Coding\word.txt', 'r', encoding = 'utf-8') as f: WORD = f.readlines()
當代碼塊結束的時候,局部變量f自動消失,文件也自動關閉了,省去了前面使用f.()關閉文件的步驟。這樣,我們再也不用擔心忘記寫f.()而把文件留在內存里了。甚至有時候讀取文件出錯,程序意外中斷,with open…as也會自動把文件關閉,省去了不少煩惱。
3. r字符串與f字符串
不知道大家有沒有在程序里注意到,我在使用open()語句的時候,文件路徑的字符串前面有個小寫的“r”,而在后面的()語句里,我在字符串的前面又有個小寫的“f”。
print(f'您本輪一共猜對了{correct}個成語,再接再厲!')
下面我們來簡單聊一下這兩種字符串的作用。
r字符串
r字符串的“r”是英文單詞raw的意思,代表了后面跟著的引號里的字符串是以原始文本輸入,不作轉義。問哥在程序里使用r字符串也是因為,open()方法里需要用字符串提供文件的路徑,但是操作系統的路徑符為“\”,而我們之前說過會把“\”字符轉義,所以不能正常識別。于是我在字符串的前面寫上一個r,就表示不要轉義“\”,于是程序就可以正常識別路徑字符串了。
當然也可以識別“/”的路徑符,把“\”改成“/”就可以,或者再加一個“\”將反斜杠再轉義回來,問哥這里為了介紹r字符串就沒這樣做了。而且問哥覺得r字符串最省事,所以強烈推薦。
f字符串
f字符串是在.6版本才開始引入的。“f”是英文單詞的簡寫,它的出現幫助我們簡化了字符串拼接變量的操作。比如在程序里有這么一句:
print('很遺憾,你已經猜錯' + str(len(錯詞表)) + '次,游戲結束。' + '正確的成語是“' + 電腦選詞 + '”。')
這里要打印一句話,里面有固定的字符串(引號里的內容),也有變量(電腦選詞),還有語句(str(len(錯詞表))),要把這三種不同形式的內容拼接成字符串,我們有以下幾種辦法:
使用“+”將它們連接起來,就想例子里的那樣。但是這樣會顯得整個語句十分臃腫,而且還需要將非字符串的變量,比如數字,先轉換成字符串。當變量和語句過多的時候,很容易輸錯。很多C語言過來的同學會使用一種字符串插值的表示方法。使用%加一個英文字母占位,比如%s代表字符串,%d代表數字等等,然后在字符串的后面跟上%( ),并在括號里按順序放進變量名。比如上面這個例子可以寫成下面這樣:
print('很遺憾,你已經猜錯%d次,游戲結束。正確的成語是“%s”。'%(len(錯詞表),電腦選詞))
為字符串的對象提供了一種()方法。在字符串里變量出現的位置用大括號{ }代替,括號里可以放入變量名,也可以放入編號,也可以不填,只要后面跟上()方法,小括號里再按順序放入變量名。這種方式其實就是函數的傳參,大家多看幾遍就能明白。
print('很遺憾,你已經猜錯{}次,游戲結束。正確的成語是“{}”。'.format(len(錯詞表),電腦選詞))
最后一種方法就是.6版本以后為我們提供的 f 字符串,它的使用方法和()方法類似,只不過是直接把變量放入了大括號{ }里,使得字符串變得更加可讀,也是問哥極力推薦的一種方法。比如本例可以寫成下面這樣:
print(f'很遺憾,你已經猜錯{len(錯詞表)}次,游戲結束。正確的成語是“{電腦選詞}”。')
這樣一來,其他位置的 f字符串就很容易看懂了。
4. 列表生成式
先出一道思考題,如果有一個列表a = [1, 2, 3, 4, 5],我們想把列表里的每個元素按順序拿出來計算平方,然后將平方值放在一起組成一個新列表,就像[1, 4, 9, 16, 25]。使用,我們要怎么做呢?
一般情況下,無外乎以下三步:
創建一個空列表b;for i in (a) 循環遍歷列表a;使用列表b的()方法將 i 的平方加到列表b里。
寫成語句就像下面這樣:
b = []for i in a: b.append(i**2)
覺得這樣可能不夠簡潔,于是給我們提供了一種根據已有列表(或()來決定列表長度)來創建新列表的方法,叫做列表生成式。上面三行語句使用下面一條語句就可以解決:
b = [ i**2 for i in a ]
注意看生成式里的位置,第一部分i**2是原來三行語句里的()里的內容,第二部分for i in a就是照搬原來三行語句里的第二行,然后生成式兩邊用中括號[ ]括起來。如此便是生成式的格式了。別急,強大的列表生成式還支持if判斷語句,只要把它作為第三部分放在后面就可以了。
比如我想得到列表a里所有奇數的平方組成的列表,也就是[1, 9, 25],用生成式就可以這樣寫:
b = [ i**2 for i in a if i%2 == 1]
注意:生成式里不能出現逗號“,”和賦值等號“=”
列表生成式還可以配合()生成固定長度的列表,比如下例就生成了一個由5個字符0組成的列表:
大家多觀察,多用心體會一下列表生成式的規則,其實并不復雜。以后我們再寫列表的時候,可以先用()方法寫一遍,然后再試著把它變成生成式的寫法,用不了幾次就沒問題了。而且這種寫法確實更省事,熟練了以后,你會愛上它的(笑)。
此外,列表生成式也支持嵌套判斷,從外向內依次并排放入判斷語句即可,比如下面這個例子,先寫外循環i,再寫內循環j:
但是因為嵌套判斷的生成式使用的也比較少,初學者并不用太著急掌握。列表生成式甚至自己也可以嵌套,這里就不詳細展開了。我們只要掌握最實用的,其它的只要見到能認識就夠了。
字符串的()方法
另外,如前面展示的,本例中從txt文件里直接讀出的詞庫里每個元素都帶有一個“\n”換行符,這里我們就可以使用字符串的()方法,將其左右的空格、換行符等統統刪除掉,然后得到一個新的列表,方便我們后面對這些成語做進一步的字符串處理。
5. .()方法
我們已經學習了模塊里不少方法,今天再加一種,()方法。
英文就是“洗牌”的意思。顧名思義,該方法將一個列表中的所有元素就地隨機打散重新排列。一定要注意“就地”兩個字,因為該方法不返回任何值,而是直接修改原列表。
本例為了實現將成語的漢字打亂的效果,自定義了函數亂序(),其中便使用了.()方法。而為了使用該方法,首先要把原本成語的一個字符串拆成由它的字組成的列表。于是我們使用list()函數直接就可以將字符串轉變成列表。
def 亂序(原詞): # 把原有的詞序打亂,組成新的詞序 亂序 = list(原詞) random.shuffle(亂序) # 檢查亂序后的成語是否湊巧和原詞相同,如果相同則再次打亂 while ''.join(亂序) == 原詞: random.shuffle(亂序) # 返回亂序后的字組成的列表 return 亂序
當然,由于()方法是隨機打亂列表元素,所以是有概率打亂后的列表看起來并沒有變化(尤其是只有四個元素的時候,大概1/24的機會沒有變化),于是我們需要檢查一下打亂后的列表中字的順序是否與原詞相同。而為了將列表與字符串比較,我們需要先統一格式。我們既可以使用list()函數將字符串變成列表,也可以將列表轉換為字符串。注意,可不是使用str()函數哦。
字符串的join()方法
本例中使用了字符串的join()方法將一個列表轉換為字符串。join()方法的小括號里放入一個全字符串元素的列表,意思就是“使用該字符串將列表中的所有字符串元素連起來”。比如:
而如果使用空字符串,比如本例,就相當于把列表中的所有元素“無縫”連接起來了。于是,我們就可以用它來和原詞進行比較,看看是否改變了順序。
四、進一步擴展:猜單詞
這樣我們就完成了一個新版本的猜成語游戲,雖然它看起來好像更簡單一些,但借著這個版本,我們又學習到不少新的方法。尤其是我們還可以將其進一步擴展,比如,猜單詞:
沒有用到任何新的知識,只是把詞庫換成英文單詞猜的成語,然后把顯示的“成語”兩個字換成“單詞”,就可以變成一個新游戲——猜單詞。但是由于語言習慣的不同,亂序猜英文單詞,要比猜成語難得多,所以也可以增加更多試錯的機會。
因為代碼高度雷同,問哥就不貼出來了,大家可以做一個課后練習題,動手改改看。
我們甚至還可以再進一步,比如為了避免同樣的字母可以組成多個單詞,也是為了降低難度,我們可以固定單詞的首尾字母不變,只是亂序中間的其他字母,如果這樣的話,程序要怎樣改呢?也請大家開動腦筋,思考一下吧。
總結與思考
問哥拋磚引玉,希望大家能夠發掘出更多有趣的點子,不管是玩法上,還是形式上,比如字體排版等等,都可以把老游戲改成自己喜歡的樣子。在這個過程中,大家也能更加熟練掌握的知識。
另外,問哥想指出一點,有時候為了實現某個功能,我們可以通過不同的代碼來實現。這其實沒有多少差別,尤其在我們這樣的小游戲上,差別可謂不值一提。但是在比較大的程序里,不同的代碼所實現功能的速度,可能就天差地別了。這里和算法與數據結構相關,并不是問哥這個《通過游戲編程學》系列的范疇。
但是,如果看到別人提供的不同實現方法,我們應該持積極開放態度,因為它可能會為我們帶來新的啟示,甚至讓我們學到新的知識。
比如,本例中亂序成語/單詞的方法,問哥為了偷懶,使用了的函數(),但是還有另一種字符串切片的方法實現亂序的效果,如下:
while 原詞: 位置 = random.choice(range(len(原詞))) 亂序詞 += 原詞[位置] 原詞= 原詞[:位置] + 原詞[(位置+ 1):]print('亂序后的詞語:', 亂序詞)
這里就不展開討論了,大家感興趣的話也可以學習一下。
好了,問哥要接著準備的新知識點——字典了,畢竟上節課說好的。今天內容也不少,就先到這里啦,大家別忘記練習哦。下節課我們會做個實用一點的小程序,因為我們真的會做一個字典(笑)。
免責聲明:本文系轉載,版權歸原作者所有;旨在傳遞信息,不代表本站的觀點和立場和對其真實性負責。如需轉載,請聯系原作者。如果來源標注有誤或侵犯了您的合法權益或者其他問題不想在本站發布,來信即刪。