tile.rs

6.3 kB · rust · 200 lines

1use mrlycore::errors::{value_error, Result};2use mrlycore::state::{boolean, choice, sample, shuffle};3use mrlycore::tile::{4    generals, nestings, powers, products, uniform, Catalog, Group, Parity, Source, Tile,5};67/// The constraints a random tile is drawn under.8#[derive(Clone, Debug)]9pub struct ConfigNd<const N: usize> {10    /// The tile groups allowed.11    pub groups: Vec<Group>,12    /// The catalog the sources are drawn from.13    pub catalog: Catalog,14    /// The smallest allowed side.15    pub min_size: usize,16    /// The largest allowed side.17    pub max_size: usize,18    /// The parity the sizes must keep.19    pub parity: Parity,20    /// The forced inversion flag, or None to flip a coin.21    pub invert: Option<bool>,22    /// The forced anti flag for every source, or None to flip coins.23    pub anti: Option<bool>,24}2526impl<const N: usize> Default for ConfigNd<N> {27    fn default() -> ConfigNd<N> {28        ConfigNd {29            groups: Group::all().to_vec(),30            catalog: Catalog::Classics,31            min_size: 3,32            max_size: 9,33            parity: Parity::Odds,34            invert: None,35            anti: None,36        }37    }38}3940impl<const N: usize> ConfigNd<N> {41    fn sources(&self) -> Vec<Source> {42        crate::bang::sources(&self.catalog, N)43    }44    fn source(&self) -> Source {45        choice(&self.sources())46    }47}4849type Rotation = fn(Source) -> usize;5051fn general<const N: usize>(config: &ConfigNd<N>, rotation: Rotation) -> Option<Tile> {52    let numbers = generals(config.min_size, config.max_size, config.parity);53    if numbers.is_empty() {54        return None;55    }56    let n = choice(&numbers);57    let source = config.source();58    let mut tile = Tile::new(Group::General).size(n, n);59    tile.sources = vec![source];60    tile.numbers = vec![n];61    tile.levels = vec![1];62    tile.rotations = vec![rotation(source)];63    tile.factor = n;64    Some(tile)65}6667fn fractal<const N: usize>(config: &ConfigNd<N>, rotation: Rotation) -> Option<Tile> {68    let options = powers(config.min_size, config.max_size, config.parity);69    if options.is_empty() {70        return None;71    }72    let (n, level) = choice(&options);73    let source = config.source();74    let size = n.pow(level as u32);75    let mut tile = Tile::new(Group::Fractal).size(size, size);76    tile.sources = vec![source];77    tile.numbers = vec![n];78    tile.levels = vec![level];79    tile.rotations = vec![rotation(source)];80    tile.factor = n;81    Some(tile)82}8384fn mixed(numbers: Vec<usize>, sources: &[Source], options: &[Vec<usize>]) -> Vec<usize> {85    if !uniform(sources) || !uniform(&numbers) {86        return numbers;87    }88    let fresh: Vec<Vec<usize>> = options89        .iter()90        .filter(|option| option.len() == numbers.len() && !uniform(option))91        .cloned()92        .collect();93    match fresh.is_empty() {94        true => numbers,95        false => choice(&fresh),96    }97}9899fn magic<const N: usize>(config: &ConfigNd<N>, rotation: Rotation) -> Option<Tile> {100    let options = nestings(config.min_size, config.max_size, config.parity);101    if options.is_empty() {102        return None;103    }104    let drawn = choice(&options);105    let sources: Vec<Source> = drawn.iter().map(|_| config.source()).collect();106    let numbers = mixed(drawn, &sources, &options);107    let count = numbers.len();108    let size: usize = numbers.iter().product();109    let mut tile = Tile::new(Group::Magic).size(size, size);110    tile.sources = sources.clone();111    tile.numbers = numbers.clone();112    tile.levels = vec![1; count];113    tile.rotations = sources.iter().map(|&s| rotation(s)).collect();114    tile.factor = numbers[0];115    Some(tile)116}117118fn special<const N: usize>(config: &ConfigNd<N>, rotation: Rotation) -> Option<Tile> {119    let options = products(config.min_size, config.max_size, 2, config.parity);120    if options.is_empty() {121        return None;122    }123    let pair = choice(&options);124    let (factor, n) = (pair[0], pair[1]);125    let source = config.source();126    let size = factor * n;127    let mut tile = Tile::new(Group::Special).size(size, size);128    tile.sources = vec![source];129    tile.numbers = vec![n];130    tile.levels = vec![1];131    tile.rotations = vec![rotation(source)];132    tile.factor = factor;133    tile.flip = boolean();134    Some(tile)135}136137fn mosaic<const N: usize>(config: &ConfigNd<N>, rotation: Rotation) -> Option<Tile> {138    let palette = config.sources();139    if palette.len() < 3 {140        return None;141    }142    let options = products(config.min_size, config.max_size, 2, config.parity);143    if options.is_empty() {144        return None;145    }146    let pair = choice(&options);147    let (factor, n) = (pair[0], pair[1]);148    let sources = sample(&palette, 3);149    let size = factor * n;150    let mut tile = Tile::new(Group::Mosaic).size(size, size);151    tile.sources = sources.clone();152    tile.numbers = vec![n, n, n];153    tile.levels = vec![1, 1, 1];154    tile.rotations = sources.iter().map(|&s| rotation(s)).collect();155    tile.factor = factor;156    Some(tile)157}158159fn creator<const N: usize>(group: Group) -> fn(&ConfigNd<N>, Rotation) -> Option<Tile> {160    match group {161        Group::General => general,162        Group::Fractal => fractal,163        Group::Magic => magic,164        Group::Special => special,165        Group::Mosaic => mosaic,166    }167}168169/// Draws a random tile satisfying the config, or an error when no group fits the size constraints.170pub fn create<const N: usize>(config: &ConfigNd<N>, rotation: Rotation) -> Result<Tile> {171    let mut groups = config.groups.clone();172    shuffle(&mut groups);173    let mut tile = None;174    for group in groups {175        if let Some(candidate) = creator::<N>(group)(config, rotation) {176            tile = Some(candidate);177            break;178        }179    }180    let mut tile = match tile {181        Some(tile) => tile,182        None => return value_error("could not generate a tile within the size constraints."),183    };184    let count = tile.sources.len();185    tile.anti = match config.anti {186        Some(flag) => vec![flag; count],187        None => (0..count).map(|_| boolean()).collect(),188    };189    tile.invert = config.invert.unwrap_or_else(boolean);190    Ok(tile)191}192193/// Draws a random tile up to the given size under the default config.194pub fn random_tile<const N: usize>(max_size: usize, rotation: Rotation) -> Result<Tile> {195    let config: ConfigNd<N> = ConfigNd {196        max_size,197        ..Default::default()198    };199    create(&config, rotation)200}