civ_map_generator 0.1.5

A civilization map generator
Documentation
use rand::Rng;

use crate::{
    tile::Tile,
    tile_component::{BaseTerrain, TerrainType},
    tile_map::{MapParameters, TileMap, impls::generate_area_and_landmass::LandmassType},
};

impl TileMap {
    /// Generate [`BaseTerrain::Lake`] on the map.
    ///
    /// This function is used because when we create the map by [`TileMap::generate_terrain_types`], some water areas will be created surrounded by land.
    /// If these water areas are small enough, they will be considered as lakes and will be replaced by [`BaseTerrain::Lake`].
    pub fn generate_lakes(&mut self, map_parameters: &MapParameters) {
        self.all_tiles().for_each(|tile| {
            let landmass_id = tile.landmass_id(self);
            if self.landmass_list[landmass_id].landmass_type == LandmassType::Water
                && self.landmass_list[landmass_id].size <= map_parameters.max_lake_area_size
            {
                tile.set_base_terrain(self, BaseTerrain::Lake);
            }
        });
    }

    /// Add lakes to the map.
    ///
    /// Besides the lakes generated by [`TileMap::generate_lakes`], this function will add more lakes to the map.
    pub fn add_lakes(&mut self, map_parameters: &MapParameters) {
        let num_large_lake = map_parameters.num_large_lakes;
        // TODO: `lake_tile_rand` should be configurable by the user in the future.
        // That means that when the tile is eligible for adding a lake, we will randomly decide whether to add a lake to the tile based on `lake_tile_rand`. Larger `lake_tile_rand`, less likely to add a lake.
        // `lake_tile_rand` is set to 25, which means that when the tile is eligible for adding a lake, there is a 1/25 chance to add a lake to the tile.
        let lake_tile_rand = 25;

        let mut num_large_lakes_added = 0;

        self.all_tiles().for_each(|tile| {
            if self.can_add_lake(tile)
                && self.random_number_generator.random_range(0..lake_tile_rand) == 0
            {
                if num_large_lakes_added < num_large_lake {
                    let add_more_lakes = self.add_more_lake(tile);

                    if add_more_lakes {
                        num_large_lakes_added += 1;
                    }
                }
                tile.set_terrain_type(self, TerrainType::Water);
                tile.set_base_terrain(self, BaseTerrain::Lake);
                tile.clear_feature(self);
            }
        });
    }

    /// Transform the neighboring tiles of the given tile into lakes if possible.
    ///
    /// # Notice
    ///
    /// This function is only used in CIV6.
    fn add_more_lake(&mut self, tile: Tile) -> bool {
        let grid = self.world_grid.grid;

        let mut large_lake = 0;

        let mut lake_tiles = Vec::new();

        // Get the candidate tiles for the lake.
        // Notice: Don't transform the candidate tiles into lakes in this loop,
        // because if we do so, the result will not be as expected.
        tile.neighbor_tiles(grid).for_each(|neighbor_tile| {
            // 1. Check if the tile can have a lake.
            // 2. Randomly decide whether to add a lake to the tile. Larger `large_lake`, less likely to add a lake.
            if self.can_add_lake(neighbor_tile)
                && self
                    .random_number_generator
                    .random_range(0..(large_lake + 4))
                    < 3
            {
                lake_tiles.push(neighbor_tile);
                large_lake += 1;
            }
        });

        lake_tiles.into_iter().for_each(|tile| {
            tile.set_terrain_type(self, TerrainType::Water);
            tile.set_base_terrain(self, BaseTerrain::Lake);
            tile.clear_feature(self);
        });

        large_lake > 2
    }

    /// Checks if a tile can have a lake.
    ///
    /// A tile can have a lake if it meets all of the following conditions:
    /// 1. It is not water.
    /// 2. It is not a natural wonder.
    /// 3. It is not adjacent to a river.
    /// 4. It is not adjacent to water.
    /// 5. It is not adjacent to a natural wonder.
    ///
    /// # Arguments
    ///
    /// - `tile`: The tile being checked.
    ///
    /// # Returns
    ///
    /// Returns `true` if the tile can have a lake, otherwise `false`.
    fn can_add_lake(&self, tile: Tile) -> bool {
        let grid = self.world_grid.grid;
        // Check if the current tile is suitable for a lake
        if tile.terrain_type(self) == TerrainType::Water
            || tile.natural_wonder(self).is_some()
            || tile.has_river(self)
        {
            return false;
        }

        // Check if all neighbor tiles are also suitable
        tile.neighbor_tiles(grid).all(|neighbor_tile| {
            neighbor_tile.terrain_type(self) != TerrainType::Water
                && neighbor_tile.natural_wonder(self).is_none()
        })
    }
}