From 009525bd46edfc77068da212e36f38d410503f2c Mon Sep 17 00:00:00 2001 From: Elizabeth Alexander Hunt Date: Fri, 13 Nov 2026 15:28:48 -0800 Subject: 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 -. 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 --- .../kotlin/coffee/liz/rookery/AlgSetsTest.kt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 core/src/commonTest/kotlin/coffee/liz/rookery/AlgSetsTest.kt (limited to 'core/src/commonTest/kotlin/coffee/liz') 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()) + } +} -- cgit v1.3