音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

教你如何用python开发一款数字推盘小游戏
日期:2021-09-08 14:04:31   来源:脚本之家

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

数字推盘游戏(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开发数字推盘小游戏内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    ASP编程JSP编程PHP编程.NET编程python编程