use std::collections::HashSet;
use arrayvec::ArrayVec;
use rand::{
RngExt,
distr::{Distribution, weighted::WeightedIndex},
seq::SliceRandom,
};
use crate::{
grid::WorldSizeType,
map_parameters::MapParameters,
ruleset::terrain_type,
tile_component::{Resource, TerrainType},
tile_map::{TileMap, impls::generate_regions::RegionType},
};
impl TileMap {
pub fn assign_luxury_roles(&mut self, map_parameters: &MapParameters) {
self.region_list.sort_by_cached_key(|region| {
let region_type = region.region_type;
let random_number: u8 = self.random_number_generator.random();
(region_type as u8, random_number)
});
for region_index in 0..self.region_list.len() {
let resource = self.assign_luxury_to_region(region_index, map_parameters);
self.region_exclusive_luxury_list.push(resource);
}
let luxury_city_state_weights: Vec<(Resource, usize)> = vec![
(Resource::Whales, 15),
(Resource::Pearls, 15),
(Resource::GoldOre, 10),
(Resource::Silver, 10),
(Resource::Gems, 10),
(Resource::Ivory, 10),
(Resource::Furs, 15),
(Resource::Dyes, 10),
(Resource::Spices, 15),
(Resource::Silk, 15),
(Resource::Sugar, 10),
(Resource::Cotton, 10),
(Resource::Wine, 10),
(Resource::Incense, 15),
(Resource::Copper, 10),
(Resource::Salt, 10),
(Resource::Citrus, 15),
(Resource::Truffles, 15),
(Resource::Crab, 15),
(Resource::Cocoa, 10),
];
let mut luxury_candidates_and_weights: Vec<_> = luxury_city_state_weights
.iter()
.filter(|(luxury, _)| !self.region_exclusive_luxury_list.contains(luxury))
.collect();
let mut luxury_assigned_to_city_state = ArrayVec::new();
for _ in 0..MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_CITY_STATES {
if luxury_candidates_and_weights.is_empty() {
break;
}
let dist = WeightedIndex::new(
luxury_candidates_and_weights
.iter()
.map(|(_, weight)| *weight),
)
.unwrap();
let index = dist.sample(&mut self.random_number_generator);
let &(resource, _) = luxury_candidates_and_weights.swap_remove(index);
luxury_assigned_to_city_state.push(resource);
}
let luxury_assigned_to_special_case = vec![Resource::Marble];
let num_disabled_luxury_type =
get_disabled_luxuries_target_number(map_parameters.world_grid.world_size_type);
let mut remaining_resource_list = luxury_city_state_weights
.iter()
.filter(|(luxury, _)| {
!self.region_exclusive_luxury_list.contains(luxury)
&& !luxury_assigned_to_city_state.contains(luxury)
})
.map(|&(luxury, _)| luxury)
.collect::<Vec<_>>();
remaining_resource_list.shuffle(&mut self.random_number_generator);
let luxury_assigned_to_random = remaining_resource_list
.split_off(num_disabled_luxury_type.min(remaining_resource_list.len() as u32) as usize);
let luxury_not_being_used = remaining_resource_list;
let mut seen = HashSet::new();
let mut regions_exclusive: ArrayVec<
Resource,
{ MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_REGIONS },
> = ArrayVec::new();
for resource in self.region_exclusive_luxury_list.iter() {
if seen.insert(*resource) {
regions_exclusive.push(*resource);
}
}
self.luxury_resource_role = LuxuryResourceRole {
regions_exclusive,
city_states_exclusive: luxury_assigned_to_city_state,
special_cases: luxury_assigned_to_special_case,
random_placement: luxury_assigned_to_random,
disabled: luxury_not_being_used,
};
}
fn assign_luxury_to_region(
&mut self,
region_index: usize,
map_parameters: &MapParameters,
) -> Resource {
let region = &self.region_list[region_index];
let region_type = region.region_type;
let terrain_statistic = region.terrain_statistic.get().unwrap();
let start_location_condition = region.start_location_condition.get().unwrap();
let luxury_fallback_weights = vec![
(Resource::Whales, 10),
(Resource::Pearls, 10),
(Resource::GoldOre, 10),
(Resource::Silver, 5),
(Resource::Gems, 10),
(Resource::Ivory, 5),
(Resource::Furs, 10),
(Resource::Dyes, 5),
(Resource::Spices, 5),
(Resource::Silk, 5),
(Resource::Sugar, 5),
(Resource::Cotton, 5),
(Resource::Wine, 5),
(Resource::Incense, 5),
(Resource::Copper, 5),
(Resource::Salt, 5),
(Resource::Citrus, 5),
(Resource::Truffles, 5),
(Resource::Crab, 10),
(Resource::Cocoa, 5),
];
let luxury_candidates = match region_type {
RegionType::Undefined => luxury_fallback_weights.clone(),
RegionType::Tundra => vec![
(Resource::Furs, 40),
(Resource::Whales, 35),
(Resource::Crab, 30),
(Resource::Silver, 25),
(Resource::Copper, 15),
(Resource::Salt, 15),
(Resource::Gems, 5),
(Resource::Dyes, 5),
],
RegionType::Jungle => vec![
(Resource::Cocoa, 35),
(Resource::Citrus, 35),
(Resource::Spices, 30),
(Resource::Gems, 20),
(Resource::Sugar, 20),
(Resource::Pearls, 20),
(Resource::Copper, 5),
(Resource::Truffles, 5),
(Resource::Crab, 5),
(Resource::Silk, 5),
(Resource::Dyes, 5),
],
RegionType::Forest => vec![
(Resource::Dyes, 30),
(Resource::Silk, 30),
(Resource::Truffles, 30),
(Resource::Furs, 10),
(Resource::Spices, 10),
(Resource::Citrus, 5),
(Resource::Salt, 5),
(Resource::Copper, 5),
(Resource::Cocoa, 5),
(Resource::Crab, 10),
(Resource::Whales, 10),
(Resource::Pearls, 10),
],
RegionType::Desert => vec![
(Resource::Incense, 35),
(Resource::Salt, 15),
(Resource::GoldOre, 25),
(Resource::Copper, 10),
(Resource::Cotton, 15),
(Resource::Sugar, 15),
(Resource::Pearls, 5),
(Resource::Citrus, 5),
],
RegionType::Hill => vec![
(Resource::GoldOre, 30),
(Resource::Silver, 30),
(Resource::Copper, 30),
(Resource::Gems, 15),
(Resource::Pearls, 15),
(Resource::Salt, 10),
(Resource::Crab, 10),
(Resource::Whales, 10),
],
RegionType::Plain => vec![
(Resource::Ivory, 35),
(Resource::Wine, 35),
(Resource::Salt, 25),
(Resource::Incense, 10),
(Resource::Spices, 5),
(Resource::Whales, 5),
(Resource::Pearls, 5),
(Resource::Crab, 5),
(Resource::Truffles, 5),
(Resource::GoldOre, 5),
],
RegionType::Grassland => vec![
(Resource::Cotton, 30),
(Resource::Silver, 20),
(Resource::Sugar, 20),
(Resource::Copper, 20),
(Resource::Crab, 20),
(Resource::Pearls, 10),
(Resource::Whales, 10),
(Resource::Cocoa, 10),
(Resource::Truffles, 5),
(Resource::Spices, 5),
(Resource::Gems, 5),
],
RegionType::Hybrid => vec![
(Resource::Ivory, 15),
(Resource::Cotton, 15),
(Resource::Wine, 15),
(Resource::Silver, 10),
(Resource::Salt, 15),
(Resource::Copper, 20),
(Resource::Whales, 20),
(Resource::Pearls, 20),
(Resource::Crab, 20),
(Resource::Truffles, 10),
(Resource::Cocoa, 10),
(Resource::Spices, 5),
(Resource::Sugar, 5),
(Resource::Incense, 5),
(Resource::Silk, 5),
(Resource::Gems, 5),
(Resource::GoldOre, 5),
],
};
let max_regions_per_exclusive_luxury =
match map_parameters.world_size_type_profile.num_civilizations as usize {
n if n >= MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_REGIONS * 3 / 2 => {
MapParameters::MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE
}
n if n > MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_REGIONS => 2,
_ => 1,
};
let mut resource_list = Vec::new();
let mut resource_weight_list = Vec::new();
for &(luxury, weight) in luxury_candidates.iter() {
let luxury_assign_to_region_count: u32 =
self.assigned_region_exclusive_luxury_count(luxury);
if self
.is_eligible_luxury_for_region_exclusion(luxury, max_regions_per_exclusive_luxury)
{
match (luxury, region_type) {
(Resource::Whales, RegionType::Jungle)
| (Resource::Pearls, RegionType::Tundra)
| (Resource::Crab, RegionType::Desert) => unreachable!(),
(Resource::Whales | Resource::Pearls | Resource::Crab, _) => {
if start_location_condition.along_ocean
&& terrain_statistic.terrain_type_count[TerrainType::Water] >= 12
{
resource_list.push(luxury);
let adjusted_weight = weight / (1 + luxury_assign_to_region_count);
resource_weight_list.push(adjusted_weight);
}
}
_ => {
resource_list.push(luxury);
let adjusted_weight = weight / (1 + luxury_assign_to_region_count);
resource_weight_list.push(adjusted_weight);
}
}
}
}
if resource_list.is_empty()
&& region_type != RegionType::Undefined
&& max_regions_per_exclusive_luxury
!= MapParameters::MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE
{
for &(luxury, weight) in luxury_fallback_weights.iter() {
let luxury_assign_to_region_count: u32 =
self.assigned_region_exclusive_luxury_count(luxury);
if self.is_eligible_luxury_for_region_exclusion(
luxury,
MapParameters::MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE,
) {
if luxury == Resource::Whales
|| luxury == Resource::Pearls
|| luxury == Resource::Crab
{
match (luxury, region_type) {
(Resource::Whales, RegionType::Jungle)
| (Resource::Pearls, RegionType::Tundra)
| (Resource::Crab, RegionType::Desert) => continue,
(Resource::Whales | Resource::Pearls | Resource::Crab, _) => {
if start_location_condition.along_ocean
&& terrain_statistic.terrain_type_count[TerrainType::Water]
>= 12
{
resource_list.push(luxury);
let adjusted_weight =
weight / (1 + luxury_assign_to_region_count);
resource_weight_list.push(adjusted_weight);
}
}
_ => {
resource_list.push(luxury);
let adjusted_weight = weight / (1 + luxury_assign_to_region_count);
resource_weight_list.push(adjusted_weight);
}
}
}
}
}
}
if resource_list.is_empty() {
for &(luxury, weight) in luxury_fallback_weights.iter() {
if self.is_eligible_luxury_for_region_exclusion(
luxury,
MapParameters::MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE,
) {
let luxury_assign_to_region_count: u32 =
self.assigned_region_exclusive_luxury_count(luxury);
resource_list.push(luxury);
let adjusted_weight = weight / (1 + luxury_assign_to_region_count);
resource_weight_list.push(adjusted_weight);
}
}
}
if resource_list.is_empty() {
panic!("No luxury resource available to assign to the region.");
}
let dist: WeightedIndex<u32> = WeightedIndex::new(&resource_weight_list).unwrap();
resource_list[dist.sample(&mut self.random_number_generator)]
}
fn is_eligible_luxury_for_region_exclusion(
&self,
luxury: Resource,
max_regions_per_luxury_type: u32,
) -> bool {
let luxury_assign_to_region_count: u32 =
self.assigned_region_exclusive_luxury_count(luxury);
let num_assigned_luxury_types = self
.region_exclusive_luxury_list
.iter()
.collect::<HashSet<_>>()
.len();
let count_within_limit = luxury_assign_to_region_count < max_regions_per_luxury_type;
let is_region_exclusive = luxury_assign_to_region_count > 0;
let type_limit_ok = num_assigned_luxury_types
< MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_REGIONS
|| is_region_exclusive;
count_within_limit && type_limit_ok
}
pub fn assigned_region_exclusive_luxury_count(&self, luxury: Resource) -> u32 {
self.region_exclusive_luxury_list
.iter()
.filter(|&&r| r == luxury)
.count() as u32
}
}
#[derive(PartialEq, Eq, Default, Debug)]
pub struct LuxuryResourceRole {
pub regions_exclusive:
ArrayVec<Resource, { MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_REGIONS }>,
pub city_states_exclusive:
ArrayVec<Resource, { MapParameters::NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_CITY_STATES }>,
pub special_cases: Vec<Resource>,
pub random_placement: Vec<Resource>,
pub disabled: Vec<Resource>,
}
fn get_disabled_luxuries_target_number(world_size_type: WorldSizeType) -> u32 {
match world_size_type {
WorldSizeType::Duel => 11,
WorldSizeType::Tiny => 8,
WorldSizeType::Small => 6,
WorldSizeType::Standard => 4,
WorldSizeType::Large => 2,
WorldSizeType::Huge => 1,
}
}