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}