draw.rs

8.4 kB · rust · 289 lines

1use crate::core::error::{value_error, Result};2use crate::core::rng::Rng;3use crate::gen::recipe::{4    generals, nestings, powers, products, uniform, Catalog, Group, Parity, Source, Tile,5};6use serde::{Deserialize, Serialize};78/// The constraints a random tile is drawn under.9#[derive(Clone, Debug, Serialize, Deserialize)]10pub struct ConfigNd<const N: usize> {11    /// The tile groups allowed.12    pub groups: Vec<Group>,13    /// The catalog the sources are drawn from.14    pub catalog: Catalog,15    /// The smallest allowed side.16    pub min_size: usize,17    /// The largest allowed side.18    pub max_size: usize,19    /// The parity the sizes must keep.20    pub parity: Parity,21    /// The forced inversion flag, or None to flip a coin.22    pub invert: Option<bool>,23    /// The forced anti flag for every source, or None to flip coins.24    pub anti: Option<bool>,25}2627impl<const N: usize> Default for ConfigNd<N> {28    fn default() -> ConfigNd<N> {29        ConfigNd {30            groups: Group::all().to_vec(),31            catalog: Catalog::Classics,32            min_size: 3,33            max_size: 9,34            parity: Parity::Odds,35            invert: None,36            anti: None,37        }38    }39}4041impl<const N: usize> ConfigNd<N> {42    fn sources(&self) -> Vec<Source> {43        crate::math::bang::sources(&self.catalog, N).unwrap_or_default()44    }45    fn source(&self, rng: &mut Rng) -> Result<Source> {46        Ok(*rng.choice(&self.sources())?)47    }48}4950type Rotation = fn(&mut Rng) -> usize;5152fn general<const N: usize>(53    config: &ConfigNd<N>,54    rotation: Rotation,55    rng: &mut Rng,56) -> Result<Option<Tile>> {57    let numbers = generals(config.min_size, config.max_size, config.parity);58    if numbers.is_empty() {59        return Ok(None);60    }61    let n = *rng.choice(&numbers)?;62    let source = config.source(rng)?;63    let mut tile = Tile::new(Group::General).size(n, n);64    tile.sources = vec![source];65    tile.numbers = vec![n];66    tile.levels = vec![1];67    tile.rotations = vec![rotation(rng)];68    tile.factor = n;69    Ok(Some(tile))70}7172fn fractal<const N: usize>(73    config: &ConfigNd<N>,74    rotation: Rotation,75    rng: &mut Rng,76) -> Result<Option<Tile>> {77    let options = powers(config.min_size, config.max_size, config.parity);78    if options.is_empty() {79        return Ok(None);80    }81    let (n, level) = *rng.choice(&options)?;82    let source = config.source(rng)?;83    let size = n.pow(level as u32);84    let mut tile = Tile::new(Group::Fractal).size(size, size);85    tile.sources = vec![source];86    tile.numbers = vec![n];87    tile.levels = vec![level];88    tile.rotations = vec![rotation(rng)];89    tile.factor = n;90    Ok(Some(tile))91}9293fn mixed(94    numbers: Vec<usize>,95    sources: &[Source],96    options: &[Vec<usize>],97    rng: &mut Rng,98) -> Result<Vec<usize>> {99    if !uniform(sources) || !uniform(&numbers) {100        return Ok(numbers);101    }102    let fresh: Vec<Vec<usize>> = options103        .iter()104        .filter(|option| option.len() == numbers.len() && !uniform(option))105        .cloned()106        .collect();107    match fresh.is_empty() {108        true => Ok(numbers),109        false => Ok(rng.choice(&fresh)?.clone()),110    }111}112113fn magic<const N: usize>(114    config: &ConfigNd<N>,115    rotation: Rotation,116    rng: &mut Rng,117) -> Result<Option<Tile>> {118    let options = nestings(config.min_size, config.max_size, config.parity);119    if options.is_empty() {120        return Ok(None);121    }122    let drawn = rng.choice(&options)?.clone();123    let sources: Vec<Source> = drawn124        .iter()125        .map(|_| config.source(rng))126        .collect::<Result<Vec<Source>>>()?;127    let numbers = mixed(drawn, &sources, &options, rng)?;128    let count = numbers.len();129    let size: usize = numbers.iter().product();130    let mut tile = Tile::new(Group::Magic).size(size, size);131    tile.sources = sources.clone();132    tile.numbers = numbers.clone();133    tile.levels = vec![1; count];134    tile.rotations = sources.iter().map(|_| rotation(rng)).collect();135    tile.factor = numbers[0];136    Ok(Some(tile))137}138139fn special<const N: usize>(140    config: &ConfigNd<N>,141    rotation: Rotation,142    rng: &mut Rng,143) -> Result<Option<Tile>> {144    let options = products(config.min_size, config.max_size, 2, config.parity);145    if options.is_empty() {146        return Ok(None);147    }148    let pair = rng.choice(&options)?;149    let (factor, n) = (pair[0], pair[1]);150    let source = config.source(rng)?;151    let size = factor * n;152    let mut tile = Tile::new(Group::Special).size(size, size);153    tile.sources = vec![source];154    tile.numbers = vec![n];155    tile.levels = vec![1];156    tile.rotations = vec![rotation(rng)];157    tile.factor = factor;158    tile.flip = rng.boolean();159    Ok(Some(tile))160}161162fn mosaic<const N: usize>(163    config: &ConfigNd<N>,164    rotation: Rotation,165    rng: &mut Rng,166) -> Result<Option<Tile>> {167    let palette = config.sources();168    if palette.len() < 3 {169        return Ok(None);170    }171    let options = products(config.min_size, config.max_size, 2, config.parity);172    if options.is_empty() {173        return Ok(None);174    }175    let pair = rng.choice(&options)?;176    let (factor, n) = (pair[0], pair[1]);177    let sources: Vec<Source> = rng178        .sample_indices(palette.len(), 3)179        .into_iter()180        .map(|i| palette[i])181        .collect();182    let size = factor * n;183    let mut tile = Tile::new(Group::Mosaic).size(size, size);184    tile.sources = sources.clone();185    tile.numbers = vec![n, n, n];186    tile.levels = vec![1, 1, 1];187    tile.rotations = sources.iter().map(|_| rotation(rng)).collect();188    tile.factor = factor;189    Ok(Some(tile))190}191192type Creator<const N: usize> = fn(&ConfigNd<N>, Rotation, &mut Rng) -> Result<Option<Tile>>;193194fn creator<const N: usize>(group: Group) -> Creator<N> {195    match group {196        Group::General => general,197        Group::Fractal => fractal,198        Group::Magic => magic,199        Group::Special => special,200        Group::Mosaic => mosaic,201    }202}203204/// Draws a random tile satisfying the config from the stream.205///206/// ```207/// use mrlyrs::core::rng::Rng;208/// use mrlyrs::gen::draw::{create, ConfigNd};209/// let mut rng = Rng::new(1);210/// let tile = create(&ConfigNd::<2>::default(), |rng| rng.below(4), &mut rng)?;211/// assert!((3..=9).contains(&tile.max_size()));212/// # Ok::<(), mrlyrs::Error>(())213/// ```214///215/// # Errors216///217/// Errs when no allowed group fits the size constraints, or when the catalog holds no source.218pub fn create<const N: usize>(219    config: &ConfigNd<N>,220    rotation: Rotation,221    rng: &mut Rng,222) -> Result<Tile> {223    let mut groups = config.groups.clone();224    rng.shuffle(&mut groups);225    let mut tile = None;226    for group in groups {227        if let Some(candidate) = creator::<N>(group)(config, rotation, rng)? {228            tile = Some(candidate);229            break;230        }231    }232    let mut tile = match tile {233        Some(tile) => tile,234        None => return value_error("could not generate a tile within the size constraints."),235    };236    let count = tile.sources.len();237    tile.anti = match config.anti {238        Some(flag) => vec![flag; count],239        None => (0..count).map(|_| rng.boolean()).collect(),240    };241    tile.invert = config.invert.unwrap_or_else(|| rng.boolean());242    Ok(tile)243}244245/// Draws a random tile up to the given size under the default config.246///247/// # Errors248///249/// Errs when no group fits a tile inside the size, or when the catalog holds no source.250pub fn random_tile<const N: usize>(251    max_size: usize,252    rotation: Rotation,253    rng: &mut Rng,254) -> Result<Tile> {255    let config: ConfigNd<N> = ConfigNd {256        max_size,257        ..Default::default()258    };259    create(&config, rotation, rng)260}261262#[cfg(test)]263mod tests {264    use super::*;265    fn turn(rng: &mut Rng) -> usize {266        rng.below(4)267    }268    #[test]269    fn refuses_a_config_that_draws_no_tile() {270        let mut rng = Rng::new(1);271        let narrow: ConfigNd<2> = ConfigNd {272            min_size: 9,273            max_size: 3,274            ..Default::default()275        };276        assert!(create(&narrow, turn, &mut rng).is_err());277        let groupless: ConfigNd<2> = ConfigNd {278            groups: Vec::new(),279            ..Default::default()280        };281        assert!(create(&groupless, turn, &mut rng).is_err());282        let sourceless: ConfigNd<2> = ConfigNd {283            catalog: Catalog::Codes(Vec::new()),284            ..Default::default()285        };286        assert!(create(&sourceless, turn, &mut rng).is_err());287        assert!(random_tile::<2>(1, turn, &mut rng).is_err());288    }289}