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"))
}
}
|