world-map-gen 0.1.1

Command line tool and library to generate random game world maps for both Rust and WebAssembly
Documentation
//! This module provides WebAssembly support
//!
//! This module is only enabled when target architecture is "wasm32". In Wasm, full-set of
//! world-map-gen library is not available due to restriction of wasm-bindgen. wasm-bindgen does
//! not allow lifetimes and type parameters in structs. Many types are not supported to be serialized
//! into Wasm ABI.
//!
//! This module provides some structs which are suitable for being used by JavaScript through
//! wasm-bindgen. The structs provides only limited features of world-map-gen but primary features
//! are covered.
//!
//! Wasm package can be installed as npm package: https://www.npmjs.com/package/world-map-gen
//!
//! ```javascript
//! import { Generator, LandKind } from 'world-map-gen';
//!
//! // Generate a new random map generator
//! const gen = Generator.new();
//!
//! // Generate random 200x200 map with automatic resolution
//! const board = gen.gen_auto(200, 200);
//!
//! for (let x = 0; x < board.width(); x++) {
//!     for (let y = 0; y < board.height(); y++) {
//!         // Get cell of specific position
//!         const cell = board.at(x, y);
//!
//!         // Get land kind like Sea, Forest, Mountain, ...
//!         console.log('Kind:', cell.kind, 'at', x, y);
//!
//!         // Get altitude of the cell
//!         console.log('  Altitude:', cell.altitude);
//!
//!         // Get color code of the cell as #rrggbb format
//!         console.log('  Color:', cell.color_code());
//!
//!         // Get land legend
//!         console.log(' Legend:', cell.legend());
//!
//!         // Check the cell is town
//!         console.log('  town?:', cell.kind === LandKind.Town);
//!     }
//! }
//!
//! // Get JSON representation of board
//! console.log(JSON.parse(board.as_json()))
//! ```

extern crate rand;
extern crate serde_json;
extern crate termcolor2rgb;
extern crate wasm_bindgen;
#[cfg(feature = "wasm_debug")]
extern crate web_sys;

use self::rand::rngs;
use crate::board;
use crate::gen::{RandomBoardGen, Resolution};
use crate::land::LandKind;
use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;

cfg_if! {
    if #[cfg(feature = "console_error_panic_hook")] {
        extern crate console_error_panic_hook;
        use self::console_error_panic_hook::set_once as set_panic_hook;
    } else {
        #[inline]
        fn set_panic_hook() {}
    }
}

cfg_if! {
    if #[cfg(feature = "wasm_debug")] {
        macro_rules! log {
            ($($t:tt)*) => {
                web_sys::console::log_1(&format!($($t)*).into());
            }
        }
    } else {
        macro_rules! log {
            ($($t:tt)*) => {}
        }
    }
}

/// Represents one cell in board. In contrast to `land::Land`, it only contains its land kind and
/// altitude in order to reduce total memory size.
#[wasm_bindgen]
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Cell {
    /// Kind for the cell (Sea, Forest, Mountain, ...)
    pub kind: LandKind,
    /// Altitude of the cell in 0..99
    pub altitude: u8,
}

impl Cell {
    #[inline]
    fn land_color(&self) -> Option<(u8, u8, u8)> {
        use termcolor2rgb::ColorExt;
        self.kind.preset_ref().color.fg().map(|c| c.to_rgb())
    }
}

#[wasm_bindgen]
impl Cell {
    /// Returns color code of land as `#rrggbb` format string from land kind.
    /// When no color is set, returns `None`. It means `undefined` in JavaScript side.
    pub fn color_code(&self) -> Option<String> {
        self.land_color()
            .map(|(r, g, b)| format!("#{:02x}{:02x}{:02x}", r, g, b))
    }

    /// Returns RGB color as u32. 0~8 bits for B, 9~16 bits for G, 17~24 bits for R.
    /// When no color is set, returns `None`. It means `undefined` in JavaScript side.
    pub fn rgb_color(&self) -> Option<u32> {
        self.land_color()
            .map(|(r, g, b)| (u32::from(r) << 16) + (u32::from(g) << 8) + u32::from(b))
    }

    /// Returns legend of land as string from land kind.
    pub fn legend(&self) -> String {
        self.kind.legend().to_string()
    }
}

/// Represents one board generated by random map generator.
#[wasm_bindgen]
pub struct Board {
    inner: board::Board<'static>,
}

#[wasm_bindgen]
impl Board {
    /// Returns width as number of cells in number
    pub fn width(&self) -> usize {
        self.inner.width()
    }

    /// Returns height as number of cells in number
    pub fn height(&self) -> usize {
        self.inner.height()
    }

    /// Returns cell at position (x, y).
    pub fn at(&self, x: usize, y: usize) -> Cell {
        let c = self.inner.at(x, y);
        Cell {
            kind: c.kind,
            altitude: c.altitude,
        }
    }

    /// Returns a board by serializing as JSON. On failure, it returns `None` (`undefined` in
    /// JavaScript)
    pub fn as_json(&self) -> Option<String> {
        serde_json::to_string_pretty(&self.inner).ok()
    }
}

/// Represents random map generator. In contrast to `gen::RandomBoardGen`, it only provides limited
/// functionality. It cannot be initialized with specific seed. And map resolution is always detected
/// from its size.
#[wasm_bindgen]
pub struct Generator {
    inner: RandomBoardGen<rngs::ThreadRng>,
}

#[allow(clippy::new_without_default_derive)]
#[wasm_bindgen]
impl Generator {
    /// Create a new generator instance initialized with thread-default random number generator.
    /// No seedable RNG is provided for Wasm interface for now.
    pub fn new() -> Generator {
        set_panic_hook();
        Generator {
            inner: RandomBoardGen::default(),
        }
    }

    /// Generates random map board with given width and height. Parameters are in number of cells.
    pub fn gen_auto(&mut self, width: usize, height: usize) -> Board {
        log!("Generate board: width={}, height={}", width, height);
        let inner = self.inner.gen_auto(width, height);
        #[cfg(feature = "wasm_debug")]
        {
            for (y, row) in inner.rows().enumerate() {
                for (x, cell) in row.iter().enumerate() {
                    log!("At ({}, {}): {:?}", x, y, cell);
                }
            }
        }
        Board { inner }
    }

    /// Generates random map board with given resolution, width and height. Width and height are
    /// in number of cells.
    pub fn gen(&mut self, res: Resolution, width: usize, height: usize) -> Board {
        log!(
            "Generate board with resolution={:?}: width={}, height={}",
            res,
            width,
            height,
        );
        let inner = match res {
            Resolution::Low => self.inner.gen_small(width, height),
            Resolution::Middle => self.inner.gen_middle(width, height),
            Resolution::High => self.inner.gen_large(width, height),
        };
        Board { inner }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    extern crate wasm_bindgen_test;
    use wasm_bindgen_test::*;

    wasm_bindgen_test_configure!(run_in_browser);

    #[wasm_bindgen_test]
    pub fn test_generate_board() {
        let mut gen = Generator::new();
        let board = gen.gen_auto(10, 20);
        let width = board.width();
        let height = board.height();
        assert_eq!(width, 10);
        assert_eq!(height, 20);

        for x in 0..width {
            for y in 0..height {
                let cell = board.at(x, y);

                let color_code = cell.color_code().unwrap();
                assert!(color_code.len() > 0, "{}", color_code);
                assert!(
                    color_code.chars().skip(1).all(|c| c.is_ascii_hexdigit()),
                    "{}",
                    color_code
                );

                let rgb_color = cell.rgb_color();
                assert!(rgb_color.is_some(), "{:?}", rgb_color);

                let altitude = cell.altitude;
                assert!(altitude < 100, "{}", altitude);

                let legend = cell.legend();
                let kind = format!("{:?}", cell.kind);
                assert!(
                    legend.contains(&kind),
                    "Legend '{}' does not contain kind '{}'",
                    legend,
                    kind
                );
            }
        }
    }
} // mod tests