It's not actually used anywhere yet, but I've implemented a wizmode testing function (x on monster, then 'w') that calculates the shortest path to any playerchosen destination on the level, and it seems to work quite well. The monsters tend to take zigzag paths but that should be easy enough to smooth out and in any case doesn't matter all that much since the player usually won't witness this. Oh, and though I tested the whole thing in a labyrinth, it went ridiculously fast! I'd rather doubted that but Darshan was completely right, as usually. :p
Please don't remove the debugging output yet, I still need them.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@5476 c06c8d41-db1a-0410-9941-cceddc491573
Y4NA3JSN63RLATF4NNBPSR5CWF5Z7UEMWCGVX4B6NOAR47CGM4GQC
Y5RFQ6KNJCBQUSV2T6WDR7TPZLZYLOAWBVMUTHDXGOZQDZ2U423AC
MDFQRJ6QZNFUBVSFWLXUJ6EBXOU47T3CVDI2XKBGNNRF4DXDKESQC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
K2GMFKXUWN5R3KCW6OYVXHN47MIQZKEEIOSAU6LFFKBNKF6JBVWAC
ID373JATLMWAY526Q6Q5FXHRNFWMEOFXPHGPAUUY5OAMPFDN5SJAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
WDEFQ6YABDQIGJXW5KT3OGR3EO6FZHXZELIRVIXQ4XDYTVOV5V6AC
3V52MSSK7QX7FWLLUW63DTWCBAJEK674EFZLKP45FLZ5KZKVARHAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
SELQ6AD2GNYLGHJVONUUFDHSCMGYMMNQF5D72D7KTAYOV36T2BBAC
LW4N5EHKL776DURXZMAM6JEW3JPWWX5BSNP7TCZHTLCDOQTTGFCAC
L254F6ZIU2HWGLFFGPIORTN4C3TDQ3E5JZ7Z7GQA5AEDIKL6PKDAC
ASLW3Z5PAVZSWJEMMMVZT226P44EKSAD47QS72JIFJESAI3RPN3AC
4PUWNQO7QMEWY3GSUHLBKMYOAI7ASYSRM32KDGTA7DLNDIGFAWFAC
PI5BATR2SER3RFE76IUGHM2AGXVFOUM3PLU7WC2K2Q2BA5K2E73QC
VD4KDTGHVKCN35AWREYB4TEOUMCTW7SAUPAMTMF5ABC7VBHVKP4AC
GVCGKTH5IJ4VSQEIN4CRC7ZFVZW26JPIYNCPTO7GY66CSZZEW3ZQC
TJRYL3NXPW5IUGEV3YOC7JYWEXCZDBFPLT4AUG4P227WVKVB72ZAC
25CH7HH4LKXFIZ75YNMXS3TSXO6O27DYSOPLOD45K4OCNFWLS4LQC
JN4GPMQCXOY5ICTLPLWP6DXBFULN4GMAEK7T4GXTZVIJAUUKBBYAC
HSRRNAU5UAYC6B6IQWGJPFROMZBTJICPCH6DJVZDHDTAGOQ6IOYAC
J6APXOT4QOGQFONWB7G546VTVF6QG42HVOROMHF7YBDJPR4K26OAC
LUNOTEIMYZJ7JL5P55GEHUVSDEZMYX3TWYUB2ABRHAYJEWQSSXIAC
BWAQ3FHBBM6G3K3KYP75CRTR343RDQZJRYX5ZGYUEXYBAC3APDLAC
CIPVRZGLOZHCERK6YPOBV3P2E4IAB4H6D5EHLRQE2O5E4P4VCBUAC
IXLNOTBJGHKESBCTME6QAR6XVWFNCHYGMS62V62ZJEA7VLQHXO2QC
class monster_pathfind
{
public:
monster_pathfind();
virtual ~monster_pathfind();
// public methods
bool start_pathfind(monsters *mon, coord_def dest, bool msg = false);
std::vector<coord_def> backtrack(void);
protected:
// protected methods
bool calc_path_to_neighbours(void);
bool traversable(coord_def p);
int travel_cost(coord_def npos);
int estimated_cost(coord_def npos);
void add_new_pos(coord_def pos, int total);
void update_pos(coord_def pos, int total);
bool get_best_position(void);
// The monster trying to find a path.
monsters *mons;
// Our destination, and the current position we're looking at.
coord_def start, target, pos;
// Currently shortest and longest possible total length of the path.
int min_length;
int max_length;
// The array of distances from start to any already tried point.
int dist[GXM][GYM];
// An array to store where we came from on a given shortest path.
int prev[GXM][GYM];
FixedVector<std::vector<coord_def>, GXM * GYM> hash;
};
}
/////////////////////////////////////////////////////////////////////////////
// monster_pathfind
monster_pathfind::monster_pathfind()
: mons(), target(), min_length(0), dist(), prev()
{
}
monster_pathfind::~monster_pathfind()
{
}
// Returns true if a path was found, else false.
bool monster_pathfind::start_pathfind(monsters *mon, coord_def dest, bool msg)
{
mons = mon;
// We're doing a reverse search from target to monster.
start = dest;
target = coord_def(mon->x, mon->y);
pos = start;
// Easy enough. :P
if (start == target)
{
if (msg)
mpr("The monster is already there!");
return (true);
}
max_length = min_length = grid_distance(pos.x, pos.y, target.x, target.y);
// memset(dist, INFINITE_DISTANCE, sizeof(dist));
for (int i = 0; i < GXM; i++)
for (int j = 0; j < GYM; j++)
dist[i][j] = INFINITE_DISTANCE;
dist[pos.x][pos.y] = 0;
bool success = false;
do
{
success = calc_path_to_neighbours();
if (success)
return (true);
success = get_best_position();
if (!success)
{
if (msg)
{
mprf("Couldn't find a path from (%d,%d) to (%d,%d).",
target.x, target.y, start.x, start.y);
}
return (false);
}
}
while (true);
}
// Returns true as soon as we encounter the target.
bool monster_pathfind::calc_path_to_neighbours()
{
// mprf("in calc_path_to_neighbours() for (%d,%d)", pos.x, pos.y);
coord_def npos;
int distance, old_dist, total;
for (int dir = 0; dir < 8; dir++)
{
npos = pos + Compass[dir];
// mprf("Looking at neighbour (%d,%d)", npos.x, npos.y);
if (!in_bounds(npos))
continue;
if (!traversable(npos))
continue;
distance = dist[pos.x][pos.y] + travel_cost(npos);
old_dist = dist[npos.x][npos.y];
// mprf("old dist: %d, new dist: %d, infinite: %d", old_dist, distance,
// INFINITE_DISTANCE);
if (distance < old_dist)
{
// Calculate new total path length.
total = distance + estimated_cost(npos);
if (old_dist == INFINITE_DISTANCE)
{
mprf("Adding (%d,%d) to hash (total dist = %d)",
npos.x, npos.y, total);
add_new_pos(npos, total);
if (total > max_length)
max_length = total;
}
else
{
mprf("Improving (%d,%d) to total dist %d",
npos.x, npos.y, total);
update_pos(npos, total);
}
// Update distance start->pos.
dist[npos.x][npos.y] = distance;
// Set backtracking information.
// Converts the Compass direction to their counterpart.
// 7 0 1
// 6 . 2
// 5 4 3
prev[npos.x][npos.y] = (dir + 4) % 8;
// Are we finished?
if (npos == target)
{
mpr("Arrived at target.");
return (true);
}
}
}
return (false);
}
bool monster_pathfind::get_best_position()
{
// mprf("minlength: %d, maxlength: %d", min_length, max_length);
for (int i = min_length; i <= max_length; i++)
{
if (!hash[i].empty())
{
if (i > min_length)
min_length = i;
std::vector<coord_def> &vec = hash[i];
pos = vec[vec.size()-1];
vec.pop_back();
mprf("Returning (%d, %d) as best pos with total dist %d.",
pos.x, pos.y, min_length);
return (true);
}
// mprf("No positions for path length %d.", i);
}
// Nothing found? Then there's no path! :(
return (false);
}
std::vector<coord_def> monster_pathfind::backtrack()
{
mpr("Backtracking...");
std::vector<coord_def> path;
pos = target;
path.push_back(pos);
if (pos == start)
return path;
int dir;
do
{
dir = prev[pos.x][pos.y];
pos = pos + Compass[dir];
ASSERT(in_bounds(pos));
mprf("prev: (%d, %d), pos: (%d, %d)", Compass[dir].x, Compass[dir].y,
pos.x, pos.y);
path.push_back(pos);
if (pos.x == 0 && pos.y == 0)
break;
}
while (pos != start);
ASSERT(pos == start);
return path;
bool monster_pathfind::traversable(coord_def p)
{
const int montype = mons_is_zombified(mons) ? mons_zombie_base(mons)
: mons->type;
if (!monster_habitable_grid(montype, grd(p)))
{
// mprf("Feature %d is not a habitable grid.", grd(p));
return (false);
}
// Don't generate monsters on top of teleport traps.
const int trap = trap_at_xy(p.x, p.y);
if (trap >= 0)
{
// mpr("There's a trap here!");
if (!_can_place_on_trap(montype, env.trap[trap].type))
{
// mpr("Monster can't be placed on trap.");
return (false);
}
}
// mprf("Grid (%d, %d) is traversable.", p.x, p.y);
return (true);
}
int monster_pathfind::travel_cost(coord_def npos)
{
ASSERT(grid_distance(pos.x, pos.y, npos.x, npos.y) <= 1);
// TODO: Make traps/shallow water more expensive, etc.
return 1;
}
int monster_pathfind::estimated_cost(coord_def p)
{
return (grid_distance(p.x, p.y, target.x, target.y));
}
void monster_pathfind::add_new_pos(coord_def npos, int total)
{
hash[total].push_back(npos);
}
void monster_pathfind::update_pos(coord_def npos, int total)
{
// Find hash position of old distance and delete it,
// then call_add_new_pos.
int old_total = dist[npos.x][npos.y] + estimated_cost(npos);
std::vector<coord_def> &vec = hash[old_total];
for (unsigned int i = 0; i < vec.size(); i++)
{
if (vec[i] == npos)
{
// mpr("Attempting to erase entry.");
vec.erase(vec.begin() + i);
break;
}
}
add_new_pos(npos, total);
}
if ( moves.isTarget
&& moves.tx == you.x_pos && moves.ty == you.y_pos
&& mode == TARG_ENEMY
&& !yesno("Really target yourself?", false, 'n'))
if (moves.isTarget
&& moves.tx == you.x_pos && moves.ty == you.y_pos
&& mode == TARG_ENEMY
&& !yesno("Really target yourself?", false, 'n'))
}
void debug_pathfind(int mid)
{
if (mid == NON_MONSTER)
return;
mpr("Choose a destination!");
#ifdef USE_TILE
more();
#endif
coord_def dest;
show_map(dest, true);
redraw_screen();
monsters &mon = menv[mid];
mprf("Attempting to calculate a path from (%d, %d) to (%d, %d)...",
mon.x, mon.y, dest.x, dest.y);
monster_pathfind mp;
bool success = mp.start_pathfind(&mon, dest, true);
if (success)
{
std::vector<coord_def> path = mp.backtrack();
std::string path_str = "";
mpr("Here's the shortest path: ");
for (unsigned int i = 0; i < path.size(); i++)
{
snprintf(info, INFO_SIZE, "(%d, %d) ", path[i].x, path[i].y);
path_str += info;
}
mpr(path_str.c_str());
}