我正在为一个基于文本的游戏编写一个AI,我遇到的问题是我如何确定如何确定墙的最薄部分。例如,以下代表2D地图,其中'^'是想要通过'*'字符表示的墙到达标记为'X'的位置的字符:
------------------
| X * |
|***** |
|**** |
|*** |
|** ^ |
| |
| |
| |
------------------
我现在已经连续几天都在考虑这个问题,而且我已经没想完了。我尝试过使用*算法,当它试图通过墙角时,g-cost非常高。不幸的是,算法决定永远不会在墙上找到路径。
代理人只能左右上下而不是对角线,一次只能移动一个空格。
上面例子中穿过墙壁的最短路径是一条,因为它只需要通过一个'*'字符。
我只需要几个简单的想法:)
答案 0 :(得分:4)
所有流行的图搜索算法通常以具有一些实数(即浮动/双重)成本的成本来制定。但这不是必需的。所有您真正需要的成本都是严格的订购和操作,如添加。
您可以将标准A *应用于此。
(a,b)
的费用
a
墙上单元格的移动次数 b
正常细胞上的移动次数。[(a1,b1) < (a2,b2)] == [(a1<a2) || (a1==a2 && b1<b2)]
(a1,b1) + (a2,b2) == (a1+a2,b1+b2)
(0,b)
,其中b
是曼哈顿到达目标的距离一个直接的反对意见可能是“有了启发式,在试图穿过墙壁之前,必须探索墙外的整个空间!” - 但这正是要求的。
根据您提供的信息和要求,这实际上是最佳 A *启发式。
更复杂的方法可以提供更好的最佳案例表现,即将上述内容与bidirectional search结合起来。如果你的目标是在一个很小的围墙区域内,双向搜索可以在搜索的早期找到一些候选“最便宜的路径”。
答案 1 :(得分:2)
只需将其作为加权图表,并给予所有“墙壁”一个荒谬的大重量。
小心不要溢出整数。
答案 2 :(得分:1)
假设任何数量的移动总是比穿越墙壁便宜(意味着10000000000非穿墙移动比1穿墙移动便宜)(否则设置成本适当) ,我可以看到一个特殊情况,我能想到的任何算法不涉及实际搜索整个地图,所以......
跳过已经探索过的位置 - 新路径应该总是比已经存在的路径长。
你真正想要做A *而不是BFS的唯一原因是因为当你到达包含目标的区域时,这将允许更少的探索。这是否更有效取决于地图。
正如Sint所提到的,如果开始时间始终处于一个开阔的区域并且终点位于一个小区域内,那么反转此搜索会更有效率。但是,如果您知道是否真的,这只是真的适用。检测它不太可能有效,一旦你有了,你已经完成了大部分工作,如果两者都在合理大小的区域,你就会失败。
示例:
X** |
** |
** |
** ^|
初始BFS:
X**3
**32
**21
**10
穿过墙壁和BFS(没有BFS发生,因为他们无处可去但是通过墙壁):
(我们可以忽略的那些标有%
)
X*4%
*4%%
*3%%
*2%%
进入墙壁和BFS(BFS到目标):
65%%
5%%%
4%%%
3%%%
答案 3 :(得分:1)
使用Dijkstra。
由于你正在处理一个基于文本的游戏,我发现你不太可能谈论大于1000×1000字符的地图。这将为您提供保证最佳的答案,并且成本非常低O(n*logn)
,并且代码非常简单直接。
基本上,每个搜索状态都必须跟踪两件事:到目前为止,您已经介绍了多少墙,以及有多少常规空白空间。对于搜索和标记矩阵,这可以编码为单个整数,例如假设每个墙的成本为2 ^ 16要遍历。因此,Dijkstra的自然顺序将确保首先尝试具有最少墙壁的路径,并且在穿过墙壁之后,您不会重复已经到达的路径而不经过尽可能多的墙壁。
基本上,假设一个32位整数,一个已经通过5个空格和3个墙的状态,将如下所示:
0000000000000011 0000000000000101
。如果你的地图真的很大,迷宫般的,大量的墙壁,很少的墙壁或诸如此类的东西,你可以调整这种表示来为每个信息使用更多或更少的位,或者如果你觉得更舒服,甚至可以使用更长的整数,如如果存在需要遍历超过65,000个空白区域的最短路径,则此特定编码示例将“溢出”。
使用单个整数而不是两个(对于墙/空格)的主要优点是,您可以使用单个简单的int mark[MAXN][MAXM];
矩阵来跟踪搜索。如果您在走过5
墙时到达了特定的广场,则无需检查是否可以通过4个,3个或更少的墙到达它以防止无用状态的传播 - 此信息将自动生成嵌入到整数中,只要将walls
的数量存储到较高位,就不会重复路径,同时具有更高的“墙成本”。
这是一个用C ++完全实现的算法,考虑它是伪代码,以便更好地可视化和理解上面提出的想法:)
int rx[4] = {1,0,-1,0};
int ry[4] = {0,1,0,-1};
int text[height][width]; // your map
int mark[height][width]; //set every position to "infinite" cost
int parent[height][width]; //to recover the final path
priority_queue<int64_t, vector<int64_t>, greater<int64_t> > q;
int64_t state = (initial_y<<16) + initial_x;
q.push(state);
while(!q.empty()){
state = q.top(); q.pop();
int x = state & 0xFF;
int y = (state>>16) & 0xFF;
int cost = state>>32;
if(cost > mark[x][y]) continue;
if(text[x][y] == 'X') break;
for(int i = 0; i < 4; ++i){
int xx = x+rx[i];
int yy = y+ry[i];
if(xx > -1 && xx < width && yy > -1 && yy < height){
int newcost = cost;
if(text[yy][xx] == ' ') newcost += 1;
else newcost += 1<<16;
if(newcost < mark[yy][xx]){
mark[yy][xx] = newcost;
parent[yy][xx] = i; //you know which direction you came from
q.push( ((int64_t)newcost << 32) + (y<<16) + x);
}
}
}
}
// The number of walls in the final answer:
// walls = state>>48;
// steps = (state>>32) & 0xFF; // (non walls)
// you can recover the exact path traversing back using the information in parent[][]