r_matrix/
lib.rs

1extern crate pancurses;
2extern crate rand;
3extern crate structopt;
4extern crate term_size;
5
6use std::collections::VecDeque;
7
8pub mod config;
9
10use config::Config;
11
12use pancurses::*;
13use rand::distributions::{Distribution, Standard};
14use rand::rngs::SmallRng;
15use rand::{Rng, SeedableRng};
16use std::cell::RefCell;
17
18thread_local! {
19    static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
20}
21
22fn rng<T>() -> T
23where
24    Standard: Distribution<T>,
25{
26    RNG.with(|rng| (*rng).borrow_mut().r#gen::<T>())
27}
28
29fn rand_char() -> char {
30    let (randnum, randmin) = (93, 33);
31    RNG.with(|rng| (*rng).borrow_mut().r#gen::<u8>() % randnum + randmin) as char
32}
33
34fn coin_flip() -> bool {
35    RNG.with(|rng| (*rng).borrow_mut().r#gen())
36}
37
38#[derive(Clone)]
39pub struct Block {
40    val: char,
41    white: bool,
42    color: i16,
43}
44
45impl Block {
46    fn is_space(&self) -> bool {
47        self.val == ' '
48    }
49}
50
51impl Default for Block {
52    fn default() -> Self {
53        Block {
54            val: ' ',
55            white: false,
56            color: COLOR_RED,
57        }
58    }
59}
60
61pub struct Column {
62    length: usize,        // The length of the stream
63    spaces: usize,        // The spaces between streams
64    col: VecDeque<Block>, // The actual column
65}
66
67impl Column {
68    /// Return a column keyed by a random number generator
69    fn new(lines: usize) -> Self {
70        Column {
71            length: rng::<usize>() % (lines - 3) + 3,
72            spaces: rng::<usize>() % lines + 1,
73            col: (0..lines).map(|_| Block::default()).collect(),
74        }
75    }
76    fn head_is_empty(&self) -> bool {
77        self.col[1].val == ' '
78    }
79    fn new_rand_char(&mut self) {
80        self.col[0].val = rand_char();
81        self.col[0].color = self.col[1].color;
82    }
83    fn new_rand_head(&mut self, config: &Config) {
84        self.col[0].val = rand_char();
85        self.col[0].color = if config.rainbow {
86            match rng::<usize>() % 6 {
87                0 => COLOR_GREEN,
88                1 => COLOR_BLUE,
89                2 => COLOR_WHITE,
90                3 => COLOR_YELLOW,
91                4 => COLOR_CYAN,
92                5 => COLOR_MAGENTA,
93                _ => unreachable!(),
94            }
95        } else {
96            config.colour
97        };
98        // 50/50 chance the head is white
99        self.col[0].white = coin_flip();
100    }
101}
102
103impl std::ops::Index<usize> for Column {
104    type Output = Block;
105    fn index(&self, i: usize) -> &Self::Output {
106        &self.col[i]
107    }
108}
109
110pub struct Matrix {
111    m: Vec<Column>,
112}
113
114impl std::ops::Index<usize> for Matrix {
115    type Output = Column;
116    fn index(&self, i: usize) -> &Self::Output {
117        &self.m[i]
118    }
119}
120
121impl Default for Matrix {
122    /// Create a new matrix with the dimensions of the screen
123    fn default() -> Self {
124        // Get the screen dimensions
125        let (lines, cols) = get_term_size();
126
127        // Create the matrix
128        Matrix {
129            m: (0..cols).map(|_| Column::new(lines)).collect(),
130        }
131    }
132}
133
134impl Matrix {
135    fn num_columns(&self) -> usize {
136        self.m.len()
137    }
138
139    fn num_lines(&self) -> usize {
140        self[0].col.len()
141    }
142
143    /// Make the next iteration of matrix
144    pub fn arrange(&mut self, config: &Config) {
145        let lines = self.num_lines();
146
147        self.m.iter_mut().for_each(|col| {
148            if col.head_is_empty() && col.spaces != 0 {
149                // Decrement the spaces until the next stream starts
150                col.spaces -= 1;
151            } else if col.head_is_empty() && col.spaces == 0 {
152                // Start a new stream
153                col.new_rand_head(config);
154
155                // Decrement length of stream
156                col.length -= 1;
157
158                // Reset number of spaces until next stream
159                col.spaces = rng::<usize>() % lines + 1;
160            } else if col.length != 0 {
161                // Continue producing stream
162                col.new_rand_char();
163                col.length -= 1;
164            } else {
165                // Display spaces until next stream
166                col.col[0].val = ' ';
167                col.length = rng::<usize>() % (lines - 3) + 3;
168            }
169        });
170        if config.oldstyle {
171            self.old_style_move_down();
172        } else {
173            self.move_down();
174        }
175    }
176    fn move_down(&mut self) {
177        self.m.iter_mut().for_each(|col| {
178            // Reset for each column
179            let mut in_stream = false;
180
181            let mut last_was_white = false; // Keep track of white heads
182            let mut running_color = COLOR_CYAN;
183
184            col.col.iter_mut().for_each(|block| {
185                if !in_stream {
186                    if !block.is_space() {
187                        block.val = ' ';
188                        in_stream = true; // We're now in a stream
189                        running_color = block.color;
190                    }
191                } else if block.is_space() {
192                    // New rand char for head of stream
193                    block.val = rand_char();
194                    block.white = last_was_white;
195                    in_stream = false;
196                }
197                // Swapped to "pass on" whiteness and prepare the variable for the next iteration
198                std::mem::swap(&mut last_was_white, &mut block.white);
199                block.color = running_color;
200            })
201        })
202    }
203    fn old_style_move_down(&mut self) {
204        // Iterate over all columns and swap spaces
205        self.m.iter_mut().for_each(|col| {
206            col.col.pop_back();
207            col.col.push_back(Block::default()); // Put a Blank space at the head.
208            col.col.rotate_right(1)
209        });
210    }
211    /// Draw the matrix on the screen
212    pub fn draw(&self, window: &Window, config: &Config) {
213        //TODO: Use an iterator or something nicer
214        for j in 1..self.num_lines() {
215            // Saving the last colour allows us to call `attron` only when the colour changes.
216            let mut last_colour: i16 = self[0][j].color;
217            window.attron(COLOR_PAIR(last_colour as chtype));
218
219            for i in 0..self.num_columns() {
220                // Pick the colour we need
221                let mcolour = if self[i][j].white {
222                    COLOR_WHITE
223                } else {
224                    self[i][j].color
225                };
226
227                window.mv(j as i32 - 1, 2 * i as i32); // Move the cursor
228                if last_colour != mcolour {
229                    // Set the colour in ncurses.
230                    window.attron(COLOR_PAIR(mcolour as chtype));
231                    last_colour = mcolour;
232                }
233                // Draw the character.
234                window.addch(self[i][j].val as chtype);
235            }
236        }
237        napms(config.update as i32 * 10);
238    }
239}
240
241/// Clean up ncurses stuff when we're ready to exit
242pub fn finish() {
243    curs_set(1);
244    endwin();
245    std::process::exit(0);
246}
247
248/// ncurses functions calls that set up the screen and set important variables
249pub fn ncurses_init() -> Window {
250    let window = initscr();
251    window.nodelay(true);
252    window.refresh();
253
254    noecho();
255    nonl();
256    cbreak();
257    curs_set(0);
258
259    if has_colors() {
260        start_color();
261        if use_default_colors() != ERR {
262            init_pair(COLOR_BLACK, -1, -1);
263            init_pair(COLOR_GREEN, COLOR_GREEN, -1);
264            init_pair(COLOR_WHITE, COLOR_WHITE, -1);
265            init_pair(COLOR_RED, COLOR_RED, -1);
266            init_pair(COLOR_CYAN, COLOR_CYAN, -1);
267            init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1);
268            init_pair(COLOR_BLUE, COLOR_BLUE, -1);
269            init_pair(COLOR_YELLOW, COLOR_YELLOW, -1);
270        } else {
271            init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK);
272            init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
273            init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
274            init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK);
275            init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK);
276            init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
277            init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK);
278            init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
279        }
280    }
281
282    window
283}
284
285fn get_term_size() -> (usize, usize) {
286    match term_size::dimensions() {
287        Some((mut width, mut height)) => {
288            // Minimum size for terminal
289            if width < 10 {
290                width = 10
291            }
292            if height < 10 {
293                height = 10
294            }
295            if width % 2 != 0 {
296                // Makes odd-columned screens print on the rightmost edge
297                (height + 1, (width / 2) + 1)
298            } else {
299                (height + 1, width / 2)
300            }
301        }
302        None => (10, 10),
303    }
304}
305
306pub fn resize_window() {
307    //TODO: Find a way to do this without exiting ncurses
308    endwin();
309    initscr();
310}