diff --git a/src/main/kotlin/be/nielandt/Counter.kt b/src/main/kotlin/be/nielandt/Counter.kt index 551b30c..edc25d0 100644 --- a/src/main/kotlin/be/nielandt/Counter.kt +++ b/src/main/kotlin/be/nielandt/Counter.kt @@ -1,16 +1,14 @@ package be.nielandt -import kotlin.math.min - /** * Counter for X digits of a given base. */ -open class Counter(size: Int, val base: Int = 10) { +open class Counter(size: Int, val base: Int = 18) { /** * Empty counter, all 0 values for each digit. */ - protected var counter: Array = Array(size) { 0 } + var counter: Array = Array(size) { 0 } /** * The last (highest significance) index that overflowed and has been changed in the counter. Could be null, if it never overflowed. @@ -40,33 +38,6 @@ open class Counter(size: Int, val base: Int = 10) { return true } - fun increaseAndSkipInvalid(): Boolean { - var lmi = lastModifiedIndex - var last = increase() - lmi = min(lastModifiedIndex, lmi) - // are we having an invalid situation? this would be two consecutive moves on the same face - while (containsConsecutiveSameFaceMoves() && !atMax()) { - last = increase() - lmi = min(lastModifiedIndex, lmi) - } - // we have to set the lastmodified index to the lowest point that it got to... otherwise we might be skipping some cases - this.lastModifiedIndex = lmi - return last - } - - /** - * Are there two moves in the current counter / chain that act on the same face? This would be F+F2 for example. - */ - private fun containsConsecutiveSameFaceMoves(): Boolean { - for (i in 1 until this.counter.size) { - val current = Move.values()[this.counter[i]] - val previous = Move.values()[this.counter[i - 1]] - if (current sameFace previous) - return true - } - return false - } - /** * How many digits does this counter contain? */ diff --git a/src/main/kotlin/be/nielandt/CounterSkipSameFaces.kt b/src/main/kotlin/be/nielandt/CounterSkip.kt similarity index 94% rename from src/main/kotlin/be/nielandt/CounterSkipSameFaces.kt rename to src/main/kotlin/be/nielandt/CounterSkip.kt index 806d612..31eed33 100644 --- a/src/main/kotlin/be/nielandt/CounterSkipSameFaces.kt +++ b/src/main/kotlin/be/nielandt/CounterSkip.kt @@ -5,7 +5,7 @@ import kotlin.math.min /** * Counter for X digits of a given base. Skips situations where faces are the same. */ -class CounterSkipSameFaces(size: Int, base: Int = 10): Counter(size, base) { +class CounterSkip(size: Int): Counter(size, 18) { /** * Increase the counter. diff --git a/src/main/kotlin/be/nielandt/CrossSolver.kt b/src/main/kotlin/be/nielandt/CrossSolver.kt index 3277445..fbf70da 100644 --- a/src/main/kotlin/be/nielandt/CrossSolver.kt +++ b/src/main/kotlin/be/nielandt/CrossSolver.kt @@ -7,9 +7,9 @@ open abstract class CrossSolver { /** * Solve all crosses, look for a minimal set of moves for each color's cross. */ - abstract fun solveCrosses(edgeModel: EdgeModel): Map> + abstract fun solveCrosses(edgeModel: EdgeModel): Map> - fun solveCrossesTimed(edgeModel: EdgeModel): Map> { + fun solveCrossesTimed(edgeModel: EdgeModel): Map> { val now = Instant.now() val solveCrosses = solveCrosses(edgeModel) val between = Duration.between(now, Instant.now()) @@ -18,10 +18,10 @@ open abstract class CrossSolver { } companion object { - fun printResults(results: Map>) { + fun printResults(results: Map>) { println("results: ") results.forEach { color, moveList -> - println("> color ${colorLetter(color)}, moves (${moveList.size}) $moveList") + println("> color ${colorLetter(color)}, moves (${moveList.size}) ${moveList.map { it -> decodeMove(it) }}") } } } @@ -62,23 +62,26 @@ fun main(args: Array) { // do a fixed scramble for testing purposes - val fixedMoves = Move.parse("L L_ R R2") + // noskip Move.enum 30s, skip Move.enum 32s + val fixedMoves = parseMoves("L_, D, U, L2, F, D, B, D, U2, D, B_, F2, D2, U_, R, D2, R_, L, B_, R") println("fixedMoves = ${fixedMoves}") - - // scramble random - val moves = Move.random(10) - println("Scramble: $moves") - val scrambledModel = EdgeModel(moves) - println(scrambledModel) + val randomMoves = randomMoves(20) + + val moves = fixedMoves + println("Scramble: ${moves.map { decodeMove(it) }}") + + + val usedModel = EdgeModel(moves) + println(usedModel) // val baseSolve = CrossSolverBase().solveCrossesTimed(scrambledModel) // CrossSolver.printResults(baseSolve) - val upgradedSolve = CrossSolverUpgraded().solveCrossesTimed(scrambledModel) + val upgradedSolve = CrossSolverUpgraded().solveCrossesTimed(usedModel) CrossSolver.printResults(upgradedSolve) - val upgradedSolveSkip = CrossSolverUpgradedSkip().solveCrossesTimed(scrambledModel) + val upgradedSolveSkip = CrossSolverUpgradedSkip().solveCrossesTimed(usedModel) CrossSolver.printResults(upgradedSolveSkip) // val allCrossMoveCountUpgradedSkip = allCrossMoveCountUpgradedSkip(scrambledModel) diff --git a/src/main/kotlin/be/nielandt/CrossSolverBase.kt b/src/main/kotlin/be/nielandt/CrossSolverBase.kt index e9ca2e5..c180608 100644 --- a/src/main/kotlin/be/nielandt/CrossSolverBase.kt +++ b/src/main/kotlin/be/nielandt/CrossSolverBase.kt @@ -4,26 +4,24 @@ class CrossSolverBase : CrossSolver() { /** * Solve the minimal cross for all colors. */ - override fun solveCrosses(edgeModel: EdgeModel): Map> { - val moveCounts = mutableMapOf>() + override fun solveCrosses(edgeModel: EdgeModel): Map> { + val moveCounts = mutableMapOf>() for (moveCount in 1..8) { // build a counter of moveCount big println("allCrossMoveCount basic doing $moveCount") - val counter = Counter(moveCount, Move.values().size) + val counter = Counter(moveCount, 18) // count up, each state of the counter corresponds to a combination of moves do { - // what is the move combination we're looking at? - val moves = Move.combo(counter) // execute the moves - val afterMoves = edgeModel.doMoves(moves) + val afterMoves = edgeModel.doMoves(counter.counter) // check crosses that have not been found yet (0..5).forEach { color -> if (!moveCounts.containsKey(color)) { val crossSolved = afterMoves.crossSolved(color) if (crossSolved) { - moveCounts[color] = moves + moveCounts[color] = counter.counter.toList() } } } diff --git a/src/main/kotlin/be/nielandt/CrossSolverUpgraded.kt b/src/main/kotlin/be/nielandt/CrossSolverUpgraded.kt index 042f0ab..6a5a738 100644 --- a/src/main/kotlin/be/nielandt/CrossSolverUpgraded.kt +++ b/src/main/kotlin/be/nielandt/CrossSolverUpgraded.kt @@ -5,12 +5,12 @@ package be.nielandt */ class CrossSolverUpgraded : CrossSolver() { - override fun solveCrosses(edgeModel: EdgeModel): Map> { - val moveCounts = mutableMapOf>() + override fun solveCrosses(edgeModel: EdgeModel): Map> { + val moveCounts = mutableMapOf>() for (moveCount in 1..8) { println("all cross move count upgrade doing $moveCount") // build a counter of moveCount big - val counter = Counter(moveCount, Move.values().size) + val counter = Counter(moveCount, 18) val edgeModelFactory = EdgeModelFactory(edgeModel, counter) while (edgeModelFactory.hasNext()) { @@ -23,8 +23,7 @@ class CrossSolverUpgraded : CrossSolver() { val crossSolved = next.crossSolved(color) if (crossSolved) { // what is the move combination we're looking at? - val moves = Move.combo(counter) - moveCounts[color] = moves + moveCounts[color] = counter.counter.toList() } } } diff --git a/src/main/kotlin/be/nielandt/CrossSolverUpgradedSkip.kt b/src/main/kotlin/be/nielandt/CrossSolverUpgradedSkip.kt index af9a634..46faf7b 100644 --- a/src/main/kotlin/be/nielandt/CrossSolverUpgradedSkip.kt +++ b/src/main/kotlin/be/nielandt/CrossSolverUpgradedSkip.kt @@ -5,12 +5,12 @@ package be.nielandt */ class CrossSolverUpgradedSkip : CrossSolver() { - override fun solveCrosses(edgeModel: EdgeModel): Map> { - val moveCounts = mutableMapOf>() + override fun solveCrosses(edgeModel: EdgeModel): Map> { + val moveCounts = mutableMapOf>() for (moveCount in 1..8) { println("all cross move count upgrade doing $moveCount") // build a counter of moveCount big - val counter = CounterSkipSameFaces(moveCount, Move.values().size) + val counter = CounterSkip(moveCount) val edgeModelFactory = EdgeModelFactory(edgeModel, counter) while (edgeModelFactory.hasNext()) { @@ -23,8 +23,7 @@ class CrossSolverUpgradedSkip : CrossSolver() { val crossSolved = next.crossSolved(color) if (crossSolved) { // what is the move combination we're looking at? - val moves = Move.combo(counter) - moveCounts[color] = moves + moveCounts[color] = counter.counter.toList() } } } diff --git a/src/main/kotlin/be/nielandt/EdgeModel.kt b/src/main/kotlin/be/nielandt/EdgeModel.kt index a1825d8..3120441 100644 --- a/src/main/kotlin/be/nielandt/EdgeModel.kt +++ b/src/main/kotlin/be/nielandt/EdgeModel.kt @@ -9,7 +9,7 @@ package be.nielandt - | 18 | ------------------------------------- | 12 | 00 | 04 | 08 | -|15 O 13|03 G 01|07 R 05|11 B 09| +|15 O 13|03 G 01|07 R_indices 05|11 B_indices 09| | 14 | 02 | 06 | 10 | ------------------------------------- - | 20 | @@ -28,16 +28,16 @@ class EdgeModel { val model: Array - private val F = intArrayOf(13, 18, 7, 20, 3, 0, 1, 2) - private val B = intArrayOf(8, 9, 10, 11, 16, 15, 22, 5) - private val L = intArrayOf(3, 23, 9, 19, 12, 13, 14, 15) - private val U = intArrayOf(16, 17, 18, 19, 0, 12, 8, 4) - private val D = intArrayOf(2, 6, 10, 14, 20, 21, 22, 23) - private val R = intArrayOf(4, 5, 6, 7, 1, 17, 11, 21) + private val F_indices = intArrayOf(13, 18, 7, 20, 3, 0, 1, 2) + private val B_indices = intArrayOf(8, 9, 10, 11, 16, 15, 22, 5) + private val L_indices = intArrayOf(3, 23, 9, 19, 12, 13, 14, 15) + private val U_indices = intArrayOf(16, 17, 18, 19, 0, 12, 8, 4) + private val D_indices = intArrayOf(2, 6, 10, 14, 20, 21, 22, 23) + private val R_indices = intArrayOf(4, 5, 6, 7, 1, 17, 11, 21) constructor() { // do a sanity check - val entries = mutableListOf(F, B, L, U, D, R).flatMap { it.asList() }.groupBy { it }.entries + val entries = mutableListOf(F_indices, B_indices, L_indices, U_indices, D_indices, R_indices).flatMap { it.asList() }.groupBy { it }.entries // println("entries = ${entries}") if (entries.any { it.value.size != 2 @@ -57,7 +57,8 @@ class EdgeModel { constructor(randomMoves: Int) { val edgeModel = EdgeModel() - val doMoves = edgeModel.doMoves(Move.random(randomMoves)) + val r: Array = randomMoves(randomMoves) + val doMoves = edgeModel.doMoves(r) this.model = doMoves.model } @@ -65,7 +66,7 @@ class EdgeModel { this.model = model } - constructor(moves: List) { + constructor(moves: List) { val edgeModel = EdgeModel() val newModel = edgeModel.doMoves(moves) this.model = newModel.model @@ -74,7 +75,7 @@ class EdgeModel { /** * Do a single move and calculate the resulting edge model. */ - fun doMove(move: Move): EdgeModel { + fun doMove(move: Int): EdgeModel { val copyOf = this.model.copyOf() // execute the move @@ -126,24 +127,24 @@ class EdgeModel { } when (move) { - Move.F -> nonPrime(F) - Move.F_ -> prime(F) - Move.F2 -> double(F) - Move.B -> nonPrime(B) - Move.B_ -> prime(B) - Move.B2 -> double(B) - Move.L -> nonPrime(L) - Move.L_ -> prime(L) - Move.L2 -> double(L) - Move.R -> nonPrime(R) - Move.R_ -> prime(R) - Move.R2 -> double(R) - Move.U -> nonPrime(U) - Move.U_ -> prime(U) - Move.U2 -> double(U) - Move.D -> nonPrime(D) - Move.D_ -> prime(D) - Move.D2 -> double(D) + F -> nonPrime(F_indices) + F_ -> prime(F_indices) + F2 -> double(F_indices) + B -> nonPrime(B_indices) + B_ -> prime(B_indices) + B2 -> double(B_indices) + L -> nonPrime(L_indices) + L_ -> prime(L_indices) + L2 -> double(L_indices) + R -> nonPrime(R_indices) + R_ -> prime(R_indices) + R2 -> double(R_indices) + U -> nonPrime(U_indices) + U_ -> prime(U_indices) + U2 -> double(U_indices) + D -> nonPrime(D_indices) + D_ -> prime(D_indices) + D2 -> double(D_indices) } return EdgeModel(copyOf) } @@ -170,7 +171,7 @@ class EdgeModel { return trimMargin } - fun doMoves(f: Collection): EdgeModel { + fun doMoves(f: Array) : EdgeModel { var edgeModel = this f.forEach { edgeModel = edgeModel.doMove(it) @@ -178,10 +179,21 @@ class EdgeModel { return edgeModel } - fun doMoves(vararg f: Move): EdgeModel { + fun doMoves(f: Collection): EdgeModel { + var edgeModel = this + f.forEach { + edgeModel = edgeModel.doMove(it) + } + return edgeModel + } + + fun doMoves(vararg f: Int): EdgeModel { return this.doMoves(f.toList()) } + /** + * Pass any of the colors WHITE, YELLOW, RED, ... + */ fun crossSolved(color: Int): Boolean { return when (color) { WHITE -> { diff --git a/src/main/kotlin/be/nielandt/EdgeModelFactory.kt b/src/main/kotlin/be/nielandt/EdgeModelFactory.kt index 00d1b7a..425debc 100644 --- a/src/main/kotlin/be/nielandt/EdgeModelFactory.kt +++ b/src/main/kotlin/be/nielandt/EdgeModelFactory.kt @@ -16,9 +16,9 @@ class EdgeModelFactory(val original: EdgeModel, val counter: Counter) { init { // init the history - this.history.add(original.doMove(Move.values()[counter.digit(0)])) + this.history.add(original.doMove(counter.digit(0))) for (i in 1 until counter.size()) { - this.history.add(this.history.last().doMove(Move.values()[counter.digit(i)])) + this.history.add(this.history.last().doMove(counter.digit(i))) } } @@ -31,14 +31,13 @@ class EdgeModelFactory(val original: EdgeModel, val counter: Counter) { val lastOverflowIndex = counter.lastModifiedIndex // we only need to redo everything starting from the lastoverflowindex // these are our moves, but we can salvage everything up to lastoverflowindex - val moves = Move.combo(counter) // we have a history to work with... only redo what's necessary for (i in counter.lastModifiedIndex until counter.size()) { var start: EdgeModel = if (i == 0) original else history[i - 1] - history[i] = start.doMove(Move.values()[counter.digit(i)]) + history[i] = start.doMove(counter.digit(i)) } // increase the counter for next time if (!counter.increase()) { diff --git a/src/main/kotlin/be/nielandt/Move.kt b/src/main/kotlin/be/nielandt/Move.kt index 05e9aba..b7a4476 100644 --- a/src/main/kotlin/be/nielandt/Move.kt +++ b/src/main/kotlin/be/nielandt/Move.kt @@ -2,75 +2,95 @@ package be.nielandt import java.util.* -/** - * All the possible moves on the cube. - */ -enum class Move { - // these are purposely put in this order: Move.index % 6 will result in the same value for the same face - F, B, U, D, L, R, - F_, B_, U_, D_, L_, R_, - F2, B2, U2, D2, L2, R2; +// constant values for all the moves +// these are purposely put in this order: Move.index % 6 will result in the same value for the same face +const val F = 0 +const val B = 1 +const val U = 2 +const val D = 3 +const val L = 4 +const val R = 5 - /** - * Static methods for the Move object. - */ - companion object { - /** - * Make a random set of moves that makes sense. - */ - fun random(amount: Int): List { - val rgen = Random() - val result = mutableListOf() - while (result.size < amount) { - val randomMove = Move.values()[rgen.nextInt(Move.values().size)] - // check if it makes sense? - if (result.isNotEmpty() && result.last() otherFace randomMove) - result.add(randomMove) - else if (result.isEmpty()) - result.add(randomMove) - } - return result - } +const val F_ = 6 +const val B_ = 7 +const val U_ = 8 +const val D_ = 9 +const val L_ = 10 +const val R_ = 11 - /** - * Use the given counter, which contains digits that correspond with the amount of Moves, to generate a list of Moves. - * Highest significant digits of the counter are added first (are first in the result list). - */ - fun combo(counter: Counter): List { - val res = mutableListOf() - for (i in 0 until counter.size()) { - res.add(Move.values()[counter.digit(i)]) - } - return res - } +const val F2 = 12 +const val B2 = 13 +const val U2 = 14 +const val D2 = 15 +const val L2 = 16 +const val R2 = 17 - /** - * Parse a set of moves, separated by spaces, using the Move.enum names. - */ - fun parse(s: String): List { - val result = mutableListOf() - s.split(" ", ",", ";").forEach { - if (it.isNotEmpty()) { - result.add(Move.valueOf(it)) - } - } - return result +fun decodeMove(i: Int): String { + return when(i) { + F -> "F" + B -> "B" + U -> "U" + D -> "D" + L -> "L" + R -> "R" + + F_ -> "F_" + B_ -> "B_" + U_ -> "U_" + D_ -> "D_" + L_ -> "L_" + R_ -> "R_" + + F2 -> "F2" + B2 -> "B2" + U2 -> "U2" + D2 -> "D2" + L2 -> "L2" + R2 -> "R2" + else -> { + println("i = ${i}") + throw IllegalArgumentException() } } +} - /** - * Use this as `Move otherFace Move`, a quick way to check if the moves manipulate the same face or not. - * Example: `Move.F otherFace Move.U_ == false` - */ - infix fun otherFace(otherMove: Move): Boolean { - return this.toString().substring(0, 1) != otherMove.toString().substring(0, 1) +fun parseMoves(s: String): List { + return s.split(" ", ",", ";").filter { it?.length > 0 }.map { parseMove(it) }.toList() +} + +fun parseMove(s: String): Int { + return when (s) { + "F" -> F + "B" -> B + "U" -> U + "D" -> D + "L" -> L + "R" -> R + + "F_" -> F_ + "B_" -> B_ + "U_" -> U_ + "D_" -> D_ + "L_" -> L_ + "R_" -> R_ + + "F2" -> F2 + "B2" -> B2 + "U2" -> U2 + "D2" -> D2 + "L2" -> L2 + "R2" -> R2 + else -> { + println("s = ${s}") + throw IllegalArgumentException() + } } +} - /** - * Are both moves on the same face? Would be true for F2 and F_, for example. - */ - infix fun sameFace(otherMove: Move): Boolean { - return !otherFace(otherMove) +fun randomMoves(amount: Int): Array { + val rgen = Random() + return Array(amount) { + rgen.nextInt(18) } +} -} \ No newline at end of file diff --git a/src/test/kotlin/be/nielandt/EdgeModelTest.kt b/src/test/kotlin/be/nielandt/EdgeModelTest.kt index 65195dd..5a905db 100644 --- a/src/test/kotlin/be/nielandt/EdgeModelTest.kt +++ b/src/test/kotlin/be/nielandt/EdgeModelTest.kt @@ -1,21 +1,24 @@ package be.nielandt -import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test class EdgeModelTest { @Test - fun singleMoveAlwaysOneSolvedCross() { - // try each move - Move.values().forEach { move -> - val doMove = EdgeModel().doMove(move) - val count = (0..5).map { color -> - val crossSolved = doMove.crossSolved(color) - crossSolved - }.count { it } - assertEquals(1, count) + fun testSingleMove() { + val edgeModel = EdgeModel() + (0..5).forEach { color -> + assertTrue(edgeModel.crossSolved(color)) } } + @Test + fun testSingleMoves() { + val edgeModel = EdgeModel() + println(edgeModel) + val final = edgeModel.doMove(F) + println(final) + assertTrue(final.crossSolved(BLUE)) + } } \ No newline at end of file