基于Python怎么设计开发实现一个数字华容道小游戏
Admin 2022-07-06 群英技术资讯 401 次浏览
本文使用 PyQt5 进行设计与实现,PyQt5 是该程序的一个呈现方式,最重要的是算法,学会了算法,完全可以使用 PyGame 或者 Tkinter 实现。
PyQt5安装:pip install PyQt5
本文使用环境:
系统:Windows 10 64位Python版本:3.6
做一个简版的数字华容道,布局设计如图所示:
图中灰色的部分使用 QWidget 作为整个游戏的载体;黄色部分使用 QGridLayout 作为数字方块的布局;红色部分使用 QLabel 作为数字方块。
如上图所示,本游戏共需要15个方块,每个方块代表一个数字。我们可以使用 一个二维 list 来存储方块上的数字。其实我们要创建一个 4x4 的 list 存储 0~15 各个数字,0 代表空的位置。
import random # 用来存放位置信息的二维数组 blocks = [] # 产生随机数组,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]) # 打印结果 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]
假如移动之前个数字位置如左图所示,那么当按下左箭头时,会变成如右图所示:
可以看到 (1, 2) 和 (1, 3) 两个位置上的数字互换了,即 0 和 8 互换;如果右图所示再次按下左箭头,那么所有数字都不会改变,因为 数字 0 右边没有数了。
总结一下:如果 数字 0 所在位置为 (row, column),并且 column≠3 那么按下左箭头之后,(row, column) 和 (row, column+1) 位置上的数组互换,同理可得:
将移动算法封装成一个函数如下:
# 移动 # zero_row 代表数字0 所在二维数组的行下标,zero_column代表数字0 所在二维数组的列下标 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个位置分别对应,最后一个为0即为胜利 ,不过为了避免不必要的计算,我们先检测最后一个是否为 0 ,如果不为0 前面的就不用比较了。具体代码实现如下:
# 检测是否完成 def checkResult(): # 先检测最右下角是否为0 if blocks[3][3] != 0: return False for row in range(4): for column in range(4): # 运行到此处说名最右下角已经为0,pass即可 if row == 3 and column == 3: pass # 值是否对应 elif blocks[row][column] != row * 4 + column + 1: return False return True
下面讲解所有功能模块的实现。
创建 QWidget 作为整个游戏的载体:
import sys from PyQt5.QtWidgets import QWidget, QApplication class NumberHuaRong(QWidget): """ 华容道主体 """ def __init__(self): super().__init__() self.initUI() def initUI(self): # 设置宽和高 self.setFixedSize(400, 400) # 设置标题 self.setWindowTitle('数字华容道') # 设置背景颜色 self.setStyleSheet("background-color:gray;") self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = NumberHuaRong() sys.exit(app.exec_())
运行结果如下图所示:
前面已经提到,用一个二维数组来存放 0~16 个数字,最终我们要转换成一个数字方块,单独创建一个类:
class Block(QLabel): """ 数字方块 """ def __init__(self, number): super().__init__() self.number = number self.setFixedSize(80, 80) # 设置字体 font = QFont() font.setPointSize(30) font.setBold(True) self.setFont(font) # 设置字体颜色 pa = QPalette() pa.setColor(QPalette.WindowText, Qt.white) self.setPalette(pa) # 设置文字位置 self.setAlignment(Qt.AlignCenter) # 设置背景颜色\圆角和文本内容 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 ,初始化需要传入一个参数 number ,number就是数字方块上显示的数字。
布局采用 QGridLayout 创建一个 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)
初始化布局包括随机数据的产生与将数字转换成方块添加到布局:
# 初始化布局 def onInit(self): # 产生随机数组,0 代表空的位置 arr = range(16) self.numbers = random.sample(arr, 16) # 将数字方块添加到布局 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 事件句柄,我们只需要重新实现该方法即可:
# 检测按键 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, '挑战结果', '恭喜您完成挑战!'): self.onInit()
按键检测到按键按下之后判断该键值是否为 “↑↓←→”或“WSAD”,并作出相应的移动(move),移动之后刷新布局(updatePannel),最后检测是否完成挑战(checkResult),如果完成挑战,弹出提示框。如果点击了 OK 按钮,游戏重新开始(onInit)。
至此,所有功能模块介绍完毕,不要着急看完整代码,我们先运行一下程序看是否还有 Bugs。
玩了几局之后发现,并不是所有的局都能都还原,如下面这种情况:
如图所示,14 和 15 方块位置反了,无论如何也还原不聊了,这种情况是随机出现的。到底是怎么回事呢?经过一番上网搜索,确实如果只有两个数字的位置反了,无论如何也还原不了的。那这是由什么造成的呢?还记得我们的二维数组是怎么产生的吧,随机的,也就是说可能会随机到无法还原的情况。
如何避免这种情况呢?初始化数组时,所有的位置都是正确的数字,然后使用 move 进行移动打乱。
由于前面已经将各个功能模块单独写成了方法,因此我们只需修改 onInit 方法即可。
# 初始化布局 def onInit(self): # 产生顺序数组 self.numbers = list(range(1, 16)) self.numbers.append(0) # 将数字添加到二维数组 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) # 打乱数组 for i in range(500): random_num = random.randint(0, 3) self.move(Direction(random_num)) self.updatePanel()
先生成一个顺序数组,里面保存着[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0]
,然后转为二维数组 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): # 设置方块间隔 self.gltMain.setSpacing(10) self.onInit() # 设置布局 self.setLayout(self.gltMain) # 设置宽和高 self.setFixedSize(400, 400) # 设置标题 self.setWindowTitle('数字华容道') # 设置背景颜色 self.setStyleSheet("background-color:gray;") self.show() # 初始化布局 def onInit(self): # 产生顺序数组 self.numbers = list(range(1, 16)) self.numbers.append(0) # 将数字添加到二维数组 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) # 打乱数组 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, '挑战结果', '恭喜您完成挑战!'): 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): # 运行到此处说名最右下角已经为0,pass即可 if row == 3 and column == 3: pass #值是否对应 elif self.blocks[row][column] != row * 4 + column + 1: return False return True class Block(QLabel): """ 数字方块 """ def __init__(self, number): super().__init__() self.number = number self.setFixedSize(80, 80) # 设置字体 font = QFont() font.setPointSize(30) font.setBold(True) self.setFont(font) # 设置字体颜色 pa = QPalette() pa.setColor(QPalette.WindowText, Qt.white) self.setPalette(pa) # 设置文字位置 self.setAlignment(Qt.AlignCenter) # 设置背景颜色\圆角和文本内容 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_())
在做的过程中遇到最大的坑就是随机数组导致无法还原。另外在做这个游戏的时候我已经找到还原规律了,这样在测试的时候可以做完完整测试,否则根本无法测试都挑战成功那一步。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
摘要:本篇文章主要讲解Python调用OpenCV实现图像腐蚀和图像膨胀的算法。本文分享自华为云社区《[Python图像处理]八.图像腐蚀与图像膨胀》,作者:eastmount。本篇文章主要讲解Python调用OpenCV实现图像腐蚀和图像膨胀的算法,基础性知识希望对您有所帮助。1.基础...
这篇文章主要为大家详细介绍了如何利用Python实现一个简易的截图工具,可以截完图之后显示并且永远前置,感兴趣的小伙伴可以尝试一下
这篇文章主要介绍了pytorch_detach 切断网络反传方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
这篇文章主要介绍了讲解Python 中的with关键字,文章基于python的相关资料展开 with 语句的一些基本概念和用法及其底层工作原理,下文更多内容感兴趣的小伙伴可以参考一下
这篇文章主要为大家介绍了python神经网络tf.name_scope和tf.variable_scope函数的使用区别,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008