terrain_filter.cpp

Go to the documentation of this file.
00001 /* $Id: terrain_filter.cpp 25782 2008-04-13 06:43:09Z sapient $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 #include "global.hpp"
00016 
00017 #include "actions.hpp"
00018 #include "config.hpp"
00019 #include "gamestatus.hpp"
00020 #include "log.hpp"
00021 #include "terrain_filter.hpp"
00022 #include "util.hpp"
00023 
00024 #include <algorithm>
00025 #include <cassert>
00026 #include <cctype>
00027 #include <cstdlib>
00028 #include <iostream>
00029 #include <sstream>
00030 
00031 #define ERR_CF LOG_STREAM(err, config)
00032 #define LOG_G LOG_STREAM(info, general)
00033 #define ERR_NG LOG_STREAM(err, engine)
00034 
00035 
00036 terrain_filter::terrain_filter(const vconfig& cfg, const gamemap& map,
00037     const gamestatus& game_status, const unit_map& units, const bool flat_tod,
00038     const size_t max_loop) : cfg_(cfg), map_(map), status_(game_status), units_(units)
00039 {
00040     restrict(max_loop);
00041     flatten(flat_tod);
00042 }
00043 
00044 terrain_filter::terrain_filter(const vconfig& cfg, const terrain_filter& original)
00045     : cfg_(cfg), map_(original.map_), status_(original.status_), units_(original.units_)
00046 {
00047     restrict(original.max_loop_);
00048     flatten(original.flat_);
00049 }
00050 
00051 terrain_filter::terrain_filter(const terrain_filter& other) :
00052     xy_pred(), // We should construct this too, since it has no datamembers
00053                // use the default constructor.
00054     cfg_(other.cfg_),
00055     map_(other.map_),
00056     status_(other.status_),
00057     units_(other.units_)
00058 {
00059     restrict(other.max_loop_);
00060     flatten(other.flat_);
00061 }
00062 
00063 terrain_filter& terrain_filter::operator=(const terrain_filter& other)
00064 {
00065     // Use copy constructor to make sure we are coherant
00066     if (this != &other) {
00067         this->~terrain_filter();
00068         new (this) terrain_filter(other) ;
00069     }
00070     return *this ;
00071 }
00072 
00073 namespace {
00074     struct cfg_isor {
00075         bool operator() (std::pair<const std::string,const vconfig> val) {
00076             return val.first == "or";
00077         }
00078     };
00079 } //end anonymous namespace
00080 
00081 bool terrain_filter::match_internal(const gamemap::location& loc, const bool ignore_xy)
00082 {
00083     if(cfg_.has_attribute("terrain")) {
00084         if(cache_.parsed_terrain == NULL) {
00085             cache_.parsed_terrain = new t_translation::t_match(cfg_["terrain"]);
00086         }
00087         if(!cache_.parsed_terrain->is_empty) {
00088             const t_translation::t_terrain letter = map_.get_terrain_info(loc).number();
00089             if(!t_translation::terrain_matches(letter, *cache_.parsed_terrain)) {
00090                 return false;
00091             }
00092         }
00093     }
00094 
00095     //Allow filtering on location ranges
00096     if(!ignore_xy) {
00097         if(!loc.matches_range(cfg_["x"], cfg_["y"])) {
00098             return false;
00099         }
00100         //allow filtering by searching a stored variable of locations
00101         if(cfg_.has_attribute("find_in")) {
00102             variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
00103             if(!vi.is_valid) return false;
00104             if(vi.explicit_index) {
00105                 if(gamemap::location(vi.as_container(),NULL) != loc) {
00106                     return false;
00107                 }
00108             } else {
00109                 variable_info::array_range a_range;
00110                 for(a_range = vi.as_array(); a_range.first != a_range.second; ++a_range.first) {
00111                     if(gamemap::location(**a_range.first,NULL) == loc) {
00112                         break;
00113                     }
00114                 }
00115                 if(a_range.first == a_range.second) {
00116                     return false;
00117                 }
00118             }
00119         }
00120     }
00121 
00122     //Allow filtering on unit
00123     if(cfg_.has_child("filter")) {
00124         const vconfig& unit_filter = cfg_.child("filter");
00125         const unit_map::const_iterator u = units_.find(loc);
00126         if (u == units_.end() || !u->second.matches_filter(unit_filter, loc, flat_))
00127             return false;
00128     }
00129 
00130     //Allow filtering on adjacent locations
00131     if(cfg_.has_child("filter_adjacent_location")) {
00132         gamemap::location adjacent[6];
00133         get_adjacent_tiles(loc, adjacent);
00134         const vconfig::child_list& adj_cfgs = cfg_.get_children("filter_adjacent_location");
00135         vconfig::child_list::const_iterator i, i_end, i_begin = adj_cfgs.begin();
00136         for (i = i_begin, i_end = adj_cfgs.end(); i != i_end; ++i) {
00137             int match_count = 0;
00138             vconfig::child_list::difference_type index = i - i_begin;
00139             std::string adj_dirs = (*i).has_attribute("adjacent") ? (*i)["adjacent"]
00140                 : "n,ne,se,s,sw,nw";
00141             static std::vector<gamemap::location::DIRECTION> default_dirs
00142                 = gamemap::location::parse_directions("n,ne,se,s,sw,nw");
00143             std::vector<gamemap::location::DIRECTION> dirs = (*i).has_attribute("adjacent")
00144                 ? gamemap::location::parse_directions((*i)["adjacent"]) : default_dirs;
00145             std::vector<gamemap::location::DIRECTION>::const_iterator j, j_end = dirs.end();
00146             for (j = dirs.begin(); j != j_end; ++j) {
00147                 gamemap::location &adj = adjacent[*j];
00148                 if(map_.on_board(adj)) {
00149                     if(cache_.adjacent_matches == NULL) {
00150                         while(index >= std::distance(cache_.adjacent_match_cache.begin(), cache_.adjacent_match_cache.end())) {
00151                             const vconfig& adj_cfg = adj_cfgs[cache_.adjacent_match_cache.size()];
00152                             std::pair<terrain_filter, std::map<gamemap::location,bool> > amc_pair(
00153                                 terrain_filter(adj_cfg, *this),
00154                                 std::map<gamemap::location,bool>());
00155                             cache_.adjacent_match_cache.push_back(amc_pair);
00156                         }
00157                         terrain_filter &amc_filter = cache_.adjacent_match_cache[index].first;
00158                         std::map<gamemap::location,bool> &amc = cache_.adjacent_match_cache[index].second;
00159                         std::map<gamemap::location,bool>::iterator lookup = amc.find(adj);
00160                         if(lookup == amc.end()) {
00161                             if(amc_filter(adj)) {
00162                                 amc[adj] = true;
00163                                 ++match_count;
00164                             } else {
00165                                 amc[adj] = false;
00166                             }
00167                         } else if(lookup->second) {
00168                             ++match_count;
00169                         }
00170                     } else {
00171                         assert(index < std::distance(cache_.adjacent_matches->begin(), cache_.adjacent_matches->end()));
00172                         std::set<gamemap::location> &amc = (*cache_.adjacent_matches)[index];
00173                         if(amc.find(adj) != amc.end()) {
00174                             ++match_count;
00175                         }
00176                     }
00177                 }
00178             }
00179             static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-6");
00180             std::vector<std::pair<int,int> > counts = (*i).has_attribute("count")
00181                 ? utils::parse_ranges((*i)["count"]) : default_counts;
00182             std::vector<std::pair<int,int> >::const_iterator count, count_end = counts.end();
00183             bool count_matches = false;
00184             for (count = counts.begin(); count != count_end && !count_matches; ++count) {
00185                 if(count->first <= match_count && match_count <= count->second) {
00186                     count_matches = true;
00187                 }
00188             }
00189             if(!count_matches) {
00190                 return false;
00191             }
00192         }
00193     }
00194 
00195     const t_string& t_tod_type = cfg_["time_of_day"];
00196     const t_string& t_tod_id = cfg_["time_of_day_id"];
00197     const std::string& tod_type = t_tod_type;
00198     const std::string& tod_id = t_tod_id;
00199     static config const dummy_cfg;
00200     time_of_day tod(dummy_cfg);
00201     if(!tod_type.empty() || !tod_id.empty()) {
00202         if(flat_) {
00203             tod = status_.get_time_of_day(0,loc);
00204         } else {
00205             tod = timeofday_at(status_,units_,loc, map_);
00206         }
00207     }
00208     if(!tod_type.empty()) {
00209         const std::vector<std::string>& vals = utils::split(tod_type);
00210         if(tod.lawful_bonus<0) {
00211             if(std::find(vals.begin(),vals.end(),"chaotic") == vals.end()) {
00212                 return false;
00213             }
00214         } else if(tod.lawful_bonus>0) {
00215             if(std::find(vals.begin(),vals.end(),"lawful") == vals.end()) {
00216                 return false;
00217             }
00218         } else {
00219             if(std::find(vals.begin(),vals.end(),"neutral") == vals.end()) {
00220                 return false;
00221             }
00222         }
00223     }
00224     if(!tod_id.empty()) {
00225         if(tod_id != tod.id) {
00226             if(std::find(tod_id.begin(),tod_id.end(),',') != tod_id.end() &&
00227                 std::search(tod_id.begin(),tod_id.end(),
00228                 tod.id.begin(),tod.id.end()) != tod_id.end()) {
00229                 const std::vector<std::string>& vals = utils::split(tod_id);
00230                 if(std::find(vals.begin(),vals.end(),tod.id) == vals.end()) {
00231                     return false;
00232                 }
00233             } else {
00234                 return false;
00235             }
00236         }
00237     }
00238 
00239     //allow filtering on owner (for villages)
00240     const t_string& t_owner_side = cfg_["owner_side"];
00241     const std::string& owner_side = t_owner_side;
00242     if(!owner_side.empty()) {
00243         const int side_index = lexical_cast_default<int>(owner_side,0) - 1;
00244         assert(status_.teams != NULL);
00245         if(village_owner(loc, *(status_.teams)) != side_index) {
00246             return false;
00247         }
00248     }
00249 
00250     return true;
00251 }
00252 
00253 bool terrain_filter::match(const gamemap::location& loc)
00254 {
00255     if(cfg_["x"] == "recall" && cfg_["y"] == "recall") {
00256         return !map_.on_board(loc);
00257     }
00258     std::set<gamemap::location> hexes;
00259     std::vector<gamemap::location> loc_vec(1, loc);
00260 
00261     //handle radius
00262     size_t radius = lexical_cast_default<size_t>(cfg_["radius"], 0);
00263     if(radius > max_loop_) {
00264         ERR_NG << "terrain_filter: radius greater than " << max_loop_
00265         << ", restricting\n";
00266         radius = max_loop_;
00267     }
00268     if(cfg_.has_child("filter_radius")) {
00269         terrain_filter r_filter(cfg_.child("filter_radius"), *this);
00270         get_tiles_radius(map_, loc_vec, radius, hexes, &r_filter);
00271     } else {
00272         get_tiles_radius(map_, loc_vec, radius, hexes);
00273     }
00274 
00275     size_t loop_count = 0;
00276     std::set<gamemap::location>::const_iterator i;
00277     for(i = hexes.begin(); i != hexes.end(); ++i) {
00278         bool matches = match_internal(*i, false);
00279 
00280         //handle [and], [or], and [not] with in-order precedence
00281         vconfig::all_children_iterator cond = cfg_.ordered_begin();
00282         vconfig::all_children_iterator cond_end = cfg_.ordered_end();
00283         while(cond != cond_end)
00284         {
00285             const std::string& cond_name = cond.get_key();
00286             const vconfig& cond_cfg = cond.get_child();
00287 
00288             //handle [and]
00289             if(cond_name == "and")
00290             {
00291                 matches = matches && terrain_filter(cond_cfg, *this)(*i);
00292             }
00293             //handle [or]
00294             else if(cond_name == "or")
00295             {
00296                 matches = matches || terrain_filter(cond_cfg, *this)(*i);
00297             }
00298             //handle [not]
00299             else if(cond_name == "not")
00300             {
00301                 matches = matches && !terrain_filter(cond_cfg, *this)(*i);
00302             }
00303             ++cond;
00304         }
00305         if(matches) {
00306             return true;
00307         }
00308         if(++loop_count > max_loop_) {
00309             std::set<gamemap::location>::const_iterator temp = i;
00310             if(++temp != hexes.end()) {
00311                 ERR_NG << "terrain_filter: loop count greater than " << max_loop_
00312                 << ", aborting\n";
00313                 break;
00314             }
00315         }
00316     }
00317     return false;
00318 }
00319 
00320 void terrain_filter::get_locations(std::set<gamemap::location>& locs)
00321 {
00322     std::vector<gamemap::location> xy_vector = parse_location_range(cfg_["x"],cfg_["y"], &map_);
00323     std::set<gamemap::location> xy_set(xy_vector.begin(), xy_vector.end());
00324     if(xy_set.empty()) {
00325         //consider all locations on the map
00326         for(int x=0; x < map_.w(); x++) {
00327             for(int y=0; y < map_.h(); y++) {
00328                 xy_set.insert(gamemap::location(x,y));
00329             }
00330         }
00331     }
00332     if(cfg_.has_attribute("find_in")) {
00333         //remove any locations not found in the specified variable
00334         variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
00335         if(!vi.is_valid) {
00336             xy_set.clear();
00337         } else if(vi.explicit_index) {
00338             gamemap::location test_loc(vi.as_container(),NULL);
00339             if(xy_set.count(test_loc)) {
00340                 xy_set.clear();
00341                 xy_set.insert(test_loc);
00342             } else {
00343                 xy_set.clear();
00344             }
00345         } else {
00346             std::set<gamemap::location> findin_locs;
00347             variable_info::array_range a_range;
00348             for(a_range = vi.as_array(); a_range.first != a_range.second; ++a_range.first) {
00349                 gamemap::location test_loc(**a_range.first,NULL);
00350                 if(xy_set.count(test_loc)) {
00351                     findin_locs.insert(test_loc);
00352                 }
00353             }
00354             xy_set.swap(findin_locs);
00355         }
00356     }
00357 
00358     //handle location filter
00359     if(cfg_.has_child("filter_adjacent_location")) {
00360         if(cache_.adjacent_matches == NULL) {
00361             cache_.adjacent_matches = new std::vector<std::set<gamemap::location> >();
00362         }
00363         const vconfig::child_list& adj_cfgs = cfg_.get_children("filter_adjacent_location");
00364         for (unsigned i = 0; i < adj_cfgs.size(); ++i) {
00365             std::set<gamemap::location> adj_set;
00366             /* GCC-3.3 doesn't like operator[] so use at which has the same result */
00367             terrain_filter(adj_cfgs.at(i), *this).get_locations(adj_set);
00368             cache_.adjacent_matches->push_back(adj_set);
00369             if(i >= max_loop_ && i+1 < adj_cfgs.size()) {
00370                 ERR_NG << "terrain_filter: loop count greater than " << max_loop_
00371                 << ", aborting\n";
00372                 break;
00373             }
00374         }
00375     }
00376     std::set<gamemap::location>::iterator loc_itor = xy_set.begin();
00377     while(loc_itor != xy_set.end()) {
00378         if(match_internal(*loc_itor, true)) {
00379             ++loc_itor;
00380         } else {
00381             xy_set.erase(loc_itor++);
00382         }
00383     }
00384 
00385     //handle [and], [or], and [not] with in-order precedence
00386     vconfig::all_children_iterator cond = cfg_.ordered_begin();
00387     vconfig::all_children_iterator cond_end = cfg_.ordered_end();
00388     int ors_left = std::count_if(cond, cond_end, cfg_isor());
00389     while(cond != cond_end)
00390     {
00391         //if there are no locations or [or] conditions left, go ahead and return empty
00392         if(xy_set.empty() && ors_left <= 0) {
00393             return;
00394         }
00395 
00396         const std::string& cond_name = cond.get_key();
00397         const vconfig& cond_cfg = cond.get_child();
00398 
00399         //handle [and]
00400         if(cond_name == "and") {
00401             std::set<gamemap::location> intersect_hexes;
00402             terrain_filter(cond_cfg, *this).get_locations(intersect_hexes);
00403             std::set<gamemap::location>::iterator intersect_itor = xy_set.begin();
00404             while(intersect_itor != xy_set.end()) {
00405                 if(intersect_hexes.find(*intersect_itor) == intersect_hexes.end()) {
00406                     xy_set.erase(*intersect_itor++);
00407                 } else {
00408                     ++intersect_itor;
00409                 }
00410             }
00411         }
00412         //handle [or]
00413         else if(cond_name == "or") {
00414             std::set<gamemap::location> union_hexes;
00415             terrain_filter(cond_cfg, *this).get_locations(union_hexes);
00416             //xy_set.insert(union_hexes.begin(), union_hexes.end()); //doesn't compile on MSVC
00417             std::set<gamemap::location>::iterator insert_itor = union_hexes.begin();
00418             while(insert_itor != union_hexes.end()) {
00419                 xy_set.insert(*insert_itor++);
00420             }
00421             --ors_left;
00422         }
00423         //handle [not]
00424         else if(cond_name == "not") {
00425             std::set<gamemap::location> removal_hexes;
00426             terrain_filter(cond_cfg, *this).get_locations(removal_hexes);
00427             std::set<gamemap::location>::iterator erase_itor = removal_hexes.begin();
00428             while(erase_itor != removal_hexes.end()) {
00429                 xy_set.erase(*erase_itor++);
00430             }
00431         }
00432         ++cond;
00433     }
00434     if(xy_set.empty()) {
00435         return;
00436     }
00437 
00438     //handle radius
00439     size_t radius = lexical_cast_default<size_t>(cfg_["radius"], 0);
00440     if(radius > max_loop_) {
00441         ERR_NG << "terrain_filter: radius greater than " << max_loop_
00442         << ", restricting\n";
00443         radius = max_loop_;
00444     }
00445     if(radius > 0) {
00446         xy_vector.clear();
00447         std::copy(xy_set.begin(),xy_set.end(),std::inserter(xy_vector,xy_vector.end()));
00448         if(cfg_.has_child("filter_radius")) {
00449             terrain_filter r_filter(cfg_.child("filter_radius"), *this);
00450             get_tiles_radius(map_, xy_vector, radius, locs, &r_filter);
00451         } else {
00452             get_tiles_radius(map_, xy_vector, radius, locs);
00453         }
00454     } else {
00455         std::copy(xy_set.begin(),xy_set.end(),std::inserter(locs,locs.end()));
00456     }
00457 }
00458 
00459 

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