博客主页:【夜泉_ly】
本文专栏:【暂无】
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

目录

      • 前言
      • 关于如何sleep
      • 实现思路
      • Pixmaps
        • pixmaps.h
        • pixmaps.cpp
      • MapSquare
        • mapsquare.h
        • mapsquare.cpp
      • dfsthread
        • dfsthread.h
        • dfsthread.cpp
          • run dfs
          • 其他
        • Widget
          • Unit
          • 其他

Qt -DFS算法可视化

完整代码:https://gitee.com/Ye_Quan/my-cpp-project/tree/master/Qt-experiments/dfs-visualizer

前言

本来,我是在搞我的战棋小游戏的,
如果大家玩过战棋应该知道,
当我们点击一个单位时,
一般会显示出一片区域,
表示这个单位的移动范围(以及可攻击目标等等)。

我当时就想到了用DFS和BFS,
然后就写了。

之后我想,
既然已经在游戏中实现了DFS,
那能不能以现有代码为基础,
写一个DFS算法可视化呢?
感觉很有意思,
所以有了本文。

当然,虽然标题叫做DFS可视化,
但是代码部分大多是在对战棋游戏的实现进行试验,
因此存在很多‘多余’的设计,大家看看就行。

关于如何sleep

既然是可视化,
那必定不能等DFS完了再更新地图状态,
因此我决定在每次更新了一个格子的状态后,
就暂停一会儿再继续DFS。

结果问题来了,
怎么做到每次都暂停一会儿?

我本来想直接在DFS中直接加sleep就好了:
在这里插入图片描述
然后程序卡了,
DFS完了才更新界面。
我以为是没用信号触发的问题,
改成用信号触发地格更新:
在这里插入图片描述
结果还是不行。
为什么?
最开始我以为和Linux中多线程竞争锁的问题类似,
大概描述一下就是:
抢锁 - 我抢到了! - 我释放 - 欸我离得近,我再抢!
然后发现了一个本质的区别:
这里只有一个线程啊🤣!

问题出在事件队列
当前这个DFS就是事件队列的一个事件,
而DFS发送的信号所触发的事件,
不过是去事件队列后面排队罢了,
那现在执行的是谁?
还是DFS!
我DFS没跑完,
你们后面的事件都别想跑!

所以用QTimer?
我这是DFS。。。
如果用QTimer,那得搞个stack,
然后用类似非递归版的DFS来做吧?

我最终的解决方案是,
使用子线程跑DFS,
这样就不会阻塞主线程的事件队列了。

实现思路

那么大致分为几个模块

首先是图片模块,
这里搞的单例模式,
用于加载图片给所有人用。
为什么提前加载?
因为不这样做会很卡。。。

然后是地图格模块,
我感觉地图格在战棋游戏中还挺重要的,
所以把很多属性存进去了,
这里只是个DFS可视化,
所有只保留关键的一些属性。

然后是子线程模块,
主要作用是进行DFS和发送信号。

最后就是widget主窗口,
进行初始化地图,处理用户交互等工作。

Pixmaps

比较简单,比较作用就是加载和存储图片。
注意这里不能用饿汉模式,
图片的加载必须放在QApplication对象创建之后。

pixmaps.h
#ifndef PIXMAPS_H
#define PIXMAPS_H
#include <QPixmap>
class Pixmaps {static Pixmaps *getInstance();
private:Pixmaps();Pixmaps(const Pixmaps&) = delete;static void* _instance;
public:QPixmap map0, map1, map2, map3, dst, src;const int map_width = 100, map_height = 100,unit_width = 80, unit_height = 80;
};
#endif // PIXMAPS_H
pixmaps.cpp
#include "pixmaps.h"
void* Pixmaps::_instance = nullptr; // 这个不能放.h   --  会造成重定义问题// 报错会提示在其他包含了这个.h文件中, 重定义了_instance
Pixmaps* Pixmaps::getInstance(){if(_instance == nullptr){// 加锁。。。懒得加了if(_instance == nullptr){_instance = new Pixmaps;}}return (Pixmaps*)_instance;
}Pixmaps::Pixmaps(): map0("://image/map0.png"), map1("://image/map1.png"), map2("://image/map2.png"), map3("://image/map3.png"), map_src("://image/map_src.png"), map_dst("://image/map_dst.png")
{map0 = map0.scaled(map_width, map_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);map1 = map1.scaled(map_width, map_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);map2 = map2.scaled(map_width, map_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);map3 = map3.scaled(map_width, map_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);map_dst = map_dst.scaled(map_width, map_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);map_src = map_src.scaled(map_width, map_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}

单例,只是为了让别人更方便的用图片罢了,
所以不用搞太复杂 - - 至少这里不用。

MapSquare

地格类

mapsquare.h
#ifndef MAPSQUARE_H
#define MAPSQUARE_H
#include <QLabel>
#include "pixmaps.h"class MapSquare : public QLabel
{Q_OBJECT
public:MapSquare(QWidget *parent = nullptr);enum UNIT_TYPE{UNIT_TYPE_EMPTY = 0,UNIT_TYPE_PLAYER1 = 1,UNIT_TYPE_PLAYER2 = 2};enum STATUS{STATUS_UNCHECKABLE = -1,STATUS_EMPTY = 0,STATUS_MOVE = 1,STATUS_ATTACK = 2,};void setStatus(STATUS new_status, bool update_pixmap = true);void setUnitType(UNIT_TYPE new_type);void setIndex(int r, int c);void setSrc(int r = -1, int c = -1);void setUnit(void* unit = nullptr);STATUS status(){return _status;}UNIT_TYPE unit_type(){return _unit_type;}int r(){return index_r;}int c(){return index_c;}int src_r() {return _src_r;}int src_c() {return _src_c;}void* unit() {return _unit;}
private:UNIT_TYPE _unit_type;STATUS _status;int index_r, index_c;int _src_r = -1, _src_c = -1;void* _unit = nullptr;
};
#endif // MAPSQUARE_H

嗯,基本是直接cv过来的,enum都没改,
毕竟要改的话太麻烦了。

这里保留了我们需要用的,
_unit_type,即地格上面有什么,
无、阵营1(这里就是起点)、阵营2(这里就是终点)。

_status,即地格的状态,
不可达请添加图片描述、空请添加图片描述、可抵达 请添加图片描述、可攻击请添加图片描述(这里用来表示DFS找到的路径)。

r和c,表示地格在地图中的坐标。

_src_r、_src_c、_unit,这里不用管。

mapsquare.cpp
#include "mapsquare.h"MapSquare::MapSquare(QWidget *parent): QLabel(parent) {int w = Pixmaps::getInstance()->map_width,h = Pixmaps::getInstance()->map_height;setGeometry(-w, -h, w, h);setStatus(STATUS_EMPTY);_unit_type = UNIT_TYPE_EMPTY;
}void MapSquare::setStatus(STATUS new_status, bool update_pixmap){_status = new_status;if(update_pixmap){if(_status == STATUS_UNCHECKABLE){setPixmap(Pixmaps::getInstance()->map0);} else if(_status == STATUS_EMPTY){setPixmap(Pixmaps::getInstance()->map1);} else if(_status == STATUS_MOVE){setPixmap(Pixmaps::getInstance()->map2);} else if(_status == STATUS_ATTACK){setPixmap(Pixmaps::getInstance()->map3);}}
}void MapSquare::setUnitType(UNIT_TYPE new_type) {_unit_type = new_type;
}void MapSquare::setIndex(int r, int c) {index_r = r;index_c = c;
}void MapSquare::setSrc(int r, int c) {_src_r = r;_src_c = c;
}void MapSquare::setUnit(void *unit) {_unit = unit;
}

都是简单的赋值,应该不必多说吧。

dfsthread

用于DFS的线程,本篇的核心。

dfsthread.h
#ifndef DFSTHREAD_H
#define DFSTHREAD_H#include "mapsquare.h"
#include <QObject>
#include <QWidget>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>class DfsThread : public QThread
{Q_OBJECT
public:explicit DfsThread(QWidget *parent = nullptr);void init(QVector<QVector<MapSquare*>>& _map, int _r, int _c, int _max_step);public:void pause();void resume();void oneStep(bool);protected:void run() override;bool dfs(int r, int c, int step, int max_step);signals:void updateSquare(int r, int c, MapSquare::STATUS status);void dstNotFound();private:QVector<QVector<MapSquare*>>* map; // 这里要不断更改指向, 所以不能用引用QVector<QVector<bool>> visited;int src_r, src_c, max_step;int r_map, c_map;static const int dx[4];static const int dy[4];
private:bool one_step = false;bool paused = false;QMutex mutex;QWaitCondition pauseCond;
};#endif // DFSTHREAD_H

讲讲成员变量:
map用来存地图。
visited用来标记当前走过的路径。
src_r, src_c 就是起点坐标。
max_step 是最多走多少步。
r_map, c_map 是地图行、列的数量。
dx,dy 用于控制方向。

嗯,后面几个是用来控制线程的,
为了达到暂停、单步执行的效果,
用到了锁和条件变量。

dfsthread.cpp
run dfs
void DfsThread::run()
{{QMutexLocker locker(&mutex);while (paused) {pauseCond.wait(&mutex);}}if (!map) return;if (false == dfs(src_r, src_c, 0, max_step)){emit dstNotFound();}
}

先检查被暂停没有,然后开始DFS。

bool DfsThread::dfs(int r, int c, int step, int max_step)
{{QMutexLocker locker(&mutex);while (paused) {pauseCond.wait(&mutex);}}

同样,每次DFS前检查一下被暂停没有。

    if (step > max_step) return false;if (r < 0 || r >= r_map || c < 0 || c >= c_map) return false;if (visited[r][c]) return false;const QVector<QVector<MapSquare*>>& mapRef = *map;if (mapRef[r][c]->status() == MapSquare::STATUS_UNCHECKABLE) return false;visited[r][c] = true; // 标记为已访问

对当前地格是否可走进行判定,
如果可走,在visited中标记。
接下来的地格将不包括:请添加图片描述

    // 检查是否找到目标 (不同阵营)if (mapRef[r][c]->unit_type() != MapSquare::UNIT_TYPE_EMPTY &&mapRef[r][c]->unit_type() != mapRef[src_r][src_c]->unit_type()) {// 找到目标!emit updateSquare(r, c, MapSquare::STATUS_ATTACK);mapRef[r][c]->setSrc(src_r, src_c);QThread::msleep(300);return true;  // 成功找到路径}

如果找到目标,
发出信号将地格 请添加图片描述 标记为STATUS_ATTACK 请添加图片描述
并开始返回。

    if(mapRef[r][c]->status() != MapSquare::STATUS_MOVE){emit updateSquare(r, c, MapSquare::STATUS_MOVE);mapRef[r][c]->setSrc(src_r, src_c);if(one_step) {pause();} else {QThread::msleep(50); // 暂停一段时间}}

如果没找到,
发出信号将地格 请添加图片描述 标记为STATUS_MOVE 请添加图片描述
并判断是否是单步执行,
如果是,直接暂停线程,
如果不是,那么休眠50ms,
展现出地格是一步步被改变的,
而不是一下就dfs完了。

    for (int i = 0; i < 4; i++) {int x = dx[i] + r;int y = dy[i] + c;if (dfs(x, y, step + 1, max_step)) {// 到了这里, 说明找到了// 那么接着修改地格状态, 并返回 trueemit updateSquare(r, c, MapSquare::STATUS_ATTACK);QThread::msleep(300);return true;}}// 恢复状态visited[r][c] = 0;return false;
}
其他
#include "dfsthread.h"
#include <QDebug>const int DfsThread::dx[4] = {0, 0, 1, -1};
const int DfsThread::dy[4] = {1, -1, 0, 0};DfsThread::DfsThread(QWidget *parent): QThread(parent), map(nullptr)
{
}void DfsThread::init(QVector<QVector<MapSquare*>>& _map, int _r, int _c, int _max_step){map = &_map;src_r = _r;src_c = _c;max_step = _max_step;r_map = _map.size();c_map = _map[0].size();// 这里每次都得重新设置一下visited = QVector<QVector<bool>>(r_map, QVector<bool>(c_map, false));
}void DfsThread::pause(){QMutexLocker locker(&mutex);paused = true;
}void DfsThread::resume(){QMutexLocker locker(&mutex);paused = false;pauseCond.wakeAll();
}void DfsThread::oneStep(bool o){one_step = o;
}

我试了一下,
似乎不加锁和条件变量也行,
不过为了安全还是加上吧。

Widget

UI界面史中史,设计得就是依托。

完整的可以去我的代码仓库看,
这里就不列出来了。

Unit
struct Unit : public QPushButton{Unit(QWidget *parent=nullptr): QPushButton(parent){}int r = -1, c = -1;int move_range = 5;MapSquare::UNIT_TYPE unit_type;
};

这个类本来单独在一个文件里的,被我合过来了。

关于这个类,我写了几个函数:

void initUnit(Unit* unit, MapSquare::UNIT_TYPE unit_type);
void moveUnit(Unit* unit, int r, int c);
void connectUnit(Unit* &unit);

一个是初始化,
一个是移动,
一个是连接信号和槽(继承自按钮,可点击)。

initUnit :

void Widget::initUnit(Widget::Unit *unit, MapSquare::UNIT_TYPE unit_type)
{if(unit_type == MapSquare::UNIT_TYPE_PLAYER1)unit->setIcon(Pixmaps::getInstance()->map_src);else if(unit_type == MapSquare::UNIT_TYPE_PLAYER2)unit->setIcon(Pixmaps::getInstance()->map_dst);elseqDebug() << "初始化单位为未定义类型";unit->setIconSize(QSize(80, 80));unit->setStyleSheet("border : transparent;");unit->unit_type = unit_type;
}

传入一个Unit*,和单位类型,
就能初始化一个单位。

moveUnit

void Widget::moveUnit(Widget::Unit *unit, int r, int c)
{if(unit->r != -1 && unit->c != -1) {map[unit->r][unit->c]->setUnitType(MapSquare::UNIT_TYPE_EMPTY);map[unit->r][unit->c]->setUnit();}unit->setGeometry(map[r][c]->geometry());unit->r = r;unit->c = c;unit->raise();map[r][c]->setUnitType(unit->unit_type);map[r][c]->setUnit(unit);
}

首先得判断是否刚初始化,
如果已经设置过坐标,
那么得先把原地格的状态清空,
(感觉还能封装,地格可以提供一个状态清空函数)
然后才能移动单位,
并重新设置相关属性。

connectUnit

void Widget::connectUnit(Widget::Unit* &unit)
{connect(unit, &QPushButton::clicked, this, [&](){qDebug() << "unit clicked ...";if(dfsThreadIsRunning()) return;MapSquare::STATUS _status = map[unit->r][unit->c]->status();if(_status == MapSquare::STATUS_EMPTY) {resetMap(); // 重置地图dfsThread.init(map, unit->r, unit->c, unit->move_range); // 每次都要初始化dfsThread.start(); // 不能用run, 会阻塞主线程} else {resetMap(); // 重置地图}});
}

单位被点击,
如果当前地格状态为空,
则开始DFS。
如果地格状态为其他的,
暂时不做处理。

其他
connect(&dfsThread, &DfsThread::updateSquare, this, [this](int r, int c, MapSquare::STATUS status) {qDebug() << "子线程来信号了!" << status;map[r][c]->setStatus(status);
});

这个放在构造函数里,
子线程发来信号,
主线程进行地格的状态更新。

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/77878.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/77878.shtml
英文地址,请注明出处:http://en.pswp.cn/web/77878.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RHCSA Linux 系统 文件系统权限

1. 文件的一般权限 &#xff08;1&#xff09;文件权限标识解读 drwxr - xr - x. 12 root root 144 Feb 17 16:51 usr ➤d&#xff1a;文件类型&#xff08;d 表示目录&#xff09; ➤rwx&#xff1a;文件所有者权限&#xff08;读 r&#xff0c;写 w&#xff0c;执行 x&am…

华为云IoT平台与MicroPython实战:从MQTT协议到物联网设备开发

目录 前言 1. 华为云 1.1. 创建实例 1.2. 创建产品 1.3. 编辑服务模型 1.4. 注册设备 1.4.1. 复制设备连接参数 1.5. 连接参考代码 2. micropython版-物联网 2.1. 环境搭建 2.2. 实现步骤 2.3. 示例代码 结语 前言 物联网&#xff08;IoT&#xff09;技术的快速发…

2025-04-30 AIGC-如何做短片视频

摘要: 2025-04-30 AIGC-如何做短片视频 如何做短片视频: 一、画图修图 1.保存视频&#xff08;无水保存&#xff09; 2.文案提取&#xff08;提取文案&#xff09; 3. DeepSeek(提示词&#xff09; 4.小梦Ai&#xff08;图片视频&#xff09; 5.修图Ai 6.扩图Ai 7.养生…

硬件工程师面试常见问题(10)

第四十六问&#xff1a;锁存器&#xff0c;触发器&#xff0c;寄存器三者的区别 触发器&#xff1a;能够存储一位二值信号的基本单元电路统称为 "触发器"。&#xff08;单位&#xff09; 锁存器&#xff1a;一位触发器只能传送或存储一位数据&#xff0c;而在实际工…

外部访问 Kubernetes 集群中 MQ 服务的方案

外部访问 Kubernetes 集群中 MQ 服务的方案 当您在 Kubernetes 集群中部署了消息队列服务&#xff08;如 RabbitMQ、Kafka、ActiveMQ 等&#xff09;后&#xff0c;以下是外部客户端访问这些服务的几种可靠方法&#xff1a; 一、基础访问方案 1. NodePort 方式暴露服务 # M…

论文笔记(八十二)Transformers without Normalization

Transformers without Normalization 文章概括Abstract1 引言2 背景&#xff1a;归一化层3 归一化层做什么&#xff1f;4 动态 Tanh &#xff08;Dynamic Tanh (DyT)&#xff09;5 实验6 分析6.1 DyT \text{DyT} DyT 的效率6.2 tanh \text{tanh} tanh 和 α α α 的消融实验…

软考中级-软件设计师 操作系统(手写笔记)

第一章&#xff1a;基础知识 第二章&#xff1a;进程管理 状态转换图 进程同步机制 信号量机制 信号量题 死锁 第三章&#xff1a;存储管理 基础知识 分页存储管理 分段存储管理 段页式存储管理 页面置换算法 第四章&#xff1a;文件管理 基础知识 索引分配 空闲存储空间的管…

ubuntu 部署moodle

通过地址https://download.moodle.org/releases/latest/选择下载&#xff0c;下载两种压缩包都特别慢&#xff08;有可能无法下载&#xff09;。 可以使用下面git下载项目 注意图中php、mysql等版本要求&#xff0c;本次采用Ubuntu22.04下 nginxphp8.2mysql8.4部署 mkdir /var…

python实战项目67:空气质量在线检测平台js逆向

python实战项目67:空气质量在线检测平台js逆向 一、需求介绍二、完整代码一、需求介绍 项目需求是获取某个城市(以北京市为例)历年(2013年12月至2025年4月)的空气质量数据,字段包括日期、AQI、质量等级、PM2.5、PM10、NO2、CO、SO2等。改网站的网址是“https://www.aqis…

【Linux】记录一个有用PS1

PS1 是用来定义shell提示符的环境变量 下面是一个带有颜色和丰富信息的 Linux PS1 配置示例&#xff0c;包含用户名、主机名、路径、时间、Git 分支和退出状态提示&#xff1a; # 添加到 ~/.bashrc 文件末尾 PS1\[\e[1;32m\]\u\[\e[m\] # 绿色粗体用户名 PS…

Python PyTorch库【机器学习框架】全面深入讲解与实践

一、PyTorch 核心概念 1. 定义与发展背景 PyTorch 是由 Facebook AI Research (FAIR) 开发的开源机器学习框架&#xff0c;2016 年首次发布。其核心特性包括&#xff1a; 动态计算图&#xff08;Define-by-Run&#xff09;GPU 加速张量计算自动微分系统丰富的神经网络模块 …

呼叫中心座席管理系统:智能升级,高效服务

在数字化转型加速的今天&#xff0c;客户服务体验已成为企业竞争力的核心要素。传统 呼叫中心系统 依赖硬件设备、人工操作的模式已无法满足高效、智能、灵活的现代企业需求。畅信达呼叫中心 座席管理系统 V5.0应运而生&#xff0c;以WEBRTC软电话接入、智能座席辅助、知识库管…

时态--00--总述

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 时态句子结构时态标志词 时态 句子结构 时态标志词

算法每日一题 | 入门-顺序结构-字母转换

字母转换 题目描述 输入一个小写字母&#xff0c;输出其对应的大写字母。例如输入 q[回车] 时&#xff0c;会输出 Q。 输入格式 无 输出格式 无 输入输出样例 #1 输入 #1 q输出 #1 QC 首先我们要知道&#xff0c;C字符的所有转换形式都是依照ASCII码来的。 所以&…

晶振:从消费电子到航天领域的时间精度定义者

从手表到卫星&#xff1a;晶振如何在不同领域定义时间精度 在时间的长河中&#xff0c;人类对时间精度的追求永无止境。从古老的日晷到如今精密的计时仪器&#xff0c;每一次进步都离不开技术的革新。而晶振&#xff0c;作为现代计时的核心元件&#xff0c;在不同领域发挥着至…

短视频矩阵系统贴牌开发实战:批量剪辑文件夹功能设计与实现

摘要&#xff1a;在短视频矩阵系统的开发中&#xff0c;批量处理功能是提升运营效率的关键。本文将深入探讨如何实现基于文件夹的短视频批量剪辑功能&#xff0c;涵盖技术选型、核心功能实现及代码示例。 一、需求背景与场景价值 在短视频矩阵运营场景中&#xff0c;运营者常面…

读书笔记--华为从偶然到必然之创新与技术开发阅读有感

最近继续阅读一本讲述华为研发投资与管理实践方面的书籍&#xff0c;分享给大家。华为在创新与技术研发方面有体系化、系统化和延续性。创新是企业的生命线&#xff0c;是企业发展的不竭动力&#xff0c;同时将企业文化与创新精神进行了融合&#xff0c;华为的企业文化强调以客…

基于DeepSeek与HTML的可视化图表创新研究

一、研究背景 在当今数字化时代&#xff0c;数据呈指数级增长&#xff0c;广泛渗透于社会各个领域。无论是商业运营、科学研究&#xff0c;还是公共管理等方面&#xff0c;海量数据蕴含着丰富的潜在价值&#xff0c;成为驱动决策优化、推动业务发展、促进科学创新的关键要素。数…

K8S - 命名空间实战 - 从资源隔离到多环境管理

引言 在传统的物理机或虚拟机环境中&#xff0c;不同业务应用共享资源&#xff0c;容易导致权限冲突、资源争用和管理混乱。Kubernetes 通过 命名空间&#xff08;Namespace&#xff09;实现资源逻辑隔离&#xff0c;将集群划分为多个虚拟子集群&#xff0c;从而解决以下问题&…

Unity3D仿星露谷物语开发40之割草动画

1、目标 当Player选择Scythe后&#xff0c;鼠标悬浮在草上&#xff0c;会显示绿色光标。鼠标左击&#xff0c;会触发割草的动画。 2、优化Settings.cs脚本 添加以下两行代码&#xff1a; // Reaping&#xff08;收割&#xff09; public const int maxCollidersToTestPerRe…