1.A*算法
A*算法
A*算法通过下面这个函数来计算每个节点的优先级。
其中:
- f(n)是节点n的综合优先级。当我们选择下一个要遍历的节点时,我们总会选取综合优先级最高(值最小)的节点。
- g(n) 是节点n距离起点的代价。
- h(n)是节点n距离终点的预计代价,这也就是A*算法的启发函数。关于启发函数我们在下面详细讲解。
A*算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点。
2.迷宫的生成算法
首先,将地图分割成若干相互隔离的小格,将起点设置成扩展状态,然后从起点开始,向外进行扩展,同时使用随机函数选择一个可行的墙面进行打通,这样就构成了迷宫的地图
算法实现:
A*算法
这里f代表从起点开始到当前位置走过的步数,h表示当前节点到终点的曼哈顿距离,这样,总估价就是g=f+h,通过C++ stl中的优先队列进行维护;
bool flag=false;
QPair<int,int> pre[200][200];
int mpback[200][200];
struct node{
int xnow,ynow,f,h,g;
node(){
xnow=ynow=f=h=g=0;
}
bool operator < (const node& tmp)const{
return g<tmp.g;
}
node(int x,int y,int ft,int ht,int gt){
xnow=x,ynow=y,f=ft,h=ht,g=gt;
}
};
void mazeWidget::on_Astar_btn_clicked() {
std::priority_queue<node> q;
this->setFocus();
if(flag)return;
memset(pre,0,sizeof(pre));
map->map[map->p_x][map->p_y]=5;
memcpy(mpback,map->map,sizeof(map->map));
q.push(node(map->p_x,map->p_y,0,0,0));
while(q.size()){
node tmpdata=q.top();
q.pop();
for(int i=0;i<4;i++){
int dx=tmpdata.xnow+dir[i][0],dy=tmpdata.ynow+dir[i][1];
if(mpback[dx][dy]==0||mpback[dx][dy]==-1||mpback[dx][dy]==9)continue;
pre[dx][dy]=qMakePair(tmpdata.xnow,tmpdata.ynow);
if(mpback[dx][dy]==6){
flag=true;
break;
}
mpback[dx][dy]=9;
node topush;
topush.xnow=dx,topush.ynow=dy;
topush.f=tmpdata.f+1;
topush.h=manha(dx,dy);
topush.g=topush.h+topush.f;
q.push(topush);
}
if(flag) break;
}
int tmpx=map->start_x,tmpy=map->start_y;
while(map->map[tmpx][tmpy]!=5){
map->map[tmpx][tmpy]=5;
int a=pre[tmpx][tmpy].first,b=pre[tmpx][tmpy].second;
tmpx=a,tmpy=b;
}
map->map[map->start_x][map->start_y]=6;
repaint();
return;
}
运行结果:
在起点进行自动寻路
在移动过程中进行A*寻路
可以设置难度:
完整源代码:
maze.h
#ifndef MAZE_H
#define MAZE_H
#include <iostream>
/**************************************************
* -1:边界 0:墙壁
* 1:空单元(初始化后未判定的单元)
* 2:待定墙壁(新建已定单元时建立)
* 3:已定单元(确定迷宫树已延伸到的单元)
* 4:打通墙壁(延伸操作)
* 5.起点
* 6.终点
* 7.已经过路径
* **************************************************/
class maze {
private:
int para;
void base();
void expandfunc(int i, int j);
void start() ;
bool judge();
void judgement();
void operation(int i, int j);
void randomopt();
public:
maze(int in_level);
~maze();
//获取地图
int start_x, start_y;
int getlevel() ;
int map[200][200];
int** getmap();
int getside();
void makemap();
int p_x, p_y;
void rebuildmap();
int* operator[](int index);
};
#endif // MAZE_H
maze.cpp
#include "maze.h"
void maze::base() {
for (int i = 0; i < para * 2 + 1; i++) {
for (int j = 0; j < para * 2 + 1; j++) {
if (i == 0 || j == 0 || i == para * 2 + 1 - 1 ||
j == para * 2 + 1 - 1) {
map[i][j] = -1;
} else if (i % 2 != 0 && j % 2 != 0) {
map[i][j] = 1;
} else {
map[i][j] = 0;
}
}
}
}
void maze:: expandfunc(int i, int j) {
if (map[i - 1][j] == 0) {
map[i - 1][j] = 2;
}
if (map[i + 1][j] == 0) {
map[i + 1][j] = 2;
}
if (map[i][j - 1] == 0) {
map[i][j - 1] = 2;
}
if (map[i][j + 1] == 0) {
map[i][j + 1] = 2;
}
}
void maze:: start() {
map[start_x][start_y] = 5;
expandfunc(start_x, start_y);
}
bool maze:: judge() {
bool flag = 0;
for (int i = 0; i < para * 2 + 1; i++) {
for (int j = 0; j < para * 2 + 1; j++) {
if (map[i][j] == 2) {
flag = 1;
return flag;
}
}
}
return flag;
}
void maze:: operation(int i, int j) {
if ((map[i - 1][j] == 3 || map[i - 1][j] == 5) && map[i + 1][j] == 1) {
map[i][j] = 4;
map[i + 1][j] = 3;
expandfunc(i + 1, j);
start_x = i + 1;
start_y = j;
} else if ((map[i][j - 1] == 3 || map[i][j - 1] == 5) &&
map[i][j + 1] == 1) {
map[i][j] = 4;
map[i][j + 1] = 3;
expandfunc(i, j + 1);
start_x = i;
start_y = j + 1;
} else if ((map[i + 1][j] == 3 || map[i + 1][j] == 5) &&
map[i - 1][j] == 1) {
map[i][j] = 4;
map[i - 1][j] = 3;
expandfunc(i - 1, j);
start_x = i - 1;
start_y = j;
} else if ((map[i][j + 1] == 3 || map[i][j + 1] == 5) &&
map[i][j - 1] == 1) {
map[i][j] = 4;
map[i][j - 1] = 3;
expandfunc(i, j - 1);
start_x = i;
start_y = j - 1;
} else {
map[i][j] = 0;
}
}
void maze::judgement(){
if (!judge()) {
map[start_x][start_y] = 6;
}
return;
}
void maze:: randomopt() {
int t = 0;
for (int i = 0; i < para * 2 + 1; i++) {
for (int j = 0; j < para * 2 + 1; j++) {
if (map[i][j] == 2) {
t++;
}
}
}
int k = rand() % t + 1;
t = 0;
for (int i = 0; i < para * 2 + 1; i++) {
for (int j = 0; j < para * 2 + 1; j++) {
if (map[i][j] == 2) {
t++;
if (t == k) {
operation(i, j);
judgement();
return;
}
}
}
}
}
maze:: maze(int in_level):para(in_level) {
start_x = 1, start_y = 1;
}
maze::~maze() {
}
int maze:: getlevel() {
return maze::para;
}
int maze::getside() {
return para * 2 + 1;
}
void maze:: makemap() {
p_x = start_x;
p_y = start_y;
base();
start();
int a = 0;
while (judge()) {
a++;
randomopt();
}
}
int* maze::operator[](int index) {
return map[index];
}
void maze::rebuildmap() {
start_x = 1;
start_y = 1;
makemap();
}
mazewidget.h
#ifndef MAZEWIDGET_H
#define MAZEWIDGET_H
#include <QWidget>
#include <QMessageBox>
#include <QPainter>
#include <QDebug>
#include <QKeyEvent>
#include <QTimer>
#include <QInputDialog>
#include "maze.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class mazeWidget;
}
QT_END_NAMESPACE
class mazeWidget : public QWidget {
Q_OBJECT
public:
mazeWidget(QWidget* parent = nullptr);
int manha(int x,int y);
void print();
~mazeWidget();
protected:
void paintEvent(QPaintEvent*);
void keyPressEvent(QKeyEvent*);
private slots:
void on_start_btn_clicked();
void on_stop_ptn_clicked();
void on_end_btn_clicked();
void on_setting_btn_clicked();
void on_Astar_btn_clicked();
void time_update();
private:
Ui::mazeWidget* ui;
maze* map;
bool painting_switch;
bool timing_switch;
bool keybord_switch;
bool stop_switch;
int grade;
int time;
QTimer* timer;
};
#endif // MAZEWIDGET_H
mazewidget.cpp
#include "mazewidget.h"
#include "ui_mazewidget.h"
#include <QVector>
#include <QThread>
#include <QPair>
#include <bits/stdc++.h>
#include <iostream>
int dir[4][2]={
{0,-1},
{0,1},
{-1,0},
{1,0}
};
bool flag=false;
QPair<int,int> pre[200][200];
int mpback[200][200];
struct node{
int xnow,ynow,f,h,g;
node(){
xnow=ynow=f=h=g=0;
}
bool operator < (const node& tmp)const{
return g>tmp.g;
}
node(int x,int y,int ft,int ht,int gt){
xnow=x,ynow=y,f=ft,h=ht,g=gt;
}
};
mazeWidget::mazeWidget(QWidget* parent)
: QWidget(parent)
, ui(new Ui::mazeWidget), map(new maze(20)), painting_switch(false), timing_switch(false)
, keybord_switch(false), grade(0), time(0) {
ui->setupUi(this);
ui->progressBar->setVisible(false);
ui->end_btn->setEnabled(false);
ui->stop_ptn->setEnabled(false);
ui->grade_value->setText(" ");
ui->time_value->setText(" ");
map->makemap();
timer=new QTimer(this);
connect(timer, &QTimer::timeout, this, &mazeWidget::time_update);
}
mazeWidget::~mazeWidget() {
delete ui;
delete map;
delete timer;
}
void mazeWidget::paintEvent(QPaintEvent*) {
if(!painting_switch) return;
QPainter painter(this); //画笔对象
//绘图逻辑:
int perblock = (std::min(ui->frame->width(), ui->frame->height()) - 20) / (map->getside());
int start_x = ui->frame->x() + (ui->frame->width() - (ui->frame->x() + (map->getside()) * perblock)) / 2;
int strat_y = ui->frame->y() + (ui->frame->height() - (ui->frame->y() + (map->getside()) * perblock)) / 2;
for(int i = 0; i < map->getlevel() * 2 + 1; i++) {
for(int j = 0; j < map->getlevel() * 2 + 1; j++) {
if(i == map->p_x && j == map->p_y) {
painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::red));
} else if(map->map[i][j] == 7) {
painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::yellow));
} else if(map->map[i][j] == 3 || map->map[i][j] == 4) {
painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::gray));
} else if(map->map[i][j] == 5) {
painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::blue));
} else if(map->map[i][j] == 6) {
painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::green));
} else {
painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::black));
}
}
}
}
void mazeWidget::keyPressEvent(QKeyEvent* event) {
if(!keybord_switch) return;
int x = map->p_x;
int y = map->p_y;
map->map[x][y]=7;
if(event->key() == Qt::Key_Up || event->key() == Qt::Key_W) {
if((*map)[x][y - 1] == 3 || (*map)[x][y - 1] == 4 || (*map)[x][y - 1] == 5 || (*map)[x][y - 1] == 6 || (*map)[x][y - 1] == 7) {
map->p_y--;
}
} else if(event->key() == Qt::Key_Down || event->key() == Qt::Key_S) {
if((*map)[x][y + 1] == 3 || (*map)[x][y + 1] == 4 || (*map)[x][y + 1] == 5 || (*map)[x][y + 1] == 6 || (*map)[x][y + 1] == 7) {
map->p_y++;
}
} else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_A) {
if((*map)[x - 1][y] == 3 || (*map)[x - 1][y] == 4 || (*map)[x - 1][y] == 5 || (*map)[x - 1][y] == 6 || (*map)[x - 1][y] == 7) {
map->p_x--;
}
} else if(event->key() == Qt::Key_Right || event->key() == Qt::Key_D) {
if((*map)[x + 1][y] == 3 || (*map)[x + 1][y] == 4 || (*map)[x + 1][y] == 5 || (*map)[x + 1][y] == 6 || (*map)[x + 1][y] == 7) {
map->p_x++;
}
}
if((*map)[map->p_x][map->p_y] != 5 && (*map)[map->p_x][map->p_y] != 6)(*map)[map->p_x][map->p_y] = 7;
repaint();
if((*map)[map->p_x][map->p_y] == 6) {
map->makemap();
repaint();
flag=false;
grade += pow(map->getlevel(), 2);
ui->grade_value->setText(QString::number(grade));
}
map->map[map->p_x][map->p_y]=5;
}
void mazeWidget::time_update() {
if(time != 0) {
time--;
ui->time_value->setText(QString::number(time));
ui->progressBar->setValue(time / 2);
} else {
timer->stop();
ui->progressBar->setVisible(false);
keybord_switch = false;
painting_switch = false;
timing_switch = false;
repaint();
ui->start_btn->setEnabled(true);
ui->time_value->setText(" ");
ui->grade_value->setText(" ");
ui->stop_ptn->setEnabled(false);
ui->end_btn->setEnabled(false);
ui->setting_btn->setEnabled(true);
QMessageBox outgrade(QMessageBox::NoIcon, "恭喜", "您得分:" + QString::number(grade), QMessageBox::Ok);
outgrade.exec();
grade = 0;
}
}
void mazeWidget::on_start_btn_clicked() {
painting_switch = true;
timing_switch = true;
keybord_switch = true;
flag=false;
time = 200;
this->setFocus();
timer->start(1000);
ui->progressBar->setVisible(true);
ui->progressBar->setValue(100);
ui->Astar_btn->setEnabled(true);
repaint();
ui->time_value->setText(QString::number(time));
ui->grade_value->setText(QString::number(grade));
ui->start_btn->setEnabled(false);
ui->stop_ptn->setEnabled(true);
ui->end_btn->setEnabled(true);
ui->setting_btn->setEnabled(false);
}
void mazeWidget::on_stop_ptn_clicked() {
this->setFocus();
if(stop_switch) {
timing_switch = false;
keybord_switch = false;
timer->stop();
ui->stop_ptn->setText("继续");
stop_switch = false;
} else {
timing_switch = true;
keybord_switch = true;
timer->start();
ui->stop_ptn->setText("暂停");
stop_switch = true;
}
}
void mazeWidget::on_end_btn_clicked() {
timing_switch = false;
painting_switch = false;
keybord_switch = false;
stop_switch = false;
timer->stop();
this->setFocus();
time = 0;
grade = 0;
ui->progressBar->setVisible(false);
ui->grade_value->setText(" ");
ui->time_value->setText(" ");
ui->stop_ptn->setText("暂停");
ui->stop_ptn->setEnabled(false);
ui->end_btn->setEnabled(false);
ui->start_btn->setEnabled(true);
ui->setting_btn->setEnabled(true);
ui->Astar_btn->setEnabled(false);
map->rebuildmap();
repaint();
}
void mazeWidget::on_setting_btn_clicked() {
QStringList difficultys;
this->setFocus();
difficultys << tr("简单") << tr("普通") << tr("中等") << tr("困难");
QString difficulty = QInputDialog::getItem(this, tr("选择难度"),
tr("请选择一个条目"), difficultys, 0, false);
if(difficulty == tr("简单")) {
delete map;
map = new maze(5);
map->makemap();
} else if(difficulty == tr("普通")) {
delete map;
map = new maze(10);
map->makemap();
} else if(difficulty == tr("中等")) {
delete map;
map = new maze(20);
map->makemap();
} else if(difficulty == tr("困难")) {
delete map;
map = new maze(40);
map->makemap();
}
}
int mazeWidget::manha(int x,int y){
return abs(x-map->start_x)+abs(y-map->start_y);
}
void mazeWidget::on_Astar_btn_clicked() {
std::priority_queue<node> q;
this->setFocus();
if(flag)return;
int len=map->getlevel();
for(int i=0;i<=2*len;i++){
for(int j=0;j<=2*len;j++){
if(map->map[i][j]==7)
map->map[i][j]=4;
}
}
memset(pre,0,sizeof(pre));
map->map[map->p_x][map->p_y]=5;
memcpy(mpback,map->map,sizeof(map->map));
q.push(node(map->p_x,map->p_y,0,0,0));
while(q.size()){
node tmpdata=q.top();
q.pop();
for(int i=0;i<4;i++){
int dx=tmpdata.xnow+dir[i][0],dy=tmpdata.ynow+dir[i][1];
if(mpback[dx][dy]==0||mpback[dx][dy]==-1||mpback[dx][dy]==9)continue;
pre[dx][dy]=qMakePair(tmpdata.xnow,tmpdata.ynow);
if(mpback[dx][dy]==6){
flag=true;
break;
}
mpback[dx][dy]=9;
node topush;
topush.xnow=dx,topush.ynow=dy;
topush.f=tmpdata.f+1;
topush.h=manha(dx,dy);
topush.g=topush.h+topush.f;
q.push(topush);
}
if(flag) break;
}
int tmpx=map->start_x,tmpy=map->start_y;
while(map->map[tmpx][tmpy]!=5){
map->map[tmpx][tmpy]=5;
int a=pre[tmpx][tmpy].first,b=pre[tmpx][tmpy].second;
tmpx=a,tmpy=b;
}
map->map[map->start_x][map->start_y]=6;
repaint();
return;
}
main.cpp
#include "mazewidget.h"
#include "maze.h"
#include <QApplication>
#include <QStyleFactory>
int main(int argc, char* argv[]) {
srand(time(NULL));
QApplication a(argc, argv);
a.setStyle(QStyleFactory::create("Fusion"));
mazeWidget w;
w.show();
return a.exec();
}