morkovmap_rc 0.4.1

A data-driven, Markov Chain-based tilemap generator library and app. Single-thread-flavored.
Documentation
use std::borrow::Borrow;
use std::collections::HashMap;
use std::rc::{Rc};
use std::cell::{RefCell};
use smallvec::SmallVec;
use num::{Bounded, Zero};
use serde::{Deserialize, Serialize};
use crate::adjacency::{AdjacencyGenerator};
use crate::sampler::{DistributionKey, MultinomialDistribution};
use crate::map2dnode::{Map2DNode, MapNodeState, ThreadsafeNodeRef};
use crate::position::{MapPosition};

#[derive(Serialize, Deserialize, Clone)]
pub struct Map2D<AG: AdjacencyGenerator<2>, K: DistributionKey, MP: MapPosition<2>> {
    pub tiles: Vec<ThreadsafeNodeRef<AG, K, MP>>,
    position_index: HashMap<MP, ThreadsafeNodeRef<AG, K, MP>>,
    pub undecided_tiles: HashMap<MP, ThreadsafeNodeRef<AG, K, MP>>,
    pub(crate) min_pos: MP,
    pub(crate) max_pos: MP,
}

impl<AG: AdjacencyGenerator<2, Input = MP>, K: DistributionKey, MP: MapPosition<2>> Map2D<AG, K, MP> {
    pub fn from_tiles<I: IntoIterator<Item=Map2DNode<AG, K, MP>>>(tiles: I) -> Map2D<AG, K, MP> {
        let iterator = tiles.into_iter();
        let size_estimate = iterator.size_hint().0;

        let mut tile_vec: Vec<ThreadsafeNodeRef<AG, K, MP>> = Vec::with_capacity(size_estimate);
        let mut position_hashmap: HashMap<MP, ThreadsafeNodeRef<AG, K, MP>> = HashMap::with_capacity(size_estimate);
        let mut undecided_hashmap: HashMap<MP, ThreadsafeNodeRef<AG, K, MP>> = HashMap::with_capacity(size_estimate);
        let mut minx = None;
        let mut miny = None;
        let mut maxx = None;
        let mut maxy = None;

        for tile in iterator {
            let cast_tile: Map2DNode<AG, K, MP> = tile;
            let tile_pos = cast_tile.position.get_dims();

            let tile_pos_x = tile_pos.get(0).unwrap().to_owned();
            let tile_pos_y = tile_pos.get(1).unwrap().to_owned();

            let tile_arc = Rc::new(RefCell::new(cast_tile));
            let tile_arc_reader = tile_arc.try_borrow().unwrap();
            let tile_pos = tile_arc_reader.position;

            if tile_pos_x < minx.unwrap_or(MP::Key::max_value()) { minx = Some(tile_pos_x)};
            if tile_pos_y < miny.unwrap_or(MP::Key::max_value()) { miny = Some(tile_pos_y)};
            if tile_pos_x > maxx.unwrap_or(MP::Key::min_value()) { maxx = Some(tile_pos_x)};
            if tile_pos_y > maxy.unwrap_or(MP::Key::min_value()) { maxy = Some(tile_pos_y)};

            tile_vec.push(tile_arc.to_owned());
            position_hashmap.insert(tile_pos, tile_arc.to_owned());

            if !tile_arc_reader.state.is_assigned() {
                undecided_hashmap.insert(tile_pos, tile_arc.to_owned());
            }
        }

        Self {
            tiles: tile_vec,
            position_index: position_hashmap,
            undecided_tiles: undecided_hashmap,
            min_pos:  MP::from_dims([
                minx.unwrap_or(maxx.unwrap_or(MP::Key::zero())).to_owned(),
                miny.unwrap_or(maxy.unwrap_or(MP::Key::zero())).to_owned()
            ]),
            max_pos: MP::from_dims([
                maxx.unwrap_or(minx.unwrap_or(MP::Key::zero())).to_owned(),
                maxy.unwrap_or(miny.unwrap_or(MP::Key::zero())).to_owned()
            ])
        }
    }

    pub fn get<BMP: Borrow<MP>>(&self, key: BMP) -> Option<&ThreadsafeNodeRef<AG, K, MP>> {
        self.position_index.get(key.borrow())
    }

    pub fn finalize_tile<'n>(&'n mut self, tile: &'n ThreadsafeNodeRef<AG, K, MP>, assignment: K) -> Option<&ThreadsafeNodeRef<AG, K, MP>> {
        let tile_writer = tile.try_borrow_mut();
        match tile_writer {
            Ok(mut writeable) => {
                writeable.state = MapNodeState::finalized(assignment);
                let removed = self.undecided_tiles.remove(&writeable.position);
                match removed {
                    Some(_) => Some(tile),
                    None => None
                }
            },
            Err(_) => None
        }
    }

    pub fn unassign_tile<'n>(&'n mut self, tile: &'n ThreadsafeNodeRef<AG, K, MP>, distribution: &'n MultinomialDistribution<K>) -> Option<&ThreadsafeNodeRef<AG, K, MP>> {
        let tile_writer = tile.try_borrow_mut();

        match tile_writer {
            Ok(mut writeable) => {
                writeable.state = MapNodeState::Undecided(distribution.to_owned());
                let inserted = self.undecided_tiles.insert(writeable.position, tile.to_owned());
                match inserted {
                    Some(_) => Some(tile),
                    None => panic!("Failed to insert a tile into the undecided tiles map!")
                }
            },
            Err(_) => panic!("Failed to obtain a write lock on tile to un-assign!")
        }
    }

    pub fn unassign_tiles<'n, I: IntoIterator<Item=&'n ThreadsafeNodeRef<AG, K, MP>>>(&'n mut self, tiles: I, distribution: MultinomialDistribution<K>) -> Option<()> {
        let tile_iter = tiles.into_iter();
        tile_iter.for_each(|tile| {
            self.unassign_tile(tile, &distribution);
        });
        Some(())
    }
}

impl<K: DistributionKey, MP: MapPosition<2>, RMP: Borrow<MP> + From<MP>, AG: AdjacencyGenerator<2, Input=RMP>> Map2D<AG, K, MP> {
    // NOTE: we're using a magic maxcap, because generics are a bane of my existence

    pub fn adjacent_from_pos(&self, pos: RMP) -> SmallVec<[ThreadsafeNodeRef<AG, K, MP>; 8]> {
        let adjacents: AG::Output = MapPosition::adjacents::<RMP, AG>(pos);

        let result = adjacents
            .into_iter()
            .filter_map(
                |cand| {
                    let rcand = cand.borrow();
                    self.position_index
                        .get(rcand)
                        .map(|x| x.to_owned())
                }
            );

        result.collect()
    }

    pub fn adjacent<NR: Borrow<Map2DNode<AG, K, MP>>>(&self, node: NR) -> SmallVec<[ThreadsafeNodeRef<AG, K, MP>; 8]> {
        let pos = node.borrow().position;
        let borrowed_pos: RMP = pos.into();

        self.adjacent_from_pos(borrowed_pos)
    }
}

#[cfg(test)]
mod tests {
    use crate::adjacency::CardinalAdjacencyGenerator;
    use super::*;
    use crate::position2d::Position2D;

    #[test]
    fn position_vector_addition_works_positives() {
        let pos_a = Position2D { x: 5, y: 3 };
        let pos_b = Position2D { x: 2, y: 6 };
        let result_pos = pos_a + pos_b;
        assert_eq!(result_pos.x, 7);
        assert_eq!(result_pos.y, 9);
    }

    #[test]
    fn position_vector_addition_works_one_negative() {
        let pos_a = Position2D { x: -5, y: -3 };
        let pos_b = Position2D { x: 2, y: 6 };
        let result_pos = pos_a + pos_b;
        assert_eq!(result_pos.x, -3);
        assert_eq!(result_pos.y, 3);
    }

    #[test]
    fn adjacents_cardinal_sane() {
        let pos = Position2D { x: 2, y: 6 };
        let results = Position2D::adjacents::<Position2D<i32>, CardinalAdjacencyGenerator<Position2D<i32>>>(pos);
        assert_eq!(results[0], Position2D { x: 1i32, y: 6i32 });
        assert_eq!(results[1], Position2D { x: 3i32, y: 6i32 });
        assert_eq!(results[2], Position2D { x: 2i32, y: 5i32 });
        assert_eq!(results[3], Position2D { x: 2i32, y: 7i32 });
    }

    #[test]
    fn serde_pos() {
        let pos = Position2D { x: 2, y: 6 };
        let results = serde_json::to_string(&pos).unwrap();
        assert!(results.len() > 0)
    }
}