Consistently refer to drills as drills now, rename from lesson/practice
Also fix some issues in the stats screen.
This commit is contained in:
@@ -3,7 +3,7 @@ use std::time::Instant;
|
||||
|
||||
use crate::session::input::CharStatus;
|
||||
|
||||
pub struct LessonState {
|
||||
pub struct DrillState {
|
||||
pub target: Vec<char>,
|
||||
pub input: Vec<CharStatus>,
|
||||
pub cursor: usize,
|
||||
@@ -12,7 +12,7 @@ pub struct LessonState {
|
||||
pub typo_flags: HashSet<usize>,
|
||||
}
|
||||
|
||||
impl LessonState {
|
||||
impl DrillState {
|
||||
pub fn new(text: &str) -> Self {
|
||||
Self {
|
||||
target: text.chars().collect(),
|
||||
@@ -90,72 +90,72 @@ mod tests {
|
||||
use crate::session::input;
|
||||
|
||||
#[test]
|
||||
fn test_new_lesson() {
|
||||
let lesson = LessonState::new("hello");
|
||||
assert_eq!(lesson.target.len(), 5);
|
||||
assert_eq!(lesson.cursor, 0);
|
||||
assert!(!lesson.is_complete());
|
||||
assert_eq!(lesson.progress(), 0.0);
|
||||
fn test_new_drill() {
|
||||
let drill = DrillState::new("hello");
|
||||
assert_eq!(drill.target.len(), 5);
|
||||
assert_eq!(drill.cursor, 0);
|
||||
assert!(!drill.is_complete());
|
||||
assert_eq!(drill.progress(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_accuracy_starts_at_100() {
|
||||
let lesson = LessonState::new("test");
|
||||
assert_eq!(lesson.accuracy(), 100.0);
|
||||
let drill = DrillState::new("test");
|
||||
assert_eq!(drill.accuracy(), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_lesson_progress() {
|
||||
let lesson = LessonState::new("");
|
||||
assert!(lesson.is_complete());
|
||||
assert_eq!(lesson.progress(), 0.0);
|
||||
fn test_empty_drill_progress() {
|
||||
let drill = DrillState::new("");
|
||||
assert!(drill.is_complete());
|
||||
assert_eq!(drill.progress(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_correct_typing_no_typos() {
|
||||
let mut lesson = LessonState::new("abc");
|
||||
input::process_char(&mut lesson, 'a');
|
||||
input::process_char(&mut lesson, 'b');
|
||||
input::process_char(&mut lesson, 'c');
|
||||
assert!(lesson.typo_flags.is_empty());
|
||||
assert_eq!(lesson.accuracy(), 100.0);
|
||||
let mut drill = DrillState::new("abc");
|
||||
input::process_char(&mut drill, 'a');
|
||||
input::process_char(&mut drill, 'b');
|
||||
input::process_char(&mut drill, 'c');
|
||||
assert!(drill.typo_flags.is_empty());
|
||||
assert_eq!(drill.accuracy(), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_then_backspace_then_correct_counts_as_error() {
|
||||
let mut lesson = LessonState::new("abc");
|
||||
let mut drill = DrillState::new("abc");
|
||||
// Type wrong at pos 0
|
||||
input::process_char(&mut lesson, 'x');
|
||||
assert!(lesson.typo_flags.contains(&0));
|
||||
input::process_char(&mut drill, 'x');
|
||||
assert!(drill.typo_flags.contains(&0));
|
||||
// Backspace
|
||||
input::process_backspace(&mut lesson);
|
||||
input::process_backspace(&mut drill);
|
||||
// Typo flag persists
|
||||
assert!(lesson.typo_flags.contains(&0));
|
||||
assert!(drill.typo_flags.contains(&0));
|
||||
// Type correct
|
||||
input::process_char(&mut lesson, 'a');
|
||||
assert!(lesson.typo_flags.contains(&0));
|
||||
assert_eq!(lesson.typo_count(), 1);
|
||||
assert!(lesson.accuracy() < 100.0);
|
||||
input::process_char(&mut drill, 'a');
|
||||
assert!(drill.typo_flags.contains(&0));
|
||||
assert_eq!(drill.typo_count(), 1);
|
||||
assert!(drill.accuracy() < 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_errors_same_position_counts_as_one() {
|
||||
let mut lesson = LessonState::new("abc");
|
||||
let mut drill = DrillState::new("abc");
|
||||
// Wrong, backspace, wrong again, backspace, correct
|
||||
input::process_char(&mut lesson, 'x');
|
||||
input::process_backspace(&mut lesson);
|
||||
input::process_char(&mut lesson, 'y');
|
||||
input::process_backspace(&mut lesson);
|
||||
input::process_char(&mut lesson, 'a');
|
||||
assert_eq!(lesson.typo_count(), 1);
|
||||
input::process_char(&mut drill, 'x');
|
||||
input::process_backspace(&mut drill);
|
||||
input::process_char(&mut drill, 'y');
|
||||
input::process_backspace(&mut drill);
|
||||
input::process_char(&mut drill, 'a');
|
||||
assert_eq!(drill.typo_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_char_without_backspace() {
|
||||
let mut lesson = LessonState::new("abc");
|
||||
input::process_char(&mut lesson, 'x'); // wrong at pos 0
|
||||
input::process_char(&mut lesson, 'b'); // correct at pos 1
|
||||
assert_eq!(lesson.typo_count(), 1);
|
||||
assert!(lesson.typo_flags.contains(&0));
|
||||
let mut drill = DrillState::new("abc");
|
||||
input::process_char(&mut drill, 'x'); // wrong at pos 0
|
||||
input::process_char(&mut drill, 'b'); // correct at pos 1
|
||||
assert_eq!(drill.typo_count(), 1);
|
||||
assert!(drill.typo_flags.contains(&0));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::session::lesson::LessonState;
|
||||
use crate::session::drill::DrillState;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CharStatus {
|
||||
@@ -17,16 +17,16 @@ pub struct KeystrokeEvent {
|
||||
pub correct: bool,
|
||||
}
|
||||
|
||||
pub fn process_char(lesson: &mut LessonState, ch: char) -> Option<KeystrokeEvent> {
|
||||
if lesson.is_complete() {
|
||||
pub fn process_char(drill: &mut DrillState, ch: char) -> Option<KeystrokeEvent> {
|
||||
if drill.is_complete() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if lesson.started_at.is_none() {
|
||||
lesson.started_at = Some(Instant::now());
|
||||
if drill.started_at.is_none() {
|
||||
drill.started_at = Some(Instant::now());
|
||||
}
|
||||
|
||||
let expected = lesson.target[lesson.cursor];
|
||||
let expected = drill.target[drill.cursor];
|
||||
let correct = ch == expected;
|
||||
|
||||
let event = KeystrokeEvent {
|
||||
@@ -37,23 +37,23 @@ pub fn process_char(lesson: &mut LessonState, ch: char) -> Option<KeystrokeEvent
|
||||
};
|
||||
|
||||
if correct {
|
||||
lesson.input.push(CharStatus::Correct);
|
||||
drill.input.push(CharStatus::Correct);
|
||||
} else {
|
||||
lesson.input.push(CharStatus::Incorrect(ch));
|
||||
lesson.typo_flags.insert(lesson.cursor);
|
||||
drill.input.push(CharStatus::Incorrect(ch));
|
||||
drill.typo_flags.insert(drill.cursor);
|
||||
}
|
||||
lesson.cursor += 1;
|
||||
drill.cursor += 1;
|
||||
|
||||
if lesson.is_complete() {
|
||||
lesson.finished_at = Some(Instant::now());
|
||||
if drill.is_complete() {
|
||||
drill.finished_at = Some(Instant::now());
|
||||
}
|
||||
|
||||
Some(event)
|
||||
}
|
||||
|
||||
pub fn process_backspace(lesson: &mut LessonState) {
|
||||
if lesson.cursor > 0 {
|
||||
lesson.cursor -= 1;
|
||||
lesson.input.pop();
|
||||
pub fn process_backspace(drill: &mut DrillState) {
|
||||
if drill.cursor > 0 {
|
||||
drill.cursor -= 1;
|
||||
drill.input.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod input;
|
||||
pub mod lesson;
|
||||
pub mod drill;
|
||||
pub mod result;
|
||||
|
||||
@@ -2,10 +2,10 @@ use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::session::input::KeystrokeEvent;
|
||||
use crate::session::lesson::LessonState;
|
||||
use crate::session::drill::DrillState;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct LessonResult {
|
||||
pub struct DrillResult {
|
||||
pub wpm: f64,
|
||||
pub cpm: f64,
|
||||
pub accuracy: f64,
|
||||
@@ -15,11 +15,11 @@ pub struct LessonResult {
|
||||
pub elapsed_secs: f64,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub per_key_times: Vec<KeyTime>,
|
||||
#[serde(default = "default_lesson_mode")]
|
||||
pub lesson_mode: String,
|
||||
#[serde(default = "default_drill_mode", alias = "lesson_mode")]
|
||||
pub drill_mode: String,
|
||||
}
|
||||
|
||||
fn default_lesson_mode() -> String {
|
||||
fn default_drill_mode() -> String {
|
||||
"adaptive".to_string()
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ pub struct KeyTime {
|
||||
pub correct: bool,
|
||||
}
|
||||
|
||||
impl LessonResult {
|
||||
pub fn from_lesson(lesson: &LessonState, events: &[KeystrokeEvent], lesson_mode: &str) -> Self {
|
||||
impl DrillResult {
|
||||
pub fn from_drill(drill: &DrillState, events: &[KeystrokeEvent], drill_mode: &str) -> Self {
|
||||
let per_key_times: Vec<KeyTime> = events
|
||||
.windows(2)
|
||||
.map(|pair| {
|
||||
@@ -44,8 +44,8 @@ impl LessonResult {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let total_chars = lesson.target.len();
|
||||
let typo_count = lesson.typo_flags.len();
|
||||
let total_chars = drill.target.len();
|
||||
let typo_count = drill.typo_flags.len();
|
||||
let accuracy = if total_chars > 0 {
|
||||
((total_chars - typo_count) as f64 / total_chars as f64 * 100.0).clamp(0.0, 100.0)
|
||||
} else {
|
||||
@@ -53,16 +53,16 @@ impl LessonResult {
|
||||
};
|
||||
|
||||
Self {
|
||||
wpm: lesson.wpm(),
|
||||
cpm: lesson.cpm(),
|
||||
wpm: drill.wpm(),
|
||||
cpm: drill.cpm(),
|
||||
accuracy,
|
||||
correct: total_chars - typo_count,
|
||||
incorrect: typo_count,
|
||||
total_chars,
|
||||
elapsed_secs: lesson.elapsed_secs(),
|
||||
elapsed_secs: drill.elapsed_secs(),
|
||||
timestamp: Utc::now(),
|
||||
per_key_times,
|
||||
lesson_mode: lesson_mode.to_string(),
|
||||
drill_mode: drill_mode.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user