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, spaces: usize, col: VecDeque<Block>, }
95
96impl Column {
97 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 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 fn default() -> Self {
153 let (lines, cols) = get_term_size();
155
156 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 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 col.spaces -= 1;
180 } else if col.head_is_empty() && col.spaces == 0 {
181 col.new_rand_head(config);
183
184 col.length -= 1;
186
187 col.spaces = random_range(1..lines + 1);
189 } else if col.length != 0 {
190 col.new_rand_char();
192 col.length -= 1;
193 } else {
194 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 let mut in_stream = false;
209
210 let mut last_was_white = false; 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; running_color = block.color;
219 }
220 } else if block.is_space() {
221 block.val = rand_char();
223 block.white = last_was_white;
224 in_stream = false;
225 }
226 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 self.m.iter_mut().for_each(|col| {
235 col.col.pop_back();
236 col.col.push_back(Block::default()); col.col.rotate_right(1)
238 });
239 }
240 pub fn draw(&self, terminal: &mut Terminal, config: &Config) -> io::Result<()> {
242 let stdout = &mut terminal.stdout;
243
244 for j in 1..self.num_lines() {
246 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 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))?; if last_colour != mcolour {
260 queue!(stdout, SetForegroundColor(mcolour.as_crossterm()))?;
262 last_colour = mcolour;
263 }
264 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
274pub struct Terminal {
276 stdout: Stdout,
277 active: bool,
278}
279
280impl Terminal {
281 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 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 pub fn finish(mut self) -> io::Result<()> {
307 self.restore()
308 }
309
310 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 if width < 10 {
339 width = 10
340 }
341 if height < 10 {
342 height = 10
343 }
344 if width % 2 != 0 {
345 ((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}