added naming of more terrain features;

made a map generator option to build a road between castles
This commit is contained in:
Dave White 2004-05-25 02:08:17 +00:00
parent b6ad0df370
commit 1f6db07509
7 changed files with 251 additions and 140 deletions

View file

@ -139,8 +139,9 @@ road_windiness=3
[road_cost]
terrain=c
cost=5
cost=10
convert_to_bridge=|,/,\
convert_to=C
[/road_cost]
[road_cost]
@ -155,11 +156,19 @@ road_windiness=3
convert_to=r
[/road_cost]
#define MIN_COST_ROAD X
[road_cost]
terrain=r
cost=0
convert_to=r
terrain={X}
cost=0.2
convert_to={X}
[/road_cost]
#enddef
{MIN_COST_ROAD r}
{MIN_COST_ROAD |}
{MIN_COST_ROAD /}
{MIN_COST_ROAD \}
{MIN_COST_ROAD C}
#road going through snow is covered over by
#the snow (presumably the road was built when
@ -170,11 +179,12 @@ road_windiness=3
convert_to=S
[/road_cost]
[village]
terrain=g
convert_to=v
adjacent_liked=gccc|/\RRrrtvhf
rating=5
adjacent_liked=gccccccc|/\RRrrtvhf
rating=8
[/village]
[village]
terrain=d

View file

@ -451,6 +451,7 @@ landform="Landform"
inland="Inland"
coastal="Coastal"
island="Island"
link_castles="Roads Between Castles"
num_players="Players"
map_width="Width"
@ -677,7 +678,7 @@ cavewall="Cave Wall"
#naming of terrain features
bridge_name="$name's Bridge,$name's Crossing"
road_name="$name's Highway,$name's Pass"
road_name="$name's Highway,$name's Pass,Path of $name"
river_name="$name River,River $name"
forest_name="$name Forest,$name's Forest"
lake_name="$name Lake"

View file

@ -62,6 +62,8 @@ namespace {
typedef std::vector<std::vector<int> > height_map;
typedef std::vector<std::vector<char> > terrain_map;
typedef gamemap::location location;
//basically we generate alot of hills, each hill being centered at a certain point, with a certain radius - being a half sphere.
//Hills are combined additively to form a bumpy surface
//The size of each hill varies randomly from 1-hill_size.
@ -191,29 +193,32 @@ height_map generate_height_map(size_t width, size_t height,
//'lake_fall_off' % chance to make another water tile in each of the directions n,s,e,w.
//In each of the directions it does make another water tile, it will have 'lake_fall_off'/2 %
//chance to make another water tile in each of the directions. This will continue recursively.
void generate_lake(terrain_map& terrain, int x, int y, int lake_fall_off)
bool generate_lake(terrain_map& terrain, int x, int y, int lake_fall_off, std::set<location>& locs_touched)
{
if(x < 0 || y < 0 || size_t(x) >= terrain.size() || size_t(y) >= terrain.front().size()) {
return;
return false;
}
terrain[x][y] = 'c';
locs_touched.insert(location(x,y));
if((rand()%100) < lake_fall_off) {
generate_lake(terrain,x+1,y,lake_fall_off/2);
generate_lake(terrain,x+1,y,lake_fall_off/2,locs_touched);
}
if((rand()%100) < lake_fall_off) {
generate_lake(terrain,x-1,y,lake_fall_off/2);
generate_lake(terrain,x-1,y,lake_fall_off/2,locs_touched);
}
if((rand()%100) < lake_fall_off) {
generate_lake(terrain,x,y+1,lake_fall_off/2);
generate_lake(terrain,x,y+1,lake_fall_off/2,locs_touched);
}
if((rand()%100) < lake_fall_off) {
generate_lake(terrain,x,y-1,lake_fall_off/2);
generate_lake(terrain,x,y-1,lake_fall_off/2,locs_touched);
}
return true;
}
typedef gamemap::location location;
@ -274,11 +279,16 @@ bool generate_river_internal(const height_map& heights, terrain_map& terrain, in
return false;
}
void generate_river(const height_map& heights, terrain_map& terrain, int x, int y, int river_uphill)
std::vector<location> generate_river(const height_map& heights, terrain_map& terrain, int x, int y, int river_uphill)
{
std::vector<location> river;
std::set<location> seen_locations;
generate_river_internal(heights,terrain,x,y,river,seen_locations,river_uphill);
const bool res = generate_river_internal(heights,terrain,x,y,river,seen_locations,river_uphill);
if(!res) {
river.clear();
}
return river;
}
//function to return a random tile at one of the borders of a map that is
@ -360,7 +370,7 @@ double road_path_calculator::cost(const location& loc, double so_far) const
const config* const child = cfg_.find_child("road_cost","terrain",terrain);
double res = 100000.0;
if(child != NULL) {
res = double(atoi((*child)["cost"].c_str()));
res = double(atof((*child)["cost"].c_str()));
}
cache_.insert(std::pair<char,double>(c,res));
@ -624,7 +634,10 @@ std::string generate_name(const unit_race& name_generator, const std::string& id
const std::vector<std::string>& options = config::split(string_table[id]);
if(options.empty() == false) {
const size_t choice = rand()%options.size();
const std::string& name = name_generator.generate_name(unit_race::MALE);
std::map<std::string,std::string> table;
table["name"] = name;
return config::interpolate_variables_into_string(options[choice],&table);
}
return "";
@ -635,7 +648,7 @@ std::string generate_name(const unit_race& name_generator, const std::string& id
//function to generate the map.
std::string default_generate_map(size_t width, size_t height, size_t island_size, size_t island_off_center,
size_t iterations, size_t hill_size,
size_t max_lakes, size_t nvillages, size_t nplayers,
size_t max_lakes, size_t nvillages, size_t nplayers, bool roads_between_castles,
std::map<gamemap::location,std::string>* labels, const config& cfg)
{
//odd widths are nasty, so make them even
@ -706,14 +719,47 @@ std::string default_generate_map(size_t width, size_t height, size_t island_size
//we attempt to place 'max_lakes' lakes. Each lake will be placed at a random location,
//if that random location meets the minimum terrain requirements for a lake.
//We will also attempt to source a river from each lake.
std::set<location> lake_locs;
const int nlakes = max_lakes > 0 ? (rand()%max_lakes) : 0;
for(size_t lake = 0; lake != nlakes; ++lake) {
for(int tries = 0; tries != 100; ++tries) {
const int x = rand()%width;
const int y = rand()%height;
if(heights[x][y] > atoi(cfg["min_lake_height"].c_str())) {
generate_river(heights,terrain,x,y,atoi(cfg["river_frequency"].c_str()));
generate_lake(terrain,x,y,atoi(cfg["lake_size"].c_str()));
const std::vector<location> river = generate_river(heights,terrain,x,y,atoi(cfg["river_frequency"].c_str()));
if(river.empty() == false && labels != NULL) {
const std::string& name = generate_name(name_generator,"river_name");
size_t name_frequency = 20;
for(std::vector<location>::const_iterator r = river.begin(); r != river.end(); ++r) {
if(((r - river.begin())%name_frequency) == name_frequency/2) {
labels->insert(std::pair<gamemap::location,std::string>(gamemap::location(r->x-width/3,r->y-height/3),name));
}
}
}
std::set<location> locs;
const bool res = generate_lake(terrain,x,y,atoi(cfg["lake_size"].c_str()),locs);
if(res && labels != NULL) {
bool touches_other_lake = false;
//only generate a name if the lake hasn't touched any other lakes, so that we
//don't end up with one big lake with multiple names
for(std::set<location>::const_iterator i = locs.begin(); i != locs.end(); ++i) {
if(lake_locs.count(*i) != 0) {
touches_other_lake = true;
}
lake_locs.insert(*i);
}
if(!touches_other_lake) {
const std::string& name = generate_name(name_generator,"lake_name");
labels->insert(std::pair<gamemap::location,std::string>(gamemap::location(x-width/3,y-height/3),name));
}
}
break;
}
}
@ -786,104 +832,6 @@ std::string default_generate_map(size_t width, size_t height, size_t island_size
}
}
std::cerr << "placing roads...\n";
//place roads. We select two tiles at random locations on the borders of the map,
//and try to build roads between them.
const size_t nroads = atoi(cfg["roads"].c_str());
for(size_t road = 0; road != nroads; ++road) {
//we want the locations to be on the portion of the map we're actually going
//to use, since roads on other parts of the map won't have any influence,
//and doing it like this will be quicker.
location src = random_point_at_side(width/3 + 2,height/3 + 2);
location dst = random_point_at_side(width/3 + 2,height/3 + 2);
src.x += width/3 - 1;
src.y += height/3 - 1;
dst.x += width/3 - 1;
dst.y += height/3 - 1;
//if the road isn't very interesting (on the same border), don't draw it
if(src.x == dst.x || src.y == dst.y) {
continue;
}
const road_path_calculator calc(terrain,cfg);
if(calc.cost(src,0.0) >= 1000.0 || calc.cost(dst,0.0) >= 1000.0) {
continue;
}
//search a path out for the road
const paths::route rt = a_star_search(src,dst,1000.0,calc);
//draw the road. If the search failed, rt.steps will simply be empty
for(std::vector<location>::const_iterator step = rt.steps.begin(); step != rt.steps.end(); ++step) {
const int x = step->x;
const int y = step->y;
if(x < 0 || y < 0 || x >= width || y >= height)
continue;
//find the configuration which tells us what to convert this tile to
//to make it into a road.
const std::string str(1,terrain[x][y]);
const config* const child = cfg.find_child("road_cost","terrain",str);
if(child != NULL) {
//convert to bridge means that we want to convert depending
//upon the direction the road is going.
//typically it will be in a format like,
//convert_to_bridge=|,/,\
// '|' will be used if the road is going north-south
// '/' will be used if the road is going south west-north east
// '\' will be used if the road is going south east-north west
//the terrain will be left unchanged otherwise (if there is no clear
//direction)
const std::string& convert_to_bridge = (*child)["convert_to_bridge"];
if(convert_to_bridge.empty() == false) {
if(step == rt.steps.begin() || step+1 == rt.steps.end())
continue;
const location& last = *(step-1);
const location& next = *(step+1);
location adj[6];
get_adjacent_tiles(*step,adj);
size_t direction = 0;
//if we are going north-south
if(last == adj[0] && next == adj[3] || last == adj[3] && next == adj[0]) {
direction = 0;
}
//if we are going south west-north east
else if(last == adj[1] && next == adj[4] || last == adj[4] && next == adj[1]) {
direction = 1;
}
//if we are going south east-north west
else if(last == adj[2] && next == adj[5] || last == adj[5] && next == adj[2]) {
direction = 2;
} else {
continue;
}
const std::vector<std::string> items = config::split(convert_to_bridge);
if(direction < items.size() && items[direction].empty() == false) {
terrain[x][y] = items[direction][0];
}
continue;
}
//just a plain terrain substitution for a road
const std::string& convert_to = (*child)["convert_to"];
if(convert_to.empty() == false)
terrain[x][y] = convert_to[0];
}
}
}
std::cerr << "placing villages...\n";
//place villages in a 'grid', to make placing fair, but with villages
//displaced from their position according to terrain and randomness, to
@ -909,11 +857,14 @@ std::string default_generate_map(size_t width, size_t height, size_t island_size
//we try again. Definition of 'well-placed' is if no two castles are closer than
//'min_distance' hexes from each other, and the castles appear on a terrain listed
//in 'valid_terrain'.
std::vector<location> castles;
int ntries = 0;
bool placing_bad = true;
const size_t max_tries = 4;
while(placing_bad && ntries++ < max_tries) {
castles.clear();
int min_x = width/3 + 2;
int min_y = height/3 + 2;
int max_x = (width/3)*2 - 3;
@ -933,7 +884,6 @@ std::string default_generate_map(size_t width, size_t height, size_t island_size
max_y = minimum<int>(max_y,island_bot);
}
std::vector<location> castles;
for(size_t player = 0; player != nplayers; ++player) {
int x = 0, y = 0;
const int max_tries = 10;
@ -1010,22 +960,158 @@ std::string default_generate_map(size_t width, size_t height, size_t island_size
}
std::cerr << "placing " << castles.size() << " castles\n";
}
//plonk down the castles.
for(c = castles.begin(); c != castles.end(); ++c) {
const int x = c->x;
const int y = c->y;
const int player = c - castles.begin();
terrain[x][y] = '1' + player;
terrain[x-1][y] = 'C';
terrain[x+1][y] = 'C';
terrain[x][y-1] = 'C';
terrain[x][y+1] = 'C';
terrain[x-1][y-1] = 'C';
terrain[x-1][y+1] = 'C';
terrain[x+1][y-1] = 'C';
terrain[x+1][y+1] = 'C';
std::cerr << "placing roads...\n";
//place roads. We select two tiles at random locations on the borders of the map,
//and try to build roads between them.
size_t nroads = atoi(cfg["roads"].c_str());
if(roads_between_castles) {
nroads += castles.size()*castles.size();
}
for(size_t road = 0; road != nroads; ++road) {
//we want the locations to be on the portion of the map we're actually going
//to use, since roads on other parts of the map won't have any influence,
//and doing it like this will be quicker.
location src = random_point_at_side(width/3 + 2,height/3 + 2);
location dst = random_point_at_side(width/3 + 2,height/3 + 2);
src.x += width/3 - 1;
src.y += height/3 - 1;
dst.x += width/3 - 1;
dst.y += height/3 - 1;
if(roads_between_castles && road < castles.size()*castles.size()) {
const size_t src_castle = road/castles.size();
const size_t dst_castle = road%castles.size();
if(src_castle == dst_castle) {
continue;
}
src = castles[src_castle];
dst = castles[dst_castle];
}
//if the road isn't very interesting (on the same border), don't draw it
else if(src.x == dst.x || src.y == dst.y) {
continue;
}
const road_path_calculator calc(terrain,cfg);
if(calc.cost(src,0.0) >= 1000.0 || calc.cost(dst,0.0) >= 1000.0) {
continue;
}
//search a path out for the road
const paths::route rt = a_star_search(src,dst,1000.0,calc);
const std::string& name = generate_name(name_generator,"road_name");
const int name_frequency = 20;
int name_count = 0;
bool on_bridge = false;
//draw the road. If the search failed, rt.steps will simply be empty
for(std::vector<location>::const_iterator step = rt.steps.begin(); step != rt.steps.end(); ++step) {
const int x = step->x;
const int y = step->y;
if(x < 0 || y < 0 || x >= width || y >= height)
continue;
//find the configuration which tells us what to convert this tile to
//to make it into a road.
const std::string str(1,terrain[x][y]);
const config* const child = cfg.find_child("road_cost","terrain",str);
if(child != NULL) {
//convert to bridge means that we want to convert depending
//upon the direction the road is going.
//typically it will be in a format like,
//convert_to_bridge=|,/,\
// '|' will be used if the road is going north-south
// '/' will be used if the road is going south west-north east
// '\' will be used if the road is going south east-north west
//the terrain will be left unchanged otherwise (if there is no clear
//direction)
const std::string& convert_to_bridge = (*child)["convert_to_bridge"];
if(convert_to_bridge.empty() == false) {
if(step == rt.steps.begin() || step+1 == rt.steps.end())
continue;
const location& last = *(step-1);
const location& next = *(step+1);
location adj[6];
get_adjacent_tiles(*step,adj);
int direction = -1;
//if we are going north-south
if(last == adj[0] && next == adj[3] || last == adj[3] && next == adj[0]) {
direction = 0;
}
//if we are going south west-north east
else if(last == adj[1] && next == adj[4] || last == adj[4] && next == adj[1]) {
direction = 1;
}
//if we are going south east-north west
else if(last == adj[2] && next == adj[5] || last == adj[5] && next == adj[2]) {
direction = 2;
}
if(on_bridge == false) {
on_bridge = true;
const std::string& name = generate_name(name_generator,"bridge_name");
labels->insert(std::pair<gamemap::location,std::string>(gamemap::location(x-width/3,y-height/3),name));
}
if(direction != -1) {
const std::vector<std::string> items = config::split(convert_to_bridge);
if(size_t(direction) < items.size() && items[direction].empty() == false) {
terrain[x][y] = items[direction][0];
}
continue;
}
} else {
on_bridge = false;
}
//just a plain terrain substitution for a road
const std::string& convert_to = (*child)["convert_to"];
if(convert_to.empty() == false) {
if(labels != NULL && terrain[x][y] != convert_to[0] && name_count++ == name_frequency && on_bridge == false) {
labels->insert(std::pair<gamemap::location,std::string>(gamemap::location(x-width/3,y-height/3),name));
name_count = 0;
}
terrain[x][y] = convert_to[0];
}
}
}
}
//now that road drawing is done, we can plonk down the castles.
for(std::vector<location>::const_iterator c = castles.begin(); c != castles.end() && placing_bad == false; ++c) {
const int x = c->x;
const int y = c->y;
const int player = c - castles.begin();
terrain[x][y] = '1' + player;
terrain[x-1][y] = 'C';
terrain[x+1][y] = 'C';
terrain[x][y-1] = 'C';
terrain[x][y+1] = 'C';
terrain[x-1][y-1] = 'C';
terrain[x-1][y+1] = 'C';
terrain[x+1][y-1] = 'C';
terrain[x+1][y+1] = 'C';
}
if(nvillages > 0) {

View file

@ -37,7 +37,8 @@ map_generator* create_map_generator(const std::string& name, const config* cfg);
std::string default_generate_map(size_t width, size_t height, size_t island_size, size_t island_off_center,
size_t iterations, size_t hill_size,
size_t max_lakes, size_t nvillages, size_t nplayers, std::map<gamemap::location,std::string>* labels,
size_t max_lakes, size_t nvillages, size_t nplayers,
bool roads_between_castles, std::map<gamemap::location,std::string>* labels,
const config& cfg);
#endif

View file

@ -17,7 +17,7 @@ namespace {
default_map_generator::default_map_generator(const config* cfg)
: width_(40), height_(40), island_size_(0), iterations_(1000), hill_size_(10), max_lakes_(20),
nvillages_(25), nplayers_(2)
nvillages_(25), nplayers_(2), link_castles_(true)
{
if(cfg != NULL) {
cfg_ = *cfg;
@ -177,6 +177,13 @@ void default_map_generator::user_config(display& disp)
landform_slider.set_max(max_landform);
landform_slider.set_value(island_size_);
SDL_Rect link_rect = slider_rect;
link_rect.y = link_rect.y + link_rect.h + vertical_margin;
gui::button link_castles(disp,string_table["link_castles"],gui::button::TYPE_CHECK);
link_castles.set_check(link_castles_);
link_castles.set_location(link_rect);
for(bool draw = true;; draw = false) {
int mousex, mousey;
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
@ -204,6 +211,7 @@ void default_map_generator::user_config(display& disp)
hillsize_slider.set_dirty();
villages_slider.set_dirty();
landform_slider.set_dirty();
link_castles.set_dirty();
width_slider.set_min(min_width+(players_slider.value()-2)*extra_size_per_player);
height_slider.set_min(min_width+(players_slider.value()-2)*extra_size_per_player);
@ -250,6 +258,8 @@ void default_map_generator::user_config(display& disp)
SDL_Delay(10);
events::pump();
}
link_castles_ = link_castles.checked();
}
std::string default_map_generator::name() const { return "default"; }
@ -284,7 +294,7 @@ std::string default_map_generator::generate_map(const std::vector<std::string>&
std::cerr << "calculated coastal params...\n";
}
return default_generate_map(width_,height_,island_size,island_off_center,iterations,hill_size_,max_lakes,(nvillages_*width_*height_)/1000,nplayers_,labels,cfg_);
return default_generate_map(width_,height_,island_size,island_off_center,iterations,hill_size_,max_lakes,(nvillages_*width_*height_)/1000,nplayers_,link_castles_,labels,cfg_);
}
config default_map_generator::create_scenario(const std::vector<std::string>& args)
@ -305,9 +315,11 @@ config default_map_generator::create_scenario(const std::vector<std::string>& ar
std::cerr << "done generating map..\n";
for(std::map<gamemap::location,std::string>::const_iterator i = labels.begin(); i != labels.end(); ++i) {
config& label = res.add_child("label");
label["text"] = i->second;
i->first.write(label);
if(i->first.x >= 0 && i->first.y >= 0 && i->first.x < width_ && i->first.y < height_) {
config& label = res.add_child("label");
label["text"] = i->second;
i->first.write(label);
}
}
return res;

View file

@ -21,6 +21,7 @@ private:
std::string generate_map(const std::vector<std::string>& args, std::map<gamemap::location,std::string>* labels=NULL);
size_t width_, height_, island_size_, iterations_, hill_size_, max_lakes_, nvillages_, nplayers_;
bool link_castles_;
config cfg_;
};

View file

@ -76,7 +76,7 @@ int mp_connect::load_map(const std::string& era, config& scenario_data, int num_
era_ = era;
if(scenario_data.empty()) {
if(scenario_data.child("side") == NULL) {
//Load a saved game
save_ = true;
bool show_replay = false;