summaryrefslogtreecommitdiff
path: root/core/src/commonTest/kotlin/coffee/liz/rookery/SrsTest.kt
blob: 5e762869bdfba3b16b3374f15a9763a00a08e43a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
    }
}