pathfind.cpp

Go to the documentation of this file.
00001 /* $Id: pathfind.cpp 26029 2008-04-23 18:17:29Z alink $ */
00002 /*
00003 Copyright (C) 2003 by David White <dave@whitevine.net>
00004 Copyright (C) 2005 - 2008 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
00005 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007 This program is free software; you can redistribute it and/or modify
00008 it under the terms of the GNU General Public License version 2
00009 or at your option any later version.
00010 This program is distributed in the hope that it will be useful,
00011 but WITHOUT ANY WARRANTY.
00012 
00013 See the COPYING file for more details.
00014 */
00015 
00016 //! @file pathfind.cpp
00017 //! Various pathfinding functions and utilities.
00018 
00019 #include "global.hpp"
00020 
00021 #include "astarnode.hpp"
00022 #include "gamestatus.hpp"
00023 #include "gettext.hpp"
00024 #include "log.hpp"
00025 #include "pathfind.hpp"
00026 #include "util.hpp"
00027 #include "wml_exception.hpp"
00028 
00029 #include <cassert>
00030 #include <cmath>
00031 #include <iostream>
00032 
00033 #define LOG_PF LOG_STREAM(info, engine)
00034 
00035 static gamemap::location find_vacant(const gamemap& map,
00036         const unit_map& units,
00037         const gamemap::location& loc, int depth,
00038         VACANT_TILE_TYPE vacancy,
00039         std::set<gamemap::location>& touched)
00040     {
00041         if(touched.count(loc))
00042             return gamemap::location();
00043 
00044         touched.insert(loc);
00045 
00046         if (map.on_board(loc) && units.find(loc) == units.end() &&
00047             (vacancy == VACANT_ANY || map.is_castle(loc))) {
00048             return loc;
00049         } else if(depth == 0) {
00050             return gamemap::location();
00051         } else {
00052             gamemap::location adj[6];
00053             get_adjacent_tiles(loc,adj);
00054             for(int i = 0; i != 6; ++i) {
00055                 if(!map.on_board(adj[i]) || (vacancy == VACANT_CASTLE && !map.is_castle(adj[i])))
00056                     continue;
00057 
00058                 const gamemap::location res =
00059                     find_vacant(map, units, adj[i], depth - 1, vacancy, touched);
00060 
00061                 if (map.on_board(res))
00062                     return res;
00063             }
00064 
00065             return gamemap::location();
00066         }
00067     }
00068 
00069 gamemap::location find_vacant_tile(const gamemap& map,
00070                                    const unit_map& units,
00071                                    const gamemap::location& loc,
00072                                    VACANT_TILE_TYPE vacancy)
00073 {
00074     for(int i = 1; i != 50; ++i) {
00075         std::set<gamemap::location> touch;
00076         const gamemap::location res = find_vacant(map,units,loc,i,vacancy,touch);
00077         if(map.on_board(res))
00078             return res;
00079     }
00080 
00081     return gamemap::location();
00082 }
00083 
00084 bool enemy_zoc(gamemap const &map,
00085                unit_map const &units,
00086                std::vector<team> const &teams,
00087                gamemap::location const &loc, team const &viewing_team, unsigned int side, bool see_all)
00088 {
00089     gamemap::location locs[6];
00090     const team &current_team = teams[side-1];
00091     get_adjacent_tiles(loc,locs);
00092     for(int i = 0; i != 6; ++i) {
00093       unit_map::const_iterator it;
00094       it = find_visible_unit(units, locs[i], map, teams, viewing_team,see_all);
00095 
00096       if (it != units.end() && it->second.side() != side &&
00097          current_team.is_enemy(it->second.side()) && it->second.emits_zoc()) {
00098         return true;
00099       }
00100     }
00101 
00102     return false;
00103 }
00104 
00105 static void find_routes(const gamemap& map, const unit_map& units,
00106         const unit& u, const gamemap::location& loc,
00107         int move_left, paths::routes_map& routes,
00108         std::vector<team> const &teams,
00109         bool force_ignore_zocs, bool allow_teleport, int turns_left,
00110         bool starting_pos, const team &viewing_team,
00111         bool see_all, bool ignore_units)
00112 {
00113     team const &current_team = teams[u.side()-1];
00114 
00115     // Find adjacent tiles
00116     std::vector<gamemap::location> locs(6);
00117     get_adjacent_tiles(loc,&locs[0]);
00118 
00119     // Check for teleporting units -- we must be on a vacant village
00120     // (or occupied by this unit), that is controlled by our team
00121     // to be able to teleport.
00122     if (allow_teleport && map.is_village(loc) && current_team.owns_village(loc) &&
00123             (starting_pos || ignore_units ||
00124              find_visible_unit(units, loc, map, teams, viewing_team,see_all) == units.end())) {
00125 
00126         // If we are on a village, search all known empty friendly villages
00127         // that we can teleport to
00128         const std::set<gamemap::location>& villages = current_team.villages();
00129         for(std::set<gamemap::location>::const_iterator t = villages.begin(); t != villages.end(); ++t) {
00130             if ((see_all || !viewing_team.is_enemy(u.side()) || !viewing_team.fogged(*t))
00131                     && (ignore_units || find_visible_unit(units, *t, map, teams, viewing_team, see_all) == units.end())) {
00132                 locs.push_back(*t);
00133             }
00134         }
00135     }
00136 
00137     // Iterate over all adjacent tiles
00138     for(std::vector<gamemap::location>::const_iterator i=locs.begin(); i != locs.end(); ++i) {
00139         const gamemap::location& currentloc = *i;
00140 
00141         if (!map.on_board(currentloc))
00142             continue;
00143 
00144         // check if we can move on this terrain
00145         const int move_cost = u.movement_cost(map[currentloc]);
00146         if (move_cost > move_left && (turns_left < 1 || move_cost > u.total_movement()))
00147             continue;
00148 
00149         int new_move_left = move_left - move_cost;
00150         int new_turns_left = turns_left;
00151         if (new_move_left < 0) {
00152             --new_turns_left;
00153             new_move_left = u.total_movement() - move_cost;
00154         }
00155         const int new_turns_moves = new_turns_left * u.total_movement();
00156 
00157         // Search if we already have a route here, and how good it is
00158         int old_move_left = -1;
00159         const paths::routes_map::const_iterator old_rt = routes.find(currentloc);
00160         if (old_rt != routes.end())
00161             old_move_left = old_rt->second.move_left;
00162 
00163         // Test if, even with no ZoC, we already have a better route,
00164         // so no need to try with ZoC (and thus no need to search ZoC)
00165         if(old_move_left >= new_turns_moves + new_move_left)
00166             continue;
00167 
00168         if (!ignore_units) {
00169             // we can not traverse enemies
00170             const unit_map::const_iterator unit_it =
00171                 find_visible_unit(units, currentloc, map, teams, viewing_team,see_all);
00172             if (unit_it != units.end() && current_team.is_enemy(unit_it->second.side()))
00173                 continue;
00174 
00175             // Evaluation order is optimized (this is the main bottleneck)
00176             // Only check ZoC if asked and if there is move to remove.
00177             // Do skirmisher test only on ZoC (expensive and supposed to be rare)
00178             if (!force_ignore_zocs && new_move_left > 0
00179                     && enemy_zoc(map,units,teams, currentloc, viewing_team,u.side(),see_all)
00180                     && !u.get_ability_bool("skirmisher", currentloc)) {
00181                 new_move_left = 0;
00182                 // Recheck if we already have a better route, but now with the ZoC effect
00183                 if(old_move_left >= new_turns_moves + 0)
00184                     continue;
00185             }
00186         }
00187 
00188         paths::route& src_route = routes[loc];
00189         paths::route& new_route = routes[currentloc];
00190         new_route.steps = src_route.steps;
00191         new_route.steps.push_back(loc);
00192         new_route.move_left = new_turns_moves + new_move_left;
00193 
00194         if (new_route.move_left > 0) {
00195             find_routes(map, units, u, currentloc,
00196                         new_move_left, routes, teams, force_ignore_zocs,
00197                         allow_teleport, new_turns_left, false, viewing_team,
00198                         see_all, ignore_units);
00199         }
00200     }
00201 }
00202 
00203 paths::paths(gamemap const &map,
00204              unit_map const &units,
00205              gamemap::location const &loc,
00206              std::vector<team> const &teams,
00207          bool force_ignore_zoc,
00208              bool allow_teleport, const team &viewing_team,
00209            int additional_turns, bool see_all, bool ignore_units)
00210 {
00211     const unit_map::const_iterator i = units.find(loc);
00212     if(i == units.end()) {
00213         std::cerr << "unit not found\n";
00214         return;
00215     }
00216 
00217     if(i->second.side() < 1 || i->second.side() > teams.size()) {
00218         return;
00219     }
00220 
00221     routes[loc].move_left = i->second.movement_left();
00222     find_routes(map,units,i->second,loc,
00223         i->second.movement_left(),routes,teams,force_ignore_zoc,
00224         allow_teleport,additional_turns,true,viewing_team,
00225         see_all, ignore_units);
00226 }
00227 
00228 int route_turns_to_complete(const unit& u, paths::route& rt, const team &viewing_team,
00229                             const unit_map& units, const std::vector<team>& teams, const gamemap& map)
00230 {
00231     if(rt.steps.empty())
00232         return 0;
00233 
00234     int turns = 0;
00235     int movement = u.movement_left();
00236     const team& unit_team = teams[u.side()-1];
00237     bool zoc = false;
00238 
00239     for (std::vector<gamemap::location>::const_iterator i = rt.steps.begin();
00240         i !=rt.steps.end(); i++) {
00241         bool last_step = (i+1 == rt.steps.end());
00242         
00243         // move_cost of the next step is irrelevant for the last step
00244         assert(last_step || map.on_board(*(i+1)));
00245         const int move_cost = last_step ? 0 : u.movement_cost(map[*(i+1)]);
00246 
00247         if (last_step || zoc || move_cost > movement) {
00248             // check if we stop an a village and so maybe capture it
00249             // if it's an enemy unit and a fogged village, we assume a capture
00250             // (if he already owns it, we can't know that)
00251             // if it's not an enemy, we can always know if he owns the village
00252             bool capture = map.is_village(*i) && ( !unit_team.owns_village(*i)
00253                  || (viewing_team.is_enemy(u.side()) && viewing_team.fogged(*i)) );
00254 
00255             ++turns;
00256 
00257             bool invisible = u.invisible(*i,units,teams,false);
00258 
00259             rt.waypoints[*i] = paths::route::waypoint(turns, zoc, capture, invisible);
00260             
00261             if (last_step) break; // finished and we used dummy move_cost
00262 
00263             movement = u.total_movement();
00264             if(move_cost > movement) {
00265                 return -1; //we can't reach destination
00266             }
00267         }
00268 
00269         zoc = enemy_zoc(map,units,teams, *(i+1), viewing_team,u.side())
00270                     && !u.get_ability_bool("skirmisher", *(i+1));
00271 
00272         if (zoc) {
00273             movement = 0;
00274         } else {
00275             movement -= move_cost;
00276         }
00277     }
00278 
00279     return turns;
00280 }
00281 
00282 
00283 shortest_path_calculator::shortest_path_calculator(unit const &u, team const &t,
00284         unit_map const &units, std::vector<team> const &teams, gamemap const &map,
00285         bool ignore_unit, bool ignore_defense)
00286     : unit_(u), viewing_team_(t), units_(units), teams_(teams), map_(map),
00287       movement_left_(unit_.movement_left()),
00288       total_movement_(unit_.total_movement()),
00289       ignore_unit_(ignore_unit), ignore_defense_(ignore_defense)
00290 {
00291 }
00292 
00293 double shortest_path_calculator::cost(const gamemap::location& /*src*/,const gamemap::location& loc, const double so_far) const
00294 {
00295     assert(map_.on_board(loc));
00296 
00297     // loc is shrouded, consider it impassable
00298     // NOTE: This is why AI must avoid to use shroud
00299     if (viewing_team_.shrouded(loc))
00300         return getNoPathValue();
00301 
00302     const t_translation::t_terrain terrain = map_[loc];
00303     int const base_cost = unit_.movement_cost(terrain);
00304     // Pathfinding heuristic: the cost must be at least 1
00305     VALIDATE(base_cost >= 1, _("Terrain with a movement cost less than 1 encountered."));
00306 
00307     // costs more than the total movement of the unit, impassbale
00308     if (total_movement_ < base_cost)
00309         return getNoPathValue();
00310 
00311     int other_unit_subcost = 0;
00312     if (!ignore_unit_) {
00313         unit_map::const_iterator
00314             other_unit = find_visible_unit(units_, loc, map_, teams_, viewing_team_);
00315 
00316         // We can't traverse visible enemy and we also prefer empty hexes
00317         // (less blocking in multi-turn moves and better when exploring fog,
00318         // because we can't stop on a friend)
00319 
00320         if (other_unit != units_.end()) {
00321             if (teams_[unit_.side()-1].is_enemy(other_unit->second.side()))
00322                 return getNoPathValue();
00323             else
00324                 // This value will be used with the defense_subcost (see below)
00325                 // The 1 here means: consider occupied hex as a -1% defense
00326                 // (less important than 10% defense because friends may move)
00327                 other_unit_subcost = 1;
00328         }
00329     }
00330     
00331     // Compute how many movement points are left in the game turn
00332     // needed to reach the previous hex.
00333     // total_movement_ is not zero, thanks to the pathfinding heuristic
00334     int remaining_movement = movement_left_ - static_cast<int>(so_far);
00335     if (remaining_movement < 0)
00336         remaining_movement = total_movement_ - (-remaining_movement) % total_movement_;
00337 
00338     // we will always pay the terrain movement cost.
00339     int move_cost = base_cost;
00340 
00341     // Supposing we had 2 movement left, and wanted to move onto a hex
00342     // which takes 3 movement, it's going to cost us 5 movement in total,
00343     // since we sacrifice this turn's movement. So check that.
00344     bool need_new_turn = base_cost > remaining_movement;
00345 
00346     // and if it happens, all remaining movements will be lost waiting the turn's end
00347     if (need_new_turn)
00348         move_cost += remaining_movement;
00349 
00350     if (!ignore_unit_ && enemy_zoc(map_,units_,teams_, loc, viewing_team_, unit_.side())
00351             && !unit_.get_ability_bool("skirmisher", loc)) {
00352         // the ZoC cost all remaining movements, but if we already use them
00353         // in the sacrified turn, we will spend all our fresh total movement
00354         move_cost += need_new_turn ? total_movement_ : remaining_movement;
00355     }
00356 
00357     // We will add a tiny cost based on terrain defense, so the pathfinding
00358     // will prefer good terrains between 2 with the same MP cost
00359     // Keep in mind that defense_modifier is inverted (= 100 - defense%)
00360     const int defense_subcost = ignore_defense_ ? 0 : unit_.defense_modifier(terrain);
00361 
00362     // We divide subcosts by 100 * 100, because defense is 100-based and
00363     // we don't want any impact on move cost for less then 100-steps path
00364     // (even ~200 since mean defense is around ~50%)
00365     return move_cost + (defense_subcost + other_unit_subcost) / 10000.0;
00366 }
00367 
00368 emergency_path_calculator::emergency_path_calculator(const unit& u, const gamemap& map)
00369     : unit_(u), map_(map)
00370 {
00371 }
00372 
00373 double emergency_path_calculator::cost(const gamemap::location&,const gamemap::location& loc, const double) const
00374 {
00375     assert(map_.on_board(loc));
00376 
00377     return unit_.movement_cost(map_[loc]);
00378 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs