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}