ferristype 1.0.2

FerrisType is a minimalist typing game where your sole task is to type whatever appears on your terminal screen. Test and improve your typing speed and accuracy as random words and phrases appear, challenging you to type them quickly and correctly. Embrace the simplicity, hone your typing skills, and reach new levels of typing mastery in this immersive terminal-based game.
Documentation
use crate::lang::Language;
use crate::stats::Timer;
use crate::terminalui::{Input, Terminal};
use itertools::peek_nth;
use std::io::{stdin, stdout, Write};
use termion::{cursor, event::Key, input::TermRead, raw::IntoRawMode};

#[derive(Clone)]
pub enum Mode {
    Words(String, usize),
}

impl Default for Mode {
    fn default() -> Self {
        Self::Words("english".to_string(), 10)
    }
}

fn initialize_lines(words: &Vec<String>, words_per_line: u16) -> Vec<String> {
    let mut resu = vec![String::new()];
    let mut w = words.clone();
    let count = words.len() / words_per_line as usize + 1;
    for _ in 0..count {
        let mut line = vec![];
        for _ in 0..words_per_line {
            if let Some(word) = w.pop() {
                line.push(word);
            }
        }
        resu.push(line.join(" "));
    }
    resu.remove(0);
    if words.len() % words_per_line as usize == 0 {
        resu.remove(count - 1);
    }
    resu
}

pub struct FerrisType {
    mode: Mode,
    lines: Vec<String>,
    timer: Timer,
    terminal: Terminal,
    line_peek: u16,
    words_per_line: u16,
}

impl FerrisType {
    pub fn new(mode: Mode) -> Self {
        let words = match &mode {
            Mode::Words(lang, num) => {
                let language = Language::new(lang.to_string());
                language.get_random(num.to_owned())
            }
        };
        let (w, _) = termion::terminal_size().unwrap();
        let lines = initialize_lines(&words, w / 3 / 5);
        Self {
            mode,
            lines,
            timer: Timer::default(),
            terminal: Terminal::new(stdout().into_raw_mode().unwrap()),
            line_peek: 3,
            words_per_line: w / 3 / 5,
        }
    }

    pub fn words(lang: String, n: usize) -> Self {
        Self::new(Mode::Words(lang, n))
    }

    pub fn line_peek(mut self, n: u16) -> Self {
        self.line_peek = n;
        self
    }
    pub fn words_per_line(mut self, n: u16) -> Self {
        self.words_per_line = n;
        self
    }

    pub fn play(mut self) -> std::io::Result<()> {
        let mut stdin = stdin().keys();
        let mut on_last_word = false;
        self.terminal.initialize_screen()?;
        let l = self.lines.clone();
        let line_peek = self.line_peek;
        let mut lines = peek_nth(l.iter());
        let mut lastline = None;
        'lines: while let Some(current_line) = lines.next() {
            let mut input = Input::new(current_line);
            self.terminal.draw_current_line(current_line)?;
            self.terminal.draw_prev_line(lastline)?;
            self.terminal.clear_current_line()?;
            write!(self.terminal.stdout, "{}", input)?;
            for i in 0..line_peek {
                self.terminal.clear_next_line(i)?;
                let next_line = lines.peek_nth(i.into());
                self.terminal.draw_future_line(next_line, i)?;
                if i == 0 {
                    on_last_word = true;
                }
            }
            lastline = Some(current_line);
            loop {
                write!(
                    self.terminal.stdout,
                    "{}",
                    cursor::Goto(self.terminal.type_width, self.terminal.type_height)
                )?;
                self.terminal.stdout.flush()?;
                let c = stdin.next().unwrap()?;
                self.timer.record_key(c);
                match c {
                    Key::Char('\t') => return FerrisType::new(self.mode.clone()).play(),
                    Key::Char('\n') => {
                        break;
                    }
                    Key::Char(' ') if input.is_complete() => break,
                    Key::Char(c) => {
                        let correct = input.update(c);
                        if correct && on_last_word && input.is_complete() {
                            break;
                        }
                    }
                    Key::Backspace => {
                        self.terminal.clear_current_line()?;
                        input.delete_one();
                    }
                    Key::Ctrl('c') => {
                        break 'lines;
                    }
                    _ => {}
                }
                write!(self.terminal.stdout, "{}", input)?;
                self.terminal.stdout.flush()?;
            }
            self.terminal.clear_current_line()?;
        }
        self.terminal.finalize(self.timer, self.lines)?;
        Ok(())
    }
}