├── .gitignore ├── .travis.yml ├── Algorithm.md ├── Benchmark.md ├── Makefile ├── README.md ├── example ├── MainProgram.java └── demo.java ├── pruningValue.txt ├── src ├── CoordCube.java ├── CubieCube.java ├── Search.java ├── Tools.java └── Util.java └── test └── test.java /.gitignore: -------------------------------------------------------------------------------- 1 | twophase.jar 2 | cs/* 3 | ui/* 4 | m2pT.data 5 | test.class 6 | FullTable.prunP 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk11 4 | - openjdk10 5 | - openjdk9 6 | script: 7 | - make testSel 8 | - make testRnd 9 | - make testRnd maxl=21 10 | - make testRnd maxl=20 11 | - make testRnd probe=100 12 | - make testRnd probe=200 13 | - make testRnd probe=500 14 | - make testRnd probe=1000 -------------------------------------------------------------------------------- /Algorithm.md: -------------------------------------------------------------------------------- 1 | # Two-phase algorithm 2 | - See Kociemba's [page](http://kociemba.org/cube.htm) 3 | 4 | ## Improvements compared with conventional two-phase algorithm 5 | Conventional two-phase algorithm only find (sub-)optimal solutions to <U,R2,F2,D,L2,B2>. However, If we are able to find more phase1 solutions within a limited depth, the probability of finding a short solution will be increased. 6 | - Try different axes: The target of phase1 can be either <U,R2,F2,D,L2,B2>, <U2,R,F2,D2,L,B2>, or <U2,R2,F,D2,L2,B>. 7 | - Try the inverse of the state: We will try to solve the inverse state simultaneously to find more phase1 solutions. 8 | - Try pre-scramble: We can also use pre-scramble technique (which is widely used in fewest-move challenge) to find more phase1 solutions. If PreMoves * Scramble * Phase1 * Phase2 = Solved, then Scramble * (Phase1 * Phase2 * PreMoves) = Solved, Solution = Phase1 * Phase2 * PreMoves. 9 | 10 | ## Pruning table 11 | 12 | | Pruning Table | Coord1 |Coord1 Size| Coord2 | Coord2 Size | Phase | Average | 13 | |:----------------:|:---------:|:---------:|:--------:|:------------:|:---------:|:-------:| 14 | | UDSliceTwistPrun | UDSlice | 495 | TwistSym | 2187 / 324 | 1 | 6.76 | 15 | | UDSliceFlipPrun | UDSlice | 495 | FlipSym | 2048 / 336 | 1 | 6.85 | 16 | | TwistFlipPrun | Flip | 2048 | TwistSym | 2187 / 324 | 1 | 7.18 | 17 | | MCPermPrun | MPerm | 24 | CPermSym | 40320 / 2768 | 2 | 9.69 | 18 | | EPermCCombPPrun | CComb | 140 | EPermSym | 40320 / 2768 | 2 | 9.31 | 19 | 20 | ## Coordinates 21 | 22 | - UDSlice: Position of 4 edges (FL FR BL BR) without permutation among them. 23 | - Flip: Orientation of all 12 edges. 24 | - Twist: Orientation of all 8 corners. 25 | - CPerm: Permutation of 8 corners. 26 | - EPerm: Permutation of 8 edges in U and D layers. 27 | - CComb: Parity of all edges (or corners), and position of 4 corners (URF UFL ULB UBR) without permutation among them. 28 | -------------------------------------------------------------------------------- /Benchmark.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | - Memory: ~1M with twist-flip-pruning (TFP) table, ~0.7M without TFP table. 4 | 5 | ## Solving time for diffecent target length 6 | 7 | | Init | Unlimited | 21 moves | 20 moves | Init Time | 8 | |:--------:|:---------:|:---------:|:---------:|:---------:| 9 | | Full |**.685 ms**|**.805 ms**|**4.62 ms**|**195. ms**| 10 | | Partial | 4.12 ms | 4.52 ms | 19.0 ms | 60.0 ms | 11 | 12 | ## Solving time and average solution length for different probes 13 | 14 | | probeMin | Avg Length | Time | 15 | |:--------:|:----------:|:--------:| 16 | | 5 | 20.63 | .827 ms | 17 | | 10 | 20.48 | 1.07 ms | 18 | | 20 | 20.27 | 1.55 ms | 19 | | 50 | 19.95 | 2.83 ms | 20 | | 100 | 19.68 | 4.87 ms | 21 | | 200 | 19.48 | 8.58 ms | 22 | | 500 | 19.29 | 19.4 ms | 23 | | 1000 | 19.11 | 36.3 ms | 24 | | 2000 | 18.94 | - ms | 25 | | 5000 | 18.75 | - ms | 26 | | 10000 | 18.63 | - ms | 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC = \ 2 | src/CoordCube.java \ 3 | src/CubieCube.java \ 4 | src/Search.java \ 5 | src/Util.java \ 6 | src/Tools.java 7 | 8 | MAINPROG = example/MainProgram.java 9 | 10 | TESTSRC = test/test.java 11 | 12 | ifndef probe 13 | probe = 0 14 | endif 15 | 16 | ifndef maxl 17 | maxl = 30 18 | endif 19 | 20 | ifndef ntest 21 | ntest = 1000 22 | endif 23 | 24 | DIST = dist/twophase.jar 25 | 26 | DISTTEST = dist/test.class 27 | 28 | .PHONY: build clean run testRnd testRndMP testRndStd testSel demo 29 | 30 | build: $(DIST) 31 | 32 | $(DIST): $(SRC) $(MAINPROG) 33 | @javac -d dist $(SRC) $(MAINPROG) -Xlint:all 34 | @cp -f $(SRC) dist/cs/min2phase/ 35 | @cd dist && jar cfe twophase.jar ui.MainProgram ui/*.class cs/min2phase/*.class cs/min2phase/*.java 36 | 37 | run: $(DIST) 38 | @java -jar $(DIST) 39 | 40 | testRnd: $(DISTTEST) 41 | @java -ea -cp dist:$(DIST) test 40 $(ntest) $(maxl) 10000000 $(probe) 0 42 | 43 | testRndMP: $(DISTTEST) 44 | @java -ea -cp dist:$(DIST) test 72 $(ntest) $(maxl) 10000000 $(probe) 0 45 | 46 | testRndStd: $(DISTTEST) 47 | @java -ea -cp dist:$(DIST) test 40 $(ntest) 30 10000000 $(probe) 0 | grep AvgT 48 | @java -ea -cp dist:$(DIST) test 40 $(ntest) 21 10000000 $(probe) 0 | grep AvgT 49 | @java -ea -cp dist:$(DIST) test 40 $(ntest) 20 10000000 $(probe) 0 | grep AvgT 50 | 51 | testSel: $(DISTTEST) 52 | @java -ea -cp dist:$(DIST) test 24 53 | 54 | demo: $(DIST) 55 | @javac -d dist -cp dist:$(DIST) example/demo.java 56 | @java -ea -cp dist:$(DIST) demo 57 | 58 | $(DISTTEST): $(DIST) $(TESTSRC) 59 | @javac -d dist -cp dist:$(DIST) $(TESTSRC) 60 | 61 | rebuild: clean build 62 | 63 | clean: 64 | @rm -rf dist/* 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # min2phase 2 | - Rubik's Cube solver or scrambler. [![Build Status](https://travis-ci.org/cs0x7f/min2phase.svg?branch=master)](https://travis-ci.org/cs0x7f/min2phase) 3 | 4 | # Usage 5 | 6 | ```java 7 | import cs.min2phase.Search; 8 | import cs.min2phase.Tools; 9 | 10 | public class demo { 11 | 12 | public static void simpleSolve(String scrambledCube) { 13 | String result = new Search().solution(scrambledCube, 21, 100000000, 0, 0); 14 | System.out.println(result); 15 | // R2 U2 B2 L2 F2 U' L2 R2 B2 R2 D B2 F L' F U2 F' R' D' L2 R' 16 | } 17 | 18 | public static void outputControl(String scrambledCube) { 19 | String result = new Search().solution(scrambledCube, 21, 100000000, 0, Search.APPEND_LENGTH); 20 | System.out.println(result); 21 | // R2 U2 B2 L2 F2 U' L2 R2 B2 R2 D B2 F L' F U2 F' R' D' L2 R' (21f) 22 | 23 | result = new Search().solution(scrambledCube, 21, 100000000, 0, Search.USE_SEPARATOR | Search.INVERSE_SOLUTION); 24 | System.out.println(result); 25 | // R L2 D R F U2 F' L F' . B2 D' R2 B2 R2 L2 U F2 L2 B2 U2 R2 26 | } 27 | 28 | public static void findShorterSolutions(String scrambledCube) { 29 | //Find shorter solutions (try more probes even a solution has already been found) 30 | //In this example, we try AT LEAST 10000 phase2 probes to find shorter solutions. 31 | String result = new Search().solution(scrambledCube, 21, 100000000, 10000, 0); 32 | System.out.println(result); 33 | // L2 U D2 R' B U2 L F U R2 D2 F2 U' L2 U B D R' 34 | } 35 | 36 | public static void continueSearch(String scrambledCube) { 37 | //Continue to find shorter solutions 38 | Search searchObj = new Search(); 39 | String result = searchObj.solution(scrambledCube, 21, 500, 0, 0); 40 | System.out.println(result); 41 | // R2 U2 B2 L2 F2 U' L2 R2 B2 R2 D B2 F L' F U2 F' R' D' L2 R' 42 | 43 | result = searchObj.next(500, 0, 0); 44 | System.out.println(result); 45 | // D2 L' D' L2 U R2 F B L B D' B2 R2 U' R2 U' F2 R2 U' L2 46 | 47 | result = searchObj.next(500, 0, 0); 48 | System.out.println(result); 49 | // L' U B R2 F' L F' U2 L U' B' U2 B L2 F U2 R2 L2 B2 50 | 51 | result = searchObj.next(500, 0, 0); 52 | System.out.println(result); 53 | // Error 8, no solution is found after 500 phase2 probes. Let's try more probes. 54 | 55 | result = searchObj.next(500, 0, 0); 56 | System.out.println(result); 57 | // L2 U D2 R' B U2 L F U R2 D2 F2 U' L2 U B D R' 58 | } 59 | 60 | public static void main(String[] args) { 61 | // Full initialization, which will take about 200ms. 62 | // The solver will be about 5x~10x slower without full initialization. 63 | long startTime = System.nanoTime(); 64 | Search.init(); 65 | System.out.println("Init time: " + (System.nanoTime() - startTime) / 1.0E6 + " ms"); 66 | 67 | /** prepare scrambledCube as 68 | * 69 | * |************| 70 | * |*U1**U2**U3*| 71 | * |************| 72 | * |*U4**U5**U6*| 73 | * |************| 74 | * |*U7**U8**U9*| 75 | * |************| 76 | * ************|************|************|************| 77 | * *L1**L2**L3*|*F1**F2**F3*|*R1**R2**R3*|*B1**B2**B3*| 78 | * ************|************|************|************| 79 | * *L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*| 80 | * ************|************|************|************| 81 | * *L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*| 82 | * ************|************|************|************| 83 | * |************| 84 | * |*D1**D2**D3*| 85 | * |************| 86 | * |*D4**D5**D6*| 87 | * |************| 88 | * |*D7**D8**D9*| 89 | * |************| 90 | * 91 | * -> U1 U2 ... U9 R1 ... R9 F1 ... F9 D1 ... D9 L1 ... L9 B1 ... B9 92 | */ 93 | String scrambledCube = "DUUBULDBFRBFRRULLLBRDFFFBLURDBFDFDRFRULBLUFDURRBLBDUDL"; 94 | // scrambledCube can also be obtained by specific moves 95 | scrambledCube = Tools.fromScramble("R L2 D R F U2 F' L F' B2 D' R2 B2 R2 L2 U F2 L2 B2 U2 R2"); 96 | System.out.println(scrambledCube); 97 | 98 | simpleSolve(scrambledCube); 99 | outputControl(scrambledCube); 100 | findShorterSolutions(scrambledCube); 101 | continueSearch(scrambledCube); 102 | } 103 | } 104 | ``` 105 | 106 | # License GPLv3 107 | 108 | Copyright (C) 2023 Shuang Chen 109 | 110 | This program is free software: you can redistribute it and/or modify 111 | it under the terms of the GNU General Public License as published by 112 | the Free Software Foundation, either version 3 of the License, or 113 | (at your option) any later version. 114 | 115 | This program is distributed in the hope that it will be useful, 116 | but WITHOUT ANY WARRANTY; without even the implied warranty of 117 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 118 | GNU General Public License for more details. 119 | 120 | You should have received a copy of the GNU General Public License 121 | along with this program. If not, see . 122 | 123 | # License MIT 124 | 125 | Copyright (c) 2023 Chen Shuang 126 | 127 | Permission is hereby granted, free of charge, to any person obtaining a copy 128 | of this software and associated documentation files (the "Software"), to deal 129 | in the Software without restriction, including without limitation the rights 130 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 131 | copies of the Software, and to permit persons to whom the Software is 132 | furnished to do so, subject to the following conditions: 133 | 134 | The above copyright notice and this permission notice shall be included in all 135 | copies or substantial portions of the Software. 136 | 137 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 138 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 139 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 140 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 141 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 142 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 143 | SOFTWARE. 144 | -------------------------------------------------------------------------------- /example/MainProgram.java: -------------------------------------------------------------------------------- 1 | package ui; 2 | 3 | import cs.min2phase.Tools; 4 | import cs.min2phase.Search; 5 | 6 | import javax.swing.*; 7 | import javax.swing.event.ChangeEvent; 8 | import javax.swing.event.ChangeListener; 9 | import java.awt.Color; 10 | import java.awt.event.*; 11 | 12 | import java.io.*; 13 | 14 | //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 15 | //A simple GUI example to demonstrate how to use the package org.kociemba.twophase 16 | 17 | /** 18 | * This code was edited or generated using CloudGarden's Jigloo SWT/Swing GUI Builder, which is free for non-commercial 19 | * use. If Jigloo is being used commercially (ie, by a corporation, company or business for any purpose whatever) then 20 | * you should purchase a license for each developer using Jigloo. Please visit www.cloudgarden.com for details. Use of 21 | * Jigloo implies acceptance of these licensing terms. A COMMERCIAL LICENSE HAS NOT BEEN PURCHASED FOR THIS MACHINE, SO 22 | * JIGLOO OR THIS CODE CANNOT BE USED LEGALLY FOR ANY CORPORATE OR COMMERCIAL PURPOSE. 23 | */ 24 | public class MainProgram extends javax.swing.JFrame { 25 | 26 | // +++++++++++++These variables used only in the GUI-interface+++++++++++++++++++++++++++++++++++++++++++++++++++++++ 27 | private static final long serialVersionUID = 1L; 28 | private JButton[][] facelet = new JButton[6][9]; 29 | private final JButton[] colorSel = new JButton[6]; 30 | private final int FSIZE = 45; 31 | private final int[] XOFF = { 3, 6, 3, 3, 0, 9 };// Offsets for facelet display 32 | private final int[] YOFF = { 0, 3, 3, 6, 3, 3 }; 33 | private final Color[] COLORS = { Color.white, Color.red, Color.green, Color.yellow, Color.orange, Color.blue }; 34 | private JTextPane jTextPane1; 35 | private JCheckBox checkBoxShowStr; 36 | private JButton buttonRandom; 37 | private JCheckBox checkBoxUseSep; 38 | private JCheckBox checkBoxInv; 39 | private JCheckBox checkBoxShowLen; 40 | 41 | private JButton Solve; 42 | private JLabel jLabel2; 43 | private JLabel jLabel1; 44 | private JSpinner spinnerMaxMoves; 45 | private JSpinner spinnerTimeout; 46 | private Color curCol = COLORS[0]; 47 | private int maxDepth = 21, maxTime = 5; 48 | boolean useSeparator = true; 49 | boolean showString = false; 50 | boolean inverse = true; 51 | boolean showLength = true; 52 | Search search = new Search(); 53 | 54 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 55 | public static void main(String[] args) { 56 | String fileName = "m2p" + (Search.USE_TWIST_FLIP_PRUN ? "T" : "") + ".data"; 57 | try { 58 | DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName))); 59 | Tools.initFrom(dis); 60 | } catch (FileNotFoundException e) { 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | System.exit(1); 64 | } 65 | if (!Search.isInited()) { 66 | Search.init(); 67 | try { 68 | DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileName))); 69 | Tools.saveTo(dos); 70 | dos.close(); 71 | } catch (IOException e) { 72 | e.printStackTrace(); 73 | System.exit(1); 74 | } 75 | } 76 | 77 | SwingUtilities.invokeLater(new Runnable() { 78 | public void run() { 79 | MainProgram inst = new MainProgram(); 80 | inst.setLocationRelativeTo(null); 81 | inst.setVisible(true); 82 | } 83 | }); 84 | } 85 | 86 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 87 | public MainProgram() { 88 | super(); 89 | initGUI(); 90 | } 91 | 92 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 93 | private void initGUI() { 94 | 95 | getContentPane().setLayout(null); 96 | setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 97 | this.setTitle("Two-Phase Package GUI-Example"); 98 | this.setPreferredSize(new java.awt.Dimension(546, 521)); 99 | 100 | // ++++++++++++++++++++++++++++++++++ Set up Solve Cube Button ++++++++++++++++++++++++++++++++++++++++++++++++++++ 101 | Solve = new JButton("Solve Cube"); 102 | getContentPane().add(Solve); 103 | Solve.setBounds(422, 64, 114, 48); 104 | Solve.addActionListener(new ActionListener() { 105 | public void actionPerformed(ActionEvent evt) { 106 | solveCube(evt); 107 | } 108 | }); 109 | 110 | // ++++++++++++++++++++++++++++++++++ Set up Move Limit Spinner +++++++++++++++++++++++++++++++++++++++++++++++++++ 111 | { 112 | jLabel1 = new JLabel(); 113 | getContentPane().add(jLabel1); 114 | jLabel1.setText("Move Limit"); 115 | jLabel1.setBounds(282, 65, 72, 16); 116 | } 117 | { 118 | SpinnerModel model = new SpinnerNumberModel(21, 1, 24, 1); 119 | spinnerMaxMoves = new JSpinner(model); 120 | getContentPane().add(spinnerMaxMoves); 121 | spinnerMaxMoves.setBounds(354, 62, 56, 21); 122 | spinnerMaxMoves.getEditor().setPreferredSize(new java.awt.Dimension(37, 19)); 123 | spinnerMaxMoves.addChangeListener(new ChangeListener() { 124 | public void stateChanged(ChangeEvent evt) { 125 | maxDepth = ((Integer) spinnerMaxMoves.getValue()).intValue(); 126 | } 127 | }); 128 | } 129 | 130 | // ++++++++++++++++++++++++++++++++++ Set up Time Limit Spinner +++++++++++++++++++++++++++++++++++++++++++++++++++ 131 | { 132 | jLabel2 = new JLabel(); 133 | getContentPane().add(jLabel2); 134 | jLabel2.setText("Time Limit"); 135 | jLabel2.setBounds(282, 93, 72, 16); 136 | } 137 | { 138 | SpinnerModel model = new SpinnerNumberModel(5, 1, 3600, 1); 139 | spinnerTimeout = new JSpinner(model); 140 | getContentPane().add(spinnerTimeout); 141 | spinnerTimeout.setModel(model); 142 | spinnerTimeout.setBounds(354, 90, 56, 21); 143 | spinnerTimeout.getEditor().setPreferredSize(new java.awt.Dimension(36, 17)); 144 | spinnerTimeout.addChangeListener(new ChangeListener() { 145 | public void stateChanged(ChangeEvent evt) { 146 | maxTime = ((Integer) spinnerTimeout.getValue()).intValue(); 147 | } 148 | }); 149 | } 150 | 151 | // ++++++++++++++++++++++++++++++++++ Set up Use Separator CheckBox +++++++++++++++++++++++++++++++++++++++++++++++ 152 | { 153 | checkBoxInv = new JCheckBox("Inverse", true); 154 | getContentPane().add(checkBoxInv); 155 | checkBoxInv.setBounds(12, 297, 121, 20); 156 | checkBoxInv.addActionListener(new ActionListener() { 157 | public void actionPerformed(ActionEvent evt) { 158 | inverse = checkBoxInv.isSelected(); 159 | } 160 | }); 161 | checkBoxUseSep = new JCheckBox("Use Separator", true); 162 | getContentPane().add(checkBoxUseSep); 163 | checkBoxUseSep.setBounds(12, 320, 121, 20); 164 | checkBoxUseSep.addActionListener(new ActionListener() { 165 | public void actionPerformed(ActionEvent evt) { 166 | useSeparator = checkBoxUseSep.isSelected(); 167 | } 168 | }); 169 | } 170 | { 171 | checkBoxShowStr = new JCheckBox("Show String", false); 172 | getContentPane().add(checkBoxShowStr); 173 | checkBoxShowStr.setBounds(12, 343, 121, 20); 174 | checkBoxShowStr.addActionListener(new ActionListener() { 175 | public void actionPerformed(ActionEvent evt) { 176 | showString = checkBoxShowStr.isSelected(); 177 | } 178 | }); 179 | checkBoxShowLen = new JCheckBox("Show Length", true); 180 | getContentPane().add(checkBoxShowLen); 181 | checkBoxShowLen.setBounds(12, 366, 121, 20); 182 | checkBoxShowLen.addActionListener(new ActionListener() { 183 | public void actionPerformed(ActionEvent evt) { 184 | showLength = checkBoxShowLen.isSelected(); 185 | } 186 | }); 187 | } 188 | 189 | // ++++++++++++++++++++++++++++++++++ Set up Random Button ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 190 | { 191 | buttonRandom = new JButton("Random Cube"); 192 | getContentPane().add(buttonRandom); 193 | buttonRandom.setBounds(422, 17, 114, 22); 194 | buttonRandom.setText("Scramble"); 195 | buttonRandom.addActionListener(new ActionListener() { 196 | public void actionPerformed(ActionEvent evt) { 197 | // +++++++++++++++++++++++++++++ Call Random function from package org.kociemba.twophase ++++++++++++++++++++ 198 | String r = Tools.randomCube(); 199 | jTextPane1.setText(r); 200 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 201 | for (int i = 0; i < 6; i++) { 202 | for (int j = 0; j < 9; j++) { 203 | switch (r.charAt(9 * i + j)) { 204 | case 'U': 205 | facelet[i][j].setBackground(COLORS[0]); 206 | break; 207 | case 'R': 208 | facelet[i][j].setBackground(COLORS[1]); 209 | break; 210 | case 'F': 211 | facelet[i][j].setBackground(COLORS[2]); 212 | break; 213 | case 'D': 214 | facelet[i][j].setBackground(COLORS[3]); 215 | break; 216 | case 'L': 217 | facelet[i][j].setBackground(COLORS[4]); 218 | break; 219 | case 'B': 220 | facelet[i][j].setBackground(COLORS[5]); 221 | break; 222 | } 223 | } 224 | } 225 | spinnerMaxMoves.setValue(21); 226 | } 227 | }); 228 | } 229 | { 230 | jTextPane1 = new JTextPane(); 231 | getContentPane().add(jTextPane1); 232 | jTextPane1.setText("jTextPane1"); 233 | jTextPane1.setBounds(12, 417, 520, 63); 234 | } 235 | 236 | // ++++++++++++++++++++++++++++++++++ Set up editable facelets ++++++++++++++++++++++++++++++++++++++++++++++++++++ 237 | for (int i = 0; i < 6; i++) 238 | for (int j = 0; j < 9; j++) { 239 | facelet[i][j] = new JButton(); 240 | getContentPane().add(facelet[i][j]); 241 | facelet[i][j].setBackground(Color.gray); 242 | facelet[i][j].setRolloverEnabled(false); 243 | facelet[i][j].setOpaque(true); 244 | facelet[i][j].setBounds(FSIZE * XOFF[i] + FSIZE * (j % 3), FSIZE * YOFF[i] + FSIZE * (j / 3), FSIZE, FSIZE); 245 | facelet[i][j].addActionListener(new ActionListener() { 246 | public void actionPerformed(ActionEvent evt) { 247 | ((JButton) evt.getSource()).setBackground(curCol); 248 | } 249 | }); 250 | } 251 | String[] txt = { "U", "R", "F", "D", "L", "B" }; 252 | for (int i = 0; i < 6; i++) 253 | facelet[i][4].setText(txt[i]); 254 | for (int i = 0; i < 6; i++) { 255 | colorSel[i] = new JButton(); 256 | getContentPane().add(colorSel[i]); 257 | colorSel[i].setBackground(COLORS[i]); 258 | colorSel[i].setOpaque(true); 259 | colorSel[i].setBounds(FSIZE * (XOFF[1] + 1) + FSIZE / 4 * 3 * i, FSIZE * (YOFF[3] + 1), FSIZE / 4 * 3, 260 | FSIZE / 4 * 3); 261 | colorSel[i].setName("" + i); 262 | colorSel[i].addActionListener(new ActionListener() { 263 | public void actionPerformed(ActionEvent evt) { 264 | curCol = COLORS[Integer.parseInt(((JButton) evt.getSource()).getName())]; 265 | } 266 | }); 267 | 268 | } 269 | pack(); 270 | this.setSize(546, 521); 271 | } 272 | 273 | // ++++++++++++++++++++++++++++++++++++ End initGUI +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 274 | 275 | // +++++++++++++++++++++++++++++++ Generate cube from GUI-Input and solve it ++++++++++++++++++++++++++++++++++++++++ 276 | private void solveCube(ActionEvent evt) { 277 | StringBuffer s = new StringBuffer(54); 278 | 279 | for (int i = 0; i < 54; i++) 280 | s.insert(i, 'B');// default initialization 281 | 282 | for (int i = 0; i < 6; i++) 283 | // read the 54 facelets 284 | for (int j = 0; j < 9; j++) { 285 | if (facelet[i][j].getBackground() == facelet[0][4].getBackground()) 286 | s.setCharAt(9 * i + j, 'U'); 287 | if (facelet[i][j].getBackground() == facelet[1][4].getBackground()) 288 | s.setCharAt(9 * i + j, 'R'); 289 | if (facelet[i][j].getBackground() == facelet[2][4].getBackground()) 290 | s.setCharAt(9 * i + j, 'F'); 291 | if (facelet[i][j].getBackground() == facelet[3][4].getBackground()) 292 | s.setCharAt(9 * i + j, 'D'); 293 | if (facelet[i][j].getBackground() == facelet[4][4].getBackground()) 294 | s.setCharAt(9 * i + j, 'L'); 295 | if (facelet[i][j].getBackground() == facelet[5][4].getBackground()) 296 | s.setCharAt(9 * i + j, 'B'); 297 | } 298 | 299 | String cubeString = s.toString(); 300 | if (showString) { 301 | JOptionPane.showMessageDialog(null, "Cube Definiton String: " + cubeString); 302 | } 303 | int mask = 0; 304 | mask |= useSeparator ? Search.USE_SEPARATOR : 0; 305 | mask |= inverse ? Search.INVERSE_SOLUTION : 0; 306 | mask |= showLength ? Search.APPEND_LENGTH : 0; 307 | long t = System.nanoTime(); 308 | String result = search.solution(cubeString, maxDepth, 100, 0, mask);; 309 | long n_probe = search.numberOfProbes(); 310 | // ++++++++++++++++++++++++ Call Search.solution method from package org.kociemba.twophase ++++++++++++++++++++++++ 311 | while (result.startsWith("Error 8") && ((System.nanoTime() - t) < maxTime * 1.0e9)) { 312 | result = search.next(100, 0, mask); 313 | n_probe += search.numberOfProbes(); 314 | } 315 | t = System.nanoTime() - t; 316 | 317 | // +++++++++++++++++++ Replace the error messages with more meaningful ones in your language ++++++++++++++++++++++ 318 | if (result.contains("Error")) { 319 | switch (result.charAt(result.length() - 1)) { 320 | case '1': 321 | result = "There are not exactly nine facelets of each color!"; 322 | break; 323 | case '2': 324 | result = "Not all 12 edges exist exactly once!"; 325 | break; 326 | case '3': 327 | result = "Flip error: One edge has to be flipped!"; 328 | break; 329 | case '4': 330 | result = "Not all 8 corners exist exactly once!"; 331 | break; 332 | case '5': 333 | result = "Twist error: One corner has to be twisted!"; 334 | break; 335 | case '6': 336 | result = "Parity error: Two corners or two edges have to be exchanged!"; 337 | break; 338 | case '7': 339 | result = "No solution exists for the given maximum move number!"; 340 | break; 341 | case '8': 342 | result = "Timeout, no solution found within given maximum time!"; 343 | break; 344 | } 345 | JOptionPane.showMessageDialog(null, result, Double.toString((t / 1000) / 1000.0) + " ms | " + n_probe + " probes", JOptionPane.INFORMATION_MESSAGE); 346 | } else { 347 | int solLen = (result.length() - (useSeparator ? 3 : 0) - (showLength ? 4 : 0)) / 3; 348 | spinnerMaxMoves.setValue(solLen - 1); 349 | jTextPane1.setText(String.format("%s\n" /*, %s ms, %d probes\n*/, result/*, Double.toString((t / 1000) / 1000.0), n_probe*/) + jTextPane1.getText()); 350 | jTextPane1.requestFocusInWindow(); 351 | jTextPane1.select(0, result.length()); 352 | } 353 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /example/demo.java: -------------------------------------------------------------------------------- 1 | import cs.min2phase.Search; 2 | import cs.min2phase.Tools; 3 | 4 | public class demo { 5 | 6 | public static void simpleSolve(String scrambledCube) { 7 | String result = new Search().solution(scrambledCube, 21, 100000000, 0, 0); 8 | System.out.println(result); 9 | // R2 U2 B2 L2 F2 U' L2 R2 B2 R2 D B2 F L' F U2 F' R' D' L2 R' 10 | } 11 | 12 | public static void outputControl(String scrambledCube) { 13 | String result = new Search().solution(scrambledCube, 21, 100000000, 0, Search.APPEND_LENGTH); 14 | System.out.println(result); 15 | // R2 U2 B2 L2 F2 U' L2 R2 B2 R2 D B2 F L' F U2 F' R' D' L2 R' (21f) 16 | 17 | result = new Search().solution(scrambledCube, 21, 100000000, 0, Search.USE_SEPARATOR | Search.INVERSE_SOLUTION); 18 | System.out.println(result); 19 | // R L2 D R F U2 F' L F' . B2 D' R2 B2 R2 L2 U F2 L2 B2 U2 R2 20 | } 21 | 22 | public static void findShorterSolutions(String scrambledCube) { 23 | //Find shorter solutions (try more probes even a solution has already been found) 24 | //In this example, we try AT LEAST 10000 phase2 probes to find shorter solutions. 25 | String result = new Search().solution(scrambledCube, 21, 100000000, 10000, 0); 26 | System.out.println(result); 27 | // L2 U D2 R' B U2 L F U R2 D2 F2 U' L2 U B D R' 28 | } 29 | 30 | public static void continueSearch(String scrambledCube) { 31 | //Continue to find shorter solutions 32 | Search searchObj = new Search(); 33 | String result = searchObj.solution(scrambledCube, 21, 500, 0, 0); 34 | System.out.println(result); 35 | // R2 U2 B2 L2 F2 U' L2 R2 B2 R2 D B2 F L' F U2 F' R' D' L2 R' 36 | 37 | result = searchObj.next(500, 0, 0); 38 | System.out.println(result); 39 | // D2 L' D' L2 U R2 F B L B D' B2 R2 U' R2 U' F2 R2 U' L2 40 | 41 | result = searchObj.next(500, 0, 0); 42 | System.out.println(result); 43 | // L' U B R2 F' L F' U2 L U' B' U2 B L2 F U2 R2 L2 B2 44 | 45 | result = searchObj.next(500, 0, 0); 46 | System.out.println(result); 47 | // Error 8, no solution is found after 500 phase2 probes. Let's try more probes. 48 | 49 | result = searchObj.next(500, 0, 0); 50 | System.out.println(result); 51 | // L2 U D2 R' B U2 L F U R2 D2 F2 U' L2 U B D R' 52 | } 53 | 54 | public static void main(String[] args) { 55 | // Full initialization, which will take about 200ms. 56 | // The solver will be about 5x~10x slower without full initialization. 57 | long startTime = System.nanoTime(); 58 | Search.init(); 59 | System.out.println("Init time: " + (System.nanoTime() - startTime) / 1.0E6 + " ms"); 60 | 61 | /** prepare scrambledCube as 62 | * 63 | * |************| 64 | * |*U1**U2**U3*| 65 | * |************| 66 | * |*U4**U5**U6*| 67 | * |************| 68 | * |*U7**U8**U9*| 69 | * |************| 70 | * ************|************|************|************| 71 | * *L1**L2**L3*|*F1**F2**F3*|*R1**R2**R3*|*B1**B2**B3*| 72 | * ************|************|************|************| 73 | * *L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*| 74 | * ************|************|************|************| 75 | * *L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*| 76 | * ************|************|************|************| 77 | * |************| 78 | * |*D1**D2**D3*| 79 | * |************| 80 | * |*D4**D5**D6*| 81 | * |************| 82 | * |*D7**D8**D9*| 83 | * |************| 84 | * 85 | * -> U1 U2 ... U9 R1 ... R9 F1 ... F9 D1 ... D9 L1 ... L9 B1 ... B9 86 | */ 87 | String scrambledCube = "DUUBULDBFRBFRRULLLBRDFFFBLURDBFDFDRFRULBLUFDURRBLBDUDL"; 88 | // scrambledCube can also be optained by specific moves 89 | scrambledCube = Tools.fromScramble("R L2 D R F U2 F' L F' B2 D' R2 B2 R2 L2 U F2 L2 B2 U2 R2"); 90 | System.out.println(scrambledCube); 91 | 92 | simpleSolve(scrambledCube); 93 | outputControl(scrambledCube); 94 | findShorterSolutions(scrambledCube); 95 | continueSearch(scrambledCube); 96 | } 97 | } -------------------------------------------------------------------------------- /pruningValue.txt: -------------------------------------------------------------------------------- 1 | 1 4 2 | 2 14 3 | 3 58 4 | 4 272 5 | 5 1118 6 | 6 3531 7 | 7 8653 8 | 8 17292 9 | 9 29420 10 | 10 41991 11 | 11 54976 12 | 12 62730 13 | 13 66096 14 | 14 66432 15 | 1 4 16 | 2 15 17 | 3 54 18 | 4 239 19 | 5 1079 20 | 6 4660 21 | 7 19183 22 | 8 71653 23 | 9 207487 24 | 10 354378 25 | 11 385203 26 | 12 387482 27 | 13 387520 28 | 1 3 29 | 2 17 30 | 3 126 31 | 4 1050 32 | 5 8761 33 | 6 51261 34 | 7 136795 35 | 8 160231 36 | 9 160380 37 | 1 4 38 | 2 30 39 | 3 226 40 | 4 1280 41 | 5 8154 42 | 6 45809 43 | 7 136145 44 | 8 166210 45 | 9 166320 46 | 1 3 47 | 2 15 48 | 3 106 49 | 4 951 50 | 5 9826 51 | 6 89711 52 | 7 447496 53 | 8 662070 54 | 9 663552 55 | -------------------------------------------------------------------------------- /src/CoordCube.java: -------------------------------------------------------------------------------- 1 | package cs.min2phase; 2 | 3 | class CoordCube { 4 | static final int N_MOVES = 18; 5 | static final int N_MOVES2 = 10; 6 | 7 | static final int N_SLICE = 495; 8 | static final int N_TWIST = 2187; 9 | static final int N_TWIST_SYM = 324; 10 | static final int N_FLIP = 2048; 11 | static final int N_FLIP_SYM = 336; 12 | static final int N_PERM = 40320; 13 | static final int N_PERM_SYM = 2768; 14 | static final int N_MPERM = 24; 15 | static final int N_COMB = Search.USE_COMBP_PRUN ? 140 : 70; 16 | static final int P2_PARITY_MOVE = Search.USE_COMBP_PRUN ? 0xA5 : 0; 17 | 18 | //XMove = Move Table 19 | //XPrun = Pruning Table 20 | //XConj = Conjugate Table 21 | 22 | //phase1 23 | static char[][] UDSliceMove = new char[N_SLICE][N_MOVES]; 24 | static char[][] TwistMove = new char[N_TWIST_SYM][N_MOVES]; 25 | static char[][] FlipMove = new char[N_FLIP_SYM][N_MOVES]; 26 | static char[][] UDSliceConj = new char[N_SLICE][8]; 27 | static int[] UDSliceTwistPrun = new int[N_SLICE * N_TWIST_SYM / 8 + 1]; 28 | static int[] UDSliceFlipPrun = new int[N_SLICE * N_FLIP_SYM / 8 + 1]; 29 | static int[] TwistFlipPrun = Search.USE_TWIST_FLIP_PRUN ? new int[N_FLIP * N_TWIST_SYM / 8 + 1] : null; 30 | 31 | //phase2 32 | static char[][] CPermMove = new char[N_PERM_SYM][N_MOVES2]; 33 | static char[][] EPermMove = new char[N_PERM_SYM][N_MOVES2]; 34 | static char[][] MPermMove = new char[N_MPERM][N_MOVES2]; 35 | static char[][] MPermConj = new char[N_MPERM][16]; 36 | static char[][] CCombPMove;// = new char[N_COMB][N_MOVES2]; 37 | static char[][] CCombPConj = new char[N_COMB][16]; 38 | static int[] MCPermPrun = new int[N_MPERM * N_PERM_SYM / 8 + 1]; 39 | static int[] EPermCCombPPrun = new int[N_COMB * N_PERM_SYM / 8 + 1]; 40 | 41 | /** 42 | * 0: not initialized, 1: partially initialized, 2: finished 43 | */ 44 | static int initLevel = 0; 45 | 46 | static synchronized void init(boolean fullInit) { 47 | if (initLevel == 2 || initLevel == 1 && !fullInit) { 48 | return; 49 | } 50 | if (initLevel == 0) { 51 | CubieCube.initPermSym2Raw(); 52 | initCPermMove(); 53 | initEPermMove(); 54 | initMPermMoveConj(); 55 | initCombPMoveConj(); 56 | 57 | CubieCube.initFlipSym2Raw(); 58 | CubieCube.initTwistSym2Raw(); 59 | initFlipMove(); 60 | initTwistMove(); 61 | initUDSliceMoveConj(); 62 | } 63 | initMCPermPrun(fullInit); 64 | initPermCombPPrun(fullInit); 65 | initSliceTwistPrun(fullInit); 66 | initSliceFlipPrun(fullInit); 67 | if (Search.USE_TWIST_FLIP_PRUN) { 68 | initTwistFlipPrun(fullInit); 69 | } 70 | initLevel = fullInit ? 2 : 1; 71 | } 72 | 73 | static void setPruning(int[] table, int index, int value) { 74 | table[index >> 3] ^= value << (index << 2); // index << 2 <=> (index & 7) << 2 75 | } 76 | 77 | static int getPruning(int[] table, int index) { 78 | return table[index >> 3] >> (index << 2) & 0xf; // index << 2 <=> (index & 7) << 2 79 | } 80 | 81 | static void initUDSliceMoveConj() { 82 | CubieCube c = new CubieCube(); 83 | CubieCube d = new CubieCube(); 84 | for (int i = 0; i < N_SLICE; i++) { 85 | c.setUDSlice(i); 86 | for (int j = 0; j < N_MOVES; j += 3) { 87 | CubieCube.EdgeMult(c, CubieCube.moveCube[j], d); 88 | UDSliceMove[i][j] = (char) d.getUDSlice(); 89 | } 90 | for (int j = 0; j < 16; j += 2) { 91 | CubieCube.EdgeConjugate(c, CubieCube.SymMultInv[0][j], d); 92 | UDSliceConj[i][j >> 1] = (char) d.getUDSlice(); 93 | } 94 | } 95 | for (int i = 0; i < N_SLICE; i++) { 96 | for (int j = 0; j < N_MOVES; j += 3) { 97 | int udslice = UDSliceMove[i][j]; 98 | for (int k = 1; k < 3; k++) { 99 | udslice = UDSliceMove[udslice][j]; 100 | UDSliceMove[i][j + k] = (char) udslice; 101 | } 102 | } 103 | } 104 | } 105 | 106 | static void initFlipMove() { 107 | CubieCube c = new CubieCube(); 108 | CubieCube d = new CubieCube(); 109 | for (int i = 0; i < N_FLIP_SYM; i++) { 110 | c.setFlip(CubieCube.FlipS2R[i]); 111 | for (int j = 0; j < N_MOVES; j++) { 112 | CubieCube.EdgeMult(c, CubieCube.moveCube[j], d); 113 | FlipMove[i][j] = (char) d.getFlipSym(); 114 | } 115 | } 116 | } 117 | 118 | static void initTwistMove() { 119 | CubieCube c = new CubieCube(); 120 | CubieCube d = new CubieCube(); 121 | for (int i = 0; i < N_TWIST_SYM; i++) { 122 | c.setTwist(CubieCube.TwistS2R[i]); 123 | for (int j = 0; j < N_MOVES; j++) { 124 | CubieCube.CornMult(c, CubieCube.moveCube[j], d); 125 | TwistMove[i][j] = (char) d.getTwistSym(); 126 | } 127 | } 128 | } 129 | 130 | static void initCPermMove() { 131 | CubieCube c = new CubieCube(); 132 | CubieCube d = new CubieCube(); 133 | for (int i = 0; i < N_PERM_SYM; i++) { 134 | c.setCPerm(CubieCube.EPermS2R[i]); 135 | for (int j = 0; j < N_MOVES2; j++) { 136 | CubieCube.CornMult(c, CubieCube.moveCube[Util.ud2std[j]], d); 137 | CPermMove[i][j] = (char) d.getCPermSym(); 138 | } 139 | } 140 | } 141 | 142 | static void initEPermMove() { 143 | CubieCube c = new CubieCube(); 144 | CubieCube d = new CubieCube(); 145 | for (int i = 0; i < N_PERM_SYM; i++) { 146 | c.setEPerm(CubieCube.EPermS2R[i]); 147 | for (int j = 0; j < N_MOVES2; j++) { 148 | CubieCube.EdgeMult(c, CubieCube.moveCube[Util.ud2std[j]], d); 149 | EPermMove[i][j] = (char) d.getEPermSym(); 150 | } 151 | } 152 | } 153 | 154 | static void initMPermMoveConj() { 155 | CubieCube c = new CubieCube(); 156 | CubieCube d = new CubieCube(); 157 | for (int i = 0; i < N_MPERM; i++) { 158 | c.setMPerm(i); 159 | for (int j = 0; j < N_MOVES2; j++) { 160 | CubieCube.EdgeMult(c, CubieCube.moveCube[Util.ud2std[j]], d); 161 | MPermMove[i][j] = (char) d.getMPerm(); 162 | } 163 | for (int j = 0; j < 16; j++) { 164 | CubieCube.EdgeConjugate(c, CubieCube.SymMultInv[0][j], d); 165 | MPermConj[i][j] = (char) d.getMPerm(); 166 | } 167 | } 168 | } 169 | 170 | static void initCombPMoveConj() { 171 | CubieCube c = new CubieCube(); 172 | CubieCube d = new CubieCube(); 173 | CCombPMove = new char[N_COMB][N_MOVES2]; 174 | for (int i = 0; i < N_COMB; i++) { 175 | c.setCComb(i % 70); 176 | for (int j = 0; j < N_MOVES2; j++) { 177 | CubieCube.CornMult(c, CubieCube.moveCube[Util.ud2std[j]], d); 178 | CCombPMove[i][j] = (char) (d.getCComb() + 70 * ((P2_PARITY_MOVE >> j & 1) ^ (i / 70))); 179 | } 180 | for (int j = 0; j < 16; j++) { 181 | CubieCube.CornConjugate(c, CubieCube.SymMultInv[0][j], d); 182 | CCombPConj[i][j] = (char) (d.getCComb() + 70 * (i / 70)); 183 | } 184 | } 185 | } 186 | 187 | static boolean hasZero(int val) { 188 | return ((val - 0x11111111) & ~val & 0x88888888) != 0; 189 | } 190 | 191 | // | 4 bits | 4 bits | 4 bits | 2 bits | 1b | 1b | 4 bits | 192 | //PrunFlag: | MIN_DEPTH | MAX_DEPTH | INV_DEPTH | Padding | P2 | E2C | SYM_SHIFT | 193 | static void initRawSymPrun(int[] PrunTable, 194 | final char[][] RawMove, final char[][] RawConj, 195 | final char[][] SymMove, final char[] SymState, 196 | final int PrunFlag, final boolean fullInit) { 197 | 198 | final int SYM_SHIFT = PrunFlag & 0xf; 199 | final int SYM_E2C_MAGIC = ((PrunFlag >> 4) & 1) == 1 ? CubieCube.SYM_E2C_MAGIC : 0x00000000; 200 | final boolean IS_PHASE2 = ((PrunFlag >> 5) & 1) == 1; 201 | final int INV_DEPTH = PrunFlag >> 8 & 0xf; 202 | final int MAX_DEPTH = PrunFlag >> 12 & 0xf; 203 | final int MIN_DEPTH = PrunFlag >> 16 & 0xf; 204 | final int SEARCH_DEPTH = fullInit ? MAX_DEPTH : MIN_DEPTH; 205 | 206 | final int SYM_MASK = (1 << SYM_SHIFT) - 1; 207 | final boolean ISTFP = RawMove == null; 208 | final int N_RAW = ISTFP ? N_FLIP : RawMove.length; 209 | final int N_SIZE = N_RAW * SymMove.length; 210 | final int N_MOVES = IS_PHASE2 ? 10 : 18; 211 | final int NEXT_AXIS_MAGIC = N_MOVES == 10 ? 0x42 : 0x92492; 212 | 213 | int depth = getPruning(PrunTable, N_SIZE) - 1; 214 | int done = 0; 215 | 216 | // long tt = System.nanoTime(); 217 | 218 | if (depth == -1) { 219 | for (int i = 0; i < N_SIZE / 8 + 1; i++) { 220 | PrunTable[i] = 0x11111111; 221 | } 222 | setPruning(PrunTable, 0, 0 ^ 1); 223 | depth = 0; 224 | done = 1; 225 | } 226 | 227 | while (depth < SEARCH_DEPTH) { 228 | int mask = (depth + 1) * 0x11111111 ^ 0xffffffff; 229 | for (int i = 0; i < PrunTable.length; i++) { 230 | int val = PrunTable[i] ^ mask; 231 | val &= val >> 1; 232 | PrunTable[i] += val & (val >> 2) & 0x11111111; 233 | } 234 | 235 | boolean inv = depth > INV_DEPTH; 236 | int select = inv ? (depth + 2) : depth; 237 | int selArrMask = select * 0x11111111; 238 | int check = inv ? depth : (depth + 2); 239 | depth++; 240 | int xorVal = depth ^ (depth + 1); 241 | int val = 0; 242 | for (int i = 0; i < N_SIZE; i++, val >>= 4) { 243 | if ((i & 7) == 0) { 244 | val = PrunTable[i >> 3]; 245 | if (!hasZero(val ^ selArrMask)) { 246 | i += 7; 247 | continue; 248 | } 249 | } 250 | if ((val & 0xf) != select) { 251 | continue; 252 | } 253 | int raw = i % N_RAW; 254 | int sym = i / N_RAW; 255 | int flip = 0, fsym = 0; 256 | if (ISTFP) { 257 | flip = CubieCube.FlipR2S[raw]; 258 | fsym = flip & 7; 259 | flip >>= 3; 260 | } 261 | 262 | for (int m = 0; m < N_MOVES; m++) { 263 | int symx = SymMove[sym][m]; 264 | int rawx; 265 | if (ISTFP) { 266 | rawx = CubieCube.FlipS2RF[ 267 | FlipMove[flip][CubieCube.Sym8Move[m << 3 | fsym]] ^ 268 | fsym ^ (symx & SYM_MASK)]; 269 | } else { 270 | rawx = RawConj[RawMove[raw][m]][symx & SYM_MASK]; 271 | 272 | } 273 | symx >>= SYM_SHIFT; 274 | int idx = symx * N_RAW + rawx; 275 | int prun = getPruning(PrunTable, idx); 276 | if (prun != check) { 277 | if (prun < depth - 1) { 278 | m += NEXT_AXIS_MAGIC >> m & 3; 279 | } 280 | continue; 281 | } 282 | done++; 283 | if (inv) { 284 | setPruning(PrunTable, i, xorVal); 285 | break; 286 | } 287 | setPruning(PrunTable, idx, xorVal); 288 | for (int j = 1, symState = SymState[symx]; (symState >>= 1) != 0; j++) { 289 | if ((symState & 1) != 1) { 290 | continue; 291 | } 292 | int idxx = symx * N_RAW; 293 | if (ISTFP) { 294 | idxx += CubieCube.FlipS2RF[CubieCube.FlipR2S[rawx] ^ j]; 295 | } else { 296 | idxx += RawConj[rawx][j ^ (SYM_E2C_MAGIC >> (j << 1) & 3)]; 297 | } 298 | if (getPruning(PrunTable, idxx) == check) { 299 | setPruning(PrunTable, idxx, xorVal); 300 | done++; 301 | } 302 | } 303 | } 304 | } 305 | // System.out.println(String.format("%2d%10d%10f", depth, done, (System.nanoTime() - tt) / 1e6d)); 306 | } 307 | } 308 | 309 | static void initTwistFlipPrun(boolean fullInit) { 310 | initRawSymPrun( 311 | TwistFlipPrun, 312 | null, null, 313 | TwistMove, CubieCube.SymStateTwist, 0x19603, 314 | fullInit 315 | ); 316 | } 317 | 318 | static void initSliceTwistPrun(boolean fullInit) { 319 | initRawSymPrun( 320 | UDSliceTwistPrun, 321 | UDSliceMove, UDSliceConj, 322 | TwistMove, CubieCube.SymStateTwist, 0x69603, 323 | fullInit 324 | ); 325 | } 326 | 327 | static void initSliceFlipPrun(boolean fullInit) { 328 | initRawSymPrun( 329 | UDSliceFlipPrun, 330 | UDSliceMove, UDSliceConj, 331 | FlipMove, CubieCube.SymStateFlip, 0x69603, 332 | fullInit 333 | ); 334 | } 335 | 336 | static void initMCPermPrun(boolean fullInit) { 337 | initRawSymPrun( 338 | MCPermPrun, 339 | MPermMove, MPermConj, 340 | CPermMove, CubieCube.SymStatePerm, 0x8ea34, 341 | fullInit 342 | ); 343 | } 344 | 345 | static void initPermCombPPrun(boolean fullInit) { 346 | initRawSymPrun( 347 | EPermCCombPPrun, 348 | CCombPMove, CCombPConj, 349 | EPermMove, CubieCube.SymStatePerm, 0x7d824, 350 | fullInit 351 | ); 352 | } 353 | 354 | 355 | int twist; 356 | int tsym; 357 | int flip; 358 | int fsym; 359 | int slice; 360 | int prun; 361 | 362 | int twistc; 363 | int flipc; 364 | 365 | CoordCube() { } 366 | 367 | void set(CoordCube node) { 368 | this.twist = node.twist; 369 | this.tsym = node.tsym; 370 | this.flip = node.flip; 371 | this.fsym = node.fsym; 372 | this.slice = node.slice; 373 | this.prun = node.prun; 374 | 375 | if (Search.USE_CONJ_PRUN) { 376 | this.twistc = node.twistc; 377 | this.flipc = node.flipc; 378 | } 379 | } 380 | 381 | void calcPruning(boolean isPhase1) { 382 | prun = Math.max( 383 | Math.max( 384 | getPruning(UDSliceTwistPrun, 385 | twist * N_SLICE + UDSliceConj[slice][tsym]), 386 | getPruning(UDSliceFlipPrun, 387 | flip * N_SLICE + UDSliceConj[slice][fsym])), 388 | Math.max( 389 | Search.USE_CONJ_PRUN ? getPruning(TwistFlipPrun, 390 | (twistc >> 3) << 11 | CubieCube.FlipS2RF[flipc ^ (twistc & 7)]) : 0, 391 | Search.USE_TWIST_FLIP_PRUN ? getPruning(TwistFlipPrun, 392 | twist << 11 | CubieCube.FlipS2RF[flip << 3 | (fsym ^ tsym)]) : 0)); 393 | } 394 | 395 | boolean setWithPrun(CubieCube cc, int depth) { 396 | twist = cc.getTwistSym(); 397 | flip = cc.getFlipSym(); 398 | tsym = twist & 7; 399 | twist = twist >> 3; 400 | 401 | prun = Search.USE_TWIST_FLIP_PRUN ? getPruning(TwistFlipPrun, 402 | twist << 11 | CubieCube.FlipS2RF[flip ^ tsym]) : 0; 403 | if (prun > depth) { 404 | return false; 405 | } 406 | 407 | fsym = flip & 7; 408 | flip = flip >> 3; 409 | 410 | slice = cc.getUDSlice(); 411 | prun = Math.max(prun, Math.max( 412 | getPruning(UDSliceTwistPrun, 413 | twist * N_SLICE + UDSliceConj[slice][tsym]), 414 | getPruning(UDSliceFlipPrun, 415 | flip * N_SLICE + UDSliceConj[slice][fsym]))); 416 | if (prun > depth) { 417 | return false; 418 | } 419 | 420 | if (Search.USE_CONJ_PRUN) { 421 | CubieCube pc = new CubieCube(); 422 | CubieCube.CornConjugate(cc, 1, pc); 423 | CubieCube.EdgeConjugate(cc, 1, pc); 424 | twistc = pc.getTwistSym(); 425 | flipc = pc.getFlipSym(); 426 | prun = Math.max(prun, 427 | getPruning(TwistFlipPrun, 428 | (twistc >> 3) << 11 | CubieCube.FlipS2RF[flipc ^ (twistc & 7)])); 429 | } 430 | 431 | return prun <= depth; 432 | } 433 | 434 | /** 435 | * @return pruning value 436 | */ 437 | int doMovePrun(CoordCube cc, int m, boolean isPhase1) { 438 | slice = UDSliceMove[cc.slice][m]; 439 | 440 | flip = FlipMove[cc.flip][CubieCube.Sym8Move[m << 3 | cc.fsym]]; 441 | fsym = (flip & 7) ^ cc.fsym; 442 | flip >>= 3; 443 | 444 | twist = TwistMove[cc.twist][CubieCube.Sym8Move[m << 3 | cc.tsym]]; 445 | tsym = (twist & 7) ^ cc.tsym; 446 | twist >>= 3; 447 | 448 | prun = Math.max( 449 | Math.max( 450 | getPruning(UDSliceTwistPrun, 451 | twist * N_SLICE + UDSliceConj[slice][tsym]), 452 | getPruning(UDSliceFlipPrun, 453 | flip * N_SLICE + UDSliceConj[slice][fsym])), 454 | Search.USE_TWIST_FLIP_PRUN ? getPruning(TwistFlipPrun, 455 | twist << 11 | CubieCube.FlipS2RF[flip << 3 | (fsym ^ tsym)]) : 0); 456 | return prun; 457 | } 458 | 459 | int doMovePrunConj(CoordCube cc, int m) { 460 | m = CubieCube.SymMove[3][m]; 461 | flipc = FlipMove[cc.flipc >> 3][CubieCube.Sym8Move[m << 3 | cc.flipc & 7]] ^ (cc.flipc & 7); 462 | twistc = TwistMove[cc.twistc >> 3][CubieCube.Sym8Move[m << 3 | cc.twistc & 7]] ^ (cc.twistc & 7); 463 | return getPruning(TwistFlipPrun, 464 | (twistc >> 3) << 11 | CubieCube.FlipS2RF[flipc ^ (twistc & 7)]); 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /src/CubieCube.java: -------------------------------------------------------------------------------- 1 | package cs.min2phase; 2 | 3 | import java.util.Arrays; 4 | 5 | class CubieCube { 6 | 7 | /** 8 | * 16 symmetries generated by S_F2, S_U4 and S_LR2 9 | */ 10 | static CubieCube[] CubeSym = new CubieCube[16]; 11 | 12 | /** 13 | * 18 move cubes 14 | */ 15 | static CubieCube[] moveCube = new CubieCube[18]; 16 | 17 | static long[] moveCubeSym = new long[18]; 18 | static int[] firstMoveSym = new int[48]; 19 | 20 | static int[][] SymMult = new int[16][16]; 21 | static int[][] SymMultInv = new int[16][16]; 22 | static int[][] SymMove = new int[16][18]; 23 | static int[] Sym8Move = new int[8 * 18]; 24 | static int[][] SymMoveUD = new int[16][18]; 25 | 26 | /** 27 | * ClassIndexToRepresentantArrays 28 | */ 29 | static char[] FlipS2R = new char[CoordCube.N_FLIP_SYM]; 30 | static char[] TwistS2R = new char[CoordCube.N_TWIST_SYM]; 31 | static char[] EPermS2R = new char[CoordCube.N_PERM_SYM]; 32 | static byte[] Perm2CombP = new byte[CoordCube.N_PERM_SYM]; 33 | static char[] PermInvEdgeSym = new char[CoordCube.N_PERM_SYM]; 34 | static byte[] MPermInv = new byte[CoordCube.N_MPERM]; 35 | 36 | /** 37 | * Notice that Edge Perm Coordnate and Corner Perm Coordnate are the same symmetry structure. 38 | * So their ClassIndexToRepresentantArray are the same. 39 | * And when x is RawEdgePermCoordnate, y*16+k is SymEdgePermCoordnate, y*16+(k^e2c[k]) will 40 | * be the SymCornerPermCoordnate of the State whose RawCornerPermCoordnate is x. 41 | */ 42 | // static byte[] e2c = {0, 0, 0, 0, 1, 3, 1, 3, 1, 3, 1, 3, 0, 0, 0, 0}; 43 | static final int SYM_E2C_MAGIC = 0x00DDDD00; 44 | static int ESym2CSym(int idx) { 45 | return idx ^ (SYM_E2C_MAGIC >> ((idx & 0xf) << 1) & 3); 46 | } 47 | 48 | /** 49 | * Raw-Coordnate to Sym-Coordnate, only for speeding up initializaion. 50 | */ 51 | static char[] FlipR2S = new char[CoordCube.N_FLIP]; 52 | static char[] TwistR2S = new char[CoordCube.N_TWIST]; 53 | static char[] EPermR2S = new char[CoordCube.N_PERM]; 54 | static char[] FlipS2RF = Search.USE_TWIST_FLIP_PRUN ? new char[CoordCube.N_FLIP_SYM * 8] : null; 55 | 56 | /** 57 | * 58 | */ 59 | static char[] SymStateTwist;// = new char[CoordCube.N_TWIST_SYM]; 60 | static char[] SymStateFlip;// = new char[CoordCube.N_FLIP_SYM]; 61 | static char[] SymStatePerm;// = new char[CoordCube.N_PERM_SYM]; 62 | 63 | static CubieCube urf1 = new CubieCube(2531, 1373, 67026819, 1367); 64 | static CubieCube urf2 = new CubieCube(2089, 1906, 322752913, 2040); 65 | static byte[][] urfMove = new byte[][] { 66 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, 67 | {6, 7, 8, 0, 1, 2, 3, 4, 5, 15, 16, 17, 9, 10, 11, 12, 13, 14}, 68 | {3, 4, 5, 6, 7, 8, 0, 1, 2, 12, 13, 14, 15, 16, 17, 9, 10, 11}, 69 | {2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9, 14, 13, 12, 17, 16, 15}, 70 | {8, 7, 6, 2, 1, 0, 5, 4, 3, 17, 16, 15, 11, 10, 9, 14, 13, 12}, 71 | {5, 4, 3, 8, 7, 6, 2, 1, 0, 14, 13, 12, 17, 16, 15, 11, 10, 9} 72 | }; 73 | 74 | byte[] ca = {0, 1, 2, 3, 4, 5, 6, 7}; 75 | byte[] ea = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22}; 76 | CubieCube temps = null; 77 | 78 | CubieCube() { 79 | } 80 | 81 | CubieCube(int cperm, int twist, int eperm, int flip) { 82 | this.setCPerm(cperm); 83 | this.setTwist(twist); 84 | Util.setNPerm(ea, eperm, 12, true); 85 | this.setFlip(flip); 86 | } 87 | 88 | CubieCube(CubieCube c) { 89 | copy(c); 90 | } 91 | 92 | void copy(CubieCube c) { 93 | for (int i = 0; i < 8; i++) { 94 | this.ca[i] = c.ca[i]; 95 | } 96 | for (int i = 0; i < 12; i++) { 97 | this.ea[i] = c.ea[i]; 98 | } 99 | } 100 | 101 | void invCubieCube() { 102 | if (temps == null) { 103 | temps = new CubieCube(); 104 | } 105 | for (byte edge = 0; edge < 12; edge++) { 106 | temps.ea[ea[edge] >> 1] = (byte) (edge << 1 | ea[edge] & 1); 107 | } 108 | for (byte corn = 0; corn < 8; corn++) { 109 | temps.ca[ca[corn] & 0x7] = (byte) (corn | 0x20 >> (ca[corn] >> 3) & 0x18); 110 | } 111 | copy(temps); 112 | } 113 | 114 | /** 115 | * prod = a * b, Corner Only. 116 | */ 117 | static void CornMult(CubieCube a, CubieCube b, CubieCube prod) { 118 | for (int corn = 0; corn < 8; corn++) { 119 | int oriA = a.ca[b.ca[corn] & 7] >> 3; 120 | int oriB = b.ca[corn] >> 3; 121 | prod.ca[corn] = (byte) (a.ca[b.ca[corn] & 7] & 7 | (oriA + oriB) % 3 << 3); 122 | } 123 | } 124 | 125 | /** 126 | * prod = a * b, Corner Only. With mirrored cases considered 127 | */ 128 | static void CornMultFull(CubieCube a, CubieCube b, CubieCube prod) { 129 | for (int corn = 0; corn < 8; corn++) { 130 | int oriA = a.ca[b.ca[corn] & 7] >> 3; 131 | int oriB = b.ca[corn] >> 3; 132 | int ori = oriA + ((oriA < 3) ? oriB : 6 - oriB); 133 | ori = ori % 3 + ((oriA < 3) == (oriB < 3) ? 0 : 3); 134 | prod.ca[corn] = (byte) (a.ca[b.ca[corn] & 7] & 7 | ori << 3); 135 | } 136 | } 137 | 138 | /** 139 | * prod = a * b, Edge Only. 140 | */ 141 | static void EdgeMult(CubieCube a, CubieCube b, CubieCube prod) { 142 | for (int ed = 0; ed < 12; ed++) { 143 | prod.ea[ed] = (byte) (a.ea[b.ea[ed] >> 1] ^ (b.ea[ed] & 1)); 144 | } 145 | } 146 | 147 | /** 148 | * b = S_idx^-1 * a * S_idx, Corner Only. 149 | */ 150 | static void CornConjugate(CubieCube a, int idx, CubieCube b) { 151 | CubieCube sinv = CubeSym[SymMultInv[0][idx]]; 152 | CubieCube s = CubeSym[idx]; 153 | for (int corn = 0; corn < 8; corn++) { 154 | int oriA = sinv.ca[a.ca[s.ca[corn] & 7] & 7] >> 3; 155 | int oriB = a.ca[s.ca[corn] & 7] >> 3; 156 | int ori = (oriA < 3) ? oriB : (3 - oriB) % 3; 157 | b.ca[corn] = (byte) (sinv.ca[a.ca[s.ca[corn] & 7] & 7] & 7 | ori << 3); 158 | } 159 | } 160 | 161 | /** 162 | * b = S_idx^-1 * a * S_idx, Edge Only. 163 | */ 164 | static void EdgeConjugate(CubieCube a, int idx, CubieCube b) { 165 | CubieCube sinv = CubeSym[SymMultInv[0][idx]]; 166 | CubieCube s = CubeSym[idx]; 167 | for (int ed = 0; ed < 12; ed++) { 168 | b.ea[ed] = (byte) (sinv.ea[a.ea[s.ea[ed] >> 1] >> 1] ^ (a.ea[s.ea[ed] >> 1] & 1) ^ (s.ea[ed] & 1)); 169 | } 170 | } 171 | 172 | static int getPermSymInv(int idx, int sym, boolean isCorner) { 173 | int idxi = PermInvEdgeSym[idx]; 174 | if (isCorner) { 175 | idxi = ESym2CSym(idxi); 176 | } 177 | return idxi & 0xfff0 | SymMult[idxi & 0xf][sym]; 178 | } 179 | 180 | static int getSkipMoves(long ssym) { 181 | int ret = 0; 182 | for (int i = 1; (ssym >>= 1) != 0; i++) { 183 | if ((ssym & 1) == 1) { 184 | ret |= firstMoveSym[i]; 185 | } 186 | } 187 | return ret; 188 | } 189 | 190 | /** 191 | * this = S_urf^-1 * this * S_urf. 192 | */ 193 | void URFConjugate() { 194 | if (temps == null) { 195 | temps = new CubieCube(); 196 | } 197 | CornMult(urf2, this, temps); 198 | CornMult(temps, urf1, this); 199 | EdgeMult(urf2, this, temps); 200 | EdgeMult(temps, urf1, this); 201 | } 202 | 203 | // ********************************************* Get and set coordinates ********************************************* 204 | // XSym : Symmetry Coordnate of X. MUST be called after initialization of ClassIndexToRepresentantArrays. 205 | 206 | // ++++++++++++++++++++ Phase 1 Coordnates ++++++++++++++++++++ 207 | // Flip : Orientation of 12 Edges. Raw[0, 2048) Sym[0, 336 * 8) 208 | // Twist : Orientation of 8 Corners. Raw[0, 2187) Sym[0, 324 * 8) 209 | // UDSlice : Positions of the 4 UDSlice edges, the order is ignored. [0, 495) 210 | 211 | int getFlip() { 212 | int idx = 0; 213 | for (int i = 0; i < 11; i++) { 214 | idx = idx << 1 | ea[i] & 1; 215 | } 216 | return idx; 217 | } 218 | 219 | void setFlip(int idx) { 220 | int parity = 0, val; 221 | for (int i = 10; i >= 0; i--, idx >>= 1) { 222 | parity ^= (val = idx & 1); 223 | ea[i] = (byte) (ea[i] & ~1 | val); 224 | } 225 | ea[11] = (byte) (ea[11] & ~1 | parity); 226 | } 227 | 228 | int getFlipSym() { 229 | return FlipR2S[getFlip()]; 230 | } 231 | 232 | int getTwist() { 233 | int idx = 0; 234 | for (int i = 0; i < 7; i++) { 235 | idx += (idx << 1) + (ca[i] >> 3); 236 | } 237 | return idx; 238 | } 239 | 240 | void setTwist(int idx) { 241 | int twst = 15, val; 242 | for (int i = 6; i >= 0; i--, idx /= 3) { 243 | twst -= (val = idx % 3); 244 | ca[i] = (byte) (ca[i] & 0x7 | val << 3); 245 | } 246 | ca[7] = (byte) (ca[7] & 0x7 | (twst % 3) << 3); 247 | } 248 | 249 | int getTwistSym() { 250 | return TwistR2S[getTwist()]; 251 | } 252 | 253 | int getUDSlice() { 254 | return 494 - Util.getComb(ea, 8, true); 255 | } 256 | 257 | void setUDSlice(int idx) { 258 | Util.setComb(ea, 494 - idx, 8, true); 259 | } 260 | 261 | // ++++++++++++++++++++ Phase 2 Coordnates ++++++++++++++++++++ 262 | // EPerm : Permutations of 8 UD Edges. Raw[0, 40320) Sym[0, 2187 * 16) 263 | // Cperm : Permutations of 8 Corners. Raw[0, 40320) Sym[0, 2187 * 16) 264 | // MPerm : Permutations of 4 UDSlice Edges. [0, 24) 265 | 266 | int getCPerm() { 267 | return Util.getNPerm(ca, 8, false); 268 | } 269 | 270 | void setCPerm(int idx) { 271 | Util.setNPerm(ca, idx, 8, false); 272 | } 273 | 274 | int getCPermSym() { 275 | return ESym2CSym(EPermR2S[getCPerm()]); 276 | } 277 | 278 | int getEPerm() { 279 | return Util.getNPerm(ea, 8, true); 280 | } 281 | 282 | void setEPerm(int idx) { 283 | Util.setNPerm(ea, idx, 8, true); 284 | } 285 | 286 | int getEPermSym() { 287 | return EPermR2S[getEPerm()]; 288 | } 289 | 290 | int getMPerm() { 291 | return Util.getNPerm(ea, 12, true) % 24; 292 | } 293 | 294 | void setMPerm(int idx) { 295 | Util.setNPerm(ea, idx, 12, true); 296 | } 297 | 298 | int getCComb() { 299 | return Util.getComb(ca, 0, false); 300 | } 301 | 302 | void setCComb(int idx) { 303 | Util.setComb(ca, idx, 0, false); 304 | } 305 | 306 | /** 307 | * Check a cubiecube for solvability. Return the error code. 308 | * 0: Cube is solvable 309 | * -2: Not all 12 edges exist exactly once 310 | * -3: Flip error: One edge has to be flipped 311 | * -4: Not all corners exist exactly once 312 | * -5: Twist error: One corner has to be twisted 313 | * -6: Parity error: Two corners or two edges have to be exchanged 314 | */ 315 | int verify() { 316 | int sum = 0; 317 | int edgeMask = 0; 318 | for (int e = 0; e < 12; e++) { 319 | edgeMask |= 1 << (ea[e] >> 1); 320 | sum ^= ea[e] & 1; 321 | } 322 | if (edgeMask != 0xfff) { 323 | return -2;// missing edges 324 | } 325 | if (sum != 0) { 326 | return -3; 327 | } 328 | int cornMask = 0; 329 | sum = 0; 330 | for (int c = 0; c < 8; c++) { 331 | cornMask |= 1 << (ca[c] & 7); 332 | sum += ca[c] >> 3; 333 | } 334 | if (cornMask != 0xff) { 335 | return -4;// missing corners 336 | } 337 | if (sum % 3 != 0) { 338 | return -5;// twisted corner 339 | } 340 | if ((Util.getNParity(Util.getNPerm(ea, 12, true), 12) ^ Util.getNParity(getCPerm(), 8)) != 0) { 341 | return -6;// parity error 342 | } 343 | return 0;// cube ok 344 | } 345 | 346 | long selfSymmetry() { 347 | CubieCube c = new CubieCube(this); 348 | CubieCube d = new CubieCube(); 349 | int cperm = c.getCPermSym() >> 4; 350 | long sym = 0L; 351 | for (int urfInv = 0; urfInv < 6; urfInv++) { 352 | int cpermx = c.getCPermSym() >> 4; 353 | if (cperm == cpermx) { 354 | for (int i = 0; i < 16; i++) { 355 | CornConjugate(c, SymMultInv[0][i], d); 356 | if (Arrays.equals(d.ca, ca)) { 357 | EdgeConjugate(c, SymMultInv[0][i], d); 358 | if (Arrays.equals(d.ea, ea)) { 359 | sym |= 1L << Math.min(urfInv << 4 | i, 48); 360 | } 361 | } 362 | } 363 | } 364 | c.URFConjugate(); 365 | if (urfInv % 3 == 2) { 366 | c.invCubieCube(); 367 | } 368 | } 369 | return sym; 370 | } 371 | 372 | // ********************************************* Initialization functions ********************************************* 373 | 374 | static void initMove() { 375 | moveCube[0] = new CubieCube(15120, 0, 119750400, 0); 376 | moveCube[3] = new CubieCube(21021, 1494, 323403417, 0); 377 | moveCube[6] = new CubieCube(8064, 1236, 29441808, 550); 378 | moveCube[9] = new CubieCube(9, 0, 5880, 0); 379 | moveCube[12] = new CubieCube(1230, 412, 2949660, 0); 380 | moveCube[15] = new CubieCube(224, 137, 328552, 137); 381 | for (int a = 0; a < 18; a += 3) { 382 | for (int p = 0; p < 2; p++) { 383 | moveCube[a + p + 1] = new CubieCube(); 384 | EdgeMult(moveCube[a + p], moveCube[a], moveCube[a + p + 1]); 385 | CornMult(moveCube[a + p], moveCube[a], moveCube[a + p + 1]); 386 | } 387 | } 388 | } 389 | 390 | public String toString() { 391 | StringBuffer sb = new StringBuffer(); 392 | for (int i = 0; i < 8; i++) { 393 | sb.append("|" + (ca[i] & 7) + " " + (ca[i] >> 3)); 394 | } 395 | sb.append("\n"); 396 | for (int i = 0; i < 12; i++) { 397 | sb.append("|" + (ea[i] >> 1) + " " + (ea[i] & 1)); 398 | } 399 | return sb.toString(); 400 | } 401 | 402 | static void initSym() { 403 | CubieCube c = new CubieCube(); 404 | CubieCube d = new CubieCube(); 405 | CubieCube t; 406 | 407 | CubieCube f2 = new CubieCube(28783, 0, 259268407, 0); 408 | CubieCube u4 = new CubieCube(15138, 0, 119765538, 7); 409 | CubieCube lr2 = new CubieCube(5167, 0, 83473207, 0); 410 | for (int i = 0; i < 8; i++) { 411 | lr2.ca[i] |= 3 << 3; 412 | } 413 | 414 | for (int i = 0; i < 16; i++) { 415 | CubeSym[i] = new CubieCube(c); 416 | CornMultFull(c, u4, d); 417 | EdgeMult(c, u4, d); 418 | t = d; d = c; c = t; 419 | if (i % 4 == 3) { 420 | CornMultFull(c, lr2, d); 421 | EdgeMult(c, lr2, d); 422 | t = d; d = c; c = t; 423 | } 424 | if (i % 8 == 7) { 425 | CornMultFull(c, f2, d); 426 | EdgeMult(c, f2, d); 427 | t = d; d = c; c = t; 428 | } 429 | } 430 | for (int i = 0; i < 16; i++) { 431 | for (int j = 0; j < 16; j++) { 432 | CornMultFull(CubeSym[i], CubeSym[j], c); 433 | for (int k = 0; k < 16; k++) { 434 | if (Arrays.equals(CubeSym[k].ca, c.ca)) { 435 | SymMult[i][j] = k; // SymMult[i][j] = (k ^ i ^ j ^ (0x14ab4 >> j & i << 1 & 2))); 436 | SymMultInv[k][j] = i; // i * j = k => k * j^-1 = i 437 | break; 438 | } 439 | } 440 | } 441 | } 442 | for (int j = 0; j < 18; j++) { 443 | for (int s = 0; s < 16; s++) { 444 | CornConjugate(moveCube[j], SymMultInv[0][s], c); 445 | for (int m = 0; m < 18; m++) { 446 | if (Arrays.equals(moveCube[m].ca, c.ca)) { 447 | SymMove[s][j] = m; 448 | SymMoveUD[s][Util.std2ud[j]] = Util.std2ud[m]; 449 | break; 450 | } 451 | } 452 | if (s % 2 == 0) { 453 | Sym8Move[j << 3 | s >> 1] = SymMove[s][j]; 454 | } 455 | } 456 | } 457 | 458 | for (int i = 0; i < 18; i++) { 459 | moveCubeSym[i] = moveCube[i].selfSymmetry(); 460 | int j = i; 461 | for (int s = 0; s < 48; s++) { 462 | if (SymMove[s % 16][j] < i) { 463 | firstMoveSym[s] |= 1 << i; 464 | } 465 | if (s % 16 == 15) { 466 | j = urfMove[2][j]; 467 | } 468 | } 469 | } 470 | } 471 | 472 | static int initSym2Raw(final int N_RAW, char[] Sym2Raw, char[] Raw2Sym, char[] SymState, int coord) { 473 | final int N_RAW_HALF = (N_RAW + 1) / 2; 474 | CubieCube c = new CubieCube(); 475 | CubieCube d = new CubieCube(); 476 | int count = 0, idx = 0; 477 | int sym_inc = coord >= 2 ? 1 : 2; 478 | boolean isEdge = coord != 1; 479 | 480 | for (int i = 0; i < N_RAW; i++) { 481 | if (Raw2Sym[i] != 0) { 482 | continue; 483 | } 484 | switch (coord) { 485 | case 0: c.setFlip(i); break; 486 | case 1: c.setTwist(i); break; 487 | case 2: c.setEPerm(i); break; 488 | } 489 | for (int s = 0; s < 16; s += sym_inc) { 490 | if (isEdge) { 491 | EdgeConjugate(c, s, d); 492 | } else { 493 | CornConjugate(c, s, d); 494 | } 495 | switch (coord) { 496 | case 0: idx = d.getFlip(); 497 | break; 498 | case 1: idx = d.getTwist(); 499 | break; 500 | case 2: idx = d.getEPerm(); 501 | break; 502 | } 503 | if (coord == 0 && Search.USE_TWIST_FLIP_PRUN) { 504 | FlipS2RF[count << 3 | s >> 1] = (char) idx; 505 | } 506 | if (idx == i) { 507 | SymState[count] |= 1 << (s / sym_inc); 508 | } 509 | int symIdx = (count << 4 | s) / sym_inc; 510 | Raw2Sym[idx] = (char) symIdx; 511 | } 512 | Sym2Raw[count++] = (char) i; 513 | } 514 | return count; 515 | } 516 | 517 | static void initFlipSym2Raw() { 518 | initSym2Raw(CoordCube.N_FLIP, FlipS2R, FlipR2S, 519 | SymStateFlip = new char[CoordCube.N_FLIP_SYM], 0); 520 | } 521 | 522 | static void initTwistSym2Raw() { 523 | initSym2Raw(CoordCube.N_TWIST, TwistS2R, TwistR2S, 524 | SymStateTwist = new char[CoordCube.N_TWIST_SYM], 1); 525 | } 526 | 527 | static void initPermSym2Raw() { 528 | initSym2Raw(CoordCube.N_PERM, EPermS2R, EPermR2S, 529 | SymStatePerm = new char[CoordCube.N_PERM_SYM], 2); 530 | CubieCube cc = new CubieCube(); 531 | for (int i = 0; i < CoordCube.N_PERM_SYM; i++) { 532 | cc.setEPerm(EPermS2R[i]); 533 | Perm2CombP[i] = (byte) (Util.getComb(cc.ea, 0, true) + (Search.USE_COMBP_PRUN ? Util.getNParity(EPermS2R[i], 8) * 70 : 0)); 534 | cc.invCubieCube(); 535 | PermInvEdgeSym[i] = (char) cc.getEPermSym(); 536 | } 537 | for (int i = 0; i < CoordCube.N_MPERM; i++) { 538 | cc.setMPerm(i); 539 | cc.invCubieCube(); 540 | MPermInv[i] = (byte) cc.getMPerm(); 541 | } 542 | } 543 | 544 | static { 545 | CubieCube.initMove(); 546 | CubieCube.initSym(); 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /src/Search.java: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2015 Shuang Chen 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | package cs.min2phase; 18 | 19 | /** 20 | * Rubik's Cube Solver.
21 | * A much faster and smaller implemention of Two-Phase Algorithm.
22 | * Symmetry is used to reduce memory used.
23 | * Total Memory used is about 1MB.
24 | * @author Shuang Chen 25 | */ 26 | public class Search { 27 | 28 | public static final boolean USE_TWIST_FLIP_PRUN = true; 29 | 30 | //Options for research purpose. 31 | static final int MAX_PRE_MOVES = 20; 32 | static final boolean TRY_INVERSE = true; 33 | static final boolean TRY_THREE_AXES = true; 34 | 35 | static final boolean USE_COMBP_PRUN = USE_TWIST_FLIP_PRUN; 36 | static final boolean USE_CONJ_PRUN = USE_TWIST_FLIP_PRUN; 37 | protected static int MIN_P1LENGTH_PRE = 7; 38 | protected static int MAX_DEPTH2 = 12; 39 | 40 | static boolean inited = false; 41 | 42 | protected int[] move = new int[31]; 43 | 44 | protected CoordCube[] nodeUD = new CoordCube[21]; 45 | protected CoordCube[] nodeRL = new CoordCube[21]; 46 | protected CoordCube[] nodeFB = new CoordCube[21]; 47 | 48 | protected long selfSym; 49 | protected int conjMask; 50 | protected int urfIdx; 51 | protected int length1; 52 | protected int depth1; 53 | protected int maxDep2; 54 | protected int solLen; 55 | protected Util.Solution solution; 56 | protected long probe; 57 | protected long probeMax; 58 | protected long probeMin; 59 | protected int verbose; 60 | protected int valid1; 61 | protected boolean allowShorter = false; 62 | protected CubieCube cc = new CubieCube(); 63 | protected CubieCube[] urfCubieCube = new CubieCube[6]; 64 | protected CoordCube[] urfCoordCube = new CoordCube[6]; 65 | protected CubieCube[] phase1Cubie = new CubieCube[21]; 66 | 67 | CubieCube[] preMoveCubes = new CubieCube[MAX_PRE_MOVES + 1]; 68 | int[] preMoves = new int[MAX_PRE_MOVES]; 69 | int preMoveLen = 0; 70 | int maxPreMoves = 0; 71 | 72 | protected boolean isRec = false; 73 | 74 | /** 75 | * Verbose_Mask determines if a " . " separates the phase1 and phase2 parts of the solver string like in F' R B R L2 F . 76 | * U2 U D for example.
77 | */ 78 | public static final int USE_SEPARATOR = 0x1; 79 | 80 | /** 81 | * Verbose_Mask determines if the solution will be inversed to a scramble/state generator. 82 | */ 83 | public static final int INVERSE_SOLUTION = 0x2; 84 | 85 | /** 86 | * Verbose_Mask determines if a tag such as "(21f)" will be appended to the solution. 87 | */ 88 | public static final int APPEND_LENGTH = 0x4; 89 | 90 | /** 91 | * Verbose_Mask determines if guaranteeing the solution to be optimal. 92 | */ 93 | public static final int OPTIMAL_SOLUTION = 0x8; 94 | 95 | 96 | public Search() { 97 | for (int i = 0; i < 21; i++) { 98 | nodeUD[i] = new CoordCube(); 99 | nodeRL[i] = new CoordCube(); 100 | nodeFB[i] = new CoordCube(); 101 | phase1Cubie[i] = new CubieCube(); 102 | } 103 | for (int i = 0; i < 6; i++) { 104 | urfCubieCube[i] = new CubieCube(); 105 | urfCoordCube[i] = new CoordCube(); 106 | } 107 | for (int i = 0; i < MAX_PRE_MOVES; i++) { 108 | preMoveCubes[i + 1] = new CubieCube(); 109 | } 110 | } 111 | 112 | /** 113 | * Computes the solver string for a given cube. 114 | * 115 | * @param facelets 116 | * is the cube definition string format.
117 | * The names of the facelet positions of the cube: 118 | *
119 |      *             |************|
120 |      *             |*U1**U2**U3*|
121 |      *             |************|
122 |      *             |*U4**U5**U6*|
123 |      *             |************|
124 |      *             |*U7**U8**U9*|
125 |      *             |************|
126 |      * ************|************|************|************|
127 |      * *L1**L2**L3*|*F1**F2**F3*|*R1**R2**R3*|*B1**B2**B3*|
128 |      * ************|************|************|************|
129 |      * *L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*|
130 |      * ************|************|************|************|
131 |      * *L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*|
132 |      * ************|************|************|************|
133 |      *             |************|
134 |      *             |*D1**D2**D3*|
135 |      *             |************|
136 |      *             |*D4**D5**D6*|
137 |      *             |************|
138 |      *             |*D7**D8**D9*|
139 |      *             |************|
140 |      * 
141 | * A cube definition string "UBL..." means for example: In position U1 we have the U-color, in position U2 we have the 142 | * B-color, in position U3 we have the L color etc. For example, the "super flip" state is represented as
143 | *
UBULURUFURURFRBRDRFUFLFRFDFDFDLDRDBDLULBLFLDLBUBRBLBDB
144 | * and the state generated by "F U' F2 D' B U R' F' L D' R' U' L U B' D2 R' F U2 D2" can be represented as
145 | *
FBLLURRFBUUFBRFDDFUULLFRDDLRFBLDRFBLUUBFLBDDBUURRBLDDR
146 | * You can also use {@link cs.min2phase.Tools#fromScramble(java.lang.String s)} to convert the scramble string to the 147 | * cube definition string. 148 | * 149 | * @param maxDepth 150 | * defines the maximal allowed maneuver length. For random cubes, a maxDepth of 21 usually will return a 151 | * 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 152 | * solution, but it may take much longer for specific cubes. 153 | * 154 | * @param probeMax 155 | * defines the maximum number of the probes of phase 2. If it does not return with a solution, it returns with 156 | * an error code. 157 | * 158 | * @param probeMin 159 | * defines the minimum number of the probes of phase 2. So, if a solution is found within given probes, the 160 | * computing will continue to find shorter solution(s). Btw, if probeMin > probeMax, probeMin will be set to probeMax. 161 | * 162 | * @param verbose 163 | * determins the format of the solution(s). see USE_SEPARATOR, INVERSE_SOLUTION, APPEND_LENGTH, OPTIMAL_SOLUTION 164 | * 165 | * @return The solution string or an error code:
166 | * Error 1: There is not exactly one facelet of each colour
167 | * Error 2: Not all 12 edges exist exactly once
168 | * Error 3: Flip error: One edge has to be flipped
169 | * Error 4: Not all corners exist exactly once
170 | * Error 5: Twist error: One corner has to be twisted
171 | * Error 6: Parity error: Two corners or two edges have to be exchanged
172 | * Error 7: No solution exists for the given maxDepth
173 | * Error 8: Probe limit exceeded, no solution within given probMax 174 | */ 175 | public synchronized String solution(String facelets, int maxDepth, long probeMax, long probeMin, int verbose) { 176 | int check = verify(facelets); 177 | if (check != 0) { 178 | return "Error " + Math.abs(check); 179 | } 180 | this.solLen = maxDepth + 1; 181 | this.probe = 0; 182 | this.probeMax = probeMax; 183 | this.probeMin = Math.min(probeMin, probeMax); 184 | this.verbose = verbose; 185 | this.solution = null; 186 | this.isRec = false; 187 | 188 | CoordCube.init(false); 189 | initSearch(); 190 | 191 | return (verbose & OPTIMAL_SOLUTION) == 0 ? search() : searchopt(); 192 | } 193 | 194 | protected void initSearch() { 195 | conjMask = (TRY_INVERSE ? 0 : 0x38) | (TRY_THREE_AXES ? 0 : 0x36); 196 | selfSym = cc.selfSymmetry(); 197 | conjMask |= (selfSym >> 16 & 0xffff) != 0 ? 0x12 : 0; 198 | conjMask |= (selfSym >> 32 & 0xffff) != 0 ? 0x24 : 0; 199 | conjMask |= (selfSym >> 48 & 0xffff) != 0 ? 0x38 : 0; 200 | selfSym &= 0xffffffffffffL; 201 | maxPreMoves = conjMask > 7 ? 0 : MAX_PRE_MOVES; 202 | 203 | for (int i = 0; i < 6; i++) { 204 | urfCubieCube[i].copy(cc); 205 | urfCoordCube[i].setWithPrun(urfCubieCube[i], 20); 206 | cc.URFConjugate(); 207 | if (i % 3 == 2) { 208 | cc.invCubieCube(); 209 | } 210 | } 211 | } 212 | 213 | public synchronized String next(long probeMax, long probeMin, int verbose) { 214 | this.probe = 0; 215 | this.probeMax = probeMax; 216 | this.probeMin = Math.min(probeMin, probeMax); 217 | this.solution = null; 218 | this.isRec = (this.verbose & OPTIMAL_SOLUTION) == (verbose & OPTIMAL_SOLUTION); 219 | this.verbose = verbose; 220 | return (verbose & OPTIMAL_SOLUTION) == 0 ? search() : searchopt(); 221 | } 222 | 223 | public static boolean isInited() { 224 | return inited; 225 | } 226 | 227 | public long numberOfProbes() { 228 | return probe; 229 | } 230 | 231 | public int length() { 232 | return solLen; 233 | } 234 | 235 | public synchronized static void init() { 236 | CoordCube.init(true); 237 | inited = true; 238 | } 239 | 240 | int verify(String facelets) { 241 | int count = 0x000000; 242 | byte[] f = new byte[54]; 243 | try { 244 | String center = new String( 245 | new char[] { 246 | facelets.charAt(Util.U5), 247 | facelets.charAt(Util.R5), 248 | facelets.charAt(Util.F5), 249 | facelets.charAt(Util.D5), 250 | facelets.charAt(Util.L5), 251 | facelets.charAt(Util.B5) 252 | } 253 | ); 254 | for (int i = 0; i < 54; i++) { 255 | f[i] = (byte) center.indexOf(facelets.charAt(i)); 256 | if (f[i] == -1) { 257 | return -1; 258 | } 259 | count += 1 << (f[i] << 2); 260 | } 261 | } catch (Exception e) { 262 | return -1; 263 | } 264 | if (count != 0x999999) { 265 | return -1; 266 | } 267 | Util.toCubieCube(f, cc); 268 | return cc.verify(); 269 | } 270 | 271 | protected int phase1PreMoves(int maxl, int lm, CubieCube cc, int ssym) { 272 | preMoveLen = maxPreMoves - maxl; 273 | if (isRec ? depth1 == length1 - preMoveLen 274 | : (preMoveLen == 0 || (0x36FB7 >> lm & 1) == 0)) { 275 | depth1 = length1 - preMoveLen; 276 | phase1Cubie[0] = cc; 277 | allowShorter = depth1 == MIN_P1LENGTH_PRE && preMoveLen != 0; 278 | 279 | if (nodeUD[depth1 + 1].setWithPrun(cc, depth1) 280 | && phase1(nodeUD[depth1 + 1], ssym, depth1, -1) == 0) { 281 | return 0; 282 | } 283 | } 284 | 285 | if (maxl == 0 || preMoveLen + MIN_P1LENGTH_PRE >= length1) { 286 | return 1; 287 | } 288 | 289 | int skipMoves = CubieCube.getSkipMoves(ssym); 290 | if (maxl == 1 || preMoveLen + 1 + MIN_P1LENGTH_PRE >= length1) { //last pre move 291 | skipMoves |= 0x36FB7; // 11 0110 1111 1011 0111 292 | } 293 | 294 | lm = lm / 3 * 3; 295 | for (int m = 0; m < 18; m++) { 296 | if (m == lm || m == lm - 9 || m == lm + 9) { 297 | m += 2; 298 | continue; 299 | } 300 | if (isRec && m != preMoves[maxPreMoves - maxl] || (skipMoves & 1 << m) != 0) { 301 | continue; 302 | } 303 | CubieCube.CornMult(CubieCube.moveCube[m], cc, preMoveCubes[maxl]); 304 | CubieCube.EdgeMult(CubieCube.moveCube[m], cc, preMoveCubes[maxl]); 305 | preMoves[maxPreMoves - maxl] = m; 306 | int ret = phase1PreMoves(maxl - 1, m, preMoveCubes[maxl], ssym & (int) CubieCube.moveCubeSym[m]); 307 | if (ret == 0) { 308 | return 0; 309 | } 310 | } 311 | return 1; 312 | } 313 | 314 | protected String search() { 315 | for (length1 = isRec ? length1 : 0; length1 < solLen; length1++) { 316 | maxDep2 = Math.min(MAX_DEPTH2, solLen - length1 - 1); 317 | for (urfIdx = isRec ? urfIdx : 0; urfIdx < 6; urfIdx++) { 318 | if ((conjMask & 1 << urfIdx) != 0) { 319 | continue; 320 | } 321 | if (phase1PreMoves(maxPreMoves, -30, urfCubieCube[urfIdx], (int) (selfSym & 0xffff)) == 0) { 322 | return solution == null ? "Error 8" : solution.toString(); 323 | } 324 | } 325 | } 326 | return solution == null ? "Error 7" : solution.toString(); 327 | } 328 | 329 | /** 330 | * @return 331 | * 0: Found or Probe limit exceeded 332 | * 1: at least 1 + maxDep2 moves away, Try next power 333 | * 2: at least 2 + maxDep2 moves away, Try next axis 334 | */ 335 | protected int initPhase2Pre() { 336 | isRec = false; 337 | if (probe >= (solution == null ? probeMax : probeMin)) { 338 | return 0; 339 | } 340 | ++probe; 341 | 342 | for (int i = valid1; i < depth1; i++) { 343 | CubieCube.CornMult(phase1Cubie[i], CubieCube.moveCube[move[i]], phase1Cubie[i + 1]); 344 | CubieCube.EdgeMult(phase1Cubie[i], CubieCube.moveCube[move[i]], phase1Cubie[i + 1]); 345 | } 346 | valid1 = depth1; 347 | 348 | int p2corn = phase1Cubie[depth1].getCPermSym(); 349 | int p2csym = p2corn & 0xf; 350 | p2corn >>= 4; 351 | int p2edge = phase1Cubie[depth1].getEPermSym(); 352 | int p2esym = p2edge & 0xf; 353 | p2edge >>= 4; 354 | int p2mid = phase1Cubie[depth1].getMPerm(); 355 | int edgei = CubieCube.getPermSymInv(p2edge, p2esym, false); 356 | int corni = CubieCube.getPermSymInv(p2corn, p2csym, true); 357 | 358 | int lastMove = depth1 == 0 ? -1 : move[depth1 - 1]; 359 | int lastPre = preMoveLen == 0 ? -1 : preMoves[preMoveLen - 1]; 360 | 361 | int ret = 0; 362 | int p2switchMax = (preMoveLen == 0 ? 1 : 2) * (depth1 == 0 ? 1 : 2); 363 | for (int p2switch = 0, p2switchMask = (1 << p2switchMax) - 1; 364 | p2switch < p2switchMax; p2switch++) { 365 | // 0 normal; 1 lastmove; 2 lastmove + premove; 3 premove 366 | if ((p2switchMask >> p2switch & 1) != 0) { 367 | p2switchMask &= ~(1 << p2switch); 368 | ret = initPhase2(p2corn, p2csym, p2edge, p2esym, p2mid, edgei, corni); 369 | if (ret == 0 || ret > 2) { 370 | break; 371 | } else if (ret == 2) { 372 | p2switchMask &= 0x4 << p2switch; // 0->2; 1=>3; 2=>N/A 373 | } 374 | } 375 | if (p2switchMask == 0) { 376 | break; 377 | } 378 | if ((p2switch & 1) == 0 && depth1 > 0) { 379 | int m = Util.std2ud[lastMove / 3 * 3 + 1]; 380 | move[depth1 - 1] = Util.ud2std[m] * 2 - move[depth1 - 1]; 381 | 382 | p2mid = CoordCube.MPermMove[p2mid][m]; 383 | p2corn = CoordCube.CPermMove[p2corn][CubieCube.SymMoveUD[p2csym][m]]; 384 | p2csym = CubieCube.SymMult[p2corn & 0xf][p2csym]; 385 | p2corn >>= 4; 386 | p2edge = CoordCube.EPermMove[p2edge][CubieCube.SymMoveUD[p2esym][m]]; 387 | p2esym = CubieCube.SymMult[p2edge & 0xf][p2esym]; 388 | p2edge >>= 4; 389 | corni = CubieCube.getPermSymInv(p2corn, p2csym, true); 390 | edgei = CubieCube.getPermSymInv(p2edge, p2esym, false); 391 | } else if (preMoveLen > 0) { 392 | int m = Util.std2ud[lastPre / 3 * 3 + 1]; 393 | preMoves[preMoveLen - 1] = Util.ud2std[m] * 2 - preMoves[preMoveLen - 1]; 394 | 395 | p2mid = CubieCube.MPermInv[CoordCube.MPermMove[CubieCube.MPermInv[p2mid]][m]]; 396 | p2corn = CoordCube.CPermMove[corni >> 4][CubieCube.SymMoveUD[corni & 0xf][m]]; 397 | corni = p2corn & ~0xf | CubieCube.SymMult[p2corn & 0xf][corni & 0xf]; 398 | p2corn = CubieCube.getPermSymInv(corni >> 4, corni & 0xf, true); 399 | p2csym = p2corn & 0xf; 400 | p2corn >>= 4; 401 | p2edge = CoordCube.EPermMove[edgei >> 4][CubieCube.SymMoveUD[edgei & 0xf][m]]; 402 | edgei = p2edge & ~0xf | CubieCube.SymMult[p2edge & 0xf][edgei & 0xf]; 403 | p2edge = CubieCube.getPermSymInv(edgei >> 4, edgei & 0xf, false); 404 | p2esym = p2edge & 0xf; 405 | p2edge >>= 4; 406 | } 407 | } 408 | if (depth1 > 0) { 409 | move[depth1 - 1] = lastMove; 410 | } 411 | if (preMoveLen > 0) { 412 | preMoves[preMoveLen - 1] = lastPre; 413 | } 414 | return ret == 0 ? 0 : 2; 415 | } 416 | 417 | protected int initPhase2(int p2corn, int p2csym, int p2edge, int p2esym, int p2mid, int edgei, int corni) { 418 | int prun = Math.max( 419 | CoordCube.getPruning(CoordCube.EPermCCombPPrun, 420 | (edgei >> 4) * CoordCube.N_COMB + CoordCube.CCombPConj[CubieCube.Perm2CombP[corni >> 4] & 0xff][CubieCube.SymMultInv[edgei & 0xf][corni & 0xf]]), 421 | Math.max( 422 | CoordCube.getPruning(CoordCube.EPermCCombPPrun, 423 | p2edge * CoordCube.N_COMB + CoordCube.CCombPConj[CubieCube.Perm2CombP[p2corn] & 0xff][CubieCube.SymMultInv[p2esym][p2csym]]), 424 | CoordCube.getPruning(CoordCube.MCPermPrun, 425 | p2corn * CoordCube.N_MPERM + CoordCube.MPermConj[p2mid][p2csym]))); 426 | 427 | if (prun > maxDep2) { 428 | return prun - maxDep2; 429 | } 430 | 431 | int depth2; 432 | for (depth2 = maxDep2; depth2 >= prun; depth2--) { 433 | int ret = phase2(p2edge, p2esym, p2corn, p2csym, p2mid, depth2, depth1, 10); 434 | if (ret < 0) { 435 | break; 436 | } 437 | depth2 -= ret; 438 | solLen = 0; 439 | solution = new Util.Solution(); 440 | solution.setArgs(verbose, urfIdx, depth1); 441 | for (int i = 0; i < depth1 + depth2; i++) { 442 | solution.appendSolMove(move[i]); 443 | } 444 | for (int i = preMoveLen - 1; i >= 0; i--) { 445 | solution.appendSolMove(preMoves[i]); 446 | } 447 | solLen = solution.length; 448 | } 449 | 450 | if (depth2 != maxDep2) { //At least one solution has been found. 451 | maxDep2 = Math.min(MAX_DEPTH2, solLen - length1 - 1); 452 | return probe >= probeMin ? 0 : 1; 453 | } 454 | return 1; 455 | } 456 | 457 | /** 458 | * @return 459 | * 0: Found or Probe limit exceeded 460 | * 1: Try Next Power 461 | * 2: Try Next Axis 462 | */ 463 | protected int phase1(CoordCube node, int ssym, int maxl, int lm) { 464 | if (node.prun == 0 && maxl < 5) { 465 | if (allowShorter || maxl == 0) { 466 | depth1 -= maxl; 467 | int ret = initPhase2Pre(); 468 | depth1 += maxl; 469 | return ret; 470 | } else { 471 | return 1; 472 | } 473 | } 474 | 475 | int skipMoves = CubieCube.getSkipMoves(ssym); 476 | 477 | for (int axis = 0; axis < 18; axis += 3) { 478 | if (axis == lm || axis == lm - 9) { 479 | continue; 480 | } 481 | for (int power = 0; power < 3; power++) { 482 | int m = axis + power; 483 | 484 | if (isRec && m != move[depth1 - maxl] 485 | || skipMoves != 0 && (skipMoves & 1 << m) != 0) { 486 | continue; 487 | } 488 | 489 | int prun = nodeUD[maxl].doMovePrun(node, m, true); 490 | if (prun > maxl) { 491 | break; 492 | } else if (prun == maxl) { 493 | continue; 494 | } 495 | 496 | if (USE_CONJ_PRUN) { 497 | prun = nodeUD[maxl].doMovePrunConj(node, m); 498 | if (prun > maxl) { 499 | break; 500 | } else if (prun == maxl) { 501 | continue; 502 | } 503 | } 504 | 505 | move[depth1 - maxl] = m; 506 | valid1 = Math.min(valid1, depth1 - maxl); 507 | int ret = phase1(nodeUD[maxl], ssym & (int) CubieCube.moveCubeSym[m], maxl - 1, axis); 508 | if (ret == 0) { 509 | return 0; 510 | } else if (ret >= 2) { 511 | break; 512 | } 513 | } 514 | } 515 | return 1; 516 | } 517 | 518 | protected String searchopt() { 519 | int maxprun1 = 0; 520 | int maxprun2 = 0; 521 | for (int i = 0; i < 6; i++) { 522 | urfCoordCube[i].calcPruning(false); 523 | if (i < 3) { 524 | maxprun1 = Math.max(maxprun1, urfCoordCube[i].prun); 525 | } else { 526 | maxprun2 = Math.max(maxprun2, urfCoordCube[i].prun); 527 | } 528 | } 529 | urfIdx = maxprun2 > maxprun1 ? 3 : 0; 530 | phase1Cubie[0] = urfCubieCube[urfIdx]; 531 | for (length1 = isRec ? length1 : 0; length1 < solLen; length1++) { 532 | CoordCube ud = urfCoordCube[0 + urfIdx]; 533 | CoordCube rl = urfCoordCube[1 + urfIdx]; 534 | CoordCube fb = urfCoordCube[2 + urfIdx]; 535 | 536 | if (ud.prun <= length1 && rl.prun <= length1 && fb.prun <= length1 537 | && phase1opt(ud, rl, fb, selfSym, length1, -1) == 0) { 538 | return solution == null ? "Error 8" : solution.toString(); 539 | } 540 | } 541 | return solution == null ? "Error 7" : solution.toString(); 542 | } 543 | 544 | /** 545 | * @return 546 | * 0: Found or Probe limit exceeded 547 | * 1: Try Next Power 548 | * 2: Try Next Axis 549 | */ 550 | protected int phase1opt(CoordCube ud, CoordCube rl, CoordCube fb, long ssym, int maxl, int lm) { 551 | if (ud.prun == 0 && rl.prun == 0 && fb.prun == 0 && maxl < 5) { 552 | maxDep2 = maxl; 553 | depth1 = length1 - maxl; 554 | return initPhase2Pre() == 0 ? 0 : 1; 555 | } 556 | 557 | int skipMoves = CubieCube.getSkipMoves(ssym); 558 | 559 | for (int axis = 0; axis < 18; axis += 3) { 560 | if (axis == lm || axis == lm - 9) { 561 | continue; 562 | } 563 | for (int power = 0; power < 3; power++) { 564 | int m = axis + power; 565 | 566 | if (isRec && m != move[length1 - maxl] 567 | || skipMoves != 0 && (skipMoves & 1 << m) != 0) { 568 | continue; 569 | } 570 | 571 | // UD Axis 572 | int prun_ud = Math.max(nodeUD[maxl].doMovePrun(ud, m, false), 573 | USE_CONJ_PRUN ? nodeUD[maxl].doMovePrunConj(ud, m) : 0); 574 | if (prun_ud > maxl) { 575 | break; 576 | } else if (prun_ud == maxl) { 577 | continue; 578 | } 579 | 580 | // RL Axis 581 | m = CubieCube.urfMove[2][m]; 582 | 583 | int prun_rl = Math.max(nodeRL[maxl].doMovePrun(rl, m, false), 584 | USE_CONJ_PRUN ? nodeRL[maxl].doMovePrunConj(rl, m) : 0); 585 | if (prun_rl > maxl) { 586 | break; 587 | } else if (prun_rl == maxl) { 588 | continue; 589 | } 590 | 591 | // FB Axis 592 | m = CubieCube.urfMove[2][m]; 593 | 594 | int prun_fb = Math.max(nodeFB[maxl].doMovePrun(fb, m, false), 595 | USE_CONJ_PRUN ? nodeFB[maxl].doMovePrunConj(fb, m) : 0); 596 | if (prun_ud == prun_rl && prun_rl == prun_fb && prun_fb != 0) { 597 | prun_fb++; 598 | } 599 | 600 | if (prun_fb > maxl) { 601 | break; 602 | } else if (prun_fb == maxl) { 603 | continue; 604 | } 605 | 606 | m = CubieCube.urfMove[2][m]; 607 | 608 | move[length1 - maxl] = m; 609 | valid1 = Math.min(valid1, length1 - maxl); 610 | int ret = phase1opt(nodeUD[maxl], nodeRL[maxl], nodeFB[maxl], ssym & CubieCube.moveCubeSym[m], maxl - 1, axis); 611 | if (ret == 0) { 612 | return 0; 613 | } 614 | } 615 | } 616 | return 1; 617 | } 618 | 619 | //-1: no solution found 620 | // X: solution with X moves shorter than expectation. Hence, the length of the solution is depth - X 621 | protected int phase2(int edge, int esym, int corn, int csym, int mid, int maxl, int depth, int lm) { 622 | if (edge == 0 && corn == 0 && mid == 0) { 623 | return maxl; 624 | } 625 | int moveMask = Util.ckmv2bit[lm]; 626 | for (int m = 0; m < 10; m++) { 627 | if ((moveMask >> m & 1) != 0) { 628 | m += 0x42 >> m & 3; 629 | continue; 630 | } 631 | int midx = CoordCube.MPermMove[mid][m]; 632 | int cornx = CoordCube.CPermMove[corn][CubieCube.SymMoveUD[csym][m]]; 633 | int csymx = CubieCube.SymMult[cornx & 0xf][csym]; 634 | cornx >>= 4; 635 | int edgex = CoordCube.EPermMove[edge][CubieCube.SymMoveUD[esym][m]]; 636 | int esymx = CubieCube.SymMult[edgex & 0xf][esym]; 637 | edgex >>= 4; 638 | int edgei = CubieCube.getPermSymInv(edgex, esymx, false); 639 | int corni = CubieCube.getPermSymInv(cornx, csymx, true); 640 | 641 | int prun = CoordCube.getPruning(CoordCube.EPermCCombPPrun, 642 | (edgei >> 4) * CoordCube.N_COMB + CoordCube.CCombPConj[CubieCube.Perm2CombP[corni >> 4] & 0xff][CubieCube.SymMultInv[edgei & 0xf][corni & 0xf]]); 643 | if (prun > maxl + 1) { 644 | return maxl - prun + 1; 645 | } else if (prun >= maxl) { 646 | m += 0x42 >> m & 3 & (maxl - prun); 647 | continue; 648 | } 649 | prun = Math.max( 650 | CoordCube.getPruning(CoordCube.MCPermPrun, 651 | cornx * CoordCube.N_MPERM + CoordCube.MPermConj[midx][csymx]), 652 | CoordCube.getPruning(CoordCube.EPermCCombPPrun, 653 | edgex * CoordCube.N_COMB + CoordCube.CCombPConj[CubieCube.Perm2CombP[cornx] & 0xff][CubieCube.SymMultInv[esymx][csymx]])); 654 | if (prun >= maxl) { 655 | m += 0x42 >> m & 3 & (maxl - prun); 656 | continue; 657 | } 658 | int ret = phase2(edgex, esymx, cornx, csymx, midx, maxl - 1, depth + 1, m); 659 | if (ret >= 0) { 660 | move[depth] = Util.ud2std[m]; 661 | return ret; 662 | } 663 | if (ret < -2) { 664 | break; 665 | } 666 | if (ret < -1) { 667 | m += 0x42 >> m & 3; 668 | } 669 | } 670 | return -1; 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /src/Tools.java: -------------------------------------------------------------------------------- 1 | package cs.min2phase; 2 | 3 | import java.util.Random; 4 | import java.io.DataInput; 5 | import java.io.DataOutput; 6 | import java.io.IOException; 7 | 8 | /** 9 | * Some useful functions. 10 | */ 11 | public class Tools { 12 | 13 | private static Random gen = new Random(); 14 | 15 | private static void read(char[] arr, DataInput in) throws IOException { 16 | for (int i = 0; i < arr.length; i++) { 17 | arr[i] = in.readChar(); 18 | } 19 | } 20 | 21 | private static void read(int[] arr, DataInput in) throws IOException { 22 | for (int i = 0; i < arr.length; i++) { 23 | arr[i] = in.readInt(); 24 | } 25 | } 26 | 27 | private static void read(char[][] arr, DataInput in) throws IOException { 28 | for (int i = 0; i < arr.length; i++) { 29 | read(arr[i], in); 30 | } 31 | } 32 | 33 | private static void read(int[][] arr, DataInput in) throws IOException { 34 | for (int i = 0; i < arr.length; i++) { 35 | read(arr[i], in); 36 | } 37 | } 38 | 39 | private static void write(char[] arr, DataOutput out) throws IOException { 40 | for (int i = 0; i < arr.length; i++) { 41 | out.writeChar(arr[i]); 42 | } 43 | } 44 | 45 | private static void write(int[] arr, DataOutput out) throws IOException { 46 | for (int i = 0; i < arr.length; i++) { 47 | out.writeInt(arr[i]); 48 | } 49 | } 50 | 51 | private static void write(char[][] arr, DataOutput out) throws IOException { 52 | for (int i = 0; i < arr.length; i++) { 53 | write(arr[i], out); 54 | } 55 | } 56 | 57 | private static void write(int[][] arr, DataOutput out) throws IOException { 58 | for (int i = 0; i < arr.length; i++) { 59 | write(arr[i], out); 60 | } 61 | } 62 | 63 | protected Tools() {} 64 | 65 | /** 66 | * initializing from cached tables(move table, pruning table, etc.) 67 | * 68 | * @param in 69 | * Where to read tables. 70 | * 71 | * @see cs.min2phase.Tools#saveTo(java.io.DataOutput) 72 | */ 73 | public static void initFrom(DataInput in) throws IOException { 74 | if (Search.inited && CoordCube.initLevel == 2) { 75 | return; 76 | } 77 | CubieCube.initMove(); 78 | CubieCube.initSym(); 79 | 80 | read(CubieCube.FlipS2R, in); 81 | read(CubieCube.TwistS2R, in); 82 | read(CubieCube.EPermS2R, in); 83 | read(CubieCube.FlipR2S, in); 84 | read(CubieCube.TwistR2S, in); 85 | read(CubieCube.EPermR2S, in); 86 | in.readFully(CubieCube.Perm2CombP); 87 | in.readFully(CubieCube.MPermInv); 88 | read(CubieCube.PermInvEdgeSym, in); 89 | 90 | read(CoordCube.UDSliceMove, in); 91 | read(CoordCube.TwistMove, in); 92 | read(CoordCube.FlipMove, in); 93 | read(CoordCube.UDSliceConj, in); 94 | read(CoordCube.UDSliceTwistPrun, in); 95 | read(CoordCube.UDSliceFlipPrun, in); 96 | read(CoordCube.CPermMove, in); 97 | read(CoordCube.EPermMove, in); 98 | read(CoordCube.MPermMove, in); 99 | read(CoordCube.MPermConj, in); 100 | read(CoordCube.CCombPConj, in); 101 | read(CoordCube.MCPermPrun, in); 102 | read(CoordCube.EPermCCombPPrun, in); 103 | 104 | if (Search.USE_TWIST_FLIP_PRUN) { 105 | read(CubieCube.FlipS2RF, in); 106 | read(CoordCube.TwistFlipPrun, in); 107 | } 108 | Search.inited = true; 109 | CoordCube.initLevel = 2; 110 | } 111 | 112 | /** 113 | * cache tables (move tables, pruning table, etc.), and read it while initializing. 114 | * 115 | * @param out 116 | * Where to cache tables. 117 | * 118 | * @see cs.min2phase.Tools#initFrom(java.io.DataInput) 119 | */ 120 | public static void saveTo(DataOutput out) throws IOException { 121 | Search.init(); 122 | while (CoordCube.initLevel != 2) { 123 | CoordCube.init(true); 124 | } // w/o TFP w/ TFP 125 | write(CubieCube.FlipS2R, out); // 672 126 | write(CubieCube.TwistS2R, out); // 648 127 | write(CubieCube.EPermS2R, out); // 5,536 128 | write(CubieCube.FlipR2S, out); // 3,072 129 | write(CubieCube.TwistR2S, out); // 3,281 130 | write(CubieCube.EPermR2S, out); // 20,160 131 | out.write(CubieCube.Perm2CombP); // 2,768 132 | out.write(CubieCube.MPermInv); 133 | write(CubieCube.PermInvEdgeSym, out); // 5,536 134 | 135 | write(CoordCube.UDSliceMove, out); // 17,820 136 | write(CoordCube.TwistMove, out); // 11,664 137 | write(CoordCube.FlipMove, out); // 12,096 138 | write(CoordCube.UDSliceConj, out); // 7,920 139 | write(CoordCube.UDSliceTwistPrun, out); // 80,192 140 | write(CoordCube.UDSliceFlipPrun, out); // 83,164 141 | write(CoordCube.CPermMove, out); // 55,360 142 | write(CoordCube.EPermMove, out); // 55,360 143 | write(CoordCube.MPermMove, out); // 480 144 | write(CoordCube.MPermConj, out); // 768 145 | write(CoordCube.CCombPConj, out); // 2,240 + 2,240 146 | write(CoordCube.MCPermPrun, out); // 33,220 147 | write(CoordCube.EPermCCombPPrun, out); // 96,884 + 96,880 148 | 149 | if (Search.USE_TWIST_FLIP_PRUN) { 150 | write(CubieCube.FlipS2RF, out); // + 5,376 151 | write(CoordCube.TwistFlipPrun, out); // + 331,780 152 | } // = 498,841 + 436,276 = 935,117 153 | } 154 | 155 | /** 156 | * Set Random Source. 157 | * @param gen new random source. 158 | */ 159 | public static void setRandomSource(Random gen) { 160 | Tools.gen = gen; 161 | } 162 | 163 | /** 164 | * Generates a random cube.
165 | * 166 | * The random source can be set by {@link cs.min2phase.Tools#setRandomSource(java.util.Random)} 167 | * 168 | * @return A random cube in the string representation. Each cube of the cube space has almost (depends on randomSource) the same probability. 169 | * 170 | * @see cs.min2phase.Tools#setRandomSource(java.util.Random) 171 | * @see cs.min2phase.Search#solution(java.lang.String facelets, int maxDepth, long timeOut, long timeMin, int verbose) 172 | */ 173 | public static String randomCube() { 174 | return randomState(STATE_RANDOM, STATE_RANDOM, STATE_RANDOM, STATE_RANDOM, gen); 175 | } 176 | 177 | public static String randomCube(Random gen) { 178 | return randomState(STATE_RANDOM, STATE_RANDOM, STATE_RANDOM, STATE_RANDOM, gen); 179 | } 180 | 181 | private static int resolveOri(byte[] arr, int base) { 182 | int sum = 0, idx = 0, lastUnknown = -1; 183 | for (int i = 0; i < arr.length; i++) { 184 | if (arr[i] == -1) { 185 | arr[i] = (byte) gen.nextInt(base); 186 | lastUnknown = i; 187 | } 188 | sum += arr[i]; 189 | } 190 | if (sum % base != 0 && lastUnknown != -1) { 191 | arr[lastUnknown] = (byte) ((30 + arr[lastUnknown] - sum) % base); 192 | } 193 | for (int i = 0; i < arr.length - 1; i++) { 194 | idx *= base; 195 | idx += arr[i]; 196 | } 197 | return idx; 198 | } 199 | 200 | private static int countUnknown(byte[] arr) { 201 | if (arr == STATE_SOLVED) { 202 | return 0; 203 | } 204 | int cnt = 0; 205 | for (int i = 0; i < arr.length; i++) { 206 | if (arr[i] == -1) { 207 | cnt++; 208 | } 209 | } 210 | return cnt; 211 | } 212 | 213 | private static int resolvePerm(byte[] arr, int cntU, int parity) { 214 | if (arr == STATE_SOLVED) { 215 | return 0; 216 | } else if (arr == STATE_RANDOM) { 217 | return parity == -1 ? gen.nextInt(2) : parity; 218 | } 219 | byte[] val = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; 220 | for (int i = 0; i < arr.length; i++) { 221 | if (arr[i] != -1) { 222 | val[arr[i]] = -1; 223 | } 224 | } 225 | int idx = 0; 226 | for (int i = 0; i < arr.length; i++) { 227 | if (val[i] != -1) { 228 | int j = gen.nextInt(idx + 1); 229 | byte temp = val[i]; 230 | val[idx++] = val[j]; 231 | val[j] = temp; 232 | } 233 | } 234 | int last = -1; 235 | for (idx = 0; idx < arr.length && cntU > 0; idx++) { 236 | if (arr[idx] == -1) { 237 | if (cntU == 2) { 238 | last = idx; 239 | } 240 | arr[idx] = val[--cntU]; 241 | } 242 | } 243 | int p = Util.getNParity(getNPerm(arr, arr.length), arr.length); 244 | if (p == 1 - parity && last != -1) { 245 | byte temp = arr[idx - 1]; 246 | arr[idx - 1] = arr[last]; 247 | arr[last] = temp; 248 | } 249 | return p; 250 | } 251 | 252 | static int getNPerm(byte[] arr, int n) { 253 | int idx = 0; 254 | for (int i = 0; i < n; i++) { 255 | idx *= (n - i); 256 | for (int j = i + 1; j < n; j++) { 257 | if (arr[j] < arr[i]) { 258 | idx++; 259 | } 260 | } 261 | } 262 | return idx; 263 | } 264 | 265 | protected static final byte[] STATE_RANDOM = null; 266 | protected static final byte[] STATE_SOLVED = new byte[0]; 267 | 268 | protected static String randomState(byte[] cp, byte[] co, byte[] ep, byte[] eo, Random gen) { 269 | int parity; 270 | int cntUE = ep == STATE_RANDOM ? 12 : countUnknown(ep); 271 | int cntUC = cp == STATE_RANDOM ? 8 : countUnknown(cp); 272 | int cpVal, epVal; 273 | if (cntUE < 2) { //ep != STATE_RANDOM 274 | if (ep == STATE_SOLVED) { 275 | epVal = parity = 0; 276 | } else { 277 | parity = resolvePerm(ep, cntUE, -1); 278 | epVal = getNPerm(ep, 12); 279 | } 280 | if (cp == STATE_SOLVED) { 281 | cpVal = 0; 282 | } else if (cp == STATE_RANDOM) { 283 | do { 284 | cpVal = gen.nextInt(40320); 285 | } while (Util.getNParity(cpVal, 8) != parity); 286 | } else { 287 | resolvePerm(cp, cntUC, parity); 288 | cpVal = getNPerm(cp, 8); 289 | } 290 | } else { //ep != STATE_SOLVED 291 | if (cp == STATE_SOLVED) { 292 | cpVal = parity = 0; 293 | } else if (cp == STATE_RANDOM) { 294 | cpVal = gen.nextInt(40320); 295 | parity = Util.getNParity(cpVal, 8); 296 | } else { 297 | parity = resolvePerm(cp, cntUC, -1); 298 | cpVal = getNPerm(cp, 8); 299 | } 300 | if (ep == STATE_RANDOM) { 301 | do { 302 | epVal = gen.nextInt(479001600); 303 | } while (Util.getNParity(epVal, 12) != parity); 304 | } else { 305 | resolvePerm(ep, cntUE, parity); 306 | epVal = getNPerm(ep, 12); 307 | } 308 | } 309 | return Util.toFaceCube( 310 | new CubieCube( 311 | cpVal, 312 | co == STATE_RANDOM ? gen.nextInt(2187) : (co == STATE_SOLVED ? 0 : resolveOri(co, 3)), 313 | epVal, 314 | eo == STATE_RANDOM ? gen.nextInt(2048) : (eo == STATE_SOLVED ? 0 : resolveOri(eo, 2)))); 315 | } 316 | 317 | 318 | public static String randomLastLayer() { 319 | return randomState( 320 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7}, 321 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0}, 322 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11}, 323 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0}, gen); 324 | } 325 | 326 | public static String randomLastSlot() { 327 | return randomState( 328 | new byte[] { -1, -1, -1, -1, -1, 5, 6, 7}, 329 | new byte[] { -1, -1, -1, -1, -1, 0, 0, 0}, 330 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7, -1, 9, 10, 11}, 331 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0, -1, 0, 0, 0}, gen); 332 | } 333 | 334 | public static String randomZBLastLayer() { 335 | return randomState( 336 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7}, 337 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0}, 338 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11}, 339 | STATE_SOLVED, gen); 340 | } 341 | 342 | public static String randomCornerOfLastLayer() { 343 | return randomState( 344 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7}, 345 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0}, 346 | STATE_SOLVED, 347 | STATE_SOLVED, gen); 348 | } 349 | 350 | public static String randomEdgeOfLastLayer() { 351 | return randomState( 352 | STATE_SOLVED, 353 | STATE_SOLVED, 354 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11}, 355 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0}, gen); 356 | } 357 | 358 | public static String randomCrossSolved() { 359 | return randomState( 360 | STATE_RANDOM, 361 | STATE_RANDOM, 362 | new byte[] { -1, -1, -1, -1, 4, 5, 6, 7, -1, -1, -1, -1}, 363 | new byte[] { -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1}, gen); 364 | } 365 | 366 | public static String randomEdgeSolved() { 367 | return randomState( 368 | STATE_RANDOM, 369 | STATE_RANDOM, 370 | STATE_SOLVED, 371 | STATE_SOLVED, gen); 372 | } 373 | 374 | public static String randomCornerSolved() { 375 | return randomState( 376 | STATE_SOLVED, 377 | STATE_SOLVED, 378 | STATE_RANDOM, 379 | STATE_RANDOM, gen); 380 | } 381 | 382 | public static String superFlip() { 383 | return Util.toFaceCube(new CubieCube(0, 0, 0, 2047)); 384 | } 385 | 386 | 387 | public static String fromScramble(int[] scramble) { 388 | CubieCube c1 = new CubieCube(); 389 | CubieCube c2 = new CubieCube(); 390 | CubieCube tmp; 391 | for (int i = 0; i < scramble.length; i++) { 392 | CubieCube.CornMult(c1, CubieCube.moveCube[scramble[i]], c2); 393 | CubieCube.EdgeMult(c1, CubieCube.moveCube[scramble[i]], c2); 394 | tmp = c1; c1 = c2; c2 = tmp; 395 | } 396 | return Util.toFaceCube(c1); 397 | } 398 | 399 | /** 400 | * Convert a scramble string to its cube definition string. 401 | * 402 | * @param s scramble string. Only outer moves (U, R, F, D, L, B, U2, R2, ...) are recognized. 403 | * @return cube definition string, which represent the state generated by the scramble
404 | */ 405 | public static String fromScramble(String s) { 406 | int[] arr = new int[s.length()]; 407 | int j = 0; 408 | int axis = -1; 409 | for (int i = 0, length = s.length(); i < length; i++) { 410 | switch (s.charAt(i)) { 411 | case 'U': axis = 0; break; 412 | case 'R': axis = 3; break; 413 | case 'F': axis = 6; break; 414 | case 'D': axis = 9; break; 415 | case 'L': axis = 12; break; 416 | case 'B': axis = 15; break; 417 | case ' ': 418 | if (axis != -1) { 419 | arr[j++] = axis; 420 | } 421 | axis = -1; 422 | break; 423 | case '2': axis++; break; 424 | case '\'': axis += 2; break; 425 | default: continue; 426 | } 427 | 428 | } 429 | if (axis != -1) arr[j++] = axis; 430 | int[] ret = new int[j]; 431 | while (--j >= 0) { 432 | ret[j] = arr[j]; 433 | } 434 | return fromScramble(ret); 435 | } 436 | 437 | /** 438 | * Check whether the cube definition string s represents a solvable cube. 439 | * 440 | * @param facelets is the cube definition string , see {@link cs.min2phase.Search#solution(java.lang.String facelets, int maxDepth, long timeOut, long timeMin, int verbose)} 441 | * @return 0: Cube is solvable
442 | * -1: There is not exactly one facelet of each colour
443 | * -2: Not all 12 edges exist exactly once
444 | * -3: Flip error: One edge has to be flipped
445 | * -4: Not all 8 corners exist exactly once
446 | * -5: Twist error: One corner has to be twisted
447 | * -6: Parity error: Two corners or two edges have to be exchanged 448 | */ 449 | public static int verify(String facelets) { 450 | return new Search().verify(facelets); 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/Util.java: -------------------------------------------------------------------------------- 1 | package cs.min2phase; 2 | 3 | class Util { 4 | //Moves 5 | static final byte Ux1 = 0; 6 | static final byte Ux2 = 1; 7 | static final byte Ux3 = 2; 8 | static final byte Rx1 = 3; 9 | static final byte Rx2 = 4; 10 | static final byte Rx3 = 5; 11 | static final byte Fx1 = 6; 12 | static final byte Fx2 = 7; 13 | static final byte Fx3 = 8; 14 | static final byte Dx1 = 9; 15 | static final byte Dx2 = 10; 16 | static final byte Dx3 = 11; 17 | static final byte Lx1 = 12; 18 | static final byte Lx2 = 13; 19 | static final byte Lx3 = 14; 20 | static final byte Bx1 = 15; 21 | static final byte Bx2 = 16; 22 | static final byte Bx3 = 17; 23 | 24 | //Facelets 25 | static final byte U1 = 0; 26 | static final byte U2 = 1; 27 | static final byte U3 = 2; 28 | static final byte U4 = 3; 29 | static final byte U5 = 4; 30 | static final byte U6 = 5; 31 | static final byte U7 = 6; 32 | static final byte U8 = 7; 33 | static final byte U9 = 8; 34 | static final byte R1 = 9; 35 | static final byte R2 = 10; 36 | static final byte R3 = 11; 37 | static final byte R4 = 12; 38 | static final byte R5 = 13; 39 | static final byte R6 = 14; 40 | static final byte R7 = 15; 41 | static final byte R8 = 16; 42 | static final byte R9 = 17; 43 | static final byte F1 = 18; 44 | static final byte F2 = 19; 45 | static final byte F3 = 20; 46 | static final byte F4 = 21; 47 | static final byte F5 = 22; 48 | static final byte F6 = 23; 49 | static final byte F7 = 24; 50 | static final byte F8 = 25; 51 | static final byte F9 = 26; 52 | static final byte D1 = 27; 53 | static final byte D2 = 28; 54 | static final byte D3 = 29; 55 | static final byte D4 = 30; 56 | static final byte D5 = 31; 57 | static final byte D6 = 32; 58 | static final byte D7 = 33; 59 | static final byte D8 = 34; 60 | static final byte D9 = 35; 61 | static final byte L1 = 36; 62 | static final byte L2 = 37; 63 | static final byte L3 = 38; 64 | static final byte L4 = 39; 65 | static final byte L5 = 40; 66 | static final byte L6 = 41; 67 | static final byte L7 = 42; 68 | static final byte L8 = 43; 69 | static final byte L9 = 44; 70 | static final byte B1 = 45; 71 | static final byte B2 = 46; 72 | static final byte B3 = 47; 73 | static final byte B4 = 48; 74 | static final byte B5 = 49; 75 | static final byte B6 = 50; 76 | static final byte B7 = 51; 77 | static final byte B8 = 52; 78 | static final byte B9 = 53; 79 | 80 | //Colors 81 | static final byte U = 0; 82 | static final byte R = 1; 83 | static final byte F = 2; 84 | static final byte D = 3; 85 | static final byte L = 4; 86 | static final byte B = 5; 87 | 88 | static final byte[][] cornerFacelet = { 89 | { U9, R1, F3 }, { U7, F1, L3 }, { U1, L1, B3 }, { U3, B1, R3 }, 90 | { D3, F9, R7 }, { D1, L9, F7 }, { D7, B9, L7 }, { D9, R9, B7 } 91 | }; 92 | static final byte[][] edgeFacelet = { 93 | { U6, R2 }, { U8, F2 }, { U4, L2 }, { U2, B2 }, { D6, R8 }, { D2, F8 }, 94 | { D4, L8 }, { D8, B8 }, { F6, R4 }, { F4, L6 }, { B6, L4 }, { B4, R6 } 95 | }; 96 | 97 | static int[][] Cnk = new int[13][13]; 98 | static String[] move2str = { 99 | "U ", "U2", "U'", "R ", "R2", "R'", "F ", "F2", "F'", 100 | "D ", "D2", "D'", "L ", "L2", "L'", "B ", "B2", "B'" 101 | }; 102 | static int[] ud2std = {Ux1, Ux2, Ux3, Rx2, Fx2, Dx1, Dx2, Dx3, Lx2, Bx2, Rx1, Rx3, Fx1, Fx3, Lx1, Lx3, Bx1, Bx3}; 103 | static int[] std2ud = new int[18]; 104 | static int[] ckmv2bit = new int[11]; 105 | 106 | static class Solution { 107 | int length = 0; 108 | int depth1 = 0; 109 | int verbose = 0; 110 | int urfIdx = 0; 111 | int[] moves = new int[31]; 112 | 113 | Solution() {} 114 | 115 | void setArgs(int verbose, int urfIdx, int depth1) { 116 | this.verbose = verbose; 117 | this.urfIdx = urfIdx; 118 | this.depth1 = depth1; 119 | } 120 | 121 | void appendSolMove(int curMove) { 122 | if (length == 0) { 123 | moves[length++] = curMove; 124 | return; 125 | } 126 | int axisCur = curMove / 3; 127 | int axisLast = moves[length - 1] / 3; 128 | if (axisCur == axisLast) { 129 | int pow = (curMove % 3 + moves[length - 1] % 3 + 1) % 4; 130 | if (pow == 3) { 131 | length--; 132 | } else { 133 | moves[length - 1] = axisCur * 3 + pow; 134 | } 135 | return; 136 | } 137 | if (length > 1 138 | && axisCur % 3 == axisLast % 3 139 | && axisCur == moves[length - 2] / 3) { 140 | int pow = (curMove % 3 + moves[length - 2] % 3 + 1) % 4; 141 | if (pow == 3) { 142 | moves[length - 2] = moves[length - 1]; 143 | length--; 144 | } else { 145 | moves[length - 2] = axisCur * 3 + pow; 146 | } 147 | return; 148 | } 149 | moves[length++] = curMove; 150 | } 151 | 152 | public String toString() { 153 | StringBuffer sb = new StringBuffer(); 154 | int urf = (verbose & Search.INVERSE_SOLUTION) != 0 ? (urfIdx + 3) % 6 : urfIdx; 155 | if (urf < 3) { 156 | for (int s = 0; s < length; s++) { 157 | if ((verbose & Search.USE_SEPARATOR) != 0 && s == depth1) { 158 | sb.append(". "); 159 | } 160 | sb.append(move2str[CubieCube.urfMove[urf][moves[s]]]).append(' '); 161 | } 162 | } else { 163 | for (int s = length - 1; s >= 0; s--) { 164 | sb.append(move2str[CubieCube.urfMove[urf][moves[s]]]).append(' '); 165 | if ((verbose & Search.USE_SEPARATOR) != 0 && s == depth1) { 166 | sb.append(". "); 167 | } 168 | } 169 | } 170 | if ((verbose & Search.APPEND_LENGTH) != 0) { 171 | sb.append("(").append(length).append("f)"); 172 | } 173 | return sb.toString(); 174 | } 175 | } 176 | 177 | static void toCubieCube(byte[] f, CubieCube ccRet) { 178 | byte ori; 179 | for (int i = 0; i < 8; i++) { 180 | ccRet.ca[i] = 0; 181 | } 182 | for (int i = 0; i < 12; i++) { 183 | ccRet.ea[i] = 0; 184 | } 185 | byte col1, col2; 186 | for (byte i = 0; i < 8; i++) { 187 | for (ori = 0; ori < 3; ori++){ 188 | if (f[cornerFacelet[i][ori]] == U || f[cornerFacelet[i][ori]] == D) 189 | break; 190 | } 191 | col1 = f[cornerFacelet[i][(ori + 1) % 3]]; 192 | col2 = f[cornerFacelet[i][(ori + 2) % 3]]; 193 | for (byte j = 0; j < 8; j++) { 194 | if (col1 == cornerFacelet[j][1] / 9 && col2 == cornerFacelet[j][2] / 9) { 195 | ccRet.ca[i] = (byte) (ori % 3 << 3 | j); 196 | break; 197 | } 198 | } 199 | } 200 | for (byte i = 0; i < 12; i++) { 201 | for (byte j = 0; j < 12; j++) { 202 | if (f[edgeFacelet[i][0]] == edgeFacelet[j][0] / 9 203 | && f[edgeFacelet[i][1]] == edgeFacelet[j][1] / 9) { 204 | ccRet.ea[i] = (byte) (j << 1); 205 | break; 206 | } 207 | if (f[edgeFacelet[i][0]] == edgeFacelet[j][1] / 9 208 | && f[edgeFacelet[i][1]] == edgeFacelet[j][0] / 9) { 209 | ccRet.ea[i] = (byte) (j << 1 | 1); 210 | break; 211 | } 212 | } 213 | } 214 | } 215 | 216 | static String toFaceCube(CubieCube cc) { 217 | char[] f = new char[54]; 218 | char[] ts = {'U', 'R', 'F', 'D', 'L', 'B'}; 219 | for (int i = 0; i < 54; i++) { 220 | f[i] = ts[i / 9]; 221 | } 222 | for (byte c = 0; c < 8; c++) { 223 | int j = cc.ca[c] & 0x7; 224 | int ori = cc.ca[c] >> 3; 225 | for (byte n = 0; n < 3; n++) { 226 | f[cornerFacelet[c][(n + ori) % 3]] = ts[cornerFacelet[j][n] / 9]; 227 | } 228 | } 229 | for (byte e = 0; e < 12; e++) { 230 | int j = cc.ea[e] >> 1; 231 | int ori = cc.ea[e] & 1; 232 | for (byte n = 0; n < 2; n++) { 233 | f[edgeFacelet[e][(n + ori) % 2]] = ts[edgeFacelet[j][n] / 9]; 234 | } 235 | } 236 | return new String(f); 237 | } 238 | 239 | static int getNParity(int idx, int n) { 240 | int p = 0; 241 | for (int i = n - 2; i >= 0; i--) { 242 | p ^= idx % (n - i); 243 | idx /= (n - i); 244 | } 245 | return p & 1; 246 | } 247 | 248 | static byte setVal(int val0, int val, boolean isEdge) { 249 | return (byte) (isEdge ? (val << 1 | val0 & 1) : (val | val0 & ~7)); 250 | } 251 | 252 | static int getVal(int val0, boolean isEdge) { 253 | return isEdge ? val0 >> 1 : val0 & 7; 254 | } 255 | 256 | static void setNPerm(byte[] arr, int idx, int n, boolean isEdge) { 257 | long val = 0xFEDCBA9876543210L; 258 | long extract = 0; 259 | for (int p = 2; p <= n; p++) { 260 | extract = extract << 4 | idx % p; 261 | idx /= p; 262 | } 263 | for (int i = 0; i < n - 1; i++) { 264 | int v = ((int) extract & 0xf) << 2; 265 | extract >>= 4; 266 | arr[i] = setVal(arr[i], (int) (val >> v & 0xf), isEdge); 267 | long m = (1L << v) - 1; 268 | val = val & m | val >> 4 & ~m; 269 | } 270 | arr[n - 1] = setVal(arr[n - 1], (int) (val & 0xf), isEdge); 271 | } 272 | 273 | static int getNPerm(byte[] arr, int n, boolean isEdge) { 274 | int idx = 0; 275 | long val = 0xFEDCBA9876543210L; 276 | for (int i = 0; i < n - 1; i++) { 277 | int v = getVal(arr[i], isEdge) << 2; 278 | idx = (n - i) * idx + (int) (val >> v & 0xf); 279 | val -= 0x1111111111111110L << v; 280 | } 281 | return idx; 282 | } 283 | 284 | static int getComb(byte[] arr, int mask, boolean isEdge) { 285 | int end = arr.length - 1; 286 | int idxC = 0, r = 4; 287 | for (int i = end; i >= 0; i--) { 288 | int perm = getVal(arr[i], isEdge); 289 | if ((perm & 0xc) == mask) { 290 | idxC += Cnk[i][r--]; 291 | } 292 | } 293 | return idxC; 294 | } 295 | 296 | static void setComb(byte[] arr, int idxC, int mask, boolean isEdge) { 297 | int end = arr.length - 1; 298 | int r = 4, fill = end; 299 | for (int i = end; i >= 0; i--) { 300 | if (idxC >= Cnk[i][r]) { 301 | idxC -= Cnk[i][r--]; 302 | arr[i] = setVal(arr[i], r | mask, isEdge); 303 | } else { 304 | if ((fill & 0xc) == mask) { 305 | fill -= 4; 306 | } 307 | arr[i] = setVal(arr[i], fill--, isEdge); 308 | } 309 | } 310 | } 311 | 312 | static { 313 | for (int i = 0; i < 18; i++) { 314 | std2ud[ud2std[i]] = i; 315 | } 316 | for (int i = 0; i < 10; i++) { 317 | int ix = ud2std[i] / 3; 318 | ckmv2bit[i] = 0; 319 | for (int j = 0; j < 10; j++) { 320 | int jx = ud2std[j] / 3; 321 | ckmv2bit[i] |= ((ix == jx) || ((ix % 3 == jx % 3) && (ix >= jx)) ? 1 : 0) << j; 322 | } 323 | } 324 | ckmv2bit[10] = 0; 325 | for (int i = 0; i < 13; i++) { 326 | Cnk[i][0] = Cnk[i][i] = 1; 327 | for (int j = 1; j < i; j++) { 328 | Cnk[i][j] = Cnk[i - 1][j - 1] + Cnk[i - 1][j]; 329 | } 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /test/test.java: -------------------------------------------------------------------------------- 1 | import java.util.Random; 2 | import java.io.*; 3 | import java.util.concurrent.*; 4 | import java.util.concurrent.atomic.*; 5 | import cs.min2phase.Tools; 6 | import cs.min2phase.Search; 7 | 8 | public class test { 9 | 10 | static void other() { 11 | } 12 | 13 | static long cumNProbe = 0L; 14 | static long maxNProbe = 0L; 15 | final static long PROBES_PER_NEXT = 1L; 16 | 17 | static int testOptimal(int maxl, int length, int[] arr, int lm, Search search, int verbose) { 18 | if (maxl == 0) { 19 | String sol = search.solution(Tools.fromScramble(arr), 100, 0, 0, verbose | Search.INVERSE_SOLUTION); 20 | long nProbe = 0; 21 | while (sol.startsWith("Error") || search.length() > length) { 22 | if (sol.startsWith("Error") && !sol.startsWith("Error 8")) { 23 | throw new RuntimeException(String.format("Cannot find the optimal solution: %s\n%s", sol, Tools.fromScramble(arr))); 24 | } 25 | sol = search.next(PROBES_PER_NEXT, 0, verbose | Search.INVERSE_SOLUTION); 26 | nProbe += search.numberOfProbes(); 27 | } 28 | if (nProbe > maxNProbe) { 29 | maxNProbe = nProbe; 30 | } 31 | cumNProbe += nProbe; 32 | assert Tools.fromScramble(sol).equals(Tools.fromScramble(arr)); 33 | return 1; 34 | } 35 | int ret = 0; 36 | for (int axis = 0; axis < 18; axis += 3) { 37 | if (axis == lm || axis == lm - 9) { 38 | continue; 39 | } 40 | for (int pow = 0; pow < 3; pow++) { 41 | arr[length] = axis + pow; 42 | ret += testOptimal(maxl - 1, length + 1, arr, axis, search, verbose); 43 | } 44 | } 45 | return ret; 46 | } 47 | 48 | static void testRandomOptimal(int length, Search search, Random gen, int verbose) { 49 | int[] arr = new int[length]; 50 | for (int i = 0; i < length; i++) { 51 | int axis = 0; 52 | do { 53 | axis = gen.nextInt(6) * 3; 54 | } while (i != 0 && (axis == arr[i - 1] / 3 * 3 || axis == arr[i - 1] / 3 * 3 - 9)); 55 | int pow = gen.nextInt(3); 56 | arr[i] = axis + pow; 57 | } 58 | 59 | String sol = search.solution(Tools.fromScramble(arr), 100, 0, 0, verbose | Search.INVERSE_SOLUTION); 60 | long nProbe = 0; 61 | while (sol.startsWith("Error") || search.length() > length) { 62 | if (sol.startsWith("Error") && !sol.startsWith("Error 8")) { 63 | throw new RuntimeException(String.format("Cannot find the optimal solution: %s\n%s", sol, Tools.fromScramble(arr))); 64 | } 65 | sol = search.next(PROBES_PER_NEXT, 0, verbose | Search.INVERSE_SOLUTION); 66 | nProbe += search.numberOfProbes(); 67 | } 68 | if (nProbe > maxNProbe) { 69 | maxNProbe = nProbe; 70 | } 71 | cumNProbe += nProbe; 72 | assert Tools.fromScramble(sol).equals(Tools.fromScramble(arr)); 73 | } 74 | 75 | static ArrayBlockingQueue jobQueue; 76 | 77 | static ArrayBlockingQueue retQueue; 78 | 79 | static class solvingThread extends Thread { 80 | int nSolves; 81 | int maxLength; 82 | int probeMax; 83 | int probeMin; 84 | int verbose; 85 | Search search = new Search(); 86 | 87 | solvingThread(int nSolves, int maxLength, int probeMax, int probeMin, int verbose) { 88 | this.nSolves = nSolves; 89 | this.maxLength = maxLength; 90 | this.probeMax = probeMax; 91 | this.probeMin = probeMin; 92 | this.verbose = verbose; 93 | setDaemon(true); 94 | } 95 | 96 | public void run() { 97 | try { 98 | while (true) { 99 | String cube = jobQueue.take(); 100 | long curTime = System.nanoTime(); 101 | String s = search.solution(cube, maxLength, probeMax, probeMin, verbose | search.INVERSE_SOLUTION); 102 | curTime = System.nanoTime() - curTime; 103 | String cube2 = Tools.fromScramble(s); 104 | assert(cube.equals(cube2)); 105 | retQueue.put(new long[] {search.length(), curTime, search.numberOfProbes()}); 106 | } 107 | } catch (InterruptedException e) { 108 | e.printStackTrace(); 109 | } 110 | } 111 | } 112 | 113 | static void multiThreadRandomSolving(int nThreads, int nSolves, int maxLength, int probeMax, int probeMin, int verbose) { 114 | jobQueue = new ArrayBlockingQueue(nSolves); 115 | retQueue = new ArrayBlockingQueue(nSolves); 116 | for (int i = 0; i < nThreads; i++) { 117 | new solvingThread(nSolves, maxLength, probeMax, probeMin, verbose).start(); 118 | } 119 | 120 | try { 121 | Tools.setRandomSource(new Random(42L)); 122 | for (int i = 0; i < nSolves; i++) { 123 | String cube = Tools.randomCube(); 124 | jobQueue.put(cube); 125 | } 126 | long minT = 1L << 62; 127 | long maxT = 0L; 128 | long totalTime = 0; 129 | int totalLength = 0; 130 | int[] lengthDis = new int[30]; 131 | long totalProbe2 = 0; 132 | long[] timeList = new long[nSolves]; 133 | long tm = System.nanoTime(); 134 | for (int i = 0; i < nSolves; i++) { 135 | long[] ret = retQueue.take(); 136 | int length = (int) ret[0]; 137 | long curTime = ret[1]; 138 | long nprobes = ret[2]; 139 | 140 | totalTime += curTime; 141 | totalProbe2 += nprobes; 142 | maxT = Math.max(maxT, curTime); 143 | minT = Math.min(minT, curTime); 144 | totalLength += length; 145 | lengthDis[length]++; 146 | timeList[i] = curTime; 147 | int x = i + 1; 148 | if (x % 100 == 0 || System.nanoTime() - tm > 100000000) { 149 | tm = System.nanoTime(); 150 | System.out.print(String.format("%6d AvgT: %6.3f ms, MaxT: %8.3f ms, MinT: %6.3f ms, AvgL: %6.3f, AvgP: %6.3f\r", x, 151 | (totalTime / 1000000d) / x, maxT / 1000000d, minT / 1000000d, totalLength / 1.0d / x, totalProbe2 / 1.0d / x)); 152 | } 153 | System.out.print(String.format("%6d AvgT: %6.3f ms, MaxT: %8.3f ms, MinT: %6.3f ms, AvgL: %6.3f, AvgP: %6.3f\r", x, 154 | (totalTime / 1000000d) / x, maxT / 1000000d, minT / 1000000d, totalLength / 1.0d / x, totalProbe2 / 1.0d / x)); 155 | } 156 | 157 | java.util.Arrays.sort(timeList); 158 | System.out.println( 159 | String.format( 160 | "\nAvgT: %6.3f ms\n" + 161 | "MaxT: %8.3f ms\n" + 162 | "MinT: %6.3f ms\n" + 163 | "L50T: %6.3f ms\n" + 164 | "L75T: %6.3f ms\n" + 165 | "L90T: %6.3f ms\n" + 166 | "L95T: %6.3f ms\n" + 167 | "L99T: %6.3f ms\n" + 168 | "AvgL: %6.3f\n" + 169 | "AvgP: %6.3f\n", 170 | (totalTime / 1000000d) / nSolves, 171 | maxT / 1000000d, 172 | minT / 1000000d, 173 | timeList[(int) (nSolves * 0.50)] / 1000000d, 174 | timeList[(int) (nSolves * 0.75)] / 1000000d, 175 | timeList[(int) (nSolves * 0.90)] / 1000000d, 176 | timeList[(int) (nSolves * 0.95)] / 1000000d, 177 | timeList[(int) (nSolves * 0.99)] / 1000000d, 178 | totalLength / 1.0d / nSolves, 179 | totalProbe2 / 1.0d / nSolves)); 180 | 181 | System.out.println(nSolves + " Random Cube(s) Solved"); 182 | System.out.println("Length Distribution: "); 183 | for (int i = 0; i < 30; i++) { 184 | if (lengthDis[i] != 0) { 185 | System.out.println(String.format("%2d: %d", i, lengthDis[i])); 186 | } 187 | } 188 | } catch (InterruptedException e) { 189 | e.printStackTrace(); 190 | } 191 | } 192 | 193 | static void selectedScrambleSolvingTest(boolean isOptimal, Search search, int maxDepth) { 194 | System.out.println( 195 | isOptimal 196 | ? "========== Selected Scramble Solving Test (Optimal Solver) ==========" 197 | : "========== Selected Scramble Solving Test (Two-phase Solver) =========="); 198 | String scr = Tools.fromScramble(new int[0]); 199 | System.out.println(String.format("IdCube Test: \"%s\"", search.solution(scr, 21, 100000, 0, Search.OPTIMAL_SOLUTION))); 200 | 201 | int n_test = 0; 202 | long curTime; 203 | final int MAX_OPT_ALL = 3; 204 | for (int length = 1; length <= MAX_OPT_ALL; length++) { 205 | System.out.print(String.format("%d-Move: ", length)); 206 | curTime = System.nanoTime(); 207 | cumNProbe = 0; 208 | maxNProbe = 0; 209 | n_test = testOptimal(length, 0, new int[length], -1, search, isOptimal ? Search.OPTIMAL_SOLUTION : 0); 210 | System.out.println(String.format("OK, All %d Cube(s) Solved Optimally. AvgTime: %1.3f ms. TotP: %d. MaxP: %d", n_test, (System.nanoTime() - curTime) / 1000000d / n_test, cumNProbe, maxNProbe)); 211 | } 212 | 213 | final long MAX_TEST_TIME = 100000000; 214 | for (int length = MAX_OPT_ALL + 1; length <= maxDepth; length++) { 215 | System.out.print(String.format("%d-Move: ", length)); 216 | Random gen0 = new Random(42L); 217 | Random gen = new Random(); 218 | curTime = System.nanoTime(); 219 | n_test = 0; 220 | cumNProbe = 0; 221 | maxNProbe = 0; 222 | while (System.nanoTime() - curTime < MAX_TEST_TIME) { 223 | gen.setSeed(gen0.nextLong()); 224 | testRandomOptimal(length, search, gen, isOptimal ? Search.OPTIMAL_SOLUTION : 0); 225 | ++n_test; 226 | } 227 | System.out.println(String.format("OK, All %d Cube(s) Solved Optimally. AvgTime: %1.3f ms. TotP: %d. MaxP: %d", n_test, (System.nanoTime() - curTime) / 1000000d / n_test, cumNProbe, maxNProbe)); 228 | } 229 | } 230 | 231 | public static void main(String[] args) { 232 | if (args.length == 0) { 233 | System.out.println("java -client test testValue [nSolves maxLength maxProbe minProbe verbose]"); 234 | return; 235 | } 236 | int testValue = args.length < 1 ? 0 : Integer.parseInt(args[0]); 237 | int nSolves = args.length < 2 ? 1000 : Integer.parseInt(args[1]); 238 | int maxLength = args.length < 3 ? 21 : Integer.parseInt(args[2]); 239 | int probeMax = args.length < 4 ? 100000 : Integer.parseInt(args[3]); 240 | int probeMin = args.length < 5 ? 0 : Integer.parseInt(args[4]); 241 | int verbose = args.length < 6 ? 0 : Integer.parseInt(args[5]); 242 | 243 | long tm; 244 | if ((testValue & 0x01) != 0) { 245 | tm = System.nanoTime(); 246 | other(); 247 | System.out.println(System.nanoTime() - tm); 248 | } 249 | 250 | DataInputStream dis = null; 251 | if ((testValue & 0x02) != 0) { 252 | tm = System.nanoTime(); 253 | try { 254 | dis = new DataInputStream(new BufferedInputStream(new FileInputStream("data"))); 255 | Tools.initFrom(dis); 256 | } catch (Exception e) { 257 | dis = null; 258 | e.printStackTrace(); 259 | } 260 | System.out.println(System.nanoTime() - tm); 261 | } 262 | if ((testValue & 0x04) != 0) { 263 | tm = System.nanoTime(); 264 | if (dis == null) { 265 | DataOutputStream dos = null; 266 | try { 267 | dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data"))); 268 | Tools.saveTo(dos); 269 | dos.close(); 270 | } catch (Exception e) { 271 | e.printStackTrace(); 272 | } 273 | } 274 | System.out.println(System.nanoTime() - tm); 275 | } 276 | 277 | tm = System.nanoTime(); 278 | Search.init(); 279 | if ((testValue & 0x08) != 0) { 280 | System.out.println(String.format("Initialization Time: %d ms\r", System.nanoTime() - tm)); 281 | } 282 | 283 | Search search = new Search(); 284 | String sol; 285 | if ((testValue & 0x10) != 0) { 286 | selectedScrambleSolvingTest(true, search, 13); 287 | selectedScrambleSolvingTest(false, search, 14); 288 | 289 | System.out.print("SuperFlip: "); 290 | long curTime = System.nanoTime(); 291 | sol = search.solution(Tools.superFlip(), 20, 1000000000, 0, 0); 292 | while (sol.startsWith("Error") || search.length() > 20) { 293 | if (sol.startsWith("Error") && !sol.startsWith("Error 8")) { 294 | throw new RuntimeException(String.format("Cannot find the optimal solution: %s", sol)); 295 | } 296 | sol = search.next(100000, 0, 0); 297 | } 298 | System.out.println(String.format("OK:\n%s\nTime: %1.3f ms.", sol, (System.nanoTime() - curTime) / 1000000d)); 299 | System.out.print("20-Depth: "); 300 | String[] depth20 = new String[] { 301 | "B2 L B2 R' F' U' B' L D' F' L U L2 B2 L' D2 B2 D2 R2 B2", 302 | "R U2 R D2 R2 B2 L' D' B' F U B' R' U2 L' D R2 F' U2 L2", 303 | "D2 R2 F2 D2 F2 D2 R' F2 D' L2 R B L' F U R' B F2 R2 F'", 304 | "D' F' U B2 R2 F R' U2 B' L D F R D2 R2 L2 D' R2 F2 D'", 305 | "U2 R2 F2 D' U F2 U2 B U B' R U' F L B R' F L2 D' B ", 306 | "D B2 D' B2 R2 D' R2 U L R' D B' D R F' D2 R2 U' F' R ", 307 | "B D' L' F' L F B U' D2 F' R2 B' U F2 R' L U2 R2 F2 B2", 308 | "U2 L' U2 F2 L' R D2 L2 B' D2 L F' R' U' L U2 F' D' R B ", 309 | "F' L B2 R U' B' L U2 D' F L' R2 U2 D2 B2 R2 D R2 L2 F2", 310 | "U2 R2 D2 B U2 B' F D' B' R' D U2 B2 F2 R' D' B U' F' R2" 311 | }; 312 | int n_test = depth20.length; 313 | curTime = System.nanoTime(); 314 | for (int i = 0; i < depth20.length; i++) { 315 | sol = search.solution(Tools.fromScramble(depth20[i]), 20, 100000, 0, Search.INVERSE_SOLUTION); 316 | assert Tools.fromScramble(depth20[i]).equals(Tools.fromScramble(sol)); 317 | } 318 | System.out.println(String.format("OK, Random %d Cube(s) Solved. AvgTime: %1.3f ms.", depth20.length, (System.nanoTime() - curTime) / 1000000d / n_test)); 319 | } 320 | 321 | if ((testValue & 0x20) != 0) { 322 | System.out.println("========== Random Scramble Solving Test (Two-phase Solver) =========="); 323 | System.out.println(String.format("Solve Random %d Cubes:", nSolves)); 324 | System.out.println( 325 | "MaxLength: " + maxLength + "\n" + 326 | "ProbeMax: " + probeMax + "\n" + 327 | "ProbeMin: " + probeMin + "\n" + 328 | "verbose: " + verbose); 329 | //Let JIT do optimization. 330 | while (System.nanoTime() - tm < 1e9) { 331 | search.solution(Tools.randomCube(), 20, 50, 0, 0); 332 | } 333 | int total = 0; 334 | int x = 0; 335 | // System.out.print("Average Solving Time: - nanoSecond(s)\r"); 336 | long minT = 1L << 62; 337 | long maxT = 0L; 338 | long totalTime = 0; 339 | Tools.setRandomSource(new Random(42L)); 340 | int totalLength = 0; 341 | int[] lengthDis = new int[30]; 342 | long totalProbe2 = 0; 343 | long[] timeList = new long[nSolves]; 344 | while (x < nSolves) { 345 | String cube = Tools.randomCube(); 346 | long curTime = System.nanoTime(); 347 | String s = search.solution(cube, maxLength, probeMax, probeMin, verbose | search.INVERSE_SOLUTION); 348 | // if (s.length() > 63) { 349 | // s = search.next(probeMax, 0, verbose); 350 | // } 351 | curTime = System.nanoTime() - curTime; 352 | String cube2 = Tools.fromScramble(s); 353 | assert(cube.equals(cube2)); 354 | totalTime += curTime; 355 | totalProbe2 += search.numberOfProbes(); 356 | maxT = Math.max(maxT, curTime); 357 | minT = Math.min(minT, curTime); 358 | totalLength += search.length(); 359 | lengthDis[search.length()]++; 360 | timeList[x++] = curTime; 361 | if (x % 100 == 0 || System.nanoTime() - tm > 100000000) { 362 | tm = System.nanoTime(); 363 | System.out.print(String.format("%6d AvgT: %6.3f ms, MaxT: %8.3f ms, MinT: %6.3f ms, AvgL: %6.3f, AvgP: %6.3f\r", x, 364 | (totalTime / 1000000d) / x, maxT / 1000000d, minT / 1000000d, totalLength / 1.0d / x, totalProbe2 / 1.0d / x)); 365 | } 366 | } 367 | java.util.Arrays.sort(timeList); 368 | System.out.println( 369 | String.format( 370 | "\nAvgT: %6.3f ms\n" + 371 | "MaxT: %8.3f ms\n" + 372 | "MinT: %6.3f ms\n" + 373 | "L50T: %6.3f ms\n" + 374 | "L75T: %6.3f ms\n" + 375 | "L90T: %6.3f ms\n" + 376 | "L95T: %6.3f ms\n" + 377 | "L99T: %6.3f ms\n" + 378 | "AvgL: %6.3f\n" + 379 | "AvgP: %6.3f\n", 380 | (totalTime / 1000000d) / x, 381 | maxT / 1000000d, 382 | minT / 1000000d, 383 | timeList[(int) (nSolves * 0.50)] / 1000000d, 384 | timeList[(int) (nSolves * 0.75)] / 1000000d, 385 | timeList[(int) (nSolves * 0.90)] / 1000000d, 386 | timeList[(int) (nSolves * 0.95)] / 1000000d, 387 | timeList[(int) (nSolves * 0.99)] / 1000000d, 388 | totalLength / 1.0d / x, 389 | totalProbe2 / 1.0d / x)); 390 | 391 | System.out.println(x + " Random Cube(s) Solved"); 392 | System.out.println("Length Distribution: "); 393 | for (int i = 0; i < 30; i++) { 394 | if (lengthDis[i] != 0) { 395 | System.out.println(String.format("%2d: %d", i, lengthDis[i])); 396 | } 397 | } 398 | } 399 | 400 | if ((testValue & 0x40) != 0) { 401 | System.out.println("========== Multi-thread Random Solving Test (Two-phase Solver) =========="); 402 | System.out.println(String.format("Solve Random %d Cubes:", nSolves)); 403 | System.out.println( 404 | "MaxLength: " + maxLength + "\n" + 405 | "ProbeMax: " + probeMax + "\n" + 406 | "ProbeMin: " + probeMin + "\n" + 407 | "verbose: " + verbose + "\n" + 408 | "nThreads: " + Runtime.getRuntime().availableProcessors()); 409 | multiThreadRandomSolving(Runtime.getRuntime().availableProcessors(), nSolves, maxLength, probeMax, probeMin, verbose); 410 | } 411 | 412 | } 413 | } 414 | --------------------------------------------------------------------------------