package coffee.liz.rookery.puzzle import kotlin.math.abs import kotlin.math.roundToInt /** * Bridges [TwistyPuzzle] cubes (what the renderer draws) and [NxnCube] slots (what BLD logic and * notation-level code use): `slotOf[i]` is the NxnCube slot of generic sticker i. */ class CubeMapping(val puzzle: TwistyPuzzle, val n: Int) { val slotOf: IntArray = IntArray(puzzle.stickers.size) { i -> slotFor(puzzle.stickers[i].centroid) } private val stickerOf: IntArray = IntArray(6 * n * n).also { inv -> slotOf.forEachIndexed { i, s -> inv[s] = i } } fun stickerOfSlot(slot: Int): Int = stickerOf[slot] /** Render an NxnCube colour array (indexed by slot) as a generic puzzle state. */ fun state(slotColors: IntArray): PuzzleState = PuzzleState(puzzle, IntArray(puzzle.stickers.size) { slotColors[slotOf[it]] }) // Inverse of NxnCube.slotPosition, on the unit cube ([-1, 1]^3). private fun slotFor(c: V3): Int { fun idx(v: Double) = ((v * n + (n - 1)) / 2).roundToInt().coerceIn(0, n - 1) val (face, row, col) = when { abs(c.y - 1) < 1e-6 -> Triple(0, idx(c.z), idx(c.x)) // U: a = x, b = z abs(c.x - 1) < 1e-6 -> Triple(1, idx(-c.y), idx(-c.z)) // R: (n, -b, -a) abs(c.z - 1) < 1e-6 -> Triple(2, idx(-c.y), idx(c.x)) // F: (a, -b, n) abs(c.y + 1) < 1e-6 -> Triple(3, idx(-c.z), idx(c.x)) // D: (a, -n, -b) abs(c.x + 1) < 1e-6 -> Triple(4, idx(-c.y), idx(c.z)) // L: (-n, -b, a) else -> Triple(5, idx(-c.y), idx(-c.x)) // B: (-a, -b, -n) } return face * n * n + row * n + col } }