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 bldScramblesWithWideMovesAreHeldByTheirCentres() { // Official 3BLD scrambles end in random wide moves that move the centres. val rng = Random(11) val setup = BldSetup() val t = Tracer(setup) val faces = listOf("U", "R", "F", "D", "L", "B") val sfx = listOf("", "'", "2") val wide = listOf("", "Rw", "Rw'", "Rw2", "Uw", "Uw'", "Uw2", "Fw", "Fw'") repeat(200) { val scr = (List(20) { faces[rng.nextInt(6)] + sfx[rng.nextInt(3)] } + wide[rng.nextInt(wide.size)] + wide[rng.nextInt(wide.size)]) .filter { it.isNotEmpty() }.joinToString(" ") val s = setup.stateOf(scr) assertEquals(setup.solvedColors[4], s[4], "U centre after $scr") assertEquals(setup.solvedColors[22], s[22], "F centre after $scr") val after = t.execute(s, t.trace(s, PieceType.CORNER) + t.trace(s, PieceType.EDGE)) assertTrue(t.isSolved(after, PieceType.CORNER) && t.isSolved(after, PieceType.EDGE), scr) } } @Test fun firstMistakePinpointsWhereATraceGoesWrong() { val rng = Random(3) val setup = BldSetup() val t = Tracer(setup) val faces = listOf("U", "R", "F", "D", "L", "B"); val sfx = listOf("", "'", "2") repeat(100) { val s = setup.stateOf(List(22) { faces[rng.nextInt(6)] + sfx[rng.nextInt(3)] }.joinToString(" ")) for (type in PieceType.entries) { val good = t.trace(s, type) assertEquals(null, t.firstMistake(s, good, type)) if (good.size < 2) continue // stopping early is reported at the end assertEquals(good.size - 1, t.firstMistake(s, good.dropLast(1), type)?.index) // (if the buffer piece starts at home, the first target is a free choice: nothing to catch) val buf = setup.buffer(type) if (Cube3.pieceId(type, t.homes(s, type)[buf]) == Cube3.pieceId(type, buf)) continue // swapping the first target for a different piece is caught at index 0, with the right answer val other = good.first { Cube3.pieceId(type, it.pos) != Cube3.pieceId(type, good[0].pos) } val m = t.firstMistake(s, listOf(other) + good.drop(1), type)!! assertEquals(0, m.index) assertEquals(good[0].pos, m.expected) } } } @Test fun viewCornerShowsTheWholePiece() { // A is the U sticker of UBL: look from up-back-left. C (UFR): the default front view. assertEquals(Triple(-1, 1, -1), Cube3.viewCorner(Cube3.slotOf(PieceType.CORNER, 0))) assertEquals(Triple(1, 1, 1), Cube3.viewCorner(Cube3.slotOf(PieceType.CORNER, 2))) // UR edge leaves front/back to the current view assertEquals(Triple(1, 1, -1), Cube3.viewCorner(Cube3.slotOf(PieceType.EDGE, 1), Triple(-1, -1, -1))) } @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") } } } class BraveImagesTest { @Test fun parsesResultsAndEncodesQueries() { val body = """{"type":"images","results":[ {"title":"a penguin","source":"example.org","thumbnail":{"src":"https://imgs.search.brave.com/t1"},"properties":{"url":"https://example.org/p.jpg"}}, {"title":"no props","thumbnail":{"src":"https://imgs.search.brave.com/t2"}}, {"title":"insecure","thumbnail":{"src":"http://x/t3"}}]}""" val hits = coffee.liz.rookery.images.BraveImages.parse(body) assertEquals(2, hits.size) assertEquals("https://example.org/p.jpg", hits[0].full) assertEquals(hits[1].thumbnail, hits[1].full) assertTrue(coffee.liz.rookery.images.BraveImages.url("bernie sanders & co").contains("q=bernie%20sanders%20%26%20co")) } }