/*
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: &str = " ";
const OPAQUE_SQUARE: &str = "█";
fn print_square(stream: &mut BufWriter, is_opaque: bool) {
stream
.write_all(
if is_opaque {
OPAQUE_SQUARE
} else {
TRANSPARENT_SQUARE
}
.to_string()
.as_bytes(),
)
.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();
}