diff --git a/lib/solve.js b/lib/solve.js index d263e3a..1b6a1cc 100644 --- a/lib/solve.js +++ b/lib/solve.js @@ -776,18 +776,22 @@ // Initialize phase 2 coordinates init2(top = true) { - if (this.parent === null) { + if (this.parent === null && !top) { return; } - // For other states, the phase 2 state is computed based on - // parent's state. + // If parent is null then this is the initial call which must mean + // the cube started in phase 2 so there is no need for this // Already assigned for the initial state - this.parent.init2(false); - this.URFtoDLF = this.move('URFtoDLF', this.parent.URFtoDLF, this.lastMove); - this.FRtoBR = this.move('FRtoBR', this.parent.FRtoBR, this.lastMove); - this.parity = this.move('parity', this.parent.parity, this.lastMove); - this.URtoUL = this.move('URtoUL', this.parent.URtoUL, this.lastMove); - this.UBtoDF = this.move('UBtoDF', this.parent.UBtoDF, this.lastMove); + if (this.parent !== null) { + // For other states, the phase 2 state is computed based on + // parent's state. + this.parent.init2(false, false); + this.URFtoDLF = this.move('URFtoDLF', this.parent.URFtoDLF, this.lastMove); + this.FRtoBR = this.move('FRtoBR', this.parent.FRtoBR, this.lastMove); + this.parity = this.move('parity', this.parent.parity, this.lastMove); + this.URtoUL = this.move('URtoUL', this.parent.URtoUL, this.lastMove); + this.UBtoDF = this.move('UBtoDF', this.parent.UBtoDF, this.lastMove); + } if (top) { // This is the initial phase 2 state. Get the URtoDF coordinate // by merging URtoUL and UBtoDF @@ -813,14 +817,14 @@ solution = null; phase1search = function(state) { var depth, m, ref, results; - depth = 0; results = []; - for (depth = m = 1, ref = maxDepth; (1 <= ref ? m <= ref : m >= ref); depth = 1 <= ref ? ++m : --m) { + for (depth = m = 0, ref = maxDepth; (0 <= ref ? m <= ref : m >= ref); depth = 0 <= ref ? ++m : --m) { phase1(state, depth); if (solution !== null) { break; + } else { + results.push(void 0); } - results.push(depth++); } return results; }; @@ -859,12 +863,13 @@ // Initialize phase 2 coordinates state.init2(); results = []; - for (depth = m = 1, ref = maxDepth - state.depth; (1 <= ref ? m <= ref : m >= ref); depth = 1 <= ref ? ++m : --m) { + for (depth = m = 0, ref = maxDepth - state.depth; (0 <= ref ? m <= ref : m >= ref); depth = 0 <= ref ? ++m : --m) { phase2(state, depth); if (solution !== null) { break; + } else { + results.push(void 0); } - results.push(depth++); } return results; }; @@ -904,11 +909,8 @@ state = freeStates.pop().init(this); phase1search(state); freeStates.push(state); - // Trim the trailing space - if (solution.length > 0) { - solution = solution.substring(0, solution.length - 1); - } - return solution; + // Trim the trailing space and return + return solution.trim(); }; faceNums = { diff --git a/spec/cube.spec.coffee b/spec/cube.spec.coffee index 1bc7c22..073392d 100644 --- a/spec/cube.spec.coffee +++ b/spec/cube.spec.coffee @@ -53,8 +53,24 @@ describe 'Cube', -> moves = Cube.inverse "F B' R" expect(moves).toBe "R' B F'" - # ignore because Travis is slow - xit 'should solve a solved cube :) ', -> + # It seems Cube init state is reset between tests so we keep it all in one + # Due to Travis being slow we skip this but if you change algorithm you should + # run it locally + xit 'should solve cubes', -> Cube.initSolver() + # Should solve empty cube cube = new Cube - expect(cube.solve()).toBe "R L U2 R L F2 R2 U2 R2 F2 R2 U2 F2 L2" + expect(cube.solve()).toBe "" + + # Should solve trivial cube efficiently + cube.move("U'") + expect(cube.solve()).toBe "U" + + # Should solve random cube + cube = Cube.random() + # Should not be solved initially + expect(cube.isSolved()).toBeFalse + solution = cube.solve() + cube.move(solution) + # Solution should have solved the cube + expect(cube.isSolved()).toBeTrue diff --git a/src/solve.coffee b/src/solve.coffee index 53b4439..7a67e58 100644 --- a/src/solve.coffee +++ b/src/solve.coffee @@ -579,19 +579,22 @@ Cube::solveUpright = (maxDepth=22) -> # Initialize phase 2 coordinates init2: (top=true) -> - if @parent is null + if @parent is null and not top # Already assigned for the initial state return - # For other states, the phase 2 state is computed based on - # parent's state. - @parent.init2(false) + # If parent is null then this is the initial call which must mean + # the cube started in phase 2 so there is no need for this + if @parent isnt null + # For other states, the phase 2 state is computed based on + # parent's state. + @parent.init2(false, false) - @URFtoDLF = @move('URFtoDLF', @parent.URFtoDLF, @lastMove) - @FRtoBR = @move('FRtoBR', @parent.FRtoBR, @lastMove) - @parity = @move('parity', @parent.parity, @lastMove) - @URtoUL = @move('URtoUL', @parent.URtoUL, @lastMove) - @UBtoDF = @move('UBtoDF', @parent.UBtoDF, @lastMove) + @URFtoDLF = @move('URFtoDLF', @parent.URFtoDLF, @lastMove) + @FRtoBR = @move('FRtoBR', @parent.FRtoBR, @lastMove) + @parity = @move('parity', @parent.parity, @lastMove) + @URtoUL = @move('URtoUL', @parent.URtoUL, @lastMove) + @UBtoDF = @move('UBtoDF', @parent.UBtoDF, @lastMove) if top # This is the initial phase 2 state. Get the URtoDF coordinate @@ -616,7 +619,8 @@ Cube::solveUpright = (maxDepth=22) -> solution = null phase1search = (state) -> - for depth in [1..maxDepth] + + for depth in [0..maxDepth] phase1(state, depth) break if solution isnt null @@ -641,7 +645,7 @@ Cube::solveUpright = (maxDepth=22) -> # Initialize phase 2 coordinates state.init2() - for depth in [1..maxDepth - state.depth] + for depth in [0..maxDepth - state.depth] phase2(state, depth) break if solution isnt null