package coffee.liz.rookery import coffee.liz.rookery.puzzle.Cube3x3 import coffee.liz.rookery.puzzle.NxnCube import coffee.liz.rookery.puzzle.ScrambleTracker import coffee.liz.rookery.scramble.TnoodleScrambler import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class ScrambleTrackerTest { private val solver = TnoodleScrambler() private fun tracker() = ScrambleTracker(solver::solve3x3) private val scramble = "R U2 F' L D B2 R' U L2 F" @Test fun followingTheScrambleWalksThePlanDown() { val t = tracker() t.reset(scramble, Cube3x3.SOLVED) assertEquals(scramble.split(" "), t.plan) t.move("R"); assertEquals("U2", t.plan[0]) assertEquals(scramble.split(" "), t.sequence); assertEquals(setOf(0), t.done); assertEquals(1, t.current) t.move("U"); assertEquals("U", t.plan[0]) // half of a double turn assertEquals(1, t.current) // still on the same move of the sequence t.move("U"); assertEquals("F'", t.plan[0]) t.move("F"); assertEquals("F2", t.plan[0]) // wrong way round: two more fixes it, no solver needed assertFalse(t.corrected) t.move("F2") for (m in listOf("L", "D", "B2", "R'", "U", "L2", "F")) t.move(m) assertTrue(t.scrambled) assertEquals(scramble.split(" "), t.sequence); assertEquals(t.sequence.indices.toSet(), t.done) } @Test fun oppositeFacesCanBeDoneInEitherOrder() { val t = tracker() t.reset("R L U", Cube3x3.SOLVED) t.move("L"); assertEquals(listOf("R", "U"), t.plan) assertFalse(t.corrected) } @Test fun strayMoveGetsAShortFixThatStaysPutAndReachesTheScramble() { val t = tracker() t.reset(scramble, Cube3x3.SOLVED) t.move("R"); t.move("U2"); t.move("D") // D is not next assertTrue(t.corrected) val fix = t.plan assertTrue(fix.size <= 21, "fix is ${fix.size} moves") // following the fix: the rest of the plan is exactly its tail each time (cached, not re-solved) val cube = NxnCube(3).apply("R U2 D") for ((i, m) in fix.withIndex()) { t.move(m); cube.apply(m); assertEquals(fix.drop(i + 1), t.plan) } assertTrue(t.scrambled) assertEquals(Cube3x3.facelets(NxnCube(3).apply(scramble)), Cube3x3.facelets(cube)) } @Test fun startsFromWhateverStateTheCubeIsIn() { val t = tracker() t.reset(scramble, Cube3x3.facelets(NxnCube(3).apply("F R U"))) assertTrue(t.known && t.corrected) val cube = NxnCube(3).apply("F R U") for (m in t.plan.toList()) { t.move(m); cube.apply(m) } assertTrue(t.scrambled) assertEquals(Cube3x3.facelets(NxnCube(3).apply(scramble)), Cube3x3.facelets(cube)) // then solving it is noticed for (m in solver.solve3x3(Cube3x3.facelets(cube))!!.split(" ")) t.move(m) assertTrue(t.solved) } @Test fun wideMovesBecomeFaceTurnsReachingTheSameCubeUpToHowItIsHeld() { val bld = "R U F2 Rw Uw'" val faces = Cube3x3.faceTurns(bld) assertEquals(listOf("R", "U", "F2", "L"), faces.take(4)) // same cube: rotating the wide-move result back onto its centres gives the face-turn result val wide = NxnCube(3).apply(bld) val plain = Cube3x3.facelets(NxnCube(3).apply(faces.joinToString(" "))) val rotations = listOf("", "x", "x2", "x'", "z", "z'").flatMap { a -> listOf("", "y", "y2", "y'").map { "$a $it".trim() } } assertTrue(rotations.any { Cube3x3.facelets(wide.copy().apply(it)) == plain }) } }