package coffee.liz.rookery import coffee.liz.rookery.srs.DrillPicker import coffee.liz.rookery.srs.Fsrs import coffee.liz.rookery.srs.Grade import coffee.liz.rookery.srs.Memory import coffee.liz.rookery.srs.Review import coffee.liz.rookery.srs.gradeAnswer import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class SrsTest { private val day = Memory.DAY_MS.toLong() @Test fun goodReviewsGrowStabilityAndMissesShrinkIt() { var m = Fsrs.first(Review("AB", 0, Grade.GOOD)) val s0 = m.stability m = Fsrs.next(m, Review("AB", 3 * day, Grade.GOOD)) assertTrue(m.stability > s0) val before = m.stability m = Fsrs.next(m, Review("AB", 10 * day, Grade.AGAIN)) assertTrue(m.stability < before) assertEquals(1, m.lapses) assertEquals(3, m.reps) } @Test fun difficultyTracksGrades() { val easy = Fsrs.replay(List(5) { Review("x", it * day, Grade.EASY) })!! val hard = Fsrs.replay(List(5) { Review("x", it * day, Grade.AGAIN) })!! assertTrue(easy.difficulty < hard.difficulty) assertTrue(hard.difficulty <= 10.0 && easy.difficulty >= 1.0) } @Test fun replayIsOrderIndependent() { val rs = listOf(Review("x", 5 * day, Grade.HARD), Review("x", 0, Grade.GOOD), Review("x", 2 * day, Grade.AGAIN)) assertEquals(Fsrs.replay(rs), Fsrs.replay(rs.reversed())) } @Test fun retrievabilityIsNinetyPercentAfterStabilityDays() { val m = Memory(stability = 4.0, difficulty = 5.0, lastReview = 0, reps = 1, lapses = 0) assertEquals(0.9, m.retrievability(4 * day), 1e-9) assertEquals(1.0, m.retrievability(0), 1e-9) } @Test fun answerGrading() { assertEquals(Grade.AGAIN, gradeAnswer(false, 100)) assertEquals(Grade.EASY, gradeAnswer(true, 1_000)) assertEquals(Grade.GOOD, gradeAnswer(true, 2_000)) assertEquals(Grade.HARD, gradeAnswer(true, 6_000)) } @Test fun pickerFavoursWeakAndMissedItems() { val now = 30 * day val strong = Memory(100.0, 2.0, now - day, 10, 0) val weak = Memory(1.0, 9.0, now - 10 * day, 10, 6) val items = listOf("strong", "weak") val picker = DrillPicker(Random(1)) val counts = items.associateWith { 0 }.toMutableMap() repeat(2_000) { val p = picker.pick(items, { if (it == "strong") strong else weak }, { if (it == "weak") 2 else 0 }, emptyList(), now)!! counts[p] = counts.getValue(p) + 1 } assertTrue(counts.getValue("weak") > 8 * counts.getValue("strong"), "$counts") } @Test fun pickerAvoidsRecentItemsWhenItCan() { val picker = DrillPicker(Random(2)) repeat(50) { assertEquals("b", picker.pick(listOf("a", "b"), { null }, { 0 }, listOf("a"), 0)) } assertEquals("a", picker.pick(listOf("a"), { null }, { 0 }, listOf("a"), 0)) // nothing else to pick } }