今年年初,新一季的《最強大腦》開播了,第一集選拔的時候大家做了一個數(shù)字游戲,名叫《數(shù)字華容道》,當時何猷君以二十幾秒的成績奪得該項目的冠軍,看了這個游戲之后我決定要寫一個《數(shù)字華容道》的程序,過去了半年,我終于記起了這件事,今天就來實現(xiàn)。
數(shù)字推盤游戲(n-puzzle)是一種智力游戲,常見的類型有十五數(shù)字推盤游戲和八數(shù)字推盤游戲等。十五數(shù)字推盤游戲的板上會有十五個方塊和一個大小相當于一個方塊的空位(供方塊移動之用),當15個數(shù)字依次排序并且最后一個格子為空位即代表挑戰(zhàn)成功。
本文使用 PyQt5 進行設(shè)計與實現(xiàn),PyQt5 是該程序的一個呈現(xiàn)方式,最重要的是算法,學(xué)會了算法,完全可以使用 PyGame 或者 Tkinter 實現(xiàn)。
PyQt5安裝:pip install PyQt5
本文使用環(huán)境:
系統(tǒng):Windows 10 64位Python版本:3.6
做一個簡版的數(shù)字華容道,布局設(shè)計如圖所示:
圖中灰色的部分使用 QWidget 作為整個游戲的載體;黃色部分使用 QGridLayout 作為數(shù)字方塊的布局;紅色部分使用 QLabel 作為數(shù)字方塊。
如上圖所示,本游戲共需要15個方塊,每個方塊代表一個數(shù)字。我們可以使用 一個二維 list 來存儲方塊上的數(shù)字。其實我們要創(chuàng)建一個 4x4 的 list 存儲 0~15 各個數(shù)字,0 代表空的位置。
import random # 用來存放位置信息的二維數(shù)組 blocks = [] # 產(chǎn)生隨機數(shù)組,0 代表空的位置 arr = range(16) numbers = random.sample(arr, 16) for row in range(4): blocks.append([]) for column in range(4): blocks[row].append(numbers[row*4 + column]) # 打印結(jié)果 for i in range(4): print(blocks[i]) [out] [2, 5, 7, 9] [11, 8, 4, 12] [6, 13, 10, 15] [1, 14, 0, 3] [Finished in 0.1s]
假如移動之前個數(shù)字位置如左圖所示,那么當按下左箭頭時,會變成如右圖所示:
可以看到 (1, 2) 和 (1, 3) 兩個位置上的數(shù)字互換了,即 0 和 8 互換;如果右圖所示再次按下左箭頭,那么所有數(shù)字都不會改變,因為 數(shù)字 0 右邊沒有數(shù)了。
總結(jié)一下:如果 數(shù)字 0 所在位置為 (row, column),并且 column≠3 那么按下左箭頭之后,(row, column) 和 (row, column+1) 位置上的數(shù)組互換,同理可得:
將移動算法封裝成一個函數(shù)如下:
# 移動 # zero_row 代表數(shù)字0 所在二維數(shù)組的行下標,zero_column代表數(shù)字0 所在二維數(shù)組的列下標 def move(direction): if(direction == 'UP'): # 上 if zero_row != 3: blocks[zero_row][zero_column] = blocks[zero_row + 1][zero_column] blocks[zero_row + 1][zero_column] = 0 zero_row += 1 if(direction == 'DOWN'): # 下 if zero_row != 0: blocks[zero_row][zero_column] = blocks[zero_row - 1][zero_column] blocks[zero_row - 1][zero_column] = 0 zero_row -= 1 if(direction == 'LEFT'): # 左 if zero_column != 3: blocks[zero_row][zero_column] = blocks[zero_row][zero_column + 1] blocks[zero_row][zero_column + 1] = 0 zero_column += 1 if(direction == 'RIGHT'): # 右 if zero_column != 0: blocks[zero_row][zero_column] = blocks[zero_row][zero_column - 1] blocks[zero_row][zero_column - 1] = 0 zero_column -= 1
檢測是否勝利其實很簡單:前15個位置分別對應(yīng),最后一個為0即為勝利 ,不過為了避免不必要的計算,我們先檢測最后一個是否為 0 ,如果不為0 前面的就不用比較了。具體代碼實現(xiàn)如下:
# 檢測是否完成 def checkResult(): # 先檢測最右下角是否為0 if blocks[3][3] != 0: return False for row in range(4): for column in range(4): # 運行到此處說名最右下角已經(jīng)為0,pass即可 if row == 3 and column == 3: pass # 值是否對應(yīng) elif blocks[row][column] != row * 4 + column + 1: return False return True
下面講解所有功能模塊的實現(xiàn)。
創(chuàng)建 QWidget 作為整個游戲的載體:
import sys from PyQt5.QtWidgets import QWidget, QApplication class NumberHuaRong(QWidget): """ 華容道主體 """ def __init__(self): super().__init__() self.initUI() def initUI(self): # 設(shè)置寬和高 self.setFixedSize(400, 400) # 設(shè)置標題 self.setWindowTitle('數(shù)字華容道') # 設(shè)置背景顏色 self.setStyleSheet("background-color:gray;") self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = NumberHuaRong() sys.exit(app.exec_())
運行結(jié)果如下圖所示:
前面已經(jīng)提到,用一個二維數(shù)組來存放 0~16 個數(shù)字,最終我們要轉(zhuǎn)換成一個數(shù)字方塊,單獨創(chuàng)建一個類:
class Block(QLabel): """ 數(shù)字方塊 """ def __init__(self, number): super().__init__() self.number = number self.setFixedSize(80, 80) # 設(shè)置字體 font = QFont() font.setPointSize(30) font.setBold(True) self.setFont(font) # 設(shè)置字體顏色 pa = QPalette() pa.setColor(QPalette.WindowText, Qt.white) self.setPalette(pa) # 設(shè)置文字位置 self.setAlignment(Qt.AlignCenter) # 設(shè)置背景顏色\圓角和文本內(nèi)容 if self.number == 0: self.setStyleSheet("background-color:white;border-radius:10px;") else: self.setStyleSheet("background-color:red;border-radius:10px;") self.setText(str(self.number))
該類繼承自 QLablel ,初始化需要傳入一個參數(shù) number ,number就是數(shù)字方塊上顯示的數(shù)字。
布局采用 QGridLayout 創(chuàng)建一個 4X4 的 self.gltMain,將16個 Block 添加到 self.gltMain:
def updatePanel(self): for row in range(4): for column in range(4): self.gltMain.addWidget(Block(self.blocks[row][column]), row, column) self.setLayout(self.gltMain)
初始化布局包括隨機數(shù)據(jù)的產(chǎn)生與將數(shù)字轉(zhuǎn)換成方塊添加到布局:
# 初始化布局 def onInit(self): # 產(chǎn)生隨機數(shù)組,0 代表空的位置 arr = range(16) self.numbers = random.sample(arr, 16) # 將數(shù)字方塊添加到布局 for row in range(4): self.blocks.append([]) for column in range(4): temp = self.numbers[row * 4 + column] if temp == 0: self.zero_row = row self.zero_column = column self.blocks[row].append(temp) self.gltMain.addWidget(Block(temp), row, column)
QWidget 有一個 keyPressEvent 事件句柄,我們只需要重新實現(xiàn)該方法即可:
# 檢測按鍵 def keyPressEvent(self, event): key = event.key() if(key == Qt.Key_Up or key == Qt.Key_W): self.move(Direction.UP) if(key == Qt.Key_Down or key == Qt.Key_S): self.move(Direction.DOWN) if(key == Qt.Key_Left or key == Qt.Key_A): self.move(Direction.LEFT) if(key == Qt.Key_Right or key == Qt.Key_D): self.move(Direction.RIGHT) self.updatePanel() if self.checkResult(): if QMessageBox.Ok == QMessageBox.information(self, '挑戰(zhàn)結(jié)果', '恭喜您完成挑戰(zhàn)!'): self.onInit()
按鍵檢測到按鍵按下之后判斷該鍵值是否為 “↑↓←→”或“WSAD”,并作出相應(yīng)的移動(move),移動之后刷新布局(updatePannel),最后檢測是否完成挑戰(zhàn)(checkResult),如果完成挑戰(zhàn),彈出提示框。如果點擊了 OK 按鈕,游戲重新開始(onInit)。
至此,所有功能模塊介紹完畢,不要著急看完整代碼,我們先運行一下程序看是否還有 Bugs。
玩了幾局之后發(fā)現(xiàn),并不是所有的局都能都還原,如下面這種情況:
如圖所示,14 和 15 方塊位置反了,無論如何也還原不聊了,這種情況是隨機出現(xiàn)的。到底是怎么回事呢?經(jīng)過一番上網(wǎng)搜索,確實如果只有兩個數(shù)字的位置反了,無論如何也還原不了的。那這是由什么造成的呢?還記得我們的二維數(shù)組是怎么產(chǎn)生的吧,隨機的,也就是說可能會隨機到無法還原的情況。
如何避免這種情況呢?初始化數(shù)組時,所有的位置都是正確的數(shù)字,然后使用 move 進行移動打亂。
由于前面已經(jīng)將各個功能模塊單獨寫成了方法,因此我們只需修改 onInit 方法即可。
# 初始化布局 def onInit(self): # 產(chǎn)生順序數(shù)組 self.numbers = list(range(1, 16)) self.numbers.append(0) # 將數(shù)字添加到二維數(shù)組 for row in range(4): self.blocks.append([]) for column in range(4): temp = self.numbers[row * 4 + column] if temp == 0: self.zero_row = row self.zero_column = column self.blocks[row].append(temp) # 打亂數(shù)組 for i in range(500): random_num = random.randint(0, 3) self.move(Direction(random_num)) self.updatePanel()
先生成一個順序數(shù)組,里面保存著[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0]
,然后轉(zhuǎn)為二維數(shù)組 blocks,再后隨即移動500次,最后添加到布局。
import sys import random from enum import IntEnum from PyQt5.QtWidgets import QLabel, QWidget, QApplication, QGridLayout, QMessageBox from PyQt5.QtGui import QFont, QPalette from PyQt5.QtCore import Qt # 用枚舉類表示方向 class Direction(IntEnum): UP = 0 DOWN = 1 LEFT = 2 RIGHT = 3 class NumberHuaRong(QWidget): """ 華容道主體 """ def __init__(self): super().__init__() self.blocks = [] self.zero_row = 0 self.zero_column = 0 self.gltMain = QGridLayout() self.initUI() def initUI(self): # 設(shè)置方塊間隔 self.gltMain.setSpacing(10) self.onInit() # 設(shè)置布局 self.setLayout(self.gltMain) # 設(shè)置寬和高 self.setFixedSize(400, 400) # 設(shè)置標題 self.setWindowTitle('數(shù)字華容道') # 設(shè)置背景顏色 self.setStyleSheet("background-color:gray;") self.show() # 初始化布局 def onInit(self): # 產(chǎn)生順序數(shù)組 self.numbers = list(range(1, 16)) self.numbers.append(0) # 將數(shù)字添加到二維數(shù)組 for row in range(4): self.blocks.append([]) for column in range(4): temp = self.numbers[row * 4 + column] if temp == 0: self.zero_row = row self.zero_column = column self.blocks[row].append(temp) # 打亂數(shù)組 for i in range(500): random_num = random.randint(0, 3) self.move(Direction(random_num)) self.updatePanel() # 檢測按鍵 def keyPressEvent(self, event): key = event.key() if(key == Qt.Key_Up or key == Qt.Key_W): self.move(Direction.UP) if(key == Qt.Key_Down or key == Qt.Key_S): self.move(Direction.DOWN) if(key == Qt.Key_Left or key == Qt.Key_A): self.move(Direction.LEFT) if(key == Qt.Key_Right or key == Qt.Key_D): self.move(Direction.RIGHT) self.updatePanel() if self.checkResult(): if QMessageBox.Ok == QMessageBox.information(self, '挑戰(zhàn)結(jié)果', '恭喜您完成挑戰(zhàn)!'): self.onInit() # 方塊移動算法 def move(self, direction): if(direction == Direction.UP): # 上 if self.zero_row != 3: self.blocks[self.zero_row][self.zero_column] = self.blocks[self.zero_row + 1][self.zero_column] self.blocks[self.zero_row + 1][self.zero_column] = 0 self.zero_row += 1 if(direction == Direction.DOWN): # 下 if self.zero_row != 0: self.blocks[self.zero_row][self.zero_column] = self.blocks[self.zero_row - 1][self.zero_column] self.blocks[self.zero_row - 1][self.zero_column] = 0 self.zero_row -= 1 if(direction == Direction.LEFT): # 左 if self.zero_column != 3: self.blocks[self.zero_row][self.zero_column] = self.blocks[self.zero_row][self.zero_column + 1] self.blocks[self.zero_row][self.zero_column + 1] = 0 self.zero_column += 1 if(direction == Direction.RIGHT): # 右 if self.zero_column != 0: self.blocks[self.zero_row][self.zero_column] = self.blocks[self.zero_row][self.zero_column - 1] self.blocks[self.zero_row][self.zero_column - 1] = 0 self.zero_column -= 1 def updatePanel(self): for row in range(4): for column in range(4): self.gltMain.addWidget(Block(self.blocks[row][column]), row, column) self.setLayout(self.gltMain) # 檢測是否完成 def checkResult(self): # 先檢測最右下角是否為0 if self.blocks[3][3] != 0: return False for row in range(4): for column in range(4): # 運行到此處說名最右下角已經(jīng)為0,pass即可 if row == 3 and column == 3: pass #值是否對應(yīng) elif self.blocks[row][column] != row * 4 + column + 1: return False return True class Block(QLabel): """ 數(shù)字方塊 """ def __init__(self, number): super().__init__() self.number = number self.setFixedSize(80, 80) # 設(shè)置字體 font = QFont() font.setPointSize(30) font.setBold(True) self.setFont(font) # 設(shè)置字體顏色 pa = QPalette() pa.setColor(QPalette.WindowText, Qt.white) self.setPalette(pa) # 設(shè)置文字位置 self.setAlignment(Qt.AlignCenter) # 設(shè)置背景顏色\圓角和文本內(nèi)容 if self.number == 0: self.setStyleSheet("background-color:white;border-radius:10px;") else: self.setStyleSheet("background-color:red;border-radius:10px;") self.setText(str(self.number)) if __name__ == '__main__': app = QApplication(sys.argv) ex = NumberHuaRong() sys.exit(app.exec_())
在做的過程中遇到最大的坑就是隨機數(shù)組導(dǎo)致無法還原。另外在做這個游戲的時候我已經(jīng)找到還原規(guī)律了,這樣在測試的時候可以做完完整測試,否則根本無法測試都挑戰(zhàn)成功那一步。
另外要對《最強大腦》做一下吐槽:這個項目根本就是有偏袒的,玩過的人會很快,沒有玩過的找規(guī)律的時間就很長。我在手機上玩4X4的最快還原用了 33 秒,對于該節(jié)目的冠軍(即便是玩過)很是敬仰。
到此這篇關(guān)于教你如何用python開發(fā)一款數(shù)字推盤小游戲的文章就介紹到這了,更多相關(guān)python開發(fā)數(shù)字推盤小游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
標簽:股票 衡水 中山 江蘇 畢節(jié) 駐馬店 呼和浩特 湖州
巨人網(wǎng)絡(luò)通訊聲明:本文標題《教你如何用python開發(fā)一款數(shù)字推盤小游戲》,本文關(guān)鍵詞 教你,如,何用,python,開發(fā),;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。