summaryrefslogtreecommitdiff
path: root/core/src/commonTest/kotlin
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2026-11-13 15:28:48 -0800
committerElizabeth Alexander Hunt <me@liz.coffee>2026-11-13 15:37:09 -0800
commit009525bd46edfc77068da212e36f38d410503f2c (patch)
treef042565bb01d070f626213b72865800ec0dbf576 /core/src/commonTest/kotlin
parent60426ce45763d0a4a5572db6926e8e677cbc0dc0 (diff)
downloadrookery-009525bd46edfc77068da212e36f38d410503f2c.tar.gz
rookery-009525bd46edfc77068da212e36f38d410503f2c.zip
Smart-cube timing, macOS Bluetooth, Android app, stackmat, pairing UI
- Timing with a smart cube: the scramble stays put and is marked off move by move (ScrambleTracker); a stray turn gets the shortest path to the same scrambled state from the two-phase solver, kept until the next stray turn. Wide moves in BLD scrambles become face turns. Once scrambled, inspection (or blind memo) starts by itself after a configurable delay; the first turn starts the clock and the solved cube stops it. The cube's own state reports are authoritative, so a move lost over Bluetooth can't keep the timer running. Blind solves show a MEMO/EXEC annunciator and store the memo split. A live view of the cube sits beside the timer. - Bluetooth through Kable on macOS and Windows (Linux keeps BlueZ) and on Android; the cube's MAC is read from its advertisement where the platform hides it. Scan/pair/connect/forget UI in settings. - Android app (:android): core and ui gain Android targets, with a jvmShared source set for code desktop and Android share. Android host for storage, sound, file import, images, Bluetooth and audio. Phone layouts: stacked panes and a full-screen timer with touch start. Scramble drawings (SVG) are desktop-only for now. - Stackmat: audio decoder and session; the mat can drive the timer. - Timer readout always shows the tens-of-seconds digit. - UI copy reduced to plain labels; marquee, snow, pointer-following and penguin speech removed. Brave key can be entered in settings. - Phone timer: one-line header, session picker and averages on one line, solves/stats in the footer; sessions can be renamed and deleted and are named <event>-<date>. Display mode fills the screen (cut-out included), fits the scramble on one line, shows the live cube, and the screen stays awake. - Readout: digits keep one size (inspection included), whole seconds while running unless set otherwise; previous scramble shown; the scramble no longer blanks while the next is generated. - Fixes: stackmat and smart cube no longer both drive the timer; a solved cube or a sub-quarter-second tap can't record a solve. - Blind memo indicator is an LCD penguin pacing the glass. - Android: system-bar insets, folder picker (all files access), Brave key synced with the data. - Algorithm trainer: PLL and OLL from J Perm's sheets, each algorithm verified by simulation; case plans, your choice of algorithm (or your own, checked against the case), new/learning/known, and a drill weighted towards weak cases. - Pair cards show the pair on a 3D see-through cube with an arrow; the memo cube can be dragged between targets. - CI image installs the Android SDK and runs core tests + desktop packaging (untested). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Diffstat (limited to 'core/src/commonTest/kotlin')
-rw-r--r--core/src/commonTest/kotlin/coffee/liz/rookery/AlgSetsTest.kt37
1 files changed, 37 insertions, 0 deletions
diff --git a/core/src/commonTest/kotlin/coffee/liz/rookery/AlgSetsTest.kt b/core/src/commonTest/kotlin/coffee/liz/rookery/AlgSetsTest.kt
new file mode 100644
index 0000000..eadef06
--- /dev/null
+++ b/core/src/commonTest/kotlin/coffee/liz/rookery/AlgSetsTest.kt
@@ -0,0 +1,37 @@
+package coffee.liz.rookery
+
+import coffee.liz.rookery.algs.AlgCase
+import coffee.liz.rookery.algs.AlgSets
+import coffee.liz.rookery.puzzle.NxnCube
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class AlgSetsTest {
+ private fun f2lSolved(c: NxnCube) = (1..5).all { f -> f == 3 || (3 until 9).all { c.stickers[f * 9 + it] == f } } && (0 until 9).all { c.stickers[3 * 9 + it] == 3 }
+ private fun topOriented(c: NxnCube) = (0 until 9).all { c.stickers[it] == 0 }
+
+ @Test fun setsAreComplete() {
+ assertEquals(21, AlgSets.cases("pll").size)
+ assertEquals(57, AlgSets.cases("oll").size)
+ }
+
+ @Test fun everyAlgorithmSolvesItsOwnCase() {
+ for (set in AlgSets.names) for (case in AlgSets.cases(set)) for (alg in case.algs) {
+ val state = case.state(alg)
+ assertTrue(f2lSolved(state), "${case.id}: '$alg' disturbs the first two layers")
+ if (set == "pll") assertTrue(topOriented(state), "${case.id}: '$alg' is not a pure permutation")
+ if (set == "oll") assertTrue(!topOriented(state), "${case.id}: '$alg' does nothing to orientation")
+ // doing the algorithm on its own setup returns a solved cube (possibly held differently)
+ val done = NxnCube(3).apply(case.setup(alg)).apply(AlgCase.clean(alg))
+ assertTrue((0 until 6).all { f -> (0 until 9).all { done.stickers[f * 9 + it] == done.stickers[f * 9 + 4] } }, "${case.id}: setup then '$alg' is not solved")
+ }
+ }
+
+ @Test fun planPutsFrontAtTheBottom() {
+ val p = AlgSets.plan(NxnCube(3))
+ assertEquals(listOf(-1, 5, 5, 5, -1), p[0].toList())
+ assertEquals(listOf(4, 0, 0, 0, 1), p[2].toList())
+ assertEquals(listOf(-1, 2, 2, 2, -1), p[4].toList())
+ }
+}