Un algoritmo *, ratones de laberinto de arroz seco genéticamente modificados y orientación en juegos

Ratón de laberinto de arroz seco genéticamente modificado

Descripción del problema

Creo que todo el mundo debería estar familiarizado con el problema del ratón laberinto, y debe haber bastantes personas que puedan usar BFS para resolver el problema de la ruta más corta del ratón laberinto de forma competente. Entonces, si ahora fortalecemos adecuadamente el problema del ratón laberinto, ¿puedes aún ser competente para resolver este problema:

Pregunta 1:

Sobre la base del problema tradicional del ratón de laberinto, piense en el problema: el ratón de arroz súper seco modificado genéticamente puede caminar en diagonal en el laberinto, pero necesita alimentarlo con 10 gatos de arroz para un movimiento lateral, y necesita alimentarlo. 14 gatos de arroz para una caminata en diagonal (porque la distancia se convierte en la raíz número 2), ¿puedes generar el camino y la comida menos seca para este ratón super laberinto?

Pregunta 2:

Sobre la base de la pregunta 1, si hay diferentes terrenos como "aguas" y "tierras altas" en el laberinto, las ratas necesitan consumir 15 o 20 kilogramos de arroz para mover un terreno, entonces ¿cómo solucionar este problema?

análisis del problema

En primer lugar, puedes pensar que la persona que escribió este tema está más o menos afectada por una parálisis cerebral, pero el problema no es infundado, este comportamiento es en realidad un comportamiento muy común en los juegos. Imagina que en un juego de estrategia en tiempo real o MOBA, el terreno del juego rara vez tiene un terreno completamente plano. En la mayoría de los casos, no solo hay más o menos obstáculos en el juego, sino que cruzar diferentes terrenos a menudo significa diferentes costos. De hecho, el método de búsqueda de caminos en el juego es mucho más complicado que el algoritmo que discutiremos a continuación.

¿No puedo utilizar el algoritmo BFS, DFS o Dijkstra?

Imposible.

Primero, analicemos BFS: BFS es un algoritmo bastante común, que se usa ampliamente en varios algoritmos de teoría de grafos, como la búsqueda de rutas, los gráficos transversales y la búsqueda de la ruta más corta. Su idea central es explorar igualmente todas las direcciones del punto de partida. Para problemas de laberintos ordinarios, este algoritmo es una excelente manera de resolver el camino más corto, sin embargo, debido a sus iguales características de exploración, se determina que BFS no se puede utilizar en este tipo de problemas con pesos.

En segundo lugar, examinemos el algoritmo DFS: en mi experiencia, en primer lugar, pocas personas usan DFS para resolver el problema de la ruta más corta, pero DFS de hecho puede usarse para encontrar la ruta más corta. El problema al que se enfrenta es el mismo que el de BFS: DFS siempre trata todos los puntos por igual, en este problema cada nodo del gráfico tiene pesos diferentes, por lo que el algoritmo DFS no se aplica a este problema.

Finalmente, observemos el algoritmo de Dijkstra, el algoritmo de Dijkstra se puede utilizar para resolver problemas con pesos. Sin embargo, el método de expansión del algoritmo de Dijkstra es expandirse desde el punto de inspiración al entorno, lo que no es bueno para la velocidad del programa, y ​​el algoritmo del juego a menudo tiene requisitos extremadamente altos de velocidad.

Por lo tanto, el mejor algoritmo que debemos considerar debe ser un algoritmo que no solo pueda considerar que la dirección transversal debe ser preferida para acercarse al punto final, sino que también pueda manejar el costo del movimiento.

Algoritmo A *

¿Para qué se utiliza el algoritmo A *?

Búsqueda de caminos
Aunque descubrimos que los tres métodos discutidos anteriormente no son adecuados para resolver este problema, nuestros esfuerzos no han sido exhaustivos. Todavía podemos referirnos al pensamiento de usar el algoritmo de Dijkstra y BFS para generar nuevas ideas. En la práctica, el algoritmo A se considera un algoritmo eficiente y factible. En la siguiente parte, intentaremos utilizar el algoritmo A para resolver el problema de los ratones de arroz seco modificados genéticamente 1

¿Cómo funciona el algoritmo A *?

Pruebe este mapa:
Inserte la descripción de la imagen aquí
en este mapa, el bloque del mapa púrpura es el obstáculo, el bloque del mapa rojo es el punto de partida, el bloque del mapa azul es el punto final y el resto de los bloques del mapa son áreas accesibles. Las otras preguntas son las los mismos que los descritos en la Pregunta 1..

Usaremos este mapa como ejemplo para introducir el uso del algoritmo A *. Antes de eso, necesitamos aclarar las herramientas que usaremos aquí.

1. Open List: Una estructura de datos utilizada para almacenar nodos, aunque su nombre es Open List, también se pueden usar otras estructuras de datos como tablas lineales.

2. Lista cerrada: Igual que la Lista abierta, una estructura de datos que se utiliza para almacenar nodos. Se pueden utilizar estructuras de datos comunes como listas enlazadas y listas lineales.

3. Cada nodo tiene tres valores:
F: Satisfacer F = G + H.
G: La distancia desde el punto de inicio hasta la cuadrícula especificada.
H: A partir del punto de partida, la distancia estimada a esta cuadrícula.

4. Cada nodo también debe tener un padre. Puede usar un puntero al nodo para indicarlo, o puede usar un método simple para registrar las coordenadas del padre.

Al ver esto, supongo que no sabes cómo usar estas cuatro herramientas. Está bien. Comencemos a buscar y usarlas mientras hablamos:

¡empezar a trabajar!

0. Ingrese las coordenadas de cada punto, dibuje un mapa y deje en blanco su OpenList y ClosedList

1. El primer paso es comenzar desde el punto de partida A, marcar todos los puntos alcanzables alrededor de A y agregarlos todos a OpenList. Por supuesto, estos puntos no pueden estar en OpenList o ClosedList. Inserte la descripción de la imagen aquí
Como se muestra en la figura, todos los bloques verdes representan que se han agregado a OpenList.

2. Establecemos padres para todos los puntos. Aquí, sus padres son los puntos de partida. La imagen después de marcar a los padres debe verse así: Inserte la descripción de la imagen aquí
Este paso es necesario porque necesitamos poder rastrear a los padres de estos puntos Para encontrar este camino . Veremos el enorme efecto de este punto más adelante.

3. En el siguiente paso, necesitamos seleccionar un punto, usar este punto como el punto seleccionado y agregar A a la Lista Cerrada. Sin embargo, aquí encontramos un problema clave del algoritmo A *:

¿Qué punto debo elegir?

La respuesta es: el que tiene el valor F más pequeño, entonces, ¿cómo encontrar el valor F?

Cómo encontrar el valor F

F = G + H! El proceso de encontrar F es un proceso de encontrar G y H. Primero analicemos el simple G

¿Cómo pides G?

G es muy fácil de entender. Es el costo de moverse del punto A a los puntos circundantes. Ya dijimos en el problema que el movimiento horizontal es 10 y el movimiento diagonal es 14. Es fácil encontrar el costo de movimiento de un punto alrededor del punto de partida. Nota: ¡Es para A! No al punto seleccionado actualmente. En un ciclo, debe ser igual al costo de movimiento del punto actual + 10 o al costo de movimiento del punto actual + 14, y este valor no siempre es constante para un punto, seguiremos ver.

Cómo pedir H

El método para encontrar H es muy variable. Aquí usamos el método Manhattan. Este método nos permite estimar la distancia desde el punto actual hasta el punto final. También podríamos establecer las coordenadas del punto final como:
end_point_x y end_point_y, y deje que las coordenadas del punto actual sean current_point_x y current_point_y. En diferentes mapas, la fórmula utilizada para calcular H no es la misma. En este mapa, esta fórmula es un buen método:

H = abs (punto_final_x - punto_actual_x) * 10 + abs (punto_final_y - punto_actual_y) * 10;

Esta distancia ignora el costo y los obstáculos, por lo que es muy fácil de usar.

Esta es solo una estimación, no la distancia real.

Después de completar las operaciones anteriores, podemos obtener el valor F de cada punto, y marqué todos sus valores en el mapa.

Inserte la descripción de la imagen aquí

¡Volver al trabajo!

Ahora, hemos obtenido los valores F de todos los puntos adyacentes a A. Como acabamos de describir, agregue A a la Lista cerrada. En el camino, uso una marca amarilla para indicar todos los puntos agregados a la Lista cerrada, y marca el punto con el valor F más pequeño. Para el punto actual, current_point, usamos el punto rojo para marcarlo:

Inserte la descripción de la imagen aquí
Ahora, repita 1, 2, 3 y agregue todos los puntos alrededor del punto seleccionado que son alcanzables, no en OpenList, no en ClosedList, en Open List, en términos generales, el padre de cada punto no se actualizará, pero en la solicitud F, G, H debe ser muy, muy cuidadoso aquí: si alcanzar el punto crítico a través del punto actual dará como resultado un valor G menor, entonces necesita actualizar G, F y padre. No hemos utilizado este punto en esta ronda del ciclo, y examinaremos los resultados del siguiente paso del ciclo:

¡Nota! ¡Lo configuramos para que no se salga de la esquina! Por lo tanto, no se puede llegar a la esquina inferior derecha del punto rojo. De hecho, puede eliminar esta configuración, se agrega aquí para facilitar la explicación.

Inserte la descripción de la imagen aquí
Mira el siguiente ciclo:

Inserte la descripción de la imagen aquí
Preste atención al punto a la izquierda del punto actual. Su valor de G se ha actualizado y sus padres han sido señalados a la cuadrícula de arriba. Puede imaginar, ¿sucedió esto realmente en este ciclo? Desafortunadamente. No. Si observa la cuadrícula actual, entonces la cuadrícula rosa debería tener G 24 + 10 = 34, que es más pequeña que la G obtenida en la ronda anterior, por lo que su G, F y sus padres no deberían haberse actualizado.

Entonces, ¿por qué debería actualizar este punto? Si selecciona el punto directamente encima de la cuadrícula rosa, entonces este punto debe actualizarse. Actualizar G, F y los padres es un punto muy importante en el algoritmo A *, pero la muestra que elegimos no es muy buena y no la refleja. , En la siguiente figura, este punto se mantendrá en un estado no actualizado, preste atención.

Continuamos en bucle hasta que se agrega el punto final a la Lista abierta. Tenga en cuenta que no olvide el acuerdo de que no podemos ir más allá de la esquina. En este momento, el estado de la imagen debe ser:

Inserte la descripción de la imagen aquí
En este punto, el punto final se agrega a la Lista Abierta. Siguiendo a su padre, podemos encontrar el camino más corto. Su valor G es el valor mínimo de consumo. Marcamos el camino más corto: ¡el Inserte la descripción de la imagen aquí
camino azul es el camino más corto!

Implementación de código (C ++)

¡Nota! El siguiente código tiene serios problemas en términos de especificaciones de código, mala organización gramatical y solo una parte de la verificación del pequeño conjunto de datos, ¡utilícelo con precaución!
¡Nota! El siguiente código tiene serios problemas en términos de especificaciones de código, mala organización gramatical y solo una parte de la verificación del pequeño conjunto de datos, ¡utilícelo con precaución!
¡Nota! El siguiente código tiene serios problemas en términos de especificaciones de código, mala organización gramatical y solo una parte de la verificación del pequeño conjunto de datos, ¡utilícelo con precaución!
(Pero el principio del algoritmo definitivamente no es un problema)

//
//  main.cpp
//  A*算法与迷宫干饭老鼠
//
//  Created by 讨狐之猛将 on 2021/3/22.
//

#include <iostream>
#include <vector>
#include <list>
#include <cmath>

/*
 转基因超级干饭老鼠可以在迷宫中斜着走,但是横向移动一下你需要喂它10斤饭,斜着走一下你需要喂它14斤饭(因为距离变成了根号2),你能输出这个超级迷宫老鼠干饭最少的路径和干饭吗?
 测试地图见:
 https://blog.csdn.net/hitwhylz/article/details/23089415
 */

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::list;

struct node{
    
    int parent_x;
    int parent_y;
    int x_;
    int y_;
    int G;
    int F;
    int H;
    bool barrier;
    
    node(int x=0, int y=0):x_(x),y_(y){
        G = 0;
        F = 0;
        H = 0;
        barrier = false;
        parent_x = -1;
        parent_y = -1;
    }
    
};

int dx[] = {0,0,1,-1,1,-1,-1,1};
int dy[] = {1,-1,0,0,1,-1,1,-1};

int map_row;
int map_col;

vector<vector<node> >map;
vector<node> open_list;
vector<node> closed_list;

//判断相邻点是否在open_list
bool in_open_list(node n,int dx,int dy){
    
    bool In_list = false;
    
    for(vector<node>::iterator i = open_list.begin(); i!= open_list.end(); i++){
        if(i->x_ == n.x_ + dx && i->y_ == n.y_ + dy)
            In_list = true;
    }
    
    return In_list;
}

//判断相邻点是否可以加入open_list
bool Judge(node n,int dx,int dy){
    bool In_list = false;
    
    for(vector<node>::iterator i = open_list.begin(); i!= open_list.end(); i++){
        if(i->x_ == n.x_ + dx && i->y_ == n.y_ + dy)
            In_list = true;
    }
    
    for(vector<node>::iterator i = closed_list.begin(); i!= closed_list.end(); i++){
        if(i->x_ == n.x_ + dx && i->y_ == n.y_ + dy)
            In_list = true;
    }
    
    if(n.x_ + dx > 0 && n.y_ + dy > 0 && n.x_ + dx <= map_row && n.y_ + dy <=map_col && map[n.x_+dx][n.y_+dy].barrier == false && !In_list)
        //超出地图边界的不可以,障碍物不可以
        return true;
    
    return false;
}

int main(int argc, const char * argv[]) {
    
    //初始化地图信息
    std::ios::sync_with_stdio(false);
    
    int start_point_x;
    int start_point_y;
    int end_point_x;
    int end_point_y;
    int barrier_x,barrier_y;
    
    cin>>map_row>>map_col;
    cin>>start_point_x>>start_point_y;
    cin>>end_point_x>>end_point_y;
    
    for(int i = 0; i <= map_row; i++){
        map.push_back(vector<node>(map_col+1));
    }
    
    for(int i = 1; i<=map_row; i++){
        for(int j = 1; j<=map_col; j++){
            map[i][j].x_ = i;
            map[i][j].y_ = j;
        }
    }
    
    while(cin>>barrier_x>>barrier_y){
        
        if(barrier_x == -1 && barrier_y == -1)
            break;
        
        map[barrier_x][barrier_y].barrier = true;
    }
    
    //将起始点加入open_list
    open_list.push_back(map[start_point_x][start_point_y]);
    node& cc_node = map[start_point_x][start_point_y];
    
    //计算起始点的H
    map[cc_node.x_][cc_node.y_].H = abs(map[cc_node.x_][cc_node.y_].x_ - end_point_x) * 10 + abs(map[cc_node.y_][cc_node.y_].y_ - end_point_y) * 10;
    
    while (true) {
        
        for(int i = 0; i<8; i++){
            
            int G_new;
            
            if(Judge(cc_node, dx[i], dy[i])){
                //设置不在open_list的点的父亲
                if(!in_open_list(cc_node,dx[i],dy[i])){
                    map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].parent_x = cc_node.x_;
                    map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].parent_y = cc_node.y_;
                    
                    
                    //将符合要求的点加入open_list,如果不在open_list,设置他们的父亲
                    open_list.push_back(map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]]);
                    
                    //如果end_point结束点被加入open_list,跳出循环,已找到最短路。
                    if(open_list.back().x_ == end_point_x && open_list.back().y_ == end_point_y)
                        break;
                }
                
                
                //计算这个点周围点的H
                map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].H = abs(map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].x_ - end_point_x) * 10 + abs(map[cc_node.y_ + dx[i]][cc_node.y_+dy[i]].y_ - end_point_y) * 10;
                
                if(abs(dx[i]-dy[i])==1)//是一个上下左右移动
                    G_new = cc_node.G + 10;
                else//是一个斜向移动
                    G_new = cc_node.G + 14;
                
                //判断点是否之前加入过open_list,如果没有,更新G
                if(map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].G == 0)
                    map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].G = G_new;
                
                else{
                    //之前该点的G已经被确定过,那么他在open_list中
                    if(in_open_list(cc_node,dx[i],dy[i]) && map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].G > G_new){//新G比老G小
                        //更新G
                        map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].G = G_new;
                        
                        //重设父亲
                        map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].parent_x = cc_node.x_;
                        map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].parent_y = cc_node.y_;
                    }
                }
                
                //一轮循环更新完G之后,计算点的F
                map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].F = map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].G + map[cc_node.x_ + dx[i]][cc_node.y_+dy[i]].H;
                
            }
        }
        //将该点放入closed list,移出open list;
        open_list.erase(std::find_if(open_list.begin(), open_list.end(), [cc_node](node a){
            return a.x_ == cc_node.x_ && a.y_ == cc_node.y_;
        }));
        closed_list.push_back(cc_node);
        
        int n_x = open_list.front().x_;
        int n_y = open_list.front().y_;
        int current_smallest_F = map[n_x][n_y].F;
        
        for(vector<node>::iterator i = open_list.begin(); i != open_list.end(); i++){
            int round_F = map[i->x_][i->y_].F;
            if(round_F < current_smallest_F){
                current_smallest_F = round_F;
                n_x = i->x_;
                n_y = i->y_;
            }
            
        }
        
        cc_node = map[n_x][n_y];
        
        //如果end_point被加入了open_list 结束循环
        if(open_list.back().x_ == end_point_x && open_list.back().y_ == end_point_y)
            break;
        
    }
    
    node temp = map[end_point_x][end_point_y];
    
    vector<std::pair<int, int> >ans;
    ans.push_back(std::make_pair(end_point_x, end_point_x));
    
    while (temp.parent_x != start_point_x || temp.parent_y != start_point_y) {
        
        int next_x = temp.parent_x;
        int next_y = temp.parent_y;
        ans.push_back(std::make_pair(temp.parent_x, temp.parent_y));
        temp = map[next_x][next_y];
        
    }
    
    ans.push_back(std::make_pair(start_point_x, start_point_y));
    
    for(const auto& e : ans){
        cout<<"("<<e.first<<" , "<<e.second<<")"<<endl;
    }
    
    return 0;
    
}

Discusión extendida

Hablamos de un modo del algoritmo A *. Sin embargo, en los juegos reales, a menudo surgen más problemas, que incluyen, entre otros:

1. Si selecciono varias unidades a la vez, ¿tenemos que hacer que cada unidad encuentre su camino? Podría decirse que solo necesitamos pedir una unidad y otras unidades siguen el camino de la búsqueda. Entonces, ¿qué pasa si las dos unidades están muy separadas? Si se establece un límite de distancia para permitir que dos unidades que exceden el límite de distancia encuentren su camino de forma independiente, ¿cómo establecer este límite? Si tienes una armada y un ejército en tu unidad elegida, ¿qué tal si buscas un camino diferente?

2. El camino en el juego no es fijo. Si una unidad insuperable ocupa la cuadrícula objetivo, u otro jugador construye un edificio en el camino encontrado, ¿se puede seguir usando el camino más corto obtenido? O si el mapa es dinámico, algunas cuadrículas que son propensas a un bajo costo se cambiarán dinámicamente a un alto costo Entonces, ¿se puede transformar A * en un algoritmo dinámico?

3. Tu mapa suele ser muy grande. En un mapa enorme, lleva mucho tiempo dividirlos en cuadrículas de 1x1 y usar el algoritmo A. Entonces, ¿podemos establecer la distancia grande para los puntos que están lejos? Cuadrícula, debería lo configuramos como una cuadrícula pequeña cuando la distancia es cercana? ¿Cómo podemos mejorar el algoritmo A para obtener un efecto similar?

4. Si su mapa no es cuadrado sino hexagonal, ¿cómo usa el algoritmo A * para encontrar el camino más corto? ¿Puede su algoritmo adaptarse a mapas con diferentes formas irregulares?

5. El camino elegido es recto, ¿hay alguna manera de cambiar el camino obtenido por el algoritmo A * a un camino suave para hacer que el movimiento de la unidad sea más natural? ¿Cómo puedes mejorar tu algoritmo?

Espero que este artículo pueda ayudarlo. Si hay algún error, comuníquese con el autor.

Arriba, la opinión de Humiao, Fu Hou Zhuo Cai.

Dirección original: https://link.csdn.net/?target=http%3A%2F%2Fwww.gamedev.net%2Freference%2Farticles%2Farticle2003.asp
Referencia: https://blog.csdn.net/hitwhylz/article / detalles / 23089415

Supongo que te gusta

Origin blog.csdn.net/qq_45795847/article/details/115309455
Recomendado
Clasificación