underworld_core 0.6.3

Small library to either play a game or assist in generating rooms
Documentation
mod builder;
mod dimensions;
mod exits;
mod fixtures;
pub mod npcs;

pub use builder::{
    ExitGenerationArgs, RoomFixtureGenerationArgs, RoomGeneratorBuilder, RoomNpcGenerationArgs,
};

use std::ops::RangeInclusive;

use rand::Rng;
use strum::IntoEnumIterator;
use uuid::Uuid;

use crate::components::{
    fixtures::FixtureType,
    rooms::{Descriptor, Dimensions, ExitType, Flavour, Room, RoomType},
    Ghost, LifeModifier, Species,
};

use self::{
    dimensions::build_dimensions, exits::build_exits, fixtures::build_fixture_positions,
    npcs::build_npc_positions,
};

use super::generator::Generator;

pub struct BuildNpcsArgs {
    pub num_groups: RangeInclusive<u16>,
    pub possible_species: Vec<Species>,
    pub possible_life_modifiers: Vec<LifeModifier>,
    pub allow_npcs_to_spawn_dead: bool,
    pub ghosts: Vec<Ghost>,
}

impl Default for BuildNpcsArgs {
    fn default() -> Self {
        Self {
            num_groups: 1..=2,
            possible_species: Species::iter().collect(),
            possible_life_modifiers: LifeModifier::iter().collect(),
            allow_npcs_to_spawn_dead: true,
            ghosts: Vec::new(),
        }
    }
}

pub struct BuildFixturesArgs {
    pub num_groups: RangeInclusive<u16>,
    pub possible_types: Vec<FixtureType>,
}

impl Default for BuildFixturesArgs {
    fn default() -> Self {
        Self {
            num_groups: 1..=2,
            possible_types: FixtureType::iter().collect(),
        }
    }
}

pub struct BuildExitArgs {
    pub num_exits: RangeInclusive<u16>,
    pub exit_types: Vec<ExitType>,
}

impl Default for BuildExitArgs {
    fn default() -> Self {
        Self {
            num_exits: 2..=3,
            exit_types: ExitType::iter().collect(),
        }
    }
}

struct RoomPrototype {
    pub num_descriptors: RangeInclusive<u16>,
    pub room_type: RoomType,
    pub possible_descriptors: Vec<Descriptor>,
    pub entrance_id: Option<Uuid>,
    pub danger_level: u32,
    pub possible_flavour_texts: Vec<Flavour>,
    pub include_flavour_text: bool,
    pub name: Option<String>,
    pub dimensions: Option<Dimensions>,
    pub build_exit_args: BuildExitArgs,
    pub build_npc_args: BuildNpcsArgs,
    pub build_fixtures_args: BuildFixturesArgs,
}

impl Generator<Room> for RoomPrototype {
    fn generate(&self) -> Room {
        let mut rng = rand::thread_rng();

        let mut descriptors: Vec<Descriptor> = Vec::new();
        let num_descriptors = rng.gen_range(self.num_descriptors.clone());
        let descriptor_range = 0..num_descriptors;
        if !descriptor_range.is_empty() {
            let mut possible_descriptors = self.possible_descriptors.clone();
            for _ in descriptor_range {
                if possible_descriptors.is_empty() {
                    break;
                }
                let index = rng.gen_range(0..possible_descriptors.len());
                let descriptor = possible_descriptors.remove(index);
                descriptors.push(descriptor);
            }
        }

        let flavour = if self.include_flavour_text {
            let index = rng.gen_range(0..self.possible_flavour_texts.len());
            self.possible_flavour_texts.get(index).cloned()
        } else {
            None
        };

        let (fixture_positions, used_fixtures) = build_fixture_positions(
            &self.build_fixtures_args,
            &self.room_type,
            self.danger_level,
        );

        Room {
            dimensions: self.dimensions.clone().unwrap_or_else(build_dimensions),
            descriptors,
            id: Uuid::new_v4(),
            name: self.name.clone(),
            room_type: self.room_type,
            fixture_positions,
            npc_positions: build_npc_positions(
                used_fixtures,
                self.danger_level,
                &self.build_npc_args,
            ),
            flavour,
            exits: build_exits(self.entrance_id, &self.build_exit_args),
            loose_items: Vec::new(),
        }
    }
}

impl RoomType {
    fn possible_descriptors(&self) -> Vec<Descriptor> {
        Descriptor::iter().collect()
    }

    fn possible_flavours(&self) -> Vec<Flavour> {
        Flavour::iter().collect()
    }
}

#[cfg(test)]
mod tests {
    use uuid::Uuid;

    use crate::{
        components::rooms::{Descriptor, RoomType},
        generators::generator::Generator,
    };

    use super::RoomGeneratorBuilder;

    #[test]
    fn test_builder() {
        let entrance_id = Uuid::new_v4();
        let generator = RoomGeneratorBuilder::new()
            .num_descriptors(1..=1)
            .room_type(RoomType::TavernHall)
            .possible_descriptors(vec![Descriptor::Freezing])
            .entrance_id(entrance_id)
            .danger_level(10)
            .build();
        let room = generator.generate();

        assert_eq!(RoomType::TavernHall, room.room_type);
        assert_eq!(1, room.descriptors.len());
        assert_eq!(
            Descriptor::Freezing,
            room.descriptors.get(0).unwrap().clone()
        );
    }
}