summaryrefslogtreecommitdiff
path: root/core/src/commonTest/kotlin/coffee/liz/rookery/BldTest.kt
blob: 4c399218b53a40ffb68a407fe5e3b518c1479b9f (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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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"))
    }
}