summaryrefslogtreecommitdiff
path: root/core/src/commonTest/kotlin/coffee/liz/rookery
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/commonTest/kotlin/coffee/liz/rookery')
-rw-r--r--core/src/commonTest/kotlin/coffee/liz/rookery/BldTest.kt96
-rw-r--r--core/src/commonTest/kotlin/coffee/liz/rookery/SrsTest.kt73
2 files changed, 169 insertions, 0 deletions
diff --git a/core/src/commonTest/kotlin/coffee/liz/rookery/BldTest.kt b/core/src/commonTest/kotlin/coffee/liz/rookery/BldTest.kt
new file mode 100644
index 0000000..cae88a1
--- /dev/null
+++ b/core/src/commonTest/kotlin/coffee/liz/rookery/BldTest.kt
@@ -0,0 +1,96 @@
+package coffee.liz.rookery
+
+import coffee.liz.rookery.bld.BldSetup
+import coffee.liz.rookery.bld.Cube3
+import coffee.liz.rookery.bld.PieceType
+import coffee.liz.rookery.bld.Tracer
+import coffee.liz.rookery.puzzle.NxnCube
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class BldTest {
+ private val wca = BldSetup(rotation = "")
+
+ @Test fun speffzPositionsAreTheRightStickers() {
+ assertEquals("UBL", Cube3.pieceName(PieceType.CORNER, 0)) // A
+ assertEquals("RDF", Cube3.stickerName(PieceType.CORNER, 15)) // P, the OP helper target
+ assertEquals("UFR", Cube3.pieceName(PieceType.CORNER, 2)) // C
+ assertEquals("UR", Cube3.pieceName(PieceType.EDGE, 1)) // B, edge buffer
+ assertEquals("UL", Cube3.pieceName(PieceType.EDGE, 3)) // D, T-perm target
+ assertEquals("UB", Cube3.pieceName(PieceType.EDGE, 0)) // A
+ assertEquals("UF", Cube3.pieceName(PieceType.EDGE, 2)) // C
+ // every piece has 3 (corner) / 2 (edge) stickers, 8 / 12 pieces
+ assertEquals(8, (0 until 24).map { Cube3.pieceId(PieceType.CORNER, it) }.distinct().size)
+ assertEquals(12, (0 until 24).map { Cube3.pieceId(PieceType.EDGE, it) }.distinct().size)
+ // UBL clockwise from U: U, L, B -> A, E, R in Speffz (the classic "AER" buffer)
+ assertEquals(listOf(0, 4, 17), Cube3.pieceFrom(PieceType.CORNER, 0).toList())
+ // UFR clockwise from U: U, R, F -> C, M, J
+ assertEquals(listOf(2, 12, 9), Cube3.pieceFrom(PieceType.CORNER, 2).toList())
+ }
+
+ @Test fun solvedCubeHasNoTargets() {
+ val t = Tracer(wca)
+ val s = wca.stateOf("")
+ assertTrue(t.trace(s, PieceType.CORNER).isEmpty())
+ assertTrue(t.trace(s, PieceType.EDGE).isEmpty())
+ }
+
+ @Test fun tPermIsOneEdgeTargetD() {
+ val t = Tracer(wca)
+ val s = wca.stateOf("R U R' U' R' F R2 U' R' U' R U R' F'")
+ assertEquals("D", t.letters(t.trace(s, PieceType.EDGE)))
+ }
+
+ @Test fun tracedMemoAlwaysSolvesWhenExecuted() {
+ val rng = Random(7)
+ val faces = listOf("U", "R", "F", "D", "L", "B")
+ val sfx = listOf("", "'", "2")
+ for (setup in listOf(wca, BldSetup())) { // WCA frame and the user's yellow-top/orange-front frame
+ val t = Tracer(setup)
+ repeat(200) {
+ val scr = List(22) { faces[rng.nextInt(6)] + sfx[rng.nextInt(3)] }.joinToString(" ")
+ val s = setup.stateOf(scr)
+ val corners = t.trace(s, PieceType.CORNER)
+ val edges = t.trace(s, PieceType.EDGE)
+ val after = t.execute(s, corners + edges)
+ assertTrue(t.isSolved(after, PieceType.CORNER), "corners after $scr: ${t.wrongPieces(after, PieceType.CORNER)}")
+ assertTrue(t.isSolved(after, PieceType.EDGE), "edges after $scr: ${t.wrongPieces(after, PieceType.EDGE)}")
+ // corner and edge target counts share parity on a legal cube
+ assertEquals(corners.size % 2, edges.size % 2, scr)
+ }
+ }
+ }
+
+ @Test fun wrongMemoReportsThePieces() {
+ val t = Tracer(wca)
+ val s = wca.stateOf("R U R' U' R' F R2 U' R' U' R U R' F'")
+ val (targets, errors) = t.parse("C", PieceType.EDGE) // should be D
+ assertTrue(errors.isEmpty())
+ val after = t.execute(s, targets)
+ assertEquals(setOf("UF", "UL", "UR"), t.wrongPieces(after, PieceType.EDGE).toSet())
+ assertEquals(listOf("'B' is on the buffer piece"), t.parse("B", PieceType.EDGE).second)
+ }
+
+ @Test fun userFrameHasYellowOnTopAndOrangeInFront() {
+ val solved = BldSetup().solvedColors
+ assertEquals(NxnCube.Face.D.ordinal, solved[4]) // U centre shows the yellow (WCA D) colour
+ assertEquals(NxnCube.Face.L.ordinal, solved[2 * 9 + 4]) // F centre shows orange (WCA L)
+ assertEquals(NxnCube.Face.B.ordinal, solved[1 * 9 + 4]) // R centre shows blue
+ }
+}
+
+class CubeMappingTest {
+ @Test fun genericStickersMapOntoNxnSlotsOneToOne() {
+ for (n in 2..5) {
+ val m = coffee.liz.rookery.puzzle.CubeMapping(coffee.liz.rookery.puzzle.Puzzles.cube(n), n)
+ assertEquals((0 until 6 * n * n).toSet(), m.slotOf.toSet())
+ // same scramble through both models gives the same picture
+ val alg = "R U2 F' L D B2"
+ val generic = coffee.liz.rookery.puzzle.Puzzles.cube(n).solved().apply(alg)
+ val viaNxn = m.state(NxnCube(n).apply(alg).stickers)
+ assertTrue(generic.colors.contentEquals(viaNxn.colors), "${n}x$n")
+ }
+ }
+}
diff --git a/core/src/commonTest/kotlin/coffee/liz/rookery/SrsTest.kt b/core/src/commonTest/kotlin/coffee/liz/rookery/SrsTest.kt
new file mode 100644
index 0000000..5e76286
--- /dev/null
+++ b/core/src/commonTest/kotlin/coffee/liz/rookery/SrsTest.kt
@@ -0,0 +1,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
+ }
+}