/* hitomezashi-rs Classic two-colored Hitomezashi stitch pattern generator Copyright (C) 2024 Nicholas Johnson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #![warn(missing_docs)] //! Classic two-colored Hitomezashi stitch pattern generator. use rand::distributions::{Bernoulli, Distribution}; use std::io::{stdout, BufWriter, StdoutLock, Write}; const TRANSPARENT_SQUARE: &[u8] = " ".as_bytes(); const OPAQUE_SQUARE: &[u8] = "█".as_bytes(); fn print_square(stream: &mut BufWriter, is_opaque: bool) { let square = if is_opaque { OPAQUE_SQUARE } else { TRANSPARENT_SQUARE }; stream.write_all(square).unwrap(); } /// Prints a two-colored Hitomezashi stitch pattern of the specified dimensions. /// /// `skew` is the probability of a row or column beginning with a stitch. Skew values near 0 or 1 /// generate orderly patterns. Skew values near 0.5 generate chaotic patterns. /// /// # Panics /// /// This function will panic if any of the following constraints are not met: /// /// `width >= 1` /// /// `height >= 1` /// /// `0 <= skew <= 1` /// /// # Examples /// /// ``` /// use hitomezashi_rs; /// /// hitomezashi_rs::generate(15, 20, Some(0.7)); /// ``` pub fn generate(width: usize, height: usize, skew: Option) { // skew=0.5 generates the most random-looking patterns let skew = skew.unwrap_or(0.5); assert!(width >= 1, "Width must be a positive number!"); assert!(height >= 1, "Height must be a positive number!"); assert!( (0.0..=1.0).contains(&skew), "Skew must be between zero and one inclusive!" ); let mut rng = rand::thread_rng(); let brn = Bernoulli::new(skew).unwrap(); // Each value represents whether to start that row with a stitch or no stitch let mut row_bits: Vec = Vec::with_capacity(height - 1); for _ in 0..row_bits.capacity() { row_bits.push(brn.sample(&mut rng)); } // Each value represents whether to start that column with a stitch or no stitch let mut col_bits: Vec = Vec::with_capacity(width - 1); for _ in 0..col_bits.capacity() { col_bits.push(brn.sample(&mut rng)); } // Precomputed values to facilitate alternating between stitch presence and stitch absence let mut alt_bits: Vec = Vec::with_capacity(width); let mut alternator = false; for _ in 0..alt_bits.capacity() { alt_bits.push(alternator); alternator = !alternator; } /* The first square is always opaque to prevent invisible 1xN or Nx1 patterns. Invisible * patterns must be prevented so that users don't mistakenly conclude that the function does * not work. */ let first_square = true; let mut cur_row: Vec = Vec::with_capacity(width); let lock = stdout().lock(); let mut stream = BufWriter::new(lock); /* Base case * * The first row has no preceding row, so it's computing using the column stitches */ cur_row.push(first_square); for col in 0..(cur_row.capacity() - 1) { print_square(&mut stream, cur_row[col]); cur_row.push(cur_row[col] ^ col_bits[col]); } print_square(&mut stream, cur_row[cur_row.capacity() - 1]); stream.write_all(b"\n").unwrap(); // End of base case for row_bit in row_bits.iter() { /* Recursive case * * Each row after the first row is computed based on the preceding row. */ cur_row .iter_mut() .zip(alt_bits.iter()) .for_each(|(x1, &x2)| { *x1 ^= x2 ^ row_bit; print_square(&mut stream, *x1); }); stream.write_all(b"\n").unwrap(); // End of recursive case } stream.flush().unwrap(); }