├── .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. [](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 |
--------------------------------------------------------------------------------