Skip to main content

r_matrix/
lib.rs

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