diff options
Diffstat (limited to 'core/src/commonTest/kotlin/coffee/liz')
| -rw-r--r-- | core/src/commonTest/kotlin/coffee/liz/rookery/BldTest.kt | 96 | ||||
| -rw-r--r-- | core/src/commonTest/kotlin/coffee/liz/rookery/SrsTest.kt | 73 |
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 + } +} |
