(c + +) maze automatic routing - queue - breadth first algorithm - with routing printing animation

0. Summary

1. Renderings

  

The square represents the obstacle, the solid diamond represents the mover (person), and the hollow diamond represents the target position (all can be modified in the code)

  2. This example uses queue (linked list implementation) to perform automatic routing with breadth first.

1. Implementation code

1. Queue method class

#pragma once
#include <iostream>
using namespace std;
//queue

//Coordinate structure
struct Point
{
    int x;
    int y;
    Point()
    {
        x = 0;
        y = 0;
    }
    Point(int in_x, int in_y)
    {
        x = in_x;
        y = in_y;
    }

    Point& operator=(const Point& right_p)
    {
        this->x = right_p.x;
        this->y = right_p.y;
        return *this;
    }
};

//Queue structure
struct coolQueue
{
    int data;
    Point cool_p;
    coolQueue* next_p;

    coolQueue(int in_data)
    {
        data = in_data;
        next_p = NULL;
    }

    coolQueue(int in_x, int in_y, int in_data = 0)
    {
        cool_p.x = in_x;
        cool_p.y = in_y;
        data     = in_data;
        next_p   = NULL;
    }
};

//Queue method class, restricted access
class queueClass
{
protected:
    coolQueue* Head_p = NULL;
    coolQueue* End_p  = NULL;

public:
    queueClass() {}

    void push(int data);        //Join the team
    void push(int in_x, int in_y, int in_data = 0);
    bool pop(int& re_data);        //Out of the team
    bool pop(coolQueue& in_coolQueue);
    void reverse_order();        //Flip

    void clear()
    {
        for (int data; pop(data););
    }

    ~queueClass()
    {
        clear();
    }
};

 

  

#include "coolQueue.h"

/*Queue function
* Transmission parameters:
*    in_data: Queue data
*/
void queueClass::push(int in_data)
{
    if (Head_p == NULL)            //Queue is empty
    {
        Head_p = new coolQueue(in_data);
        End_p  = Head_p;
    }
    else if(Head_p == End_p)    //The queue has only one element
    {
        End_p = new coolQueue(in_data);
        Head_p->next_p = End_p;
    }
    else
    {
        coolQueue* temp_p = new coolQueue(in_data);
        End_p->next_p      = temp_p;
        End_p              = temp_p;
    }
}

/*Out of the team
* Transmission parameters:
*    re_data: Receive outgoing return value
* Return value:
*    If the queue is empty, false is returned
* 
* Write value to re_ Return in data
*/
bool queueClass::pop(int& re_data)
{
    if (Head_p == NULL)        //Queue is empty
        return false;
    re_data = Head_p->data;
    coolQueue* temp_p = Head_p;
    if (Head_p == End_p)    //The queue has only one element
    {
        Head_p = NULL;
        End_p = NULL;
    }
    else
        Head_p = Head_p->next_p;
    delete temp_p;
    return true;
}

/*The same linked list adopts the head insertion method with tail pointer as the head to realize reverse order
*/
void queueClass::reverse_order()
{
    if (Head_p == NULL || Head_p == End_p)
        return;

    coolQueue* p = Head_p, * temp_p;
    do {
        temp_p = p;
        p = p->next_p;
        temp_p->next_p = End_p->next_p;
        End_p->next_p = temp_p;
    } while (p != End_p);
    p = Head_p;
    Head_p = End_p;
    End_p = p;
}

//The following overloads are used to assist automatic pathfinding
//in_data = 0
void queueClass::push(int in_x, int in_y, int in_data)
{
    if (Head_p == NULL)
    {
        Head_p = new coolQueue(in_x, in_y, in_data);
        End_p = Head_p;
    }
    else if (Head_p == End_p)
    {
        End_p = new coolQueue(in_x, in_y, in_data);
        Head_p->next_p = End_p;
    }
    else
    {
        coolQueue* temp_p = new coolQueue(in_x, in_y, in_data);
        End_p->next_p = temp_p;
        End_p = temp_p;
    }
}

bool queueClass::pop(coolQueue& in_coolQueue)
{
    if (Head_p == NULL)
        return false;
    in_coolQueue.data   = Head_p->data;        //Do not use replication directly because it is possible to Head_p of next_p It is also copied out, resulting in the invalidation of restricted access rights
    in_coolQueue.cool_p = Head_p->cool_p;
    coolQueue* temp_p = Head_p;
    if (Head_p == End_p)
    {
        Head_p = NULL;
        End_p = NULL;
    }
    else
        Head_p = Head_p->next_p;
    delete temp_p;
    return true;
}

2. Map method class

#pragma once
#include "coolQueue.h"
#include "windows.h"
#include <cmath>

using namespace std;

#ifndef PI
#define PI 3.14159265358979323846
#endif // !PI

#ifndef Sleep_num
#define Sleep_num 500
#endif // !Pause time when printing and outputting map

#ifndef Map_size
#define Map_size 10
#endif // !Map size

/*Map operation class
* Protect the inheritance queue from external calls to the queue's functions
*/
class mapClass : protected queueClass
{
protected:
    int map[Map_size][Map_size];    //Map
    Point persion_p;                //Starting point position coordinates

    void new_map();

    void reflash_map();

    bool auto_find_way(Point& target_p);

    void auto_move(int in_x, int in_y);

public:
    mapClass(const Point& in_p);

    bool auto_main();

    void into_map(int in_data, int num = 1);

    bool into_map(int in_x, int in_y, int in_data);

    void show_map();

    void clear_map(const Point& in_p);
};
#include "mapClass.h"

/*Initialize map
* 
* Set the boundary after setting the map to zero
*/
void mapClass::new_map()
{
    memset(map, 0, Map_size * Map_size);//Clear
    for (int num = Map_size; num--;)    //Set up edge obstacles
    {
        map[num][0] = 1;
        map[0][num] = 1;
        map[num][Map_size - 1] = 1;
        map[Map_size - 1][num] = 1;
    }
}

/*Refresh map
* 
* Because in auto_find_way() modifies the values in the map as direction markers
* Some marks will remain after the move. This function will clean up these marks (that is, set the marks back to 0)
*/
void mapClass::reflash_map()
{
    for (int x = Map_size - 1; --x;)
        for (int y = Map_size - 1; --y;)
            map[x][y] = map[x][y] % 1000;
    /*The direction marks are 100020003000 and 4000, so% 1000 can keep other things and clean the marks*/
}

/*Automatic pathfinding
* 
* Transmission parameters:
*    &target_p: Outgoing parameter, write the coordinates of the target after finding the path
* Return value:
*    A path returns true, but no false
* 
* Finding the optimal path to the target based on the queue will leave direction marks on the map
* If found, in other functions, you can start at the target position and push back to the starting point through the direction mark, which is the path
*/
bool mapClass::auto_find_way(Point& target_p)
{
    coolQueue out_queue(0, 0, 0);    
    for (int push_num = 1; push_num;)
    {
        push_num = 0;        //If push_num stay while It is still 0 after the loop, indicating that the queue is empty and there is no way to go
        while (this->pop(out_queue))
        {
            for (int i = 1, temp_x, temp_y; i <= 4; ++i)//Judge the four positions next to it
            {    //Use here sin()In order to be different i Time(temp_x, temp_y)Point to out_queue Different directions centered
                //Same effect auto_move()Medium switch()Use of
                temp_x = out_queue.cool_p.x + int(sin(PI / 2 * (i - 2.0)));
                temp_y = out_queue.cool_p.y + int(sin(PI / 2 * (i - 3.0)));
                switch (map[temp_x][temp_y])
                {
                case 0: //Can go
                {
                    map[temp_x][temp_y] = i * 1000;        //Write direction mark
                    this->push(temp_x, temp_y, 0);        //Join the team
                    ++push_num;
                }break;
                case 10: //Reach the target location
                {
                    map[temp_x][temp_y] += i * 1000;
                    target_p.x = temp_x;                //Write to target location
                    target_p.y = temp_y;
                    this->clear();                        //Empty queue
                    return true;
                }break;
                }
            }
            if (out_queue.data == -1)                    //The last in each round of the queue data Mark as-1
                break;                                    //Take one step outward from the starting position as a round
        }
        if (this->End_p != NULL)
            this->End_p->data = -1;
    }
    this->clear();
    return false;
}

/*Auto move (recursive function)
* Transmission parameters:
*    
* Sequential recursion: call recursion first, and then move the location
* Therefore, the purpose of this function is to pass in the target location at the beginning,
* Then push back to the next position with the direction mark on the map,
* It starts to move until it returns to the starting position. Each time it moves, call show() to refresh the map display
* You can achieve the effect of moving from the start point to the end point
*/
void mapClass::auto_move(int in_x, int in_y)
{
    /*switch ()Can be replaced by
    * temp_x = in_x + int(sin(PI / 2 * (map[in_x][in_y] / 1000));
    * temp_y = in_y + int(sin(PI / 2 * (map[in_x][in_y] / 1000 - 1.0));
    */
    int temp_x = in_x, temp_y = in_y;
    switch (map[in_x][in_y] / 1000)    //Parsing map markers
    {
    case 0:return;   break;
    case 1:++temp_x; break;
    case 2:++temp_y; break;
    case 3:--temp_x; break;
    case 4:--temp_y; break;
    }
    /*Since the function recurses back to the starting point from the end position, the last call to this function should be closer to the end
    * Therefore, the incoming value (in_x, in_y) accepted by this function is the next moving point
    * (temp_x,temp_y)This is the moving point of this time
    */
    auto_move(temp_x, temp_y);    //Recursive call to move the starting point to this position (i.e temp_x, temp_y)

    map[temp_x][temp_y] = 0;    //Clear the current position
    map[in_x][in_y] = 100;        //Set the next moving point to 100 to achieve the effect of moving from the current position to the next position
    show_map();                    //Display print
    Sleep(Sleep_num);
    return;
}

/*Constructor
* Transmission parameters:
*    in_p: Starting point position
*/
mapClass::mapClass(const Point& in_p)
{
    new_map();
    persion_p = in_p;
}

/*Automatic pathfinding dominant function
*/
bool mapClass::auto_main()
{
    show_map();                //Display map
    Sleep(Sleep_num);

    this->clear();            //Empty queue
    this->push(persion_p.x, persion_p.y, -1);//Join the team from the starting point

    Point target_p;            //Target coordinates
    if (auto_find_way(target_p) == false)    //Call automatic pathfinding
    {
        reflash_map();
        return false;
    }

    auto_move(target_p.x, target_p.y);        //move
    reflash_map();                            //Clean up map residual markers
    persion_p = target_p;                    //Reset the starting point position, and the starting point is the end point after reaching the end point
    return true;
}

/*Write data markers to the map
* 
* Transmission parameters:
*    in_data: Data value written
*    num:      frequency
* 
* Write num times in_data mark on the random empty position of the map
* 
* There are bug s:
*    If the map coordinates are full and the number of writes is not enough, it will fall into an endless loop
*    It can be solved by adding the limit of cycle times
*/
void mapClass::into_map(int in_data, int num)
{
    if (num <= 0)
        return;
    for (int i = 0, j = 0; num--;)
    {
        i = rand() % Map_size;
        j = rand() % Map_size;
        if (map[i][j] == 0)
            map[i][j] = in_data;
        else
            ++num;
    }
}

/*Write data markers to the map
* 
* Transmission parameters:
*    in_x,in_y: Written map location
*    in_data:   Data value written
* 
* Return value:
*    If the (in_x, in_y) position is empty, true will be returned if the write is successful, otherwise false will be returned
* 
* Write in_data at the (in_x, in_y) position of the map
*/
bool mapClass::into_map(int in_x, int in_y, int in_data)
{
    if (map[in_x][in_y] == 0)
    {
        map[in_x][in_y] = in_data;
        return true;
    }
    return false;
}

/*Print display map
*/
void mapClass::show_map()
{
    system("cls");                            //Clear console output
    for (int i = 0; i < Map_size; ++i)
    {
        for (int j = 0; j < Map_size; ++j)
            switch (map[i][j] % 1000)
            {
            case 0:        cout << "  "; break;//Blank position
            case 1:        cout << ""; break;//obstacle
            case 10:    cout << ""; break;//target
            case 100:    cout << ""; break;//own
            default:    cout << "  "; break;
            }
        cout << endl;
    }
}

/*Reset map
* Transmission parameters:
*    in_p: Starting point position
* 
* Empty the map, leaving only the start and boundary markers
* Implementation of obstacle routing for auxiliary cycle refresh
*/
void mapClass::clear_map(const Point& in_p)
{
    for (int x = Map_size - 1; --x;)        //Set all positions in the map to zero
        for (int y = Map_size - 1; --y;)
            map[x][y] = 0;
    persion_p = in_p;                        //Reset start point
    map[in_p.x][in_p.y] = 100;
}

  

3.main function

#include <iostream>
#include <time.h>
#include <cmath>

#include "mapClass.h"
using namespace std;


int main()
{
    srand(int(time(0)));

    Point persion_p(1, 1), target_p(1, 1);
    mapClass test_map(persion_p);

    test_map.into_map(1, 1, 100);    //Write start
    test_map.into_map(1, 20);        //Write obstacles

    while (1)
    {
        //Reset obstacle position, Uncomment the following two sentences to enable
        //test_map.clear_map(target_p);        //Empty map
        //test_map.into_map(1, 20);            //Generate obstacles

        do {
            target_p.x = rand() % (Map_size - 2) + 1;
            target_p.y = rand() % (Map_size - 2) + 1;
        } while (test_map.into_map(target_p.x, target_p.y, 10) == false);

        if (test_map.auto_main() == false)
        {
            cout << endl << "<< I can't go!" << endl;
            Sleep(1500);
        }
    }

    return 0;
}

 

2. Ideas

There is a big difference between the overall and data structure textbooks: take the starting point as the center, take each outward step as a cycle, enter the walkable positions into the team in the cycle, take the positions of the previous round out of the team in the next cycle, and then take these positions as the center to take a step outward to enter the walkable positions into the team, and keep this cycle until the end position or the queue is empty (if you can't go in each direction, the queue will be empty after the loop.).

(imagine spreading outward from the starting point in a map without obstacles)

In the above process, the direction mark (the direction of the previous position to this position) is left while the walkable position is in the team. After the cycle is completed, push backward from the end position to find a path back to the starting point.

This path is the optimal solution (there may be more than one optimal solution), because the algorithm makes a round of judgment every step from the starting point to the outside. Therefore, if the end point is found, then the end point is found within the minimum number of steps. At this time, the cycle can be ended, which is the optimal solution. If it does not end, continue to find a path with more steps.

 

This example is different from that in the book:

1. After finding the path, use the system("cls") to clear the screen and re output to realize the effect of gradually moving towards the end.

2. Use different attempts in the implementation of some details (for example, use sin() in mapClass::auto_find_way(), directly use the map as direction mark, etc.)

3. It supports multiple cycle pathfinding and resetting the position of obstacles

 

Posted by Paris! on Sun, 07 Nov 2021 12:22:13 -0800