微網(wǎng)站建設(shè)訊息百度產(chǎn)品大全入口
python 基礎(chǔ)系列篇:七、以函數(shù)方式編寫一個數(shù)字華容道
- 數(shù)字華容道
- 游戲分析
- 開始編寫
- 完整代碼
- 代碼解說
- 定義方法的規(guī)律
- 小結(jié)
數(shù)字華容道
嗯,就是一個簡單的益智游戲,把數(shù)字按照特定規(guī)律排列,并比矩陣少一個格,用來進(jìn)行移動。
具體游戲方式就不細(xì)說了,還不了解的可以自行百度一下。
正好,老顧最近是沒有什么靈感,不知道用什么舉例來講解一下怎么去劃分函數(shù),減少工作,然后就在昨天,問答有小伙伴問到了數(shù)字華容道的問題。然后老顧就決定用這個做個例子來講解。
CSDN 文盲老顧的博客, https://blog.csdn.net/supewrfei
游戲分析
老規(guī)矩,我們先分析一下需要完成的內(nèi)容有哪些。既然是做數(shù)字華容道,我們先列舉一下,這個游戲需要什么。
1、在一個特定長和寬的矩陣?yán)?#xff0c;有 長 乘 寬 減 一 個可移動的方塊
2、只有空位周邊的方塊可移動至空位
3、游戲開始時,方塊順序是混亂的
4、指定一個最終結(jié)果,作為勝利條件,如不指定,則以橫向連續(xù)為勝利條件
5、用戶可以通過上下左右(wsad)來移動方塊
在游戲需求列完了,我們再列舉一下,我們需要做哪些準(zhǔn)備
1、可由用戶指定游戲區(qū)域的寬和高
2、每個可移動塊要標(biāo)記一個記號
3、接收用戶輸入的移動方向
4、記錄移動步數(shù)
5、生成勝利指定的最終結(jié)果,用以判定勝利條件
開始編寫
相信已經(jīng)看過 2048 的小伙伴,對這個感覺非常熟悉了。沒錯,大部分的內(nèi)容,可能會與 2048 有些重合,但今天的內(nèi)容,使用函數(shù)才是重點(diǎn)哦。
我們先規(guī)劃一下,我們應(yīng)該需要定義哪些方法:
1、用以定義長和寬的用戶輸入部分
2、用以顯示游戲界面的部分
3、用來進(jìn)行游戲時,接收用戶輸入的部分及移動
4、用來進(jìn)行勝利判定的部分
5、用來進(jìn)行游戲銜接的一些內(nèi)容,比如是否開始新游戲,是否退出游戲等
完整代碼
任何游戲都有一個進(jìn)入游戲、開始游戲的指令。由于我們是在 python 開發(fā)環(huán)境里寫,所以進(jìn)入游戲就省略了,只寫一個開始即可。在開發(fā)環(huán)境外,就用 python 指定運(yùn)行文件的方式來進(jìn)入游戲即可。
這次,老顧就先放出完整代碼,然后再進(jìn)行講解。邊寫代碼邊寫博客講解有點(diǎn)費(fèi)勁了。
import sys
import re
import random
import copy# 用來呈現(xiàn)用戶界面
def ShowBoard(data):# 用戶界面用到的制表符'''─┐┌└┘├┤┬┴┼│'''# 根據(jù)用戶定義的區(qū)域大小,來確定最大數(shù)字的長度,每個數(shù)字占用位置,以此為依據(jù)計(jì)算length = len(str(data['blank'])) + 1# 輸出區(qū)域頂部邊界,依據(jù)是字符占位寬度 length 和 橫向數(shù)字?jǐn)?shù)量 data['width']print(('┌' + (('─' * length) + '┬') * data['width'])[:-1] + '┐')for i in range(data['height']):# 輸出每行的數(shù)字信息print('│' + '│'.join([str(v).rjust(length) if v != data['blank'] else ' ' * length for v in data['board'][i * data['width']:(i + 1) * data['width']]]) + '│')if i < data['height'] - 1:# 如果不是最后一行,輸出間隔行print(('├' + (('─' * length) + '┼') * data['width'])[:-1] + '┤')else:# 輸出底部邊界print(('└' + (('─' * length) + '┴') * data['width'])[:-1] + '┘')# 對游戲?qū)ο筮M(jìn)行數(shù)據(jù)填充
def InitBoard(data):sys.stdout.flush()inp = input('如果想更改區(qū)域大小,請輸入兩個數(shù)字,以空格分開:')if re.fullmatch('\s*\d+\s+\d+\s*',inp):data['width'],data['height'] = map(int,inp.split())data['blank'] = data['width'] * data['height']data['success'] = list(range(1, data['blank'] + 1))data['board'] = copy.deepcopy(data['success'])random.shuffle(data['board'])# 是否新開游戲
def NewGame():a = ''while a not in 'yYnN' or len(a) < 1:sys.stdout.flush()a = input('是否開始新游戲(Y/N)?')return a in 'yY'# 游戲主線程
def GetInput(data):ShowBoard(data)sys.stdout.flush()arrow = input('請選擇方向(上w下s左a右d,退出q):').lower()if arrow == 'q':return True# 得到當(dāng)前空位所在的位置blank = data['board'].index(data['blank'])if arrow == 'w' and blank // data['width'] < data['height'] - 1:data['steps'] += 1data['board'][blank],data['board'][blank + data['width']] = data['board'][blank + data['width']],data['board'][blank]if arrow == 'a' and blank % data['width'] < data['width'] - 1:data['steps'] += 1data['board'][blank],data['board'][blank + 1] = data['board'][blank + 1],data['board'][blank]if arrow == 's' and blank // data['width'] > 0:data['steps'] += 1data['board'][blank],data['board'][blank - data['width']] = data['board'][blank - data['width']],data['board'][blank]if arrow == 'd' and blank % data['width'] > 0:data['steps'] += 1data['board'][blank],data['board'][blank - 1] = data['board'][blank - 1],data['board'][blank]if data['board'] == data['success']:print('你用了{(lán)}步,取得了勝利。'.format(data['steps']))return Truedef HuaRongDao():while True:data = {'width' : 4,'height' : 4,'board' : [],'steps' : 0}if not NewGame():returnInitBoard(data)while True:if GetInput(data):breakif __name__ == '__main__':HuaRongDao()
代碼解說
首先,我們在 HuaRongDao 方法里定義了一個死循環(huán),通過死循環(huán),來保證用戶不會跳出游戲。每一次循環(huán),表示一輪新游戲。
data = {…}
然后,在循環(huán)里定義了一個初始字典,用來存放游戲數(shù)據(jù)。每輪的數(shù)據(jù)都需要重新定義。
在初始化游戲字典后,我們詢問用戶是否進(jìn)行新游戲,如果不進(jìn)行則跳出。
data[‘width’],data[‘height’]
在初始化游戲界面的方法 InitBoard 里,我們允許用戶輸入兩個整數(shù),來改變游戲區(qū)域大小。畢竟是小游戲,打發(fā)時間的,可以自行加難度。而我們默認(rèn)的初始難度是 4 * 4 ,算是很簡單的了。
data[‘blank’] = data[‘width’] * data[‘height’]
不管用戶是否改變區(qū)域大小,我們之后的內(nèi)容,就是根據(jù)區(qū)域大小,來填充游戲數(shù)據(jù)了,先確定最大數(shù)字是多少,將這個數(shù)字定義為空位。
data[‘success’] = list(range(1, data[‘blank’] + 1))
然后,生成一個連續(xù)序列,表示勝利時的狀態(tài)。
data[‘board’] = copy.deepcopy(data[‘success’])
再然后,用深拷貝,復(fù)制一個勝利狀態(tài)的數(shù)據(jù)。
random.shuffle(data[‘board’])
最后,用隨機(jī)洗牌函數(shù),將用戶需要進(jìn)行操作的數(shù)據(jù)打亂。
至此,游戲初始化內(nèi)容完成,可以進(jìn)行游戲了。
在這里,除了最初的 data 定義,其他都放在了 InitBoard 方法里。
其實(shí)最初的定義也可以放到 InitBoard ,然后 return data,在 之前定義的循環(huán)里接收這個結(jié)果。老顧隨手寫的是這樣,就不修改了。
因?yàn)樽值涫且粋€引用型對象,所以,我們通過傳遞 data 這個對象,并直接修改這個對象,是相當(dāng)于在原有對象上操作的,不用擔(dān)心我操作的內(nèi)容會丟失。
在初始化結(jié)束后,就是正式的用戶交互部分了,我們定義了一個 GetInput 的方法。
而在用戶輸入信息前,調(diào)用了一個 ShowBoard 方法,用來顯示游戲當(dāng)前界面。
在現(xiàn)實(shí)了界面后,用戶才會知道自己應(yīng)該怎么移動。
在輸入部分,限定一下輸入內(nèi)容,并允許跳出游戲。即只接收 asdwq 5個字符,其他字符視為無效。
blank = data[‘board’].index(data[‘blank’])
然后就是根據(jù)空位的信息,來驗(yàn)證是否移動方式可行。
data[‘steps’] += 1
如果可行,則移動步數(shù)加一。在這里,老顧定義的方向也不知道是否符合大家的習(xí)慣,如果不習(xí)慣,可以將 ws 互調(diào),ad 互調(diào)。
if data[‘board’] == data[‘success’]:
最后,用戶移動完成時,驗(yàn)證是否勝利。
這樣,一個簡單的數(shù)字華容道就完成了。
定義方法的規(guī)律
在我們定義的這幾個方法里,HuaRongDao 的使用頻率是最低的,他相當(dāng)于游戲的主控線程。定義這個,主要是為了方便外部執(zhí)行不產(chǎn)生沖突。
其次,頻率倒數(shù)第二低的,就是 NewGame 和 InitBoard 了,每新開一輪游戲,才調(diào)用一次,如果玩上一下午,調(diào)用次數(shù)還是不少的。
再然后,就是調(diào)用頻率最高的 GetInput 了,還有同樣頻率的 ShowBoard。
至于為什么分成兩個,一個是管輸出,一個是管輸入控制,分開的話,邏輯就更清晰,維護(hù)更方便罷了。
最后,老顧在 GetInput 的時候,有一些情況下具有了一個 True 的返回值,在這個代碼里,這個返回值就代表了游戲結(jié)束哦,不管是勝利還是退出,對游戲邏輯來說都是一樣的,只是對用戶反饋信息不一樣罷了。
那么,大體上的樸素邏輯就出來了,就是需要多次運(yùn)行的內(nèi)容,做成函數(shù)或方法,不同使用頻率的,則做成不同的方法。而不同用途的,或者不同功能性的,也盡量拆分開做成不同的方法,這樣后續(xù)維護(hù),也很容易定位。
在本文中,老顧就是一個舉例,具體到實(shí)際,每個人都有自己的定義方法的習(xí)慣,不用照抄老顧的習(xí)慣哦。
小結(jié)
這次,我們通過一個完整的示例代碼,來了解了函數(shù)、方法的使用方式,以及樸素規(guī)律,后邊我們就可以自行發(fā)揮,培養(yǎng)自己的代碼風(fēng)格和書寫習(xí)慣了。
多讀別人的代碼,是培養(yǎng)代碼風(fēng)格和熟悉習(xí)慣的辦法之一。
然后,今天引用的幾個包再說明一下:
1、sys 包
主要使用 sys.stdout.flush() 避免用戶輸入信息提示串行,造成用戶輸入信息時無響應(yīng)
2、re 包
用正則方式判斷用戶是否輸入了兩個整數(shù),來確定是否需要變更區(qū)域大小。如果不用正則方式,那么用戶輸入信息的可能性太多,做起驗(yàn)證也很麻煩。
3、random 包
所有使用隨機(jī)數(shù)的代碼都會用到,本文主要用到 random.shuffle,對迭代對象進(jìn)行打亂(洗牌)處理。
4、copy 包
在復(fù)制引用類型的數(shù)據(jù)時,應(yīng)該使用深拷貝,否則你可能引用的是同一個對象,最后發(fā)現(xiàn)數(shù)據(jù)全亂套了。
那么,今天就到這里,大家晚安。