1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! 🦇 BATS! 🦇

#![doc(
    html_logo_url = "https://raw.githubusercontent.com/tarcieri/bats/batcave/img/cutebat.png",
    html_root_url = "https://docs.rs/bats/0.10.31"
)]
#![warn(missing_docs, rust_2018_idioms)]

use crossterm::{cursor, terminal};
use gumdrop::Options;
use rand::{thread_rng, Rng};
use std::{thread::sleep, time::Duration};

/// Bat!
pub const BAT: char = '🦇';

/// Crab!
pub const CRAB: char = '🦀';

/// Halloween things
pub const HALLOWEEN_THINGS: &[char] = &['🧙', '🦇', '🎃', '👻', '💀', '🕸', '🔮', '🌙'];

/// Bats! A spooky bat printer
#[derive(Debug, Default, Options)]
pub struct Bats {
    /// Help
    #[options(help = "print help message")]
    help: bool,

    /// Character to print
    #[options(short = "c", long = "char", help = "character to print")]
    char: Option<char>,

    /// Draw crabs
    #[options(long = "crab", help = "draw crabs")]
    crab: bool,

    /// Speed
    #[options(
        short = "s",
        long = "speed",
        help = "speed factor (default 15, max 255)"
    )]
    pub speed: Option<u8>,

    /// Enable Halloween mode
    #[options(long = "halloween", help = "enable halloween mode")]
    pub halloween: bool,
}

impl Bats {
    /// Run the program
    pub fn run(&self) {
        println!("🦇 BATS! 🦇");
        sleep(Duration::from_millis(250));

        terminal().clear(terminal::ClearType::All).unwrap();

        let cursor = cursor();
        cursor.hide().unwrap();
        cursor.goto(0, 0).unwrap();

        let thing = self.thing_to_draw();
        let is_halloween = self.is_it_halloween();

        loop {
            if is_halloween {
                self.draw_halloween();
            } else {
                self.draw(thing);
            }
        }
    }

    /// Draw a random halloweeny-thing
    pub fn draw_halloween(&self) {
        self.draw(HALLOWEEN_THINGS[thread_rng().gen_range(0, HALLOWEEN_THINGS.len())]);
    }

    /// Draw an arbitrary string
    pub fn draw(&self, thing: char) {
        let mut rng = thread_rng();
        let terminal = terminal();
        let (term_width, term_height) = terminal.size().unwrap();

        let cursor = cursor();
        let y_position = cursor.pos().unwrap().1;

        let start_pos = term_width - 2;
        let end_pos = rng.gen_range(0, start_pos);

        let mut delay = u64::from(10 + (start_pos - end_pos) * 2);
        let delay_scale = rng.gen_range(self.speed_factor(), self.speed_factor() * 2);

        for pos in (end_pos..start_pos).rev() {
            cursor.goto(pos, y_position).unwrap();
            terminal.write(thing).unwrap();

            sleep(Duration::from_millis(delay / delay_scale));
            delay -= 1;

            if pos != end_pos {
                cursor.goto(pos, y_position).unwrap();
                terminal.write("  ").unwrap();
            }
        }

        if y_position < term_height - 1 {
            cursor.goto(0, y_position + 1).unwrap();
        } else {
            terminal.clear(terminal::ClearType::All).unwrap();
            cursor.goto(0, 0).unwrap();
        }

        sleep(Duration::from_millis(256 / self.speed_factor()));
    }

    /// Character to draw
    fn thing_to_draw(&self) -> char {
        if self.crab {
            if self.char.is_none() {
                CRAB
            } else {
                panic!("both --char and --crab options passed");
            }
        } else {
            self.char.unwrap_or(BAT)
        }
    }

    /// Get the current speed factor
    fn speed_factor(&self) -> u64 {
        u64::from(self.speed.unwrap_or(15))
    }

    /// Is it halloween?
    fn is_it_halloween(&self) -> bool {
        if self.halloween {
            return true;
        }

        use chrono::Datelike;
        let today = chrono::Local::today();
        today.month() == 10 && today.day() == 31
    }
}