Anki/rslib/src/scheduler/states/mod.rs
2021-06-21 13:09:36 +10:00

187 lines
5.1 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
pub(crate) mod filtered;
pub(crate) mod interval_kind;
pub(crate) mod learning;
pub(crate) mod new;
pub(crate) mod normal;
pub(crate) mod preview_filter;
pub(crate) mod relearning;
pub(crate) mod rescheduling_filter;
pub(crate) mod review;
pub(crate) mod steps;
pub use filtered::FilteredState;
pub(crate) use interval_kind::IntervalKind;
pub use learning::LearnState;
pub use new::NewState;
pub use normal::NormalState;
pub use preview_filter::PreviewState;
use rand::{prelude::*, rngs::StdRng};
pub use relearning::RelearnState;
pub use rescheduling_filter::ReschedulingFilterState;
pub use review::ReviewState;
use self::steps::LearningSteps;
use crate::revlog::RevlogReviewKind;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CardState {
Normal(NormalState),
Filtered(FilteredState),
}
impl CardState {
pub(crate) fn interval_kind(self) -> IntervalKind {
match self {
CardState::Normal(normal) => normal.interval_kind(),
CardState::Filtered(filtered) => filtered.interval_kind(),
}
}
pub(crate) fn revlog_kind(self) -> Option<RevlogReviewKind> {
match self {
CardState::Normal(normal) => Some(normal.revlog_kind()),
CardState::Filtered(filtered) => filtered.revlog_kind(),
}
}
pub(crate) fn next_states(self, ctx: &StateContext) -> NextCardStates {
match self {
CardState::Normal(state) => state.next_states(ctx),
CardState::Filtered(state) => state.next_states(ctx),
}
}
/// Returns underlying review state, if it exists.
pub(crate) fn review_state(self) -> Option<ReviewState> {
match self {
CardState::Normal(state) => state.review_state(),
CardState::Filtered(state) => state.review_state(),
}
}
pub(crate) fn leeched(self) -> bool {
self.review_state().map(|r| r.leeched).unwrap_or_default()
}
}
/// Info required during state transitions.
pub(crate) struct StateContext<'a> {
pub fuzz_seed: Option<u64>,
// learning
pub steps: LearningSteps<'a>,
pub graduating_interval_good: u32,
pub graduating_interval_easy: u32,
// reviewing
pub hard_multiplier: f32,
pub easy_multiplier: f32,
pub interval_multiplier: f32,
pub maximum_review_interval: u32,
pub leech_threshold: u32,
// relearning
pub relearn_steps: LearningSteps<'a>,
pub lapse_multiplier: f32,
pub minimum_lapse_interval: u32,
// filtered
pub in_filtered_deck: bool,
pub preview_step: u32,
}
impl<'a> StateContext<'a> {
pub(crate) fn with_review_fuzz(&self, interval: f32) -> u32 {
// fixme: floor() is to match python
let interval = interval.floor();
if let Some(seed) = self.fuzz_seed {
let mut rng = StdRng::seed_from_u64(seed);
let (lower, upper) = if interval < 2.0 {
(1.0, 1.0)
} else if interval < 3.0 {
(2.0, 3.0)
} else if interval < 7.0 {
fuzz_range(interval, 0.25, 0.0)
} else if interval < 30.0 {
fuzz_range(interval, 0.15, 2.0)
} else {
fuzz_range(interval, 0.05, 4.0)
};
if lower >= upper {
lower
} else {
rng.gen_range(lower, upper)
}
} else {
interval
}
.round() as u32
}
pub(crate) fn fuzzed_graduating_interval_good(&self) -> u32 {
self.with_review_fuzz(self.graduating_interval_good as f32)
}
pub(crate) fn fuzzed_graduating_interval_easy(&self) -> u32 {
self.with_review_fuzz(self.graduating_interval_easy as f32)
}
}
fn fuzz_range(interval: f32, factor: f32, minimum: f32) -> (f32, f32) {
let delta = (interval * factor).max(minimum).max(1.0);
(interval - delta, interval + delta + 1.0)
}
#[derive(Debug, Clone)]
pub struct NextCardStates {
pub current: CardState,
pub again: CardState,
pub hard: CardState,
pub good: CardState,
pub easy: CardState,
}
impl From<NewState> for CardState {
fn from(state: NewState) -> Self {
CardState::Normal(state.into())
}
}
impl From<ReviewState> for CardState {
fn from(state: ReviewState) -> Self {
CardState::Normal(state.into())
}
}
impl From<LearnState> for CardState {
fn from(state: LearnState) -> Self {
CardState::Normal(state.into())
}
}
impl From<RelearnState> for CardState {
fn from(state: RelearnState) -> Self {
CardState::Normal(state.into())
}
}
impl From<NormalState> for CardState {
fn from(state: NormalState) -> Self {
CardState::Normal(state)
}
}
impl From<PreviewState> for CardState {
fn from(state: PreviewState) -> Self {
CardState::Filtered(FilteredState::Preview(state))
}
}
impl From<ReschedulingFilterState> for CardState {
fn from(state: ReschedulingFilterState) -> Self {
CardState::Filtered(FilteredState::Rescheduling(state))
}
}