1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use std::cmp::max;
use rand::Rng;
use crate::{
fractal::{CvFractal, FractalFlags},
map_parameters::Temperature,
tile_component::{BaseTerrain, TerrainType},
tile_map::{MapParameters, TileMap},
};
impl TileMap {
/// Generate base terrains except for [`BaseTerrain::Lake`].
///
/// # Notice
///
/// We don't generate [`BaseTerrain::Lake`] here, because the lake is a special base terrain that is generated in the [`TileMap::generate_lakes`] and [`TileMap::add_lakes`] method.
pub fn generate_base_terrains(&mut self, map_parameters: &MapParameters) {
let grid = self.world_grid.grid;
let grain_amount = 3;
let temperature_shift = 0.1;
let desert_shift = 16;
let mut desert_percent = 32;
let plains_percent = 50;
// Set default base terrain bands.
// TODO: This should be moved to the map parameters and be configurable by the user in the future.
// Notice: The number should be sorted in ascending order.
let [
mut grass_latitude,
desert_bottom_latitude,
mut desert_top_latitude,
mut tundra_latitude,
mut snow_latitude,
] = [0.1, 0.2, 0.5, 0.6, 0.75];
match map_parameters.temperature {
Temperature::Cool => {
desert_percent -= desert_shift;
tundra_latitude -= temperature_shift * 1.5;
desert_top_latitude -= temperature_shift;
grass_latitude -= temperature_shift * 0.5;
}
Temperature::Normal => {}
Temperature::Hot => {
desert_percent += desert_shift;
snow_latitude += temperature_shift * 0.5;
tundra_latitude += temperature_shift;
desert_top_latitude += temperature_shift;
grass_latitude -= temperature_shift * 0.5;
}
}
let desert_top_percent = 100;
let desert_bottom_percent = max(0, 100 - desert_percent);
let plains_top_percent = 100;
let plains_bottom_percent = max(0, 100 - plains_percent);
let flags = FractalFlags::empty();
//let (seed, seed2, seed3) = self.random_number_generator.gen();
let variation_fractal = CvFractal::create(
&mut self.random_number_generator,
grid,
grain_amount,
flags,
CvFractal::DEFAULT_WIDTH_EXP,
CvFractal::DEFAULT_HEIGHT_EXP,
);
let deserts_fractal = CvFractal::create(
&mut self.random_number_generator,
grid,
grain_amount,
flags,
CvFractal::DEFAULT_WIDTH_EXP,
CvFractal::DEFAULT_HEIGHT_EXP,
);
let plains_fractal = CvFractal::create(
&mut self.random_number_generator,
grid,
grain_amount,
flags,
CvFractal::DEFAULT_WIDTH_EXP,
CvFractal::DEFAULT_HEIGHT_EXP,
);
let [desert_top, plains_top] =
deserts_fractal.get_height_from_percents([desert_top_percent, plains_top_percent]);
let [desert_bottom, plains_bottom] =
plains_fractal.get_height_from_percents([desert_bottom_percent, plains_bottom_percent]);
self.all_tiles().for_each(|tile| {
let terrain_type = tile.terrain_type(self);
match terrain_type {
TerrainType::Water => {
// Generate coast terrain.
//
// The tiles that can be coast should meet all the conditions as follows:
// 1. They are ocean, that means they are water, not lake and not already coast.
// 2. They have at least one neighbor that is not water.
if tile.base_terrain(self) == BaseTerrain::Ocean
&& tile.neighbor_tiles(grid).any(|neighbor_tile| {
neighbor_tile.terrain_type(self) != TerrainType::Water
})
{
tile.set_base_terrain(self, BaseTerrain::Coast);
}
}
TerrainType::Flatland | TerrainType::Hill | TerrainType::Mountain => {
// Generate base terrain for land tiles.
let [x, y] = tile.to_offset(grid).to_array();
// Set default base terrain of all land tiles to `BaseTerrain::Grassland` because the default base terrain is `BaseTerrain::Ocean` in the tile map.
tile.set_base_terrain(self, BaseTerrain::Grassland);
let deserts_height = deserts_fractal.get_height(x, y);
let plains_height = plains_fractal.get_height(x, y);
let mut latitude = tile.latitude(grid);
latitude += (128. - variation_fractal.get_height(x, y) as f64) / (255.0 * 5.0);
latitude = latitude.clamp(0., 1.);
if latitude >= snow_latitude {
tile.set_base_terrain(self, BaseTerrain::Snow);
} else if latitude >= tundra_latitude {
tile.set_base_terrain(self, BaseTerrain::Tundra);
} else if latitude < grass_latitude {
tile.set_base_terrain(self, BaseTerrain::Grassland);
} else if deserts_height >= desert_bottom
&& deserts_height <= desert_top
&& latitude >= desert_bottom_latitude
&& latitude < desert_top_latitude
{
tile.set_base_terrain(self, BaseTerrain::Desert);
} else if plains_height >= plains_bottom && plains_height <= plains_top {
tile.set_base_terrain(self, BaseTerrain::Plain);
}
}
}
});
}
/// Expand coast terrain.
///
/// The tiles that can be expanded should meet all the conditions as follows:
/// 1. They are water and not already coast
/// 2. They have at least one neighbor that is coast
/// 3. A random number generator will be used to determine whether the tile will be expanded.
///
/// # Notice
///
/// This method is called after the [`TileMap::generate_base_terrains`] method.
pub fn expand_coasts(&mut self, map_parameters: &MapParameters) {
let grid = self.world_grid.grid;
map_parameters
.coast_expand_chance
.iter()
.for_each(|&chance| {
let mut expansion_tile = Vec::new();
/* Don't update the base_terrain of the tile in the iteration.
Because if we update the base_terrain of the tile in the iteration,
the tile will be used in the next iteration(e.g. tile.tile_neighbors().iter().any()),
which will cause the result to be wrong. */
self.all_tiles().for_each(|tile| {
// The tiles that can be expanded should meet some conditions:
// 1. They are ocean, that means they are water, not lake and not already coast.
// 2. They have at least one neighbor that is coast.
if tile.base_terrain(self) == BaseTerrain::Ocean
&& tile.neighbor_tiles(grid).any(|neighbor_tile| {
neighbor_tile.base_terrain(self) == BaseTerrain::Coast
})
&& self.random_number_generator.random_bool(chance)
{
expansion_tile.push(tile);
}
});
expansion_tile.into_iter().for_each(|tile| {
tile.set_base_terrain(self, BaseTerrain::Coast);
});
});
}
}