1use chrono::{DateTime, Duration, Utc};
2
3use crate::{scheduler::Scheduler, Card, ImplScheduler, Parameters, Rating, SchedulingInfo};
4use crate::{Rating::*, State::*};
5pub struct BasicScheduler {
6 pub scheduler: Scheduler,
7}
8
9impl BasicScheduler {
10 pub fn new(parameters: Parameters, card: Card, now: DateTime<Utc>) -> Self {
11 Self {
12 scheduler: Scheduler::new(parameters, card, now),
13 }
14 }
15 fn new_state(&mut self, rating: Rating) -> SchedulingInfo {
16 if let Some(exist) = self.scheduler.next.get(&rating) {
17 return exist.clone();
18 }
19
20 let mut next = self.scheduler.current.clone();
21 next.difficulty = self.scheduler.parameters.init_difficulty(rating);
22 next.stability = self.scheduler.parameters.init_stability(rating);
23
24 match rating {
25 Again => {
26 next.scheduled_days = 0;
27 next.due = self.scheduler.now + Duration::minutes(1);
28 next.state = Learning;
29 }
30 Hard => {
31 next.scheduled_days = 0;
32 next.due = self.scheduler.now + Duration::minutes(5);
33 next.state = Learning;
34 }
35 Good => {
36 next.scheduled_days = 0;
37 next.due = self.scheduler.now + Duration::minutes(10);
38 next.state = Learning;
39 }
40 Easy => {
41 let easy_interval = self
42 .scheduler
43 .parameters
44 .next_interval(next.stability, next.elapsed_days);
45 next.scheduled_days = easy_interval as i64;
46 next.due = self.scheduler.now + Duration::days(easy_interval as i64);
47 next.state = Review;
48 }
49 };
50 let item = SchedulingInfo {
51 card: next,
52 review_log: self.scheduler.build_log(rating),
53 };
54
55 self.scheduler.next.insert(rating, item.clone());
56 item
57 }
58
59 fn learning_state(&mut self, rating: Rating) -> SchedulingInfo {
60 if let Some(exist) = self.scheduler.next.get(&rating) {
61 return exist.clone();
62 }
63
64 let mut next = self.scheduler.current.clone();
65 let interval = self.scheduler.current.elapsed_days;
66 next.difficulty = self
67 .scheduler
68 .parameters
69 .next_difficulty(self.scheduler.last.difficulty, rating);
70 next.stability = self
71 .scheduler
72 .parameters
73 .short_term_stability(self.scheduler.last.stability, rating);
74
75 match rating {
76 Again => {
77 next.scheduled_days = 0;
78 next.due = self.scheduler.now + Duration::minutes(5);
79 next.state = self.scheduler.last.state;
80 }
81 Hard => {
82 next.scheduled_days = 0;
83 next.due = self.scheduler.now + Duration::minutes(10);
84 next.state = self.scheduler.last.state;
85 }
86 Good => {
87 let good_interval = self
88 .scheduler
89 .parameters
90 .next_interval(next.stability, interval);
91 next.scheduled_days = good_interval as i64;
92 next.due = self.scheduler.now + Duration::days(good_interval as i64);
93 next.state = Review;
94 }
95 Easy => {
96 let good_stability = self
97 .scheduler
98 .parameters
99 .short_term_stability(self.scheduler.last.stability, Good);
100 let good_interval = self
101 .scheduler
102 .parameters
103 .next_interval(good_stability, interval);
104 let easy_interval = self
105 .scheduler
106 .parameters
107 .next_interval(next.stability, interval)
108 .max(good_interval + 1.0);
109 next.scheduled_days = easy_interval as i64;
110 next.due = self.scheduler.now + Duration::days(easy_interval as i64);
111 next.state = Review;
112 }
113 }
114 let item = SchedulingInfo {
115 card: next,
116 review_log: self.scheduler.build_log(rating),
117 };
118
119 self.scheduler.next.insert(rating, item.clone());
120 item
121 }
122
123 fn review_state(&mut self, rating: Rating) -> SchedulingInfo {
124 if let Some(exist) = self.scheduler.next.get(&rating) {
125 return exist.clone();
126 }
127
128 let next = self.scheduler.current.clone();
129 let interval = self.scheduler.current.elapsed_days;
130 let stability = self.scheduler.last.stability;
131 let difficulty = self.scheduler.last.difficulty;
132 let retrievability = self.scheduler.last.get_retrievability(self.scheduler.now);
133
134 let mut next_again = next.clone();
135 let mut next_hard = next.clone();
136 let mut next_good = next.clone();
137 let mut next_easy = next;
138
139 self.next_difficulty_stability(
140 &mut next_again,
141 &mut next_hard,
142 &mut next_good,
143 &mut next_easy,
144 difficulty,
145 stability,
146 retrievability,
147 );
148 self.next_interval(
149 &mut next_again,
150 &mut next_hard,
151 &mut next_good,
152 &mut next_easy,
153 interval,
154 );
155 self.next_state(
156 &mut next_again,
157 &mut next_hard,
158 &mut next_good,
159 &mut next_easy,
160 );
161 next_again.lapses += 1;
162
163 let item_again = SchedulingInfo {
164 card: next_again,
165 review_log: self.scheduler.build_log(Again),
166 };
167 let item_hard = SchedulingInfo {
168 card: next_hard,
169 review_log: self.scheduler.build_log(Hard),
170 };
171 let item_good = SchedulingInfo {
172 card: next_good,
173 review_log: self.scheduler.build_log(Good),
174 };
175 let item_easy = SchedulingInfo {
176 card: next_easy,
177 review_log: self.scheduler.build_log(Easy),
178 };
179
180 self.scheduler.next.insert(Again, item_again);
181 self.scheduler.next.insert(Hard, item_hard);
182 self.scheduler.next.insert(Good, item_good);
183 self.scheduler.next.insert(Easy, item_easy);
184
185 self.scheduler.next.get(&rating).unwrap().to_owned()
186 }
187
188 #[allow(clippy::too_many_arguments)]
189 fn next_difficulty_stability(
190 &self,
191 next_again: &mut Card,
192 next_hard: &mut Card,
193 next_good: &mut Card,
194 next_easy: &mut Card,
195 difficulty: f64,
196 stability: f64,
197 retrievability: f64,
198 ) {
199 next_again.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Again);
200 next_again.stability =
201 self.scheduler
202 .parameters
203 .next_forget_stability(difficulty, stability, retrievability);
204
205 next_hard.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Hard);
206 next_hard.stability = self.scheduler.parameters.next_recall_stability(
207 difficulty,
208 stability,
209 retrievability,
210 Hard,
211 );
212
213 next_good.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Good);
214 next_good.stability = self.scheduler.parameters.next_recall_stability(
215 difficulty,
216 stability,
217 retrievability,
218 Good,
219 );
220
221 next_easy.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Easy);
222 next_easy.stability = self.scheduler.parameters.next_recall_stability(
223 difficulty,
224 stability,
225 retrievability,
226 Easy,
227 );
228 }
229
230 fn next_interval(
231 &self,
232 next_again: &mut Card,
233 next_hard: &mut Card,
234 next_good: &mut Card,
235 next_easy: &mut Card,
236 elapsed_days: i64,
237 ) {
238 let mut hard_interval = self
239 .scheduler
240 .parameters
241 .next_interval(next_hard.stability, elapsed_days);
242 let mut good_interval = self
243 .scheduler
244 .parameters
245 .next_interval(next_good.stability, elapsed_days);
246 hard_interval = hard_interval.min(good_interval);
247 good_interval = good_interval.max(hard_interval + 1.0);
248 let easy_interval = self
249 .scheduler
250 .parameters
251 .next_interval(next_easy.stability, elapsed_days)
252 .max(good_interval + 1.0);
253
254 next_again.scheduled_days = 0;
255 next_again.due = self.scheduler.now + Duration::minutes(5);
256
257 next_hard.scheduled_days = hard_interval as i64;
258 next_hard.due = self.scheduler.now + Duration::days(hard_interval as i64);
259
260 next_good.scheduled_days = good_interval as i64;
261 next_good.due = self.scheduler.now + Duration::days(good_interval as i64);
262
263 next_easy.scheduled_days = easy_interval as i64;
264 next_easy.due = self.scheduler.now + Duration::days(easy_interval as i64);
265 }
266
267 fn next_state(
268 &self,
269 next_again: &mut Card,
270 next_hard: &mut Card,
271 next_good: &mut Card,
272 next_easy: &mut Card,
273 ) {
274 next_again.state = Relearning;
275 next_hard.state = Review;
276 next_good.state = Review;
277 next_easy.state = Review;
278 }
279}
280
281impl ImplScheduler for BasicScheduler {
282 fn review(&mut self, rating: Rating) -> SchedulingInfo {
283 match self.scheduler.last.state {
284 New => self.new_state(rating),
285 Learning | Relearning => self.learning_state(rating),
286 Review => self.review_state(rating),
287 }
288 }
289}