/** Copyright (C) 2012 Shuang Chen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cs.min2phase; /** * Rubik's Cube Solver.<br> * A much faster and smaller implemention of Two-Phase Algorithm.<br> * Symmetry is used to reduce memory used.<br> * Total Memory used is about 1MB.<br> * @author Shuang Chen */ public class Search { private int[] move = new int[31]; private int[] corn = new int[20]; private int[] mid4 = new int[20]; private int[] ud8e = new int[20]; private int[] twist = new int[6]; private int[] flip = new int[6]; private int[] slice = new int[6]; private int[] corn0 = new int[6]; private int[] ud8e0 = new int[6]; private int[] prun = new int[6]; private byte[] f = new byte[54]; private int urfIdx; private int depth1; private int maxDep2; private int sol; private int valid1; private int valid2; private String solution; private long timeOut; private long timeMin; private int verbose; private int firstAxisRestriction; private int lastAxisRestriction; private CubieCube cc = new CubieCube(); /** * Verbose_Mask determines if a " . " separates the phase1 and phase2 parts of the solver string like in F' R B R L2 F . * U2 U D for example.<br> */ public static final int USE_SEPARATOR = 0x1; /** * Verbose_Mask determines if the solution will be inversed to a scramble/state generator. */ public static final int INVERSE_SOLUTION = 0x2; /** * Verbose_Mask determines if a tag such as "(21f)" will be appended to the solution. */ public static final int APPEND_LENGTH = 0x4; /** * Computes the solver string for a given cube. * * @param facelets * is the cube definition string format.<br> * The names of the facelet positions of the cube: * <pre> * |************| * |*U1**U2**U3*| * |************| * |*U4**U5**U6*| * |************| * |*U7**U8**U9*| * |************| * ************|************|************|************| * *L1**L2**L3*|*F1**F2**F3*|*R1**R2**F3*|*B1**B2**B3*| * ************|************|************|************| * *L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*| * ************|************|************|************| * *L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*| * ************|************|************|************| * |************| * |*D1**D2**D3*| * |************| * |*D4**D5**D6*| * |************| * |*D7**D8**D9*| * |************| * </pre> * A cube definition string "UBL..." means for example: In position U1 we have the U-color, in position U2 we have the * B-color, in position U3 we have the L color etc. according to the order U1, U2, U3, U4, U5, U6, U7, U8, U9, R1, R2, * R3, R4, R5, R6, R7, R8, R9, F1, F2, F3, F4, F5, F6, F7, F8, F9, D1, D2, D3, D4, D5, D6, D7, D8, D9, L1, L2, L3, L4, * L5, L6, L7, L8, L9, B1, B2, B3, B4, B5, B6, B7, B8, B9 of the enum constants. * * @param maxDepth * defines the maximal allowed maneuver length. For random cubes, a maxDepth of 21 usually will return a * solution in less than 0.02 seconds on average. With a maxDepth of 20 it takes about 0.1 seconds on average to find a * solution, but it may take much longer for specific cubes. * * @param timeOut * defines the maximum computing time of the method in milliseconds. If it does not return with a solution, it returns with * an error code. * * @param timeMin * defines the minimum computing time of the method in milliseconds. So, if a solution is found within given time, the * computing will continue to find shorter solution(s). Btw, if timeMin > timeOut, timeMin will be set to timeOut. * * @param verbose * determines the format of the solution(s). see USE_SEPARATOR, INVERSE_SOLUTION, APPEND_LENGTH * * @param firstAxisRestrictionStr * The solution generated will not start by turning * any face on the axis of firstAxisRestrictionStr. * * @param lastAxisRestrictionStr * The solution generated will not end by turning * any face on the axis of lastAxisRestrictionStr. * * @return The solution string or an error code:<br> * Error 1: There is not exactly one facelet of each colour<br> * Error 2: Not all 12 edges exist exactly once<br> * Error 3: Flip error: One edge has to be flipped<br> * Error 4: Not all corners exist exactly once<br> * Error 5: Twist error: One corner has to be twisted<br> * Error 6: Parity error: Two corners or two edges have to be exchanged<br> * Error 7: No solution exists for the given maxDepth<br> * Error 8: Timeout, no solution within given time<br> * Error 9: Invalid firstAxisRestrictionStr or lastAxisRestrictionStr */ public synchronized String solution(String facelets, int maxDepth, long timeOut, long timeMin, int verbose, String firstAxisRestrictionStr, String lastAxisRestrictionStr) { int check = verify(facelets); if (check != 0) { return "Error " + Math.abs(check); } this.sol = maxDepth+1; this.timeOut = System.currentTimeMillis() + timeOut; this.timeMin = this.timeOut + Math.min(timeMin - timeOut, 0); this.verbose = verbose; this.solution = null; this.firstAxisRestriction = -1; this.lastAxisRestriction = -1; if(firstAxisRestrictionStr != null) { if(!Util.str2move.containsKey(firstAxisRestrictionStr)) { return "Error 9"; } firstAxisRestriction = Util.str2move.get(firstAxisRestrictionStr); if(firstAxisRestriction % 3 != 0) { return "Error 9"; } if(firstAxisRestriction - 9 < 0) { // firstAxisRestriction defines an axis of turns that // aren't permitted. Make sure we restrict the entire // axis, and not just one of the faces. See the axis // filtering in phase1() for more details. firstAxisRestriction += 9; } } if(lastAxisRestrictionStr != null) { if(!Util.str2move.containsKey(lastAxisRestrictionStr)) { return "Error 9"; } lastAxisRestriction = Util.str2move.get(lastAxisRestrictionStr); if(lastAxisRestriction % 3 != 0) { return "Error 9"; } if(lastAxisRestriction - 9 < 0) { // lastAxisRestriction defines an axis of turns that // aren't permitted. Make sure we restrict the entire // axis, and not just one of the faces. See the axis // filtering in phase2() for more details. lastAxisRestriction += 9; } } return solve(cc); } public synchronized String solution(String facelets, int maxDepth, long timeOut, long timeMin, int verbose) { return solution(facelets, maxDepth, timeOut, timeMin, verbose, null, null); } int verify(String facelets) { int count = 0x000000; try { String center = new String(new char[] { facelets.charAt(4), facelets.charAt(13), facelets.charAt(22), facelets.charAt(31), facelets.charAt(40), facelets.charAt(49) }); for (int i=0; i<54; i++) { f[i] = (byte) center.indexOf(facelets.charAt(i)); if (f[i] == -1) { return -1; } count += 1 << (f[i] << 2); } } catch (Exception e) { return -1; } if (count != 0x999999) { return -1; } Util.toCubieCube(f, cc); return cc.verify(); } private String solve(CubieCube c) { Tools.init(); int conjMask = 0; for (int i=0; i<6; i++) { twist[i] = c.getTwistSym(); flip[i] = c.getFlipSym(); slice[i] = c.getUDSlice(); corn0[i] = c.getCPermSym(); ud8e0[i] = c.getU4Comb() << 16 | c.getD4Comb(); for (int j=0; j<i; j++) { //If S_i^-1 * C * S_i == C, It's unnecessary to compute it again. if (twist[i] == twist[j] && flip[i] == flip[j] && slice[i] == slice[j] && corn0[i] == corn0[j] && ud8e0[i] == ud8e0[j]) { conjMask |= 1 << i; break; } } if ((conjMask & (1 << i)) == 0) { prun[i] = Math.max(Math.max( CoordCube.getPruning(CoordCube.UDSliceTwistPrun, (twist[i]>>>3) * 495 + CoordCube.UDSliceConj[slice[i]&0x1ff][twist[i]&7]), CoordCube.getPruning(CoordCube.UDSliceFlipPrun, (flip[i]>>>3) * 495 + CoordCube.UDSliceConj[slice[i]&0x1ff][flip[i]&7])), Tools.USE_TWIST_FLIP_PRUN ? CoordCube.getPruning(CoordCube.TwistFlipPrun, (twist[i]>>>3) * 2688 + (flip[i] & 0xfff8 | CubieCube.Sym8MultInv[flip[i]&7][twist[i]&7])) : 0); } c.URFConjugate(); if (i==2) { c.invCubieCube(); } } for (depth1=0; depth1<sol; depth1++) { maxDep2 = Math.min(12, sol-depth1); for (urfIdx=0; urfIdx<6; urfIdx++) { if((firstAxisRestriction != -1 || lastAxisRestriction != -1) && urfIdx >= 3) { // When urfIdx >= 3, we're solving the // inverse cube. This doesn't work // when we're also restricting the // first turn, so we just skip inverse // solutions when firstAxisRestriction has // been set. continue; } if ((conjMask & (1 << urfIdx)) != 0) { continue; } corn[0] = corn0[urfIdx]; mid4[0] = slice[urfIdx]; ud8e[0] = ud8e0[urfIdx]; valid1 = 0; int lm = firstAxisRestriction == -1 ? -1 : CubieCube.urfMoveInv[urfIdx][firstAxisRestriction]/3*3; if ((prun[urfIdx] <= depth1) && phase1(twist[urfIdx]>>>3, twist[urfIdx]&7, flip[urfIdx]>>>3, flip[urfIdx]&7, slice[urfIdx]&0x1ff, depth1, lm) == 0) { return solution == null ? "Error 8" : solution; } } } return solution == null ? "Error 7" : solution; } /** * @return * 0: Found or Timeout * 1: Try Next Power * 2: Try Next Axis */ private int phase1(int twist, int tsym, int flip, int fsym, int slice, int maxl, int lastAxis) { if (twist==0 && flip==0 && slice==0 && maxl<5) { return maxl==0 ? initPhase2() : 1; } for (int axis=0; axis<18; axis+=3) { if (axis == lastAxis || axis == lastAxis-9) { continue; } for (int power=0; power<3; power++) { int m = axis + power; int slicex = CoordCube.UDSliceMove[slice][m] & 0x1ff; int twistx = CoordCube.TwistMove[twist][CubieCube.Sym8Move[tsym][m]]; int tsymx = CubieCube.Sym8Mult[twistx & 7][tsym]; twistx >>>= 3; int prun = CoordCube.getPruning(CoordCube.UDSliceTwistPrun, twistx * 495 + CoordCube.UDSliceConj[slicex][tsymx]); if (prun > maxl) { break; } else if (prun == maxl) { continue; } int flipx = CoordCube.FlipMove[flip][CubieCube.Sym8Move[fsym][m]]; int fsymx = CubieCube.Sym8Mult[flipx & 7][fsym]; flipx >>>= 3; if (Tools.USE_TWIST_FLIP_PRUN) { prun = CoordCube.getPruning(CoordCube.TwistFlipPrun, (twistx * 336 + flipx) << 3 | CubieCube.Sym8MultInv[fsymx][tsymx]); if (prun > maxl) { break; } else if (prun == maxl) { continue; } } prun = CoordCube.getPruning(CoordCube.UDSliceFlipPrun, flipx * 495 + CoordCube.UDSliceConj[slicex][fsymx]); if (prun > maxl) { break; } else if (prun == maxl) { continue; } move[depth1-maxl] = m; valid1 = Math.min(valid1, depth1-maxl); int ret = phase1(twistx, tsymx, flipx, fsymx, slicex, maxl-1, axis); if (ret != 1) { return ret >> 1; } } } return 1; } /** * @return * 0: Found or Timeout * 1: Try Next Power * 2: Try Next Axis */ private int initPhase2() { if (System.currentTimeMillis() >= (solution == null ? timeOut : timeMin)) { return 0; } valid2 = Math.min(valid2, valid1); int cidx = corn[valid1] >>> 4; int csym = corn[valid1] & 0xf; for (int i=valid1; i<depth1; i++) { int m = move[i]; cidx = CoordCube.CPermMove[cidx][CubieCube.SymMove[csym][m]]; csym = CubieCube.SymMult[cidx & 0xf][csym]; cidx >>>= 4; corn[i+1] = cidx << 4 | csym; int cx = CoordCube.UDSliceMove[mid4[i] & 0x1ff][m]; mid4[i+1] = Util.permMult[mid4[i]>>>9][cx>>>9]<<9|cx&0x1ff; } valid1 = depth1; int mid = mid4[depth1]>>>9; int prun = CoordCube.getPruning(CoordCube.MCPermPrun, cidx * 24 + CoordCube.MPermConj[mid][csym]); if (prun >= maxDep2) { return prun > maxDep2 ? 2 : 1; } int u4e = ud8e[valid2] >>> 16; int d4e = ud8e[valid2] & 0xffff; for (int i=valid2; i<depth1; i++) { int m = move[i]; int cx = CoordCube.UDSliceMove[u4e & 0x1ff][m]; u4e = Util.permMult[u4e>>>9][cx>>>9]<<9|cx&0x1ff; cx = CoordCube.UDSliceMove[d4e & 0x1ff][m]; d4e = Util.permMult[d4e>>>9][cx>>>9]<<9|cx&0x1ff; ud8e[i+1] = u4e << 16 | d4e; } valid2 = depth1; int edge = CubieCube.MtoEPerm[494 - (u4e & 0x1ff) + (u4e >>> 9) * 70 + (d4e >>> 9) * 1680]; int esym = edge & 15; edge >>>= 4; prun = Math.max(CoordCube.getPruning(CoordCube.MEPermPrun, edge * 24 + CoordCube.MPermConj[mid][esym]), prun); if (prun >= maxDep2) { return prun > maxDep2 ? 2 : 1; } int firstAxisRestrictionUd = firstAxisRestriction == -1 ? 10 : Util.std2ud[CubieCube.urfMoveInv[urfIdx][firstAxisRestriction]/3*3+1]; int lm = depth1==0 ? firstAxisRestrictionUd : Util.std2ud[move[depth1-1]/3*3+1]; for (int depth2=prun; depth2<maxDep2; depth2++) { if (phase2(edge, esym, cidx, csym, mid, depth2, depth1, lm)) { sol = depth1 + depth2; maxDep2 = Math.min(12, sol-depth1); solution = solutionToString(); return System.currentTimeMillis() >= timeMin ? 0 : 1; } } return 1; } private boolean phase2(int eidx, int esym, int cidx, int csym, int mid, int maxl, int depth, int lm) { if(maxl == 0) { // We've done the last move we're allowed to do, make sure it's permitted // by lastAxisRestriction. if(lastAxisRestriction != -1) { int stdLm = CubieCube.urfMove[urfIdx][Util.ud2std[lm]]; int lastAxis = (stdLm/3) * 3; if (lastAxisRestriction == lastAxis || lastAxisRestriction == lastAxis+9) { return false; } } // Is the cube solved? return eidx==0 && cidx==0 && mid==0; } for (int m=0; m<10; m++) { if (Util.ckmv2[lm][m]) { continue; } int midx = CoordCube.MPermMove[mid][m]; int cidxx = CoordCube.CPermMove[cidx][CubieCube.SymMove[csym][Util.ud2std[m]]]; int csymx = CubieCube.SymMult[cidxx & 15][csym]; cidxx >>>= 4; if (CoordCube.getPruning(CoordCube.MCPermPrun, cidxx * 24 + CoordCube.MPermConj[midx][csymx]) >= maxl) { continue; } int eidxx = CoordCube.EPermMove[eidx][CubieCube.SymMoveUD[esym][m]]; int esymx = CubieCube.SymMult[eidxx & 15][esym]; eidxx >>>= 4; if (CoordCube.getPruning(CoordCube.MEPermPrun, eidxx * 24 + CoordCube.MPermConj[midx][esymx]) >= maxl) { continue; } if (phase2(eidxx, esymx, cidxx, csymx, midx, maxl-1, depth+1, m)) { move[depth] = Util.ud2std[m]; return true; } } return false; } private String solutionToString() { StringBuffer sb = new StringBuffer(); int urf = (verbose & INVERSE_SOLUTION) != 0 ? (urfIdx + 3) % 6 : urfIdx; if (urf < 3) { for (int s=0; s<depth1; s++) { sb.append(Util.move2str[CubieCube.urfMove[urf][move[s]]]).append(' '); } if ((verbose & USE_SEPARATOR) != 0) { sb.append(". "); } for (int s=depth1; s<sol; s++) { sb.append(Util.move2str[CubieCube.urfMove[urf][move[s]]]).append(' '); } } else { for (int s=sol-1; s>=depth1; s--) { sb.append(Util.move2str[CubieCube.urfMove[urf][move[s]]]).append(' '); } if ((verbose & USE_SEPARATOR) != 0) { sb.append(". "); } for (int s=depth1-1; s>=0; s--) { sb.append(Util.move2str[CubieCube.urfMove[urf][move[s]]]).append(' '); } } if ((verbose & APPEND_LENGTH) != 0) { sb.append("(").append(sol).append("f)"); } return sb.toString(); } }