今年年初,新一季的《最强大脑》开播了,第一集选拔的时候大家做了一个数字游戏,名叫《数字华容道》,当时何猷君以二十几秒的成绩夺得该项目的冠军,看了这个游戏之后我决定要写一个《数字华容道》的程序,过去了半年,我终于记起了这件事,今天就来实现。

数字推盘游戏(n-puzzle)是一种智力游戏,常见的类型有十五数字推盘游戏和八数字推盘游戏等。十五数字推盘游戏的板上会有十五个方块和一个大小相当于一个方块的空位(供方块移动之用),当15个数字依次排序并且最后一个格子为空位即代表挑战成功。

本文使用 PyQt5 进行设计与实现,PyQt5 是该程序的一个呈现方式,最重要的是算法,学会了算法,完全可以使用 PyGame 或者 Tkinter 实现。

PyQt5安装:pip install PyQt5

本文使用环境:

系统:Windows 10 64位

Python版本:3.6

1、布局设计

做一个简版的数字华容道,布局设计如图所示:

简版数字华容道布局

图中灰色的部分使用 QWidget 作为整个游戏的载体;

黄色部分使用 QGridLayout 作为数字方块的布局;

红色部分使用 QLabel 作为数字方块。

2、算法设计

如上图所示,本游戏共需要15个方块,每个方块代表一个数字。我们可以使用 一个二维 list 来存储方块上的数字。其实我们要创建一个 4x4 的 list 存储 0~15 各个数字,0 代表空的位置。

2.1 创建并初始化数组

创建数组的方法:

创建一个长度为16的数组,并且在对应位置上保存着 0~15 ;

打乱顺序

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]

2.2 移动算法

假如移动之前个数字位置如左图所示,那么当按下左箭头时,会变成如右图所示:

左移

可以看到 (1, 2) 和 (1, 3) 两个位置上的数字互换了,即 0 和 8 互换;如果右图所示再次按下左箭头,那么所有数字都不会改变,因为 数字 0 右边没有数了。

总结一下:如果 数字 0 所在位置为 (row, column),并且 column≠3 那么按下左箭头之后,(row, column) 和 (row, column+1) 位置上的数组互换,同理可得:

如果 数字 0 所在位置为 (row, column),并且 column≠0 那么按下右箭头之后,(row, column) 和 (row, column-1) 位置上的数组互换;

如果 数字 0 所在位置为 (row, column),并且 row≠3 那么按下上箭头之后,(row, column) 和 (row+1, column) 位置上的数组互换;

如果 数字 0 所在位置为 (row, column),并且 row≠0 那么按下下箭头之后,(row, column) 和 (row-1, column) 位置上的数组互换;

将移动算法封装成一个函数如下:

# 移动

# 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

2.3 是否胜利检测算法

检测是否胜利其实很简单:前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

3、实现

下面讲解所有功能模块的实现。

3.1 框架搭建

创建 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_())

运行结果如下图所示:

框架运行结果

3.2 数字方块实现

前面已经提到,用一个二维数组来存放 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就是数字方块上显示的数字。

3.3 将数字转换成方块添加到布局

布局采用 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)

3.4 初始化布局

初始化布局包括随机数据的产生与将数字转换成方块添加到布局:

# 初始化布局

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)

3.5 按键检测

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)。

3.6 试玩测试

至此,所有功能模块介绍完毕,不要着急看完整代码,我们先运行一下程序看是否还有 Bugs。

玩了几局之后发现,并不是所有的局都能都还原,如下面这种情况:

无法还原

如图所示,14 和 15 方块位置反了,无论如何也还原不聊了,这种情况是随机出现的。到底是怎么回事呢?经过一番上网搜索,确实如果只有两个数字的位置反了,无论如何也还原不了的。那这是由什么造成的呢?还记得我们的二维数组是怎么产生的吧,随机的,也就是说可能会随机到无法还原的情况。

如何避免这种情况呢?初始化数组时,所有的位置都是正确的数字,然后使用 move 进行移动打乱。

3.7 改进完善

由于前面已经将各个功能模块单独写成了方法,因此我们只需修改 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次,最后添加到布局。

4、完整代码

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_())

5、总结

在做的过程中遇到最大的坑就是随机数组导致无法还原。另外在做这个游戏的时候我已经找到还原规律了,这样在测试的时候可以做完完整测试,否则根本无法测试都挑战成功那一步。

另外要对《最强大脑》做一下吐槽:这个项目根本就是有偏袒的,玩过的人会很快,没有玩过的找规律的时间就很长。我在手机上玩4X4的最快还原用了 33 秒,对于该节目的冠军(即便是玩过)很是敬仰。

python游戏开发框架_Python游戏开发:数字华容道相关推荐

  1. python 游戏开发框架_Python开发 基于python实现坦克大战游戏

    这篇文章主要为大家详细介绍了基于python实现坦克大战游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本文实例为大家分享了python实现坦克大战游戏的具体代码, ...

  2. C语言开发数字华容道实现,jQuery实现数字华容道小游戏(实例代码)

    数字华容道 *{ padding: 0px; margin: 0px; } #btns>button{ width: 100px; height: 30px; border: none; bac ...

  3. libGDX跨平台游戏开发框架入门:开发一个小游戏之加载资源

    目录 前言 加载资源 libGDX资源目录说明 配置各个平台启动器 desktop模块启动器设置 Android模块启动器设置 加速度计和指南针 html模块启动器设置 编写加载资产代码 1. 加载图 ...

  4. 如何用python画帆船_python 游戏(船只寻宝)

    1. 游戏思路和流程图 实现功能:船只在可以在大海上移动打捞宝藏,船只可以扫描1格范围内的宝藏(后续难度,可以调整扫描范围,可以调整前进的格数) 游戏流程图 2. 使用模块和游戏提示 import r ...

  5. C语言开发数字华容道实现,从技术角度实现实现数字华容道

    目的 上周新一期的最强大脑出来了,虽然上季被称为最强黑幕,不过呢.我决定还是看看= =.它里面第一关是叫做数字华容道.说白了,就是和拼图差不多.一开始我准备下一个玩玩的.结果没搜到.所以决定写了一个. ...

  6. python后端前景_python后端开发前景-女性时尚流行美容健康娱乐mv-ida网

    女性时尚流行美容健康娱乐mv-ida网 mvida时尚娱乐网 首页 美容 护肤 化妆技巧 发型 服饰 健康 情感 美体 美食 娱乐 明星八卦 首页  > 高级搜索 python  gui 开发 ...

  7. python api开发框架_python api框架

    python常用框架 Django: Python Web应用开发框架 Django 应该是最出名的Python框架,GAE甚至Erlang都有框架受它影响.Django是走大而全的方向,它最出名的是 ...

  8. web python识花_Python Web开发之初认识

    一.具备技术能力至少熟悉一种Python web框架 熟悉Python语法 熟悉数据库.缓存.消息队列等技术的使用场景.使用方法 日常能使用Linux或Mac系统工作 有性能调优经验,能快速定位问题 ...

  9. python如何开发游戏脚本_python能开发游戏吗

    python可以写游戏,但不适合.下面我们来分析一下具体原因. 用锤子能造汽车吗? 谁也没法说不能吧?历史上也确实曾经有些汽车,是用锤子造出来的.但一般来说,还是用工业机器人更合适对吗? 比较大型的, ...

最新文章

  1. 2022-2028年中国汽车铝合金冲压件行业市场运营模式及未来发展潜力报告
  2. sql语句技巧,不敢独享,特此呈上
  3. HTML form 标签的 action 属性
  4. html5中在图片上打字的语法,HTML5 canvas 基本语法
  5. 小程序获取用户信息_App自评估指南:小程序也可参考,第三方获取信息需获用户授权...
  6. javascript:history.go()和history.back()的区别
  7. 漫步最优化二十八——三次插值法
  8. Nachos LAB1 线程机制和线程调度实现
  9. Android技术分享| 超简单!给 Android WebRTC增加美颜滤镜功能
  10. 直通车推广技巧、店铺人群标签怎么做?猜你喜欢入池检测
  11. 技校自我鉴定范文计算机600字,技校自我鉴定100字范文18篇
  12. 802.11ac物理层
  13. 定位的坑:class属性有空格
  14. python excel提取 替换_python代替excel的常用操作
  15. leaflet绘制图形,wkt模式与geoJson互转
  16. SQLserver的Always On 可用性组
  17. HDU5128The E-pang Palace(计算几何暴力枚举)
  18. hr2000 光谱Matlab,HR2000+高分辨率光谱仪
  19. Ajax上传文件的cache、processdata、contentType属性以及FormData对象的总结
  20. android防丢器设计,☆基于51单片机的无线防丢器设计|可以防止儿童走丢的神奇...

热门文章

  1. 登录超时提示+踢人下线实现(spring security)
  2. 拼多多关键词测试软件,拼多多图片怎么测试?有哪些步骤?
  3. 通俗易懂的安装MySQL步骤
  4. USACO 2021 January Contest, BronzeProblem 3. Just Stalling题解
  5. Openstack 安装之 Swift
  6. 如何做到推广链接的落地页链接实现无变化跳转?
  7. 应届生入职制造业感悟
  8. 两角和的余弦公式cos(α+β)=cosαcosβ-sinαsinβ
  9. 连连看核心算法与基本思想(附全部项目代码链接与代码详细注释)
  10. Prolog实现太阳系星体识别专家系统