Skip to main content

r_matrix/
lib.rs

1extern crate crossterm;
2extern crate rand;
3extern crate structopt;
4
5use std::cell::RefCell;
6use std::collections::VecDeque;
7use std::io::{self, Stdout, Write};
8use std::thread;
9use std::time::Duration;
10
11pub mod config;
12
13use config::Config;
14
15use crossterm::cursor;
16use crossterm::event::{self, Event};
17use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
18use crossterm::terminal::{self, Clear, ClearType};
19use crossterm::{execute, queue};
20use rand::distributions::{Distribution, Standard};
21use rand::rngs::SmallRng;
22use rand::{Rng, SeedableRng};
23
24thread_local! {
25    static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
26}
27
28fn rng<T>() -> T
29where
30    Standard: Distribution<T>,
31{
32    RNG.with(|rng| (*rng).borrow_mut().r#gen::<T>())
33}
34
35fn rand_char() -> char {
36    let (randnum, randmin) = (93, 33);
37    RNG.with(|rng| (*rng).borrow_mut().r#gen::<u8>() % randnum + randmin) as char
38}
39
40fn coin_flip() -> bool {
41    RNG.with(|rng| (*rng).borrow_mut().r#gen())
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45pub enum MatrixColor {
46    Black,
47    Green,
48    White,
49    Red,
50    Cyan,
51    Magenta,
52    Blue,
53    Yellow,
54}
55
56impl MatrixColor {
57    fn as_crossterm(self) -> Color {
58        match self {
59            MatrixColor::Black => Color::Black,
60            MatrixColor::Green => Color::Green,
61            MatrixColor::White => Color::White,
62            MatrixColor::Red => Color::Red,
63            MatrixColor::Cyan => Color::Cyan,
64            MatrixColor::Magenta => Color::Magenta,
65            MatrixColor::Blue => Color::Blue,
66            MatrixColor::Yellow => Color::Yellow,
67        }
68    }
69}
70
71#[derive(Clone)]
72pub struct Block {
73    val: char,
74    white: bool,
75    color: MatrixColor,
76}
77
78impl Block {
79    fn is_space(&self) -> bool {
80        self.val == ' '
81    }
82}
83
84impl Default for Block {
85    fn default() -> Self {
86        Block {
87            val: ' ',
88            white: false,
89            color: MatrixColor::Red,
90        }
91    }
92}
93
94pub struct Column {
95    length: usize,        // The length of the stream
96    spaces: usize,        // The spaces between streams
97    col: VecDeque<Block>, // The actual column
98}
99
100impl Column {
101    /// Return a column keyed by a random number generator
102    fn new(lines: usize) -> Self {
103        Column {
104            length: rng::<usize>() % (lines - 3) + 3,
105            spaces: rng::<usize>() % lines + 1,
106            col: (0..lines).map(|_| Block::default()).collect(),
107        }
108    }
109    fn head_is_empty(&self) -> bool {
110        self.col[1].val == ' '
111    }
112    fn new_rand_char(&mut self) {
113        self.col[0].val = rand_char();
114        self.col[0].color = self.col[1].color;
115    }
116    fn new_rand_head(&mut self, config: &Config) {
117        self.col[0].val = rand_char();
118        self.col[0].color = if config.rainbow {
119            match rng::<usize>() % 6 {
120                0 => MatrixColor::Green,
121                1 => MatrixColor::Blue,
122                2 => MatrixColor::White,
123                3 => MatrixColor::Yellow,
124                4 => MatrixColor::Cyan,
125                5 => MatrixColor::Magenta,
126                _ => unreachable!(),
127            }
128        } else {
129            config.colour
130        };
131        // 50/50 chance the head is white
132        self.col[0].white = coin_flip();
133    }
134}
135
136impl std::ops::Index<usize> for Column {
137    type Output = Block;
138    fn index(&self, i: usize) -> &Self::Output {
139        &self.col[i]
140    }
141}
142
143pub struct Matrix {
144    m: Vec<Column>,
145}
146
147impl std::ops::Index<usize> for Matrix {
148    type Output = Column;
149    fn index(&self, i: usize) -> &Self::Output {
150        &self.m[i]
151    }
152}
153
154impl Default for Matrix {
155    /// Create a new matrix with the dimensions of the screen
156    fn default() -> Self {
157        // Get the screen dimensions
158        let (lines, cols) = get_term_size();
159
160        // Create the matrix
161        Matrix {
162            m: (0..cols).map(|_| Column::new(lines)).collect(),
163        }
164    }
165}
166
167impl Matrix {
168    fn num_columns(&self) -> usize {
169        self.m.len()
170    }
171
172    fn num_lines(&self) -> usize {
173        self[0].col.len()
174    }
175
176    /// Make the next iteration of matrix
177    pub fn arrange(&mut self, config: &Config) {
178        let lines = self.num_lines();
179
180        self.m.iter_mut().for_each(|col| {
181            if col.head_is_empty() && col.spaces != 0 {
182                // Decrement the spaces until the next stream starts
183                col.spaces -= 1;
184            } else if col.head_is_empty() && col.spaces == 0 {
185                // Start a new stream
186                col.new_rand_head(config);
187
188                // Decrement length of stream
189                col.length -= 1;
190
191                // Reset number of spaces until next stream
192                col.spaces = rng::<usize>() % lines + 1;
193            } else if col.length != 0 {
194                // Continue producing stream
195                col.new_rand_char();
196                col.length -= 1;
197            } else {
198                // Display spaces until next stream
199                col.col[0].val = ' ';
200                col.length = rng::<usize>() % (lines - 3) + 3;
201            }
202        });
203        if config.oldstyle {
204            self.old_style_move_down();
205        } else {
206            self.move_down();
207        }
208    }
209    fn move_down(&mut self) {
210        self.m.iter_mut().for_each(|col| {
211            // Reset for each column
212            let mut in_stream = false;
213
214            let mut last_was_white = false; // Keep track of white heads
215            let mut running_color = MatrixColor::Cyan;
216
217            col.col.iter_mut().for_each(|block| {
218                if !in_stream {
219                    if !block.is_space() {
220                        block.val = ' ';
221                        in_stream = true; // We're now in a stream
222                        running_color = block.color;
223                    }
224                } else if block.is_space() {
225                    // New rand char for head of stream
226                    block.val = rand_char();
227                    block.white = last_was_white;
228                    in_stream = false;
229                }
230                // Swapped to "pass on" whiteness and prepare the variable for the next iteration
231                std::mem::swap(&mut last_was_white, &mut block.white);
232                block.color = running_color;
233            })
234        })
235    }
236    fn old_style_move_down(&mut self) {
237        // Iterate over all columns and swap spaces
238        self.m.iter_mut().for_each(|col| {
239            col.col.pop_back();
240            col.col.push_back(Block::default()); // Put a Blank space at the head.
241            col.col.rotate_right(1)
242        });
243    }
244    /// Draw the matrix on the screen
245    pub fn draw(&self, terminal: &mut Terminal, config: &Config) -> io::Result<()> {
246        let stdout = &mut terminal.stdout;
247
248        //TODO: Use an iterator or something nicer
249        for j in 1..self.num_lines() {
250            // Saving the last colour allows us to change colour only when the colour changes.
251            let mut last_colour = self[0][j].color;
252            queue!(stdout, SetForegroundColor(last_colour.as_crossterm()))?;
253
254            for i in 0..self.num_columns() {
255                // Pick the colour we need
256                let mcolour = if self[i][j].white {
257                    MatrixColor::White
258                } else {
259                    self[i][j].color
260                };
261
262                queue!(stdout, cursor::MoveTo(2 * i as u16, j as u16 - 1))?; // Move the cursor
263                if last_colour != mcolour {
264                    // Set the colour in the terminal.
265                    queue!(stdout, SetForegroundColor(mcolour.as_crossterm()))?;
266                    last_colour = mcolour;
267                }
268                // Draw the character.
269                queue!(stdout, Print(self[i][j].val))?;
270            }
271        }
272        stdout.flush()?;
273        thread::sleep(Duration::from_millis(config.update as u64 * 10));
274        Ok(())
275    }
276}
277
278/// Terminal state object
279pub struct Terminal {
280    stdout: Stdout,
281    active: bool,
282}
283
284impl Terminal {
285    /// Set up the screen and set important variables
286    pub fn new() -> io::Result<Self> {
287        terminal::enable_raw_mode()?;
288
289        let mut stdout = io::stdout();
290        if let Err(error) = execute!(stdout, cursor::Hide, Clear(ClearType::All)) {
291            let _ = terminal::disable_raw_mode();
292            return Err(error);
293        }
294
295        Ok(Terminal {
296            stdout,
297            active: true,
298        })
299    }
300
301    /// Return the next terminal event, if one is ready
302    pub fn get_event(&self, timeout: Duration) -> io::Result<Option<Event>> {
303        if event::poll(timeout)? {
304            return Ok(Some(event::read()?));
305        }
306        Ok(None)
307    }
308
309    /// Clean up terminal stuff when we're ready to exit
310    pub fn finish(mut self) -> io::Result<()> {
311        self.restore()
312    }
313
314    /// Clear the terminal when the window changes size
315    pub fn resize_window(&mut self) -> io::Result<()> {
316        execute!(self.stdout, Clear(ClearType::All), cursor::MoveTo(0, 0))?;
317        self.stdout.flush()
318    }
319
320    fn restore(&mut self) -> io::Result<()> {
321        if self.active {
322            let terminal_result = execute!(self.stdout, ResetColor, cursor::Show);
323            let raw_mode_result = terminal::disable_raw_mode();
324            self.active = false;
325            terminal_result?;
326            raw_mode_result?;
327        }
328        Ok(())
329    }
330}
331
332impl Drop for Terminal {
333    fn drop(&mut self) {
334        let _ = self.restore();
335    }
336}
337
338fn get_term_size() -> (usize, usize) {
339    match terminal::size() {
340        Ok((mut width, mut height)) => {
341            // Minimum size for terminal
342            if width < 10 {
343                width = 10
344            }
345            if height < 10 {
346                height = 10
347            }
348            if width % 2 != 0 {
349                // Makes odd-columned screens print on the rightmost edge
350                ((height + 1) as usize, (width / 2 + 1) as usize)
351            } else {
352                ((height + 1) as usize, (width / 2) as usize)
353            }
354        }
355        Err(_) => (10, 10),
356    }
357}