aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 92e3004e44e7092cd119ee9835f45a41cab4d66fd3c503589d3a10fc6170ad56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
    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 <https://www.gnu.org/licenses/>.
*/

#![warn(missing_docs)]

//! Classic two-colored Hitomezashi stitch pattern generator.

use rand::distributions::{Bernoulli, Distribution};

const TRANSPARENT_SQUARE: char = ' ';
const OPAQUE_SQUARE: char = '█';

fn print_square(is_opaque: bool) {
    print!(
        "{}",
        if is_opaque {
            OPAQUE_SQUARE
        } else {
            TRANSPARENT_SQUARE
        }
    );
}

/// 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<f64>) {
    // skew=0.5 generates the most random-looking patterns
    let skew: f64 = skew.unwrap_or(0.5);

    assert!(width >= 1, "Width must be a positive number!");
    assert!(height >= 1, "Height must be a positive number!");
    assert!(skew >= 0.0 && skew <= 1.0, "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<bool> = 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<bool> = 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<bool> = Vec::with_capacity(width);

    let mut alternator: bool = 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: bool = true;

    let mut cur_row: Vec<bool> = Vec::with_capacity(width);

    /* 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(cur_row[col]);
        cur_row.push(cur_row[col] ^ col_bits[col]);
    }

    print_square(cur_row[cur_row.capacity() - 1]);
    println!(); // 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(*x1);
            });

        println!(); // End of recursive case
    }
}