trace_game/
lib.rs

1use chrono::prelude::*;
2use crossterm::event::KeyCode;
3use serde::{Deserialize, Serialize};
4use std::path::Path;
5use std::path::PathBuf;
6use std::{collections::HashMap, rc::Rc};
7use tui::{
8    backend::Backend,
9    style::{Color, Style},
10    text::Span,
11    Frame,
12};
13
14pub mod windows;
15
16#[derive(Deserialize, Serialize, Clone)]
17pub struct TraceRun {
18    wpm: f64,
19    accuracy: f64,
20    total_points: f64,
21    seconds: f64,
22}
23
24impl TraceRun {
25    pub fn to_csv(&self) -> String {
26        format!(
27            "{:},{:},{:},{:}",
28            self.wpm, self.accuracy, self.total_points, self.seconds
29        )
30    }
31}
32
33pub fn get_track_record() -> Vec<TraceRun> {
34    let path = get_app_path(".runs.csv");
35    match csv::Reader::from_path(path) {
36        Ok(mut reader) => {
37            let mut records = vec![];
38            for result in reader.deserialize() {
39                if let Ok(record) = result {
40                    records.push(record)
41                }
42            }
43            records
44        }
45        Err(_) => Vec::new(),
46    }
47}
48
49#[derive(Deserialize, Clone)]
50pub struct AppParagraph {
51    content: String,
52    title: String,
53    author: String,
54    date: String,
55}
56
57impl AppParagraph {
58    pub fn new() -> AppParagraph {
59        AppParagraph {
60            content: "".to_string(),
61            title: "".to_string(),
62            author: "".to_string(),
63            date: "".to_string(),
64        }
65    }
66}
67
68#[derive(Clone)]
69pub enum CharStatus {
70    Correct,
71    Wrong,
72    Default,
73    Current,
74}
75
76#[derive(Clone)]
77pub struct ParagraphChar {
78    character: char,
79    status: CharStatus,
80}
81
82impl ParagraphChar {
83    pub fn new(c: char, status: CharStatus) -> ParagraphChar {
84        ParagraphChar {
85            character: c,
86            status,
87        }
88    }
89    pub fn to_span(&self) -> Span {
90        match self.status {
91            CharStatus::Correct => Span::styled(
92                self.character.to_string(),
93                Style::default().fg(Color::Green),
94            ),
95            CharStatus::Current => Span::styled(
96                self.character.to_string(),
97                Style::default().fg(Color::White).bg(Color::DarkGray),
98            ),
99            CharStatus::Wrong => {
100                if self.character == ' ' {
101                    Span::styled(self.character.to_string(), Style::default().bg(Color::Red))
102                } else {
103                    Span::styled(self.character.to_string(), Style::default().fg(Color::Red))
104                }
105            }
106            CharStatus::Default => Span::styled(
107                self.character.to_string(),
108                Style::default().fg(Color::DarkGray),
109            ),
110        }
111    }
112}
113
114pub fn convert_string_to_chars(s: String) -> Vec<ParagraphChar> {
115    let mut vector = vec![];
116    for elem in s.chars() {
117        vector.push(ParagraphChar::new(elem, CharStatus::Default));
118    }
119    return vector;
120}
121
122#[derive(Clone)]
123pub struct State {
124    user_name: String,
125    chars: Vec<ParagraphChar>,
126    show_bar_charts: bool,
127    paragraph: AppParagraph,
128    initial_time: DateTime<Utc>,
129    end_time: DateTime<Utc>,
130    current_error_count: usize,
131    total_error_count: usize,
132    word_count: usize,
133    index: usize,
134}
135
136impl State {
137    pub fn new() -> State {
138        State {
139            chars: vec![],
140            user_name: String::new(),
141            paragraph: AppParagraph::new(),
142            initial_time: Utc::now(),
143            end_time: Utc::now(),
144            show_bar_charts: false,
145            current_error_count: 0,
146            total_error_count: 0,
147            word_count: 0,
148            index: 0,
149        }
150    }
151    pub fn reset(&mut self) {
152        self.chars = vec![];
153        self.paragraph = AppParagraph::new();
154        self.initial_time = Utc::now();
155        self.end_time = Utc::now();
156        self.show_bar_charts = false;
157        self.current_error_count = 0;
158        self.total_error_count = 0;
159        self.word_count = 0;
160        self.index = 0;
161    }
162    pub fn create_run(&self) -> TraceRun {
163        let accuracy = (self.chars.len() - self.total_error_count) as f64 / self.chars.len() as f64;
164        let duration = self.end_time - self.initial_time;
165        let seconds = (duration.num_milliseconds() as f64) / 1000.0;
166
167        let wpm = self.word_count as f64 / seconds * 60.0;
168        let total_points = (wpm + accuracy * wpm) / 2.0;
169        TraceRun {
170            wpm,
171            accuracy,
172            total_points,
173            seconds,
174        }
175    }
176}
177
178pub struct WindowCommand<B: Backend> {
179    pub activator_key: KeyCode,
180    pub action: Box<dyn Fn(&mut State) -> Option<Window<B>>>,
181}
182
183impl<B: Backend> WindowCommand<B> {
184    pub fn new_char_command(
185        activator: char, command: Box<dyn Fn(&mut State) -> Option<Window<B>>>,
186    ) -> WindowCommand<B> {
187        WindowCommand {
188            activator_key: KeyCode::Char(activator),
189            action: command,
190        }
191    }
192}
193
194pub struct Window<B: Backend> {
195    pub commands: HashMap<KeyCode, WindowCommand<B>>,
196    pub ui: fn(Rc<State>) -> Box<dyn Fn(&mut Frame<B>)>,
197}
198
199pub fn get_app_path(file_path: &str) -> PathBuf {
200    let current_dir = std::env::current_dir().unwrap();
201    Path::new(&current_dir).join(file_path)
202}
203
204pub fn generate_all_chars() -> Vec<char> {
205    let mut chars = vec![
206        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
207        's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
208    ];
209    let mut upper_chars: Vec<char> = chars.iter().map(|a| a.to_ascii_uppercase()).collect();
210    let mut punctuation = vec![
211        ' ', ',', '.', ':', '"', '-', '@', ';', '<', '>', '+', '-', '_', '(', ')', '=', '*', '/',
212        '¡', '!', '¿', '?', '#', '$', '%', '&', '°', '\'', '^', '~', '[', ']', '{', '}',
213    ];
214    let mut numbers = vec!['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
215    let mut extras = vec![
216        'á', 'Á', 'é', 'É', 'í', 'Í', 'ó', 'Ó', 'ú', 'Ú', 'ä', 'Ä', 'ë', 'Ë', 'ï', 'Ï', 'ö', 'Ö',
217        'ü', 'Ü', 'ç', 'ñ', 'Ñ',
218    ];
219
220    chars.append(&mut upper_chars);
221    chars.append(&mut punctuation);
222    chars.append(&mut numbers);
223    chars.append(&mut extras);
224
225    chars
226}
227
228pub fn add_to_commands<B: 'static + Backend>(
229    commands: &mut HashMap<KeyCode, WindowCommand<B>>, char_array: &Vec<char>,
230    cmd: Box<dyn Fn(char) -> Box<dyn Fn(&mut State) -> Option<Window<B>>>>,
231) {
232    for elem in char_array {
233        commands.insert(
234            KeyCode::Char(*elem),
235            WindowCommand {
236                activator_key: KeyCode::Char(*elem),
237                action: cmd(*elem),
238            },
239        );
240    }
241}