dungen_minion 0.3.2

A dungeon generator focused on 2D roguelikes.
Documentation
#![allow(clippy::module_name_repetitions)]

// External includes.

// Standard includes.

// Internal includes.
use super::{invalidate_map, DoesDunGen, MapId, SupportsDunGen, MAPS};
use crate::geometry::{Position, ProvidesCount, ProvidesPosition};

/// A generator for adding one or more instances of [`SubMap`](struct.SubMap`.html) to a map.
///
/// The `SubMapGenerator` is called with a [`ProvidesCount`](geometry/trait/ProvidesCount.html) of the number of maps to be so placed; a set of local [`ProvidesPosition`](geometry/trait.ProvidesPosition.html) to provide the positions at which to place each map; and an optional boxed function to provide those maps. Each set also optionally contains a set of generators to call on those maps, and a validity check to validate the generated sub-maps. A fall-back boxed function to provide maps, should be provided; and, optionally, a set of generators to call on all generated sub-maps can be provided, as well as a validity check to validate all generated sub-maps.
///
/// Will create a map with a `Size` of 40 tiles wide by 30 tiles high, and then generate 4 to 9 sub-maps on it.
/// ```
/// # use dungen_minion::geometry::*;
/// # use dungen_minion::*;
/// use rayon::prelude::*;
/// [0..500].par_iter().for_each(|_i| {
///     // We could provide CountRange directly to EdgePortalsGenerator, but that would not let us
///     // test that we have the right number of portals.
///     // This CountRange will generate a number in the range [4, 9].
///     let num_sub_maps = CountRange::new(4, 9).provide_count();
///     let map_id =
///         DunGen::new(SparseMap::new())
///         // We want this map to be no more than 40 tiles wide by 30 tiles high.
///         .gen_with(&EmptyRoomGenerator::new(Size::new(40, 30)))
///         .gen_with(&WalledRoomGenerator::new(Size::zero()))
///         .gen_with(&SubMapGenerator::new(
///             &[SubMapGeneratorSet::new(
///                 &num_sub_maps,
///                 // By restricting the position slightly, we unsure that the *minimum* area of
///                 // the generated sub-maps does not exceed the bounds we set.
///                 &Area::new(Position::new(0, 0), Size::new(34, 26)),
///                 Some(Box::new(SparseMap::new)),
///                 Some(&[&EmptyRoomGenerator::new(SizeRange::new(
///                     Size::new(6, 6),
///                     Size::new(12, 12),
///                 ))]),
///                 None,
///             )],
///             Some(Box::new(SparseMap::new)),
///             Some(&[&WalledRoomGenerator::new(Size::zero())]),
///             Some(|position: Position, map_id: MapId| {
///                 let maps = &MAPS.read();
///                 let map = &maps[map_id].read();
///                 let mut area = *map.area();
///                 *area.position_mut() = *area.position() + position;
///                 println!("{}", map.area());
///                 !(
///                     area.left() < 0 ||
///                     area.top() < 0 ||
///                     area.right() >= 40 ||
///                     area.bottom() >= 30
///                 )
///             }),
///         ))
///         .build();
///
///     let maps = MAPS.read();
///     let map = maps[map_id].read();
///
///     println!("Map area: {}", map.area());
///     assert!(*map.size() == Size::new(40, 30));
///     assert!(map.sub_map_count() == num_sub_maps);
///     assert!(map.sub_map_count() >= 4 && map.sub_map_count() <= 9);
///     let mut sub_map_count = 0;
///     for sub_map in map.sub_maps() {
///         let target_map = maps[sub_map.value()].read();
///         assert!(target_map.size().width() >= 6 && target_map.size().width() <= 12);
///         assert!(target_map.size().height() >= 6 && target_map.size().height() <= 12);
///         assert!(target_map.tile_type_at_local(Position::new(0, 0)) == Some(TileType::Wall));
///         assert!(target_map.tile_type_at_local(Position::new(1, 1)) == Some(TileType::Floor));
///         sub_map_count += 1;
///     }
///     assert!(sub_map_count == num_sub_maps);
///     assert!(sub_map_count >= 4 && sub_map_count <= 9);
/// })
/// ```
pub struct SubMapGenerator<'a, TValidityCheck>
where
    TValidityCheck: Fn(Position, MapId) -> bool,
{
    sub_maps_generator_sets: &'a [SubMapGeneratorSet<'a>],
    fallback_map_provider: Option<Box<dyn Fn() -> MapId>>,
    global_sub_map_generators: Option<&'a [&'a dyn DoesDunGen]>,
    validity_check: Option<TValidityCheck>,
}

/// Contains information about generating a sub-map for `SubMapGenerator`.
pub struct SubMapGeneratorSet<'a> {
    provides_count: &'a dyn ProvidesCount,
    provides_position: &'a dyn ProvidesPosition,
    provides_map: Option<Box<dyn Fn() -> MapId>>,
    sub_maps_generators: Option<&'a [&'a dyn DoesDunGen]>,
    validity_check: Option<Box<dyn Fn(Position, MapId) -> bool>>,
}

impl<'a> SubMapGeneratorSet<'a> {
    /// Creates a struct which contains information about generating a sub-map for `SubMapGenerator`.
    pub fn new(
        provides_count: &'a dyn ProvidesCount,
        provides_position: &'a dyn ProvidesPosition,
        provides_map: Option<Box<dyn Fn() -> MapId>>,
        sub_maps_generators: Option<&'a [&'a dyn DoesDunGen]>,
        validity_check: Option<Box<dyn Fn(Position, MapId) -> bool>>,
    ) -> Self {
        Self {
            provides_count,
            provides_position,
            provides_map,
            sub_maps_generators,
            validity_check,
        }
    }
}

impl<'a, TValidityCheck> SubMapGenerator<'a, TValidityCheck>
where
    TValidityCheck: Fn(Position, MapId) -> bool,
{
    /// Creates a new generator for adding portals to a map.
    #[must_use]
    pub fn new(
        sub_maps_generator_sets: &'a [SubMapGeneratorSet<'a>],
        fallback_map_provider: Option<Box<dyn Fn() -> MapId>>,
        global_sub_map_generators: Option<&'a [&'a dyn DoesDunGen]>,
        validity_check: Option<TValidityCheck>,
    ) -> Self {
        Self {
            sub_maps_generator_sets,
            fallback_map_provider,
            global_sub_map_generators,
            validity_check,
        }
    }
}

impl<'a, TValidityCheck> DoesDunGen for SubMapGenerator<'a, TValidityCheck>
where
    TValidityCheck: Fn(Position, MapId) -> bool,
{
    fn dun_gen(&self, target: &mut dyn SupportsDunGen) {
        let map_id = target.get_map_id();
        self.dun_gen_map(map_id);
    }

    fn dun_gen_map(&self, map_id: MapId) {
        for sub_maps_generator_set in self.sub_maps_generator_sets {
            let (
                provides_count,
                provides_position,
                map_provider,
                sub_map_generators,
                validity_check,
            ) = (
                sub_maps_generator_set.provides_count,
                sub_maps_generator_set.provides_position,
                &sub_maps_generator_set.provides_map,
                sub_maps_generator_set.sub_maps_generators,
                sub_maps_generator_set.validity_check.as_ref(),
            );

            let count = provides_count.provide_count();
            for _ in 0..count {
                let (mut position, mut new_map_id);
                loop {
                    position = provides_position.provide_position();
                    new_map_id = if let Some(map_provider) = map_provider {
                        map_provider()
                    } else if let Some(map_provider) = &self.fallback_map_provider {
                        map_provider()
                    } else {
                        panic!("Could not find map provider to generate map with.");
                    };

                    // *MAPS.read()[new_map_id].write().position_mut() = position;

                    if let Some(sub_map_generators) = sub_map_generators {
                        for sub_map_generator in sub_map_generators {
                            sub_map_generator.dun_gen_map(new_map_id);
                        }
                    }

                    if let Some(sub_map_generators) = self.global_sub_map_generators {
                        for sub_map_generator in sub_map_generators {
                            sub_map_generator.dun_gen_map(new_map_id);
                        }
                    }

                    if validity_check.is_none() && self.validity_check.is_none() {
                        break;
                    }

                    if let Some(validity_check) = validity_check {
                        if validity_check(position, new_map_id) {
                            break;
                        }

                        invalidate_map(new_map_id);
                    }

                    if let Some(validity_check) = &self.validity_check {
                        if validity_check(position, new_map_id) {
                            break;
                        }

                        invalidate_map(new_map_id);
                    }
                }

                MAPS.read()[map_id]
                    .write()
                    .add_sub_map(position, new_map_id);
            }
        }
    }
}