summaryrefslogtreecommitdiff
path: root/core/src/jvmTest/kotlin/coffee/liz/rookery/ScrambleTrackerTest.kt
blob: 896aaa4d42b7d103beec553756d59509e82534ef (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
74
75
76
77
78
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 })
    }
}