wesnoth/src/map_location.cpp
2013-03-26 21:41:37 -04:00

472 lines
12 KiB
C++

/*
Copyright (C) 2003 - 2013 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Routines related to game-maps, terrain, locations, directions. etc.
*/
#include "global.hpp"
#include <cassert>
#include "map_location.hpp"
#include "config.hpp"
#include "formula_string_utils.hpp"
#include "map.hpp"
#include "resources.hpp"
#include "util.hpp"
#define ERR_CF LOG_STREAM(err, config)
#define LOG_G LOG_STREAM(info, general)
#define DBG_G LOG_STREAM(debug, general)
std::ostream &operator<<(std::ostream &s, map_location const &l) {
s << (l.x + 1) << ',' << (l.y + 1);
return s;
}
std::ostream &operator<<(std::ostream &s, std::vector<map_location> const &v) {
std::vector<map_location>::const_iterator i = v.begin();
for(; i!= v.end(); ++i) {
s << "(" << *i << ") ";
}
return s;
}
const map_location map_location::null_location;
map_location::DIRECTION map_location::parse_direction(const std::string& str)
{
if(!str.empty()) {
if(str == "n") {
return NORTH;
} else if(str == "ne") {
return NORTH_EAST;
} else if(str == "se") {
return SOUTH_EAST;
} else if(str == "s") {
return SOUTH;
} else if(str == "sw") {
return SOUTH_WEST;
} else if(str == "nw") {
return NORTH_WEST;
} else if(str[0] == '-' && str.length() <= 10) {
// A minus sign reverses the direction
return get_opposite_dir(parse_direction(str.substr(1)));
}
}
return NDIRECTIONS;
}
std::vector<map_location::DIRECTION> map_location::parse_directions(const std::string& str)
{
map_location::DIRECTION temp;
std::vector<map_location::DIRECTION> to_return;
std::vector<std::string> dir_strs = utils::split(str);
std::vector<std::string>::const_iterator i, i_end=dir_strs.end();
for(i = dir_strs.begin(); i != i_end; ++i) {
temp = map_location::parse_direction(*i);
// Filter out any invalid directions
if(temp != NDIRECTIONS) {
to_return.push_back(temp);
}
}
return to_return;
}
std::string map_location::write_direction(map_location::DIRECTION dir)
{
switch(dir) {
case NORTH:
return std::string("n");
case NORTH_EAST:
return std::string("ne");
case NORTH_WEST:
return std::string("nw");
case SOUTH:
return std::string("s");
case SOUTH_EAST:
return std::string("se");
case SOUTH_WEST:
return std::string("sw");
default:
return std::string();
}
}
map_location::map_location(const config& cfg, const variable_set *variables) :
x(-1000),
y(-1000)
{
std::string xs = cfg["x"], ys = cfg["y"];
if (variables)
{
xs = utils::interpolate_variables_into_string( xs, *variables);
ys = utils::interpolate_variables_into_string( ys, *variables);
}
// The co-ordinates in config files will be 1-based,
// while we want them as 0-based.
if(xs.empty() == false && xs != "recall")
x = atoi(xs.c_str()) - 1;
if(ys.empty() == false && ys != "recall")
y = atoi(ys.c_str()) - 1;
}
void map_location::write(config& cfg) const
{
cfg["x"] = x + 1;
cfg["y"] = y + 1;
}
map_location map_location::legacy_negation() const
{
return map_location(-x, -y);
}
map_location map_location::legacy_sum(const map_location& a) const
{
return map_location(*this).legacy_sum_assign(a);
}
map_location& map_location::legacy_sum_assign(const map_location &a)
{
bool parity = (x & 1) != 0;
x += a.x;
y += a.y;
if((a.x > 0) && (a.x % 2) && parity)
y++;
if((a.x < 0) && (a.x % 2) && !parity)
y--;
return *this;
}
map_location map_location::legacy_difference(const map_location &a) const
{
return legacy_sum(a.legacy_negation());
}
map_location map_location::vector_negation() const
{
return map_location(-x, -y - (x & 1)); //subtract one if we're on an odd x coordinate
}
map_location map_location::vector_sum(const map_location& a) const
{
return map_location(*this).vector_sum_assign(a);
}
map_location& map_location::vector_sum_assign(const map_location &a)
{
y += (x & 1) * (a.x & 1); //add one if both x coords are odd
x += a.x;
y += a.y;
return *this;
}
map_location& map_location::vector_difference_assign(const map_location &a)
{
return vector_sum_assign(a.vector_negation());
}
map_location map_location::get_direction(
map_location::DIRECTION dir, int n) const
{
if (n < 0 ) {
dir = get_opposite_dir(dir);
n = -n;
}
switch(dir) {
case NORTH: return map_location(x, y - n);
case SOUTH: return map_location(x, y + n);
case SOUTH_EAST: return map_location(x + n, y + (n+is_odd(x))/2 );
case SOUTH_WEST: return map_location(x - n, y + (n+is_odd(x))/2 );
case NORTH_EAST: return map_location(x + n, y - (n+is_even(x))/2 );
case NORTH_WEST: return map_location(x - n, y - (n+is_even(x))/2 );
default:
assert(false);
return map_location();
}
}
map_location::DIRECTION map_location::get_relative_dir(map_location loc) const {
map_location diff = loc.legacy_difference(*this);
if(diff == map_location(0,0)) return NDIRECTIONS;
if( diff.y < 0 && diff.x >= 0 && abs(diff.x) >= abs(diff.y)) return NORTH_EAST;
if( diff.y < 0 && diff.x < 0 && abs(diff.x) >= abs(diff.y)) return NORTH_WEST;
if( diff.y < 0 && abs(diff.x) < abs(diff.y)) return NORTH;
if( diff.y >= 0 && diff.x >= 0 && abs(diff.x) >= abs(diff.y)) return SOUTH_EAST;
if( diff.y >= 0 && diff.x < 0 && abs(diff.x) >= abs(diff.y)) return SOUTH_WEST;
if( diff.y >= 0 && abs(diff.x) < abs(diff.y)) return SOUTH;
// Impossible
assert(false);
return NDIRECTIONS;
}
map_location::DIRECTION map_location::get_opposite_dir(map_location::DIRECTION d) {
switch (d) {
case NORTH:
return SOUTH;
case NORTH_EAST:
return SOUTH_WEST;
case SOUTH_EAST:
return NORTH_WEST;
case SOUTH:
return NORTH;
case SOUTH_WEST:
return NORTH_EAST;
case NORTH_WEST:
return SOUTH_EAST;
case NDIRECTIONS:
default:
return NDIRECTIONS;
}
}
bool map_location::matches_range(const std::string& xloc, const std::string &yloc) const
{
if(std::find(xloc.begin(),xloc.end(),',') != xloc.end()
|| std::find(yloc.begin(),yloc.end(),',') != yloc.end()) {
std::vector<std::string> xlocs = utils::split(xloc);
std::vector<std::string> ylocs = utils::split(yloc);
size_t size;
for(size = xlocs.size(); size < ylocs.size(); ++size) {
xlocs.push_back("");
}
while(size > ylocs.size()) {
ylocs.push_back("");
}
for(size_t i = 0; i != size; ++i) {
if(matches_range(xlocs[i],ylocs[i]))
return true;
}
return false;
}
if(!xloc.empty()) {
const std::string::const_iterator dash =
std::find(xloc.begin(),xloc.end(),'-');
if(dash != xloc.begin() && dash != xloc.end()) {
const std::string beg(xloc.begin(),dash);
const std::string end(dash+1,xloc.end());
const int bot = atoi(beg.c_str()) - 1;
const int top = atoi(end.c_str()) - 1;
if(x < bot || x > top)
return false;
} else {
const int xval = atoi(xloc.c_str()) - 1;
if(xval != x)
return false;
}
}
if(!yloc.empty()) {
const std::string::const_iterator dash =
std::find(yloc.begin(),yloc.end(),'-');
if(dash != yloc.begin() && dash != yloc.end()) {
const std::string beg(yloc.begin(),dash);
const std::string end(dash+1,yloc.end());
const int bot = atoi(beg.c_str()) - 1;
const int top = atoi(end.c_str()) - 1;
if(y < bot || y > top)
return false;
} else {
const int yval = atoi(yloc.c_str()) - 1;
if(yval != y)
return false;
}
}
return true;
}
void get_adjacent_tiles(const map_location& a, map_location* res)
{
res->x = a.x;
res->y = a.y-1;
++res;
res->x = a.x+1;
res->y = a.y - (is_even(a.x) ? 1:0);
++res;
res->x = a.x+1;
res->y = a.y + (is_odd(a.x) ? 1:0);
++res;
res->x = a.x;
res->y = a.y+1;
++res;
res->x = a.x-1;
res->y = a.y + (is_odd(a.x) ? 1:0);
++res;
res->x = a.x-1;
res->y = a.y - (is_even(a.x) ? 1:0);
}
bool tiles_adjacent(const map_location& a, const map_location& b)
{
// Two tiles are adjacent:
// if y is different by 1, and x by 0,
// or if x is different by 1 and y by 0,
// or if x and y are each different by 1,
// and the x value of the hex with the greater y value is even.
const int xdiff = abs(a.x - b.x);
const int ydiff = abs(a.y - b.y);
return (ydiff == 1 && a.x == b.x) || (xdiff == 1 && a.y == b.y) ||
(xdiff == 1 && ydiff == 1 && (a.y > b.y ? is_even(a.x) : is_even(b.x)));
}
size_t distance_between(const map_location& a, const map_location& b)
{
const size_t hdistance = abs(a.x - b.x);
const size_t vpenalty = ( (is_even(a.x) && is_odd(b.x) && (a.y < b.y))
|| (is_even(b.x) && is_odd(a.x) && (b.y < a.y)) ) ? 1 : 0;
// For any non-negative integer i, i - i/2 - i%2 == i/2
// previously returned (hdistance + vdistance - vsavings)
// = hdistance + vdistance - minimum(vdistance,hdistance/2+hdistance%2)
// = maximum(hdistance, vdistance+hdistance-hdistance/2-hdistance%2)
// = maximum(hdistance,abs(a.y-b.y)+vpenalty+hdistance/2)
return std::max<int>(hdistance, abs(a.y - b.y) + vpenalty + hdistance/2);
}
std::vector<map_location> parse_location_range(const std::string &x, const std::string &y,
bool with_border)
{
std::vector<map_location> res;
const std::vector<std::string> xvals = utils::split(x);
const std::vector<std::string> yvals = utils::split(y);
gamemap *map = resources::game_map;
assert(map);
int xmin = 1, xmax = map->w(), ymin = 1, ymax = map->h();
if (with_border) {
int bs = map->border_size();
xmin -= bs;
xmax += bs;
ymin -= bs;
ymax += bs;
}
for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
{
std::pair<int,int> xrange, yrange;
if (i < xvals.size()) {
xrange = utils::parse_range(xvals[i]);
if (xrange.first < xmin) xrange.first = xmin;
if (xrange.second > xmax) xrange.second = xmax;
} else {
xrange.first = xmin;
xrange.second = xmax;
}
if (i < yvals.size()) {
yrange = utils::parse_range(yvals[i]);
if (yrange.first < ymin) yrange.first = ymin;
if (yrange.second > ymax) yrange.second = ymax;
} else {
yrange.first = ymin;
yrange.second = ymax;
}
for(int x = xrange.first; x <= xrange.second; ++x) {
for(int y = yrange.first; y <= yrange.second; ++y) {
res.push_back(map_location(x-1,y-1));
}
}
}
return res;
}
void write_location_range(const std::set<map_location>& locs, config& cfg)
{
if(locs.empty()){
cfg["x"] = "";
cfg["y"] = "";
return;
}
// need that operator< uses x first
assert(map_location(0,1) < map_location(1,0));
std::stringstream x, y;
std::set<map_location>::const_iterator
i = locs.begin(),
first = i,
last = i;
x << (i->x + 1);
y << (i->y + 1);
for(++i; i != locs.end(); ++i) {
if(i->x != first->x || i->y != last->y+1){
if(last->y != first->y)
y << "-" << (last->y + 1);
x << "," << (i->x + 1);
y << "," << (i->y + 1);
first = i;
}
last = i;
}
// finish last range
if(last->y != first->y)
y << "-" << (last->y + 1);
cfg["x"] = x.str();
cfg["y"] = y.str();
}
void read_locations(const config& cfg, std::vector<map_location>& locs)
{
const std::vector<std::string> xvals = utils::split(cfg["x"]);
const std::vector<std::string> yvals = utils::split(cfg["y"]);
for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
{
int x = lexical_cast<int>(xvals[i])-1;
int y = lexical_cast<int>(yvals[i])-1;
locs.push_back(map_location(x,y));
}
}
void write_locations(const std::vector<map_location>& locs, config& cfg)
{
std::stringstream x, y;
std::vector<map_location>::const_iterator i = locs.begin(),
end = locs.end();
for(; i != end; ++i) {
x << (i->x + 1);
y << (i->y + 1);
if(i+1 != end){
x << ",";
y << ",";
}
}
cfg["x"] = x.str();
cfg["y"] = y.str();
}