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, spaces: usize, col: VecDeque<Block>, }
99
100impl Column {
101 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 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 fn default() -> Self {
157 let (lines, cols) = get_term_size();
159
160 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 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 col.spaces -= 1;
184 } else if col.head_is_empty() && col.spaces == 0 {
185 col.new_rand_head(config);
187
188 col.length -= 1;
190
191 col.spaces = rng::<usize>() % lines + 1;
193 } else if col.length != 0 {
194 col.new_rand_char();
196 col.length -= 1;
197 } else {
198 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 let mut in_stream = false;
213
214 let mut last_was_white = false; 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; running_color = block.color;
223 }
224 } else if block.is_space() {
225 block.val = rand_char();
227 block.white = last_was_white;
228 in_stream = false;
229 }
230 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 self.m.iter_mut().for_each(|col| {
239 col.col.pop_back();
240 col.col.push_back(Block::default()); col.col.rotate_right(1)
242 });
243 }
244 pub fn draw(&self, terminal: &mut Terminal, config: &Config) -> io::Result<()> {
246 let stdout = &mut terminal.stdout;
247
248 for j in 1..self.num_lines() {
250 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 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))?; if last_colour != mcolour {
264 queue!(stdout, SetForegroundColor(mcolour.as_crossterm()))?;
266 last_colour = mcolour;
267 }
268 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
278pub struct Terminal {
280 stdout: Stdout,
281 active: bool,
282}
283
284impl Terminal {
285 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 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 pub fn finish(mut self) -> io::Result<()> {
311 self.restore()
312 }
313
314 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 if width < 10 {
343 width = 10
344 }
345 if height < 10 {
346 height = 10
347 }
348 if width % 2 != 0 {
349 ((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}