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

数字推盘游戏(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 秒,对于该节目的冠军(即便是玩过)很是敬仰。

下面视频是我试玩时录得,大家看一下效果:https://www.zhihu.com/video/1005740415965851648

python数字推盘游戏怎么显示步数_Python游戏开发:《最强大脑》同款游戏【数字华容道】...相关推荐

  1. python数字推盘游戏怎么显示步数_Python游戏开发:数字华容道

    今年年初,新一季的<最强大脑>开播了,第一集选拔的时候大家做了一个数字游戏,名叫<数字华容道>,当时何猷君以二十几秒的成绩夺得该项目的冠军,看了这个游戏之后我决定要写一个< ...

  2. python数字推盘游戏怎么显示步数_python机器人行走步数问题的解决

    本文实例为大家分享了python机器人行走步数问题,供大家参考,具体内容如下 #! /usr/bin/env python3 # -*- coding: utf-8 -*- # fileName : ...

  3. python数字推盘游戏怎么显示步数_python初学---猜数字游戏(游戏与AI,原创)

    最近在学习python,并用python写了一个小时候玩的益智游戏,猜数字游戏,python语言真的是简洁啊,上来就是写代码逻辑,不用加一堆外壳的东西,话不多说,贴代码,取个名字叫guessL1.py ...

  4. Python游戏开发:最强大脑第一关,数字华容道

    前言 freegames是Apache2许可的Python游戏集合,旨在用于教育和娱乐,完全是开源的,我们只要引用编写就好,当前在最强大脑的舞台上的第一关就是数字华容道,好多人都栽在了上面,如果你也想 ...

  5. 区块链游戏赌的是整个赛道,而不是某款游戏

    4月20日下午,由 IGS组委会与陀螺财经联合主办的"WeChain 2018区块链行业峰会"在成都盛大举行. 此次大会围绕着区块链技术在游戏领域的实践应用展开,为了多角度地引发观 ...

  6. 游戏巨头齐聚 Unite Shanghai 2019,揭秘爆款游戏制作精彩亮点!

    从三人行的公寓创业到全球知名游戏引擎的发展.从局限的 macOS 到高达 25 多个平台的覆盖.从单一的手游到建筑可视化和实时三维动画等类型互动内容的综合型创作工具的演进,Unity 正以" ...

  7. pc端游戏修改器_【游戏资讯】大人,时代变了,这款游戏来PC端圈钱有点着急了...

    要是说近几天游戏圈发生的大事,应该是<原神>开始PC端测试了,广告漫天飞,我在朋友圈也看到了好几个的截图,从宣布公测开始热度是居高不下. <原神>背后的公司米哈游手底下最出名的 ...

  8. python数字推盘游戏怎么显示步数_用 Python 实现手机自动答题,这下百万答题游戏谁也玩不过我!...

    引言 如果谈到这几年手机上各平台最常见的引流福利,必然是答题赢大奖系列小游戏了.像什么头号英雄,百万玩家之类的,充斥在我们生活中,同时也成为了我们生活中常见的娱乐方式. 但是有时候就会想,能不能实现手 ...

  9. python数字推盘游戏怎么显示步数_用 Python 修改微信(支付宝)运动步数,轻松 TOP1...

    原标题:用 Python 修改微信(支付宝)运动步数,轻松 TOP1 作者:Tsubasa_Ou 今天分享的文章让你霸屏微信运动,横扫支付宝榜单 1 项目意义 如果你想在支付宝蚂蚁森林收集很多能量种树 ...

最新文章

  1. 关于 eclipse startexplorer插件 快速打开文件夹
  2. Kubelet源码分析(一):启动流程分析
  3. UITableVIew的性能优化-重用原理
  4. Nginx代理功能与负载均衡详解
  5. Java基础篇:数组
  6. 豆瓣读书评分 8.7 以上! 这 40 本 Python 畅销书包邮送
  7. [攻防世界 pwn]——string(内涵peak小知识)
  8. 《python网络数据采集》读后感 第六章:读取文档
  9. ❤️ 给你的Linux把把脉(内存、磁盘、CPU、网络)❤️
  10. oracle 对象类型是什么意思,Oracle-对象类型
  11. mongodb 学习第二天,基本操作
  12. 判断Exe文件是否正在运行的函数
  13. java到js的中文无法显示,中文显示位(?)
  14. swapidc卡密充值插件
  15. flutter实战!一个Android应届生从上海离职,深度好文
  16. Hbase GC调优原理分析
  17. 北方工业大学数据结构考研
  18. JavaWeb以Maven整合Mybatis报错java.lang.NoClassDefFoundError(Web项目未能完全加载Maven下的依赖)
  19. 2007年1月 讨论:人为什么活着(人活着就是为了能够光荣的死去)
  20. mpdf导出pdf文件实例

热门文章

  1. 逻辑左移、逻辑右移、算术左移、算术右移、循环左移、循环右移
  2. 如何安装谷歌服务框架?(Google三件套下载教程)
  3. keras的数字图像识别
  4. android 阻止自动唤醒,IT之家学院:如何免ROOT阻止Android应用唤醒
  5. Sql server找不到启动图标
  6. java让线程空转_详解Java编程中对线程的中断处理
  7. Springboot整合邮箱报错502解决方法
  8. 电子商务时代网络营销
  9. can和could的用法_could的用法
  10. 分布式系统和网络系统