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::ops::Range;
9use std::thread;
10use std::time::Duration;
11
12pub mod config;
13
14use config::Config;
15
16use crossterm::cursor;
17use crossterm::event::{self, Event};
18use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
19use crossterm::terminal::{self, Clear, ClearType};
20use crossterm::{execute, queue};
21use rand::RngExt;
22use rand::rngs::SmallRng;
23
24thread_local! {
25 static RNG: RefCell<SmallRng> = RefCell::new(rand::make_rng());
26}
27
28fn random_range(range: Range<usize>) -> usize {
29 RNG.with(|rng| (*rng).borrow_mut().random_range(range))
30}
31
32fn rand_char() -> char {
33 let (randnum, randmin) = (93, 33);
34 RNG.with(|rng| (*rng).borrow_mut().random::<u8>() % randnum + randmin) as char
35}
36
37fn coin_flip() -> bool {
38 RNG.with(|rng| (*rng).borrow_mut().random())
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub enum MatrixColor {
43 Black,
44 Green,
45 White,
46 Red,
47 Cyan,
48 Magenta,
49 Blue,
50 Yellow,
51}
52
53impl MatrixColor {
54 fn as_crossterm(self) -> Color {
55 match self {
56 MatrixColor::Black => Color::Black,
57 MatrixColor::Green => Color::Green,
58 MatrixColor::White => Color::White,
59 MatrixColor::Red => Color::Red,
60 MatrixColor::Cyan => Color::Cyan,
61 MatrixColor::Magenta => Color::Magenta,
62 MatrixColor::Blue => Color::Blue,
63 MatrixColor::Yellow => Color::Yellow,
64 }
65 }
66}
67
68#[derive(Clone)]
69pub struct Block {
70 val: char,
71 white: bool,
72 color: MatrixColor,
73}
74
75impl Block {
76 fn is_space(&self) -> bool {
77 self.val == ' '
78 }
79}
80
81impl Default for Block {
82 fn default() -> Self {
83 Block {
84 val: ' ',
85 white: false,
86 color: MatrixColor::Red,
87 }
88 }
89}
90
91pub struct Column {
92 length: usize, spaces: usize, col: VecDeque<Block>, }
96
97impl Column {
98 fn new(lines: usize) -> Self {
100 Column {
101 length: random_range(3..lines),
102 spaces: random_range(1..lines + 1),
103 col: (0..lines).map(|_| Block::default()).collect(),
104 }
105 }
106 fn head_is_empty(&self) -> bool {
107 self.col[1].val == ' '
108 }
109 fn new_rand_char(&mut self) {
110 self.col[0].val = rand_char();
111 self.col[0].color = self.col[1].color;
112 }
113 fn new_rand_head(&mut self, config: &Config) {
114 self.col[0].val = rand_char();
115 self.col[0].color = if config.rainbow {
116 match random_range(0..6) {
117 0 => MatrixColor::Green,
118 1 => MatrixColor::Blue,
119 2 => MatrixColor::White,
120 3 => MatrixColor::Yellow,
121 4 => MatrixColor::Cyan,
122 5 => MatrixColor::Magenta,
123 _ => unreachable!(),
124 }
125 } else {
126 config.colour
127 };
128 self.col[0].white = coin_flip();
130 }
131}
132
133impl std::ops::Index<usize> for Column {
134 type Output = Block;
135 fn index(&self, i: usize) -> &Self::Output {
136 &self.col[i]
137 }
138}
139
140pub struct Matrix {
141 m: Vec<Column>,
142}
143
144impl std::ops::Index<usize> for Matrix {
145 type Output = Column;
146 fn index(&self, i: usize) -> &Self::Output {
147 &self.m[i]
148 }
149}
150
151impl Default for Matrix {
152 fn default() -> Self {
154 let (lines, cols) = get_term_size();
156
157 Matrix {
159 m: (0..cols).map(|_| Column::new(lines)).collect(),
160 }
161 }
162}
163
164impl Matrix {
165 fn num_columns(&self) -> usize {
166 self.m.len()
167 }
168
169 fn num_lines(&self) -> usize {
170 self[0].col.len()
171 }
172
173 pub fn arrange(&mut self, config: &Config) {
175 let lines = self.num_lines();
176
177 self.m.iter_mut().for_each(|col| {
178 if col.head_is_empty() && col.spaces != 0 {
179 col.spaces -= 1;
181 } else if col.head_is_empty() && col.spaces == 0 {
182 col.new_rand_head(config);
184
185 col.length -= 1;
187
188 col.spaces = random_range(1..lines + 1);
190 } else if col.length != 0 {
191 col.new_rand_char();
193 col.length -= 1;
194 } else {
195 col.col[0].val = ' ';
197 col.length = random_range(3..lines);
198 }
199 });
200 if config.oldstyle {
201 self.old_style_move_down();
202 } else {
203 self.move_down();
204 }
205 }
206 fn move_down(&mut self) {
207 self.m.iter_mut().for_each(|col| {
208 let mut in_stream = false;
210
211 let mut last_was_white = false; let mut running_color = MatrixColor::Cyan;
213
214 col.col.iter_mut().for_each(|block| {
215 if !in_stream {
216 if !block.is_space() {
217 block.val = ' ';
218 in_stream = true; running_color = block.color;
220 }
221 } else if block.is_space() {
222 block.val = rand_char();
224 block.white = last_was_white;
225 in_stream = false;
226 }
227 std::mem::swap(&mut last_was_white, &mut block.white);
229 block.color = running_color;
230 })
231 })
232 }
233 fn old_style_move_down(&mut self) {
234 self.m.iter_mut().for_each(|col| {
236 col.col.pop_back();
237 col.col.push_back(Block::default()); col.col.rotate_right(1)
239 });
240 }
241 pub fn draw(&self, terminal: &mut Terminal, config: &Config) -> io::Result<()> {
243 let stdout = &mut terminal.stdout;
244
245 for j in 1..self.num_lines() {
247 let mut last_colour = self[0][j].color;
249 queue!(stdout, SetForegroundColor(last_colour.as_crossterm()))?;
250
251 for i in 0..self.num_columns() {
252 let mcolour = if self[i][j].white {
254 MatrixColor::White
255 } else {
256 self[i][j].color
257 };
258
259 queue!(stdout, cursor::MoveTo(2 * i as u16, j as u16 - 1))?; if last_colour != mcolour {
261 queue!(stdout, SetForegroundColor(mcolour.as_crossterm()))?;
263 last_colour = mcolour;
264 }
265 queue!(stdout, Print(self[i][j].val))?;
267 }
268 }
269 stdout.flush()?;
270 thread::sleep(Duration::from_millis(config.update as u64 * 10));
271 Ok(())
272 }
273}
274
275pub struct Terminal {
277 stdout: Stdout,
278 active: bool,
279}
280
281impl Terminal {
282 pub fn new() -> io::Result<Self> {
284 terminal::enable_raw_mode()?;
285
286 let mut stdout = io::stdout();
287 if let Err(error) = execute!(stdout, cursor::Hide, Clear(ClearType::All)) {
288 let _ = terminal::disable_raw_mode();
289 return Err(error);
290 }
291
292 Ok(Terminal {
293 stdout,
294 active: true,
295 })
296 }
297
298 pub fn get_event(&self, timeout: Duration) -> io::Result<Option<Event>> {
300 if event::poll(timeout)? {
301 return Ok(Some(event::read()?));
302 }
303 Ok(None)
304 }
305
306 pub fn finish(mut self) -> io::Result<()> {
308 self.restore()
309 }
310
311 pub fn resize_window(&mut self) -> io::Result<()> {
313 execute!(self.stdout, Clear(ClearType::All), cursor::MoveTo(0, 0))?;
314 self.stdout.flush()
315 }
316
317 fn restore(&mut self) -> io::Result<()> {
318 if self.active {
319 let terminal_result = execute!(self.stdout, ResetColor, cursor::Show);
320 let raw_mode_result = terminal::disable_raw_mode();
321 self.active = false;
322 terminal_result?;
323 raw_mode_result?;
324 }
325 Ok(())
326 }
327}
328
329impl Drop for Terminal {
330 fn drop(&mut self) {
331 let _ = self.restore();
332 }
333}
334
335fn get_term_size() -> (usize, usize) {
336 match terminal::size() {
337 Ok((mut width, mut height)) => {
338 if width < 10 {
340 width = 10
341 }
342 if height < 10 {
343 height = 10
344 }
345 if width % 2 != 0 {
346 ((height + 1) as usize, (width / 2 + 1) as usize)
348 } else {
349 ((height + 1) as usize, (width / 2) as usize)
350 }
351 }
352 Err(_) => (10, 10),
353 }
354}