├── gui_client.jpg
├── .idea
├── encodings.xml
├── vcs.xml
├── misc.xml
├── modules.xml
├── inspectionProfiles
│ └── Project_Default.xml
└── RubiksCube-TwophaseSolver.iml
├── computer_vision.py
├── start_server.py
├── misc.py
├── .gitignore
├── vision_params.py
├── README.md
├── defs.py
├── sockets.py
├── enums.py
├── example.py
├── face.py
├── client_gui.py
├── moves.py
├── coord.py
├── symmetries.py
├── client_gui2.py
├── solver.py
├── vision2.py
├── pruning.py
├── cubie.py
└── LICENSE
/gui_client.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tazeg/RubiksCube-TwophaseSolver/master/gui_client.jpg
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/RubiksCube-TwophaseSolver.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/computer_vision.py:
--------------------------------------------------------------------------------
1 | # ############################## Start the webserver, the opencv color grabber and the GUI #############################
2 |
3 | import start_server
4 | from threading import Thread
5 | from vision2 import grab_colors
6 | background_thread = Thread(target=start_server.start, args=(8080, 20, 2))
7 | background_thread.start()
8 | # Server listens now on port 8080, maxlength 20 moves, timeout 2 seconds
9 |
10 | thr = Thread(target=grab_colors, args=())
11 | thr.start()
12 | # Run the opencv code and detect facelet colors
13 |
14 | import client_gui2
15 | # Start the GUI with several sliders to configure some opencv parameters
16 |
17 |
--------------------------------------------------------------------------------
/start_server.py:
--------------------------------------------------------------------------------
1 | # ################# Start the server and listen for connections ########################################################
2 |
3 | import sockets
4 | import sys
5 |
6 |
7 | if __name__ == '__main__': # file is executed
8 | if len(sys.argv) < 2:
9 | sys.argv.append(str(8080)) # Port 8080 default port
10 | if len(sys.argv) < 3:
11 | sys.argv.append(str(20)) # 20 moves default maximal return length of maneuver
12 | if len(sys.argv) < 4:
13 | sys.argv.append(str(3)) # 3 second default timeout for search
14 | print('startserver')
15 | sockets.server_start(sys.argv)
16 | else:
17 | def start(port, maxmoves, timeout):
18 | sockets.server_start((-1, port, maxmoves, timeout))
19 |
--------------------------------------------------------------------------------
/misc.py:
--------------------------------------------------------------------------------
1 | # ######################################## Miscellaneous functions #####################################################
2 |
3 | def rotate_right(arr, l, r):
4 | """"Rotate array arr right between l and r. r is included."""
5 | temp = arr[r]
6 | for i in range(r, l, -1):
7 | arr[i] = arr[i-1]
8 | arr[l] = temp
9 |
10 |
11 | def rotate_left(arr, l, r):
12 | """"Rotate array arr left between l and r. r is included."""
13 | temp = arr[l]
14 | for i in range(l, r):
15 | arr[i] = arr[i+1]
16 | arr[r] = temp
17 |
18 |
19 | def c_nk(n, k):
20 | """Binomial coefficient [n choose k]."""
21 | if n < k:
22 | return 0
23 | if k > n // 2:
24 | k = n - k
25 | s, i, j = 1, n, 1
26 | while i != n - k:
27 | s *= i
28 | s //= j
29 | i -= 1
30 | j += 1
31 | return s
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
--------------------------------------------------------------------------------
/vision_params.py:
--------------------------------------------------------------------------------
1 | # #################################### Computer vision parameters ######################################################
2 |
3 | # The parameters are used by functions in vision2.py and can be changed from the GUI client_gui.py
4 |
5 | # default values for the parameters
6 |
7 | # black-filter
8 | rgb_L = 50 # threshold for r, g and b values. rgb-pixels with r,g,b< rgb_L are consider to be no facelet-pixel
9 |
10 | # white-filter
11 | sat_W = 60 # hsv-pixels with a saturation s > sat_W are considered not to be a white facelet-pixel
12 | val_W = 150 # hsv-pixels with a value v < sat_W are considered not to be a white facelet-pixel
13 |
14 | # this parameter cannot be changed by the GUI
15 | sigma_W = 300 # a grid square is considered part of a white facelet if the standard deviation of the hue is <= sigma_W
16 |
17 | # color-filter
18 | sigma_C = 5 # a grid square is considered part of a facelet if the standard deviation of the hue is <= sigma_C
19 | delta_C = 5 # pixels within the interval [hue-delta,hue+delta] are considered to belong to the same facelet
20 |
21 | # These parameters depend on the actually used cube colors and the lightning conditions
22 | orange_L = 6 # lowest allowed hue for color orange
23 | orange_H = 23 # highest allowed hue for color orange
24 | yellow_H = 50 # highest allowed hue for color yellow
25 | green_H = 100 # highest allowed hue for color green
26 | blue_H = 160 # highest allowed hue for color blue
27 | # hue values > blue_H and < orange_L describe the color red
28 |
29 | # The colors of a cube face are stored here by the function vision2.grabcolors
30 | face_col = [] # the colors (as text) of a cube face
31 | face_hsv = [] # the colors (as hsv-values) of a cube face
32 |
33 | # These dictionaries define the colors of all 6 faces and are filled by the client_gui2.transfer routine
34 | cube_col = {} # the colors (as text) of the 6 faces
35 | cube_hsv = {} # the colors (as hsv-values) of the 6 faces
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RubiksCube-TwophaseSolver
2 | ## Overview
3 | This project implements the two-phase-algorithm in its fully developed form to solve Rubik's cube in Python. Though Python is much slower than for example C++ or even Java the implementation is sufficiently fast to solve random cubes in less than 20 moves on average on slow hardware like the Raspberry Pi3 within a few seconds.
4 |
5 | If you just want to solve Rubik's cube and play around with its patterns [Cube Explorer](http://kociemba.org/cube.htm) may be the better choice. But if you want to get a better understanding of the two-phase-algorithm details, you work on a project to build a cube solving robot or you write software for an NxNxN cube and use the reduction method this may be the right place to look.
6 |
7 | ## Usage
8 | There are several tables which must be created on the first run. These need about 80 MB disk space and it takes from about 1/2 to 6 hours to create them, depending on the hardware. Usually you start the cubesolving server which listens on a port of your choice and which accepts the cube definition string and returns the solving maneuver. The module example.py gives detailed examples how to start the server and a simple GUI-interface which interacts with the server. You can run the example file with
9 |
10 | "python example.py" or eventually "python3 example.py"
11 |
12 | Make sure that you use Python 3.4 or higher and you have the numpy package installed.
13 |
14 | 
15 |
16 | If you run the script "computer_vision.py" you have the possibility to enter the facelet colors with a webcam. There are several parameters which have an influence on the facelet detection quality. If you use a Raspberry Pi with the Raspberry Pi Camera Module and not an USB-webcam make sure you do "sudo modprobe bcm2835-v4l2" first.
17 |
18 | You can find some more information how to set the parameters here:
19 | [Computer vision and Rubik's cube](http://kociemba.org/computervision.html)
20 |
--------------------------------------------------------------------------------
/defs.py:
--------------------------------------------------------------------------------
1 | # ###################################### some definitions and constants ################################################
2 |
3 | from enums import Facelet as Fc, Color as Cl
4 |
5 | # Map the corner positions to facelet positions.
6 | cornerFacelet = [[Fc.U9, Fc.R1, Fc.F3], [Fc.U7, Fc.F1, Fc.L3], [Fc.U1, Fc.L1, Fc.B3], [Fc.U3, Fc.B1, Fc.R3],
7 | [Fc.D3, Fc.F9, Fc.R7], [Fc.D1, Fc.L9, Fc.F7], [Fc.D7, Fc.B9, Fc.L7], [Fc.D9, Fc.R9, Fc.B7]
8 | ]
9 |
10 | # Map the edge positions to facelet positions.
11 | edgeFacelet = [[Fc.U6, Fc.R2], [Fc.U8, Fc.F2], [Fc.U4, Fc.L2], [Fc.U2, Fc.B2], [Fc.D6, Fc.R8], [Fc.D2, Fc.F8],
12 | [Fc.D4, Fc.L8], [Fc.D8, Fc.B8], [Fc.F6, Fc.R4], [Fc.F4, Fc.L6], [Fc.B6, Fc.L4], [Fc.B4, Fc.R6]
13 | ]
14 |
15 | # Map the corner positions to facelet colors.
16 | cornerColor = [[Cl.U, Cl.R, Cl.F], [Cl.U, Cl.F, Cl.L], [Cl.U, Cl.L, Cl.B], [Cl.U, Cl.B, Cl.R],
17 | [Cl.D, Cl.F, Cl.R], [Cl.D, Cl.L, Cl.F], [Cl.D, Cl.B, Cl.L], [Cl.D, Cl.R, Cl.B]
18 | ]
19 |
20 | # Map the edge positions to facelet colors.
21 | edgeColor = [[Cl.U, Cl.R], [Cl.U, Cl.F], [Cl.U, Cl.L], [Cl.U, Cl.B], [Cl.D, Cl.R], [Cl.D, Cl.F],
22 | [Cl.D, Cl.L], [Cl.D, Cl.B], [Cl.F, Cl.R], [Cl.F, Cl.L], [Cl.B, Cl.L], [Cl.B, Cl.R]
23 | ]
24 |
25 | # ###################################### some "constants" ##############################################################
26 | N_PERM_4 = 24
27 | N_CHOOSE_8_4 = 70
28 | N_MOVE = 18 # number of possible face moves
29 |
30 | N_TWIST = 2187 # 3^7 possible corner orientations in phase 1
31 | N_FLIP = 2048 # 2^11 possible edge orientations in phase 1
32 | N_SLICE_SORTED = 11880 # 12*11*10*9 possible positions of the FR, FL, BL, BR edges in phase 1
33 | N_SLICE = N_SLICE_SORTED // N_PERM_4 # we ignore the permutation of FR, FL, BL, BR in phase 1
34 | N_FLIPSLICE_CLASS = 64430 # number of equivalence classes for combined flip+slice concerning symmetry group D4h
35 |
36 | N_U_EDGES_PHASE2 = 1680 # number of different positions of the edges UR, UF, UL and UB in phase 2
37 | # N_D_EDGES_PHASE2 = 1680 # number of different positions of the edges DR, DF, DL and DB in phase 2
38 | N_CORNERS = 40320 # 8! corner permutations in phase 2
39 | N_CORNERS_CLASS = 2768 # number of equivalence classes concerning symmetry group D4h
40 | N_UD_EDGES = 40320 # 8! permutations of the edges in the U-face and D-face in phase 2
41 |
42 | N_SYM = 48 # number of cube symmetries of full group Oh
43 | N_SYM_D4h = 16 # Number of symmetries of subgroup D4h
44 | ########################################################################################################################
45 |
--------------------------------------------------------------------------------
/sockets.py:
--------------------------------------------------------------------------------
1 | # ################## The code of the server socket which communicates with the client ##################################
2 |
3 | import socket
4 | import sys
5 | import threading
6 | import solver
7 | import time
8 |
9 |
10 | def client_thread(conn, maxlen, timeout):
11 | while True: # infinite loop only necessary for telnet client
12 | # Receiving from client
13 | data = []
14 | while not (ord('\n') in data or ord('\r') in data):
15 | try:
16 | a = conn.recv(1024).upper()
17 | if len(a) == 0:
18 | conn.close()
19 | print('Connection closed', flush=True)
20 | return
21 | except:
22 | print('Connection closed', flush=True)
23 | conn.close()
24 | return
25 | for i in range(len(a)):
26 | if a[i] in [ord('\n'), ord('\r'), ord('G'), ord('E'), ord('T'), ord('U'), ord('R'), ord('F'), ord('D'),
27 | ord('L'), ord('B')]:
28 | data.append(a[i])
29 | if data[0] == ord('X'):
30 | break
31 | defstr = ''.join(chr(i) for i in data if chr(i) > chr(32))
32 | qpos = defstr.find('GET')
33 | if qpos >= 0: # in this case we suppose the client is a webbrowser
34 | defstr = defstr[qpos+3:qpos+57]
35 | reply = 'HTTP/1.1 200 OK' + '\n\n' + '
Answer from Cubesolver' + '\n'
36 | reply += solver.solve(defstr, maxlen, timeout) + '\n' + '' + '\n'
37 | conn.sendall(reply.encode())
38 | conn.close()
39 | else: # other client, for example the GUI client or telnet
40 | reply = (solver.solve(defstr, maxlen, timeout)+'\n').encode()
41 | print(defstr)
42 | try:
43 | conn.sendall(reply)
44 | except:
45 | print('Error while sending data. Connection closed', flush=True)
46 | conn.close()
47 | return
48 | conn.close()
49 |
50 |
51 | def server_start(args):
52 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
53 | print('Server socket created')
54 | try:
55 | s.bind(('', int(args[1]))) # bind socket to local host and port
56 | except socket.error as e:
57 | print('Server socket bind failed. Error Code : ' + str(e.errno))
58 | sys.exit()
59 | s.listen(10)
60 | print('Server now listening...')
61 |
62 | while 1:
63 | conn, addr = s.accept()
64 | print('Connected with ' + addr[0] + ':' + str(addr[1]) + ', ' + time.strftime("%Y.%m.%d %H:%M:%S"))
65 | threading.Thread(target=client_thread, args=(conn, int(args[2]), int(args[3]))).start()
66 | s.close()
67 |
68 |
--------------------------------------------------------------------------------
/enums.py:
--------------------------------------------------------------------------------
1 | # #################### Enumerations which improve the readability of the code ########################################
2 |
3 | from enum import IntEnum
4 |
5 |
6 | class Facelet(IntEnum):
7 | """""
8 | The names of the facelet positions of the cube
9 | |************|
10 | |*U1**U2**U3*|
11 | |************|
12 | |*U4**U5**U6*|
13 | |************|
14 | |*U7**U8**U9*|
15 | |************|
16 | |************|************|************|************|
17 | |*L1**L2**L3*|*F1**F2**F3*|*R1**R2**R3*|*B1**B2**B3*|
18 | |************|************|************|************|
19 | |*L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*|
20 | |************|************|************|************|
21 | |*L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*|
22 | |************|************|************|************|
23 | |************|
24 | |*D1**D2**D3*|
25 | |************|
26 | |*D4**D5**D6*|
27 | |************|
28 | |*D7**D8**D9*|
29 | |************|
30 | A cube definition string "UBL..." means for example: In position U1 we have the U-color, in position U2 we have the
31 | B-color, in position U3 we have the L color etc. according to the order U1, U2, U3, U4, U5, U6, U7, U8, U9, R1, R2,
32 | R3, R4, R5, R6, R7, R8, R9, F1, F2, F3, F4, F5, F6, F7, F8, F9, D1, D2, D3, D4, D5, D6, D7, D8, D9, L1, L2, L3, L4,
33 | L5, L6, L7, L8, L9, B1, B2, B3, B4, B5, B6, B7, B8, B9 of the enum constants.
34 | """
35 | U1 = 0
36 | U2 = 1
37 | U3 = 2
38 | U4 = 3
39 | U5 = 4
40 | U6 = 5
41 | U7 = 6
42 | U8 = 7
43 | U9 = 8
44 | R1 = 9
45 | R2 = 10
46 | R3 = 11
47 | R4 = 12
48 | R5 = 13
49 | R6 = 14
50 | R7 = 15
51 | R8 = 16
52 | R9 = 17
53 | F1 = 18
54 | F2 = 19
55 | F3 = 20
56 | F4 = 21
57 | F5 = 22
58 | F6 = 23
59 | F7 = 24
60 | F8 = 25
61 | F9 = 26
62 | D1 = 27
63 | D2 = 28
64 | D3 = 29
65 | D4 = 30
66 | D5 = 31
67 | D6 = 32
68 | D7 = 33
69 | D8 = 34
70 | D9 = 35
71 | L1 = 36
72 | L2 = 37
73 | L3 = 38
74 | L4 = 39
75 | L5 = 40
76 | L6 = 41
77 | L7 = 42
78 | L8 = 43
79 | L9 = 44
80 | B1 = 45
81 | B2 = 46
82 | B3 = 47
83 | B4 = 48
84 | B5 = 49
85 | B6 = 50
86 | B7 = 51
87 | B8 = 52
88 | B9 = 53
89 |
90 |
91 | class Color(IntEnum):
92 | """ The possible colors of the cube facelets. Color U refers to the color of the U(p)-face etc.
93 | Also used to name the faces itself."""
94 | U = 0
95 | R = 1
96 | F = 2
97 | D = 3
98 | L = 4
99 | B = 5
100 |
101 |
102 | class Corner(IntEnum):
103 | """The names of the corner positions of the cube. Corner URF e.g. has an U(p), a R(ight) and a F(ront) facelet."""
104 | URF = 0
105 | UFL = 1
106 | ULB = 2
107 | UBR = 3
108 | DFR = 4
109 | DLF = 5
110 | DBL = 6
111 | DRB = 7
112 |
113 |
114 | class Edge(IntEnum):
115 | """The names of the edge positions of the cube. Edge UR e.g. has an U(p) and R(ight) facelet."""
116 | UR = 0
117 | UF = 1
118 | UL = 2
119 | UB = 3
120 | DR = 4
121 | DF = 5
122 | DL = 6
123 | DB = 7
124 | FR = 8
125 | FL = 9
126 | BL = 10
127 | BR = 11
128 |
129 |
130 | class Move(IntEnum):
131 | """The moves in the faceturn metric. Not to be confused with the names of the facelet positions in class Facelet."""
132 | U1 = 0
133 | U2 = 1
134 | U3 = 2
135 | R1 = 3
136 | R2 = 4
137 | R3 = 5
138 | F1 = 6
139 | F2 = 7
140 | F3 = 8
141 | D1 = 9
142 | D2 = 10
143 | D3 = 11
144 | L1 = 12
145 | L2 = 13
146 | L3 = 14
147 | B1 = 15
148 | B2 = 16
149 | B3 = 17
150 |
151 |
152 | class BS(IntEnum):
153 | """Basic symmetries of the cube. All 48 cube symmetries can be generated by sequences of these 4 symmetries."""
154 | ROT_URF3 = 0
155 | ROT_F2 = 1
156 | ROT_U4 = 2
157 | MIRR_LR2 = 3
158 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | # ############################ Examples how to use the cube solver #####################################################
2 |
3 | cubestring = 'DUUBULDBFRBFRRULLLBRDFFFBLURDBFDFDRFRULBLUFDURRBLBDUDL' # cube definition string of cube we want to solve
4 | # See module enums.py for the format of the cube definition string
5 |
6 | # ######################### Method 1: directly call the solve routine# #################################################
7 | # Advantage: No network layer needed. Disadvantage: Only local usage possible. #
8 | ########################################################################################################################
9 |
10 | # Uncomment this part if you want to use method 1
11 | """
12 | import solver as sv
13 | a = sv.solve(cubestring, 20, 2) # solve with a maximum of 20 moves and a timeout of 2 seconds for example
14 | print(a)
15 | a = sv.solve(cubestring, 18, 5) # solve with a maximum of 18 moves and a timeout of 5 seconds for example
16 | print(a)
17 | quit()
18 | """
19 | ########################################################################################################################
20 |
21 |
22 | # ############################### Method 2 a/b: Start the cubesolving-server# ##########################################
23 | # Advantage: Tables have to be loaded only once when the server starts. Disadvantage: Network layer must be present. #
24 | ########################################################################################################################
25 |
26 | #----------------------------------------------------------------------------------------------------------------------
27 | # Method 2a: Start the server from inside a Python script:
28 | import start_server
29 | from threading import Thread
30 | background_thread = Thread(target=start_server.start, args=(8080, 20, 2))
31 | background_thread.start()
32 | # Server listens now on port 8080, maxlength 20 moves, timeout 2 seconds
33 | # ----------------------------------------------------------------------------------------------------------------------
34 |
35 | # ----------------------------------------------------------------------------------------------------------------------
36 | # Method 2b: Start the server from a terminal with parameters for port, maxlength and timeout:
37 | # python start_server.py 8080 20 2
38 | # ----------------------------------------------------------------------------------------------------------------------
39 |
40 |
41 | # Once the server is started you can transfer the cube definition string to the server with different methods:
42 |
43 | # ----------------------------------------------------------------------------------------------------------------------
44 | # 1. With a webbrowser, if the server runs on the same machine on port 8080
45 | # http://localhost:8080/DUUBULDBFRBFRRULLLBRDFFFBLURDBFDFDRFRULBLUFDURRBLBDUDL
46 | # With a webbrowser, if the server runs on server myserver.com, port 8081
47 | # http://myserver.com:8081/DUUBULDBFRBFRRULLLBRDFFFBLURDBFDFDRFRULBLUFDURRBLBDUDL
48 | # ----------------------------------------------------------------------------------------------------------------------
49 |
50 | # ----------------------------------------------------------------------------------------------------------------------
51 | # 2. With netcat, if the server runs on the same machine on port 8080
52 | # echo DUUBULDBFRBFRRULLLBRDFFFBLURDBFDFDRFRULBLUFDURRBLBDUDL | nc localhost 8080
53 | # ----------------------------------------------------------------------------------------------------------------------
54 |
55 | # ----------------------------------------------------------------------------------------------------------------------
56 | # 3. With this little graphical interface.
57 | # From within a Python script start the interface with
58 |
59 | import client_gui
60 |
61 |
62 | # From a terminal start the interface with
63 | # python client_gui.py
64 | # ----------------------------------------------------------------------------------------------------------------------
65 |
66 |
67 | # ----------------------------------------------------------------------------------------------------------------------
68 | # Computer vision
69 |
70 | # Start the interface, the server and the webcam from a terminal with
71 |
72 | # python computer_vision.py
73 |
74 | # ----------------------------------------------------------------------------------------------------------------------
75 | ########################################################################################################################
76 |
77 |
--------------------------------------------------------------------------------
/face.py:
--------------------------------------------------------------------------------
1 | # ####### The cube on the facelet level is described by positions of the colored stickers. #############################
2 |
3 | from defs import cornerFacelet, edgeFacelet, cornerColor, edgeColor
4 | from enums import Color, Corner, Edge
5 | from cubie import CubieCube
6 |
7 |
8 | class FaceCube:
9 | """Represent a cube on the facelet level with 54 colored facelets."""
10 | def __init__(self):
11 | self.f = []
12 | for i in range(9):
13 | self.f.append(Color.U)
14 | for i in range(9):
15 | self.f.append(Color.R)
16 | for i in range(9):
17 | self.f.append(Color.F)
18 | for i in range(9):
19 | self.f.append(Color.D)
20 | for i in range(9):
21 | self.f.append(Color.L)
22 | for i in range(9):
23 | self.f.append(Color.B)
24 |
25 | def __str__(self):
26 | return self.to_string()
27 |
28 | def from_string(self, s):
29 | """Construct a facelet cube from a string. See class Facelet(IntEnum) in enums.py for string format."""
30 | if len(s) < 54:
31 | return 'Error: Cube definition string ' + s + ' contains less than 54 facelets.'
32 | elif len(s) > 54:
33 | return 'Error: Cube definition string ' + s + ' contains more than 54 facelets.'
34 | cnt = [0] * 6
35 | for i in range(54):
36 | if s[i] == 'U':
37 | self.f[i] = Color.U
38 | cnt[Color.U] += 1
39 | elif s[i] == 'R':
40 | self.f[i] = Color.R
41 | cnt[Color.R] += 1
42 | elif s[i] == 'F':
43 | self.f[i] = Color.F
44 | cnt[Color.F] += 1
45 | elif s[i] == 'D':
46 | self.f[i] = Color.D
47 | cnt[Color.D] += 1
48 | elif s[i] == 'L':
49 | self.f[i] = Color.L
50 | cnt[Color.L] += 1
51 | elif s[i] == 'B':
52 | self.f[i] = Color.B
53 | cnt[Color.B] += 1
54 | if all(x == 9 for x in cnt):
55 | return True
56 | else:
57 | return 'Error: Cube definition string ' + s + ' does not contain exactly 9 facelets of each color.'
58 |
59 |
60 | def to_string(self):
61 | """Give a string representation of the facelet cube."""
62 | s = ''
63 | for i in range(54):
64 | if self.f[i] == Color.U:
65 | s += 'U'
66 | elif self.f[i] == Color.R:
67 | s += 'R'
68 | elif self.f[i] == Color.F:
69 | s += 'F'
70 | elif self.f[i] == Color.D:
71 | s += 'D'
72 | elif self.f[i] == Color.L:
73 | s += 'L'
74 | elif self.f[i] == Color.B:
75 | s += 'B'
76 | return s
77 |
78 | def to_2dstring(self):
79 | """Give a 2dstring representation of a facelet cube."""
80 | s = self.to_string()
81 | r = ' ' + s[0:3] + '\n ' + s[3:6] + '\n ' + s[6:9] + '\n'
82 | r += s[36:39] + s[18:21] + s[9:12] + s[45:48] + '\n' + s[39:42] + s[21:24] + s[12:15] + s[48:51] \
83 | + '\n' + s[42:45] + s[24:27] + s[15:18] + s[51:54] + '\n'
84 | r += ' ' + s[27:30] + '\n ' + s[30:33] + '\n ' + s[33:36] + '\n'
85 | return r
86 |
87 | def to_cubie_cube(self):
88 | """Return a cubie representation of the facelet cube."""
89 | cc = CubieCube()
90 | cc.cp = [-1] * 8 # invalidate corner and edge permutation
91 | cc.ep = [-1] * 12
92 | for i in Corner:
93 | fac = cornerFacelet[i] # facelets of corner at position i
94 | for ori in range(3):
95 | if self.f[fac[ori]] == Color.U or self.f[fac[ori]] == Color.D:
96 | break
97 | col1 = self.f[fac[(ori + 1) % 3]] # colors which identify the corner at position i
98 | col2 = self.f[fac[(ori + 2) % 3]]
99 | for j in Corner:
100 | col = cornerColor[j] # colors of corner j
101 | if col1 == col[1] and col2 == col[2]:
102 | cc.cp[i] = j # we have corner j in corner position i
103 | cc.co[i] = ori
104 | break
105 |
106 | for i in Edge:
107 | for j in Edge:
108 | if self.f[edgeFacelet[i][0]] == edgeColor[j][0] and \
109 | self.f[edgeFacelet[i][1]] == edgeColor[j][1]:
110 | cc.ep[i] = j
111 | cc.eo[i] = 0
112 | break
113 | if self.f[edgeFacelet[i][0]] == edgeColor[j][1] and \
114 | self.f[edgeFacelet[i][1]] == edgeColor[j][0]:
115 | cc.ep[i] = j
116 | cc.eo[i] = 1
117 | break
118 | return cc
119 |
--------------------------------------------------------------------------------
/client_gui.py:
--------------------------------------------------------------------------------
1 | # ################ A simple graphical interface which communicates with the server #####################################
2 |
3 | from tkinter import *
4 | import socket
5 | import face
6 | import cubie
7 |
8 |
9 | # ################################## Some global variables and constants ###############################################
10 | DEFAULT_HOST = 'localhost'
11 | DEFAULT_PORT = '8080'
12 | width = 60 # width of a facelet in pixels
13 | facelet_id = [[[0 for col in range(3)] for row in range(3)] for face in range(6)]
14 | colorpick_id = [0 for i in range(6)]
15 | curcol = None
16 | t = ("U", "R", "F", "D", "L", "B")
17 | cols = ("yellow", "green", "red", "white", "blue", "orange")
18 | ########################################################################################################################
19 |
20 | # ################################################ Diverse functions ###################################################
21 |
22 |
23 | def show_text(txt):
24 | """Display messages."""
25 | print(txt)
26 | display.insert(INSERT, txt)
27 | root.update_idletasks()
28 |
29 |
30 | def create_facelet_rects(a):
31 | """Initialize the facelet grid on the canvas."""
32 | offset = ((1, 0), (2, 1), (1, 1), (1, 2), (0, 1), (3, 1))
33 | for f in range(6):
34 | for row in range(3):
35 | y = 10 + offset[f][1] * 3 * a + row * a
36 | for col in range(3):
37 | x = 10 + offset[f][0] * 3 * a + col * a
38 | facelet_id[f][row][col] = canvas.create_rectangle(x, y, x + a, y + a, fill="grey")
39 | if row == 1 and col == 1:
40 | canvas.create_text(x + width // 2, y + width // 2, font=("", 14), text=t[f], state=DISABLED)
41 | for f in range(6):
42 | canvas.itemconfig(facelet_id[f][1][1], fill=cols[f])
43 |
44 |
45 | def create_colorpick_rects(a):
46 | """Initialize the "paintbox" on the canvas."""
47 | global curcol
48 | global cols
49 | for i in range(6):
50 | x = (i % 3)*(a+5) + 7*a
51 | y = (i // 3)*(a+5) + 7*a
52 | colorpick_id[i] = canvas.create_rectangle(x, y, x + a, y + a, fill=cols[i])
53 | canvas.itemconfig(colorpick_id[0], width=4)
54 | curcol = cols[0]
55 |
56 |
57 | def get_definition_string():
58 | """Generate the cube definition string from the facelet colors."""
59 | color_to_facelet = {}
60 | for i in range(6):
61 | color_to_facelet.update({canvas.itemcget(facelet_id[i][1][1], "fill"): t[i]})
62 | s = ''
63 | for f in range(6):
64 | for row in range(3):
65 | for col in range(3):
66 | s += color_to_facelet[canvas.itemcget(facelet_id[f][row][col], "fill")]
67 | return s
68 | ########################################################################################################################
69 |
70 | # ############################### Solve the displayed cube with a local or remote server ###############################
71 |
72 |
73 | def solve():
74 | """Connect to the server and return the solving maneuver."""
75 | display.delete(1.0, END) # clear output window
76 | try:
77 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
78 | except socket.error:
79 | show_text('Failed to create socket')
80 | return
81 | # host = 'f9f0b2jt6zmzyo6b.myfritz.net' # my RaspberryPi, if online
82 | host = txt_host.get(1.0, END).rstrip() # default is localhost
83 | port = int(txt_port.get(1.0, END)) # default is port 8080
84 |
85 | try:
86 | remote_ip = socket.gethostbyname(host)
87 | except socket.gaierror:
88 | show_text('Hostname could not be resolved.')
89 | return
90 | try:
91 | s.connect((remote_ip, port))
92 | except:
93 | show_text('Cannot connect to server!')
94 | return
95 | show_text('Connected with ' + remote_ip + '\n')
96 | try:
97 | defstr = get_definition_string()+'\n'
98 | except:
99 | show_text('Invalid facelet configuration.\nWrong or missing colors.')
100 | return
101 | show_text(defstr)
102 | try:
103 | s.sendall((defstr+'\n').encode())
104 | except:
105 | show_text('Cannot send cube configuration to server.')
106 | return
107 | show_text(s.recv(2048).decode())
108 | ########################################################################################################################
109 |
110 | # ################################# Functions to change the facelet colors #############################################
111 |
112 |
113 | def clean():
114 | """Restore the cube to a clean cube."""
115 | for f in range(6):
116 | for row in range(3):
117 | for col in range(3):
118 | canvas.itemconfig(facelet_id[f][row][col], fill=canvas.itemcget(facelet_id[f][1][1], "fill"))
119 |
120 |
121 | def empty():
122 | """Remove the facelet colors except the center facelets colors."""
123 | for f in range(6):
124 | for row in range(3):
125 | for col in range(3):
126 | if row != 1 or col != 1:
127 | canvas.itemconfig(facelet_id[f][row][col], fill="grey")
128 |
129 |
130 | def random():
131 | """Generate a random cube and set the corresponding facelet colors."""
132 | cc = cubie.CubieCube()
133 | cc.randomize()
134 | fc = cc.to_facelet_cube()
135 | idx = 0
136 | for f in range(6):
137 | for row in range(3):
138 | for col in range(3):
139 | canvas.itemconfig(facelet_id[f][row][col], fill=cols[fc.f[idx]] )
140 | idx += 1
141 | ########################################################################################################################
142 |
143 | # ################################### Edit the facelet colors ##########################################################
144 |
145 |
146 | def click(event):
147 | """Define how to react on left mouse clicks."""
148 | global curcol
149 | idlist = canvas.find_withtag("current")
150 | if len(idlist) > 0:
151 | if idlist[0] in colorpick_id:
152 | curcol = canvas.itemcget("current", "fill")
153 | for i in range(6):
154 | canvas.itemconfig(colorpick_id[i], width=1)
155 | canvas.itemconfig("current", width=5)
156 | else:
157 | canvas.itemconfig("current", fill=curcol)
158 | ########################################################################################################################
159 |
160 | # ###################################### Generate and display the TK_widgets ##########################################
161 | root = Tk()
162 | root.wm_title("Solver Client")
163 | canvas = Canvas(root, width=12 * width + 20, height=9 * width + 20)
164 | canvas.pack()
165 |
166 | bsolve = Button(text="Solve", height=2, width=10, relief=RAISED, command=solve)
167 | bsolve_window = canvas.create_window(10 + 10.5 * width, 10 + 6.5 * width, anchor=NW, window=bsolve)
168 | bclean = Button(text="Clean", height=1, width=10, relief=RAISED, command=clean)
169 | bclean_window = canvas.create_window(10 + 10.5 * width, 10 + 7.5 * width, anchor=NW, window=bclean)
170 | bempty = Button(text="Empty", height=1, width=10, relief=RAISED, command=empty)
171 | bempty_window = canvas.create_window(10 + 10.5 * width, 10 + 8 * width, anchor=NW, window=bempty)
172 | brandom = Button(text="Random", height=1, width=10, relief=RAISED, command=random)
173 | brandom_window = canvas.create_window(10 + 10.5 * width, 10 + 8.5 * width, anchor=NW, window=brandom)
174 | display = Text(height=7, width=39)
175 | text_window = canvas.create_window(10 + 6.5 * width, 10 + .5 * width, anchor=NW, window=display)
176 | hp = Label(text=' Hostname and Port')
177 | hp_window = canvas.create_window(10 + 0 * width, 10 + 0.6 * width, anchor=NW, window=hp)
178 | txt_host = Text(height=1, width=20)
179 | txt_host_window = canvas.create_window(10 + 0 * width, 10 + 1 * width, anchor=NW, window=txt_host)
180 | txt_host.insert(INSERT, DEFAULT_HOST)
181 | txt_port = Text(height=1, width=20)
182 | txt_port_window = canvas.create_window(10 + 0 * width, 10 + 1.5 * width, anchor=NW, window=txt_port)
183 | txt_port.insert(INSERT, DEFAULT_PORT)
184 | canvas.bind("", click)
185 | create_facelet_rects(width)
186 | create_colorpick_rects(width)
187 | root.mainloop()
188 | ########################################################################################################################
189 |
190 |
--------------------------------------------------------------------------------
/moves.py:
--------------------------------------------------------------------------------
1 | # ################### Movetables describe the transformation of the coordinates by cube moves. #########################
2 |
3 | from os import path
4 | import array as ar
5 | import cubie as cb
6 | import enums
7 | from defs import N_TWIST, N_FLIP, N_SLICE_SORTED, N_CORNERS, N_UD_EDGES, N_MOVE
8 |
9 | a = cb.CubieCube()
10 | # ######################################### Move table for the twists of the corners. ##################################
11 |
12 | # The twist coordinate describes the 3^7 = 2187 possible orientations of the 8 corners
13 | # 0 <= twist < 2187 in phase 1, twist = 0 in phase 2
14 | fname = "move_twist"
15 | if not path.isfile(fname):
16 | print("creating " + fname + " table...")
17 | twist_move = ar.array('H', [0 for i in range(N_TWIST * N_MOVE)])
18 | for i in range(N_TWIST):
19 | a.set_twist(i)
20 | for j in enums.Color: # six faces U, R, F, D, L, B
21 | for k in range(3): # three moves for each face, for example U, U2, U3 = U'
22 | a.corner_multiply(cb.basicMoveCube[j])
23 | twist_move[N_MOVE * i + 3 * j + k] = a.get_twist()
24 | a.corner_multiply(cb.basicMoveCube[j]) # 4. move restores face
25 | fh = open(fname, "wb")
26 | twist_move.tofile(fh)
27 | else:
28 | print("loading " + fname + " table...")
29 | fh = open(fname, "rb")
30 | twist_move = ar.array('H')
31 | twist_move.fromfile(fh, N_TWIST * N_MOVE)
32 | fh.close()
33 | ########################################################################################################################
34 |
35 | # #################################### Move table for the flip of the edges. ##########################################
36 |
37 | # The flip coordinate describes the 2^11 = 2048 possible orientations of the 12 edges
38 | # 0 <= flip < 2048 in phase 1, flip = 0 in phase 2
39 | fname = "move_flip"
40 | if not path.isfile(fname):
41 | print("creating " + fname + " table...")
42 | flip_move = ar.array('H', [0 for i in range(N_FLIP * N_MOVE)])
43 | for i in range(N_FLIP):
44 | a.set_flip(i)
45 | for j in enums.Color:
46 | for k in range(3):
47 | a.edge_multiply(cb.basicMoveCube[j])
48 | flip_move[N_MOVE * i + 3 * j + k] = a.get_flip()
49 | a.edge_multiply(cb.basicMoveCube[j])
50 | fh = open(fname, "wb")
51 | flip_move.tofile(fh)
52 | else:
53 | print("loading " + fname + " table...")
54 | fh = open(fname, "rb")
55 | flip_move = ar.array('H')
56 | flip_move.fromfile(fh, N_FLIP * N_MOVE)
57 | fh.close()
58 | ########################################################################################################################
59 |
60 | # ###################### Move table for the four UD-slice edges FR, FL, Bl and BR. #####################################
61 |
62 | # The slice_sorted coordinate describes the 12!/8! = 11880 possible positions of the FR, FL, BL and BR edges.
63 | # Though for phase 1 only the "unsorted" slice coordinate with Binomial(12,4) = 495 positions is relevant, using the
64 | # slice_sorted coordinate gives us the permutation of the FR, FL, BL and BR edges at the beginning of phase 2 for free.
65 | # 0 <= slice_sorted < 11880 in phase 1, 0 <= slice_sorted < 24 in phase 2, slice_sorted = 0 for solved cube
66 | fname = "move_slice_sorted"
67 | if not path.isfile(fname):
68 | print("creating " + fname + " table...")
69 | slice_sorted_move = ar.array('H', [0 for i in range(N_SLICE_SORTED * N_MOVE)])
70 | for i in range(N_SLICE_SORTED):
71 | if i % 200 == 0:
72 | print('.', end='', flush=True)
73 | a.set_slice_sorted(i)
74 | for j in enums.Color:
75 | for k in range(3):
76 | a.edge_multiply(cb.basicMoveCube[j])
77 | slice_sorted_move[N_MOVE * i + 3 * j + k] = a.get_slice_sorted()
78 | a.edge_multiply(cb.basicMoveCube[j])
79 | fh = open(fname, "wb")
80 | slice_sorted_move.tofile(fh)
81 | print()
82 | else:
83 | print("loading " + fname + " table...")
84 | fh = open(fname, "rb")
85 | slice_sorted_move = ar.array('H')
86 | slice_sorted_move.fromfile(fh, N_SLICE_SORTED * N_MOVE)
87 | fh.close()
88 | ########################################################################################################################
89 |
90 | # ################# Move table for the u_edges coordinate for transition phase 1 -> phase 2 ############################
91 |
92 | # The u_edges coordinate describes the 12!/8! = 11880 possible positions of the UR, UF, UL and UB edges. It is needed at
93 | # the end of phase 1 to set up the coordinates of phase 2
94 | # 0 <= u_edges < 11880 in phase 1, 0 <= u_edges < 1680 in phase 2, u_edges = 1656 for solved cube."""
95 | fname = "move_u_edges"
96 | if not path.isfile(fname):
97 | print("creating " + fname + " table...")
98 | u_edges_move = ar.array('H', [0 for i in range(N_SLICE_SORTED * N_MOVE)])
99 | for i in range(N_SLICE_SORTED):
100 | if i % 200 == 0:
101 | print('.', end='', flush=True)
102 | a.set_u_edges(i)
103 | for j in enums.Color:
104 | for k in range(3):
105 | a.edge_multiply(cb.basicMoveCube[j])
106 | u_edges_move[N_MOVE * i + 3 * j + k] = a.get_u_edges()
107 | a.edge_multiply(cb.basicMoveCube[j])
108 | fh = open(fname, "wb")
109 | u_edges_move.tofile(fh)
110 | print()
111 | else:
112 | print("loading " + fname + " table...")
113 | fh = open(fname, "rb")
114 | u_edges_move = ar.array('H')
115 | u_edges_move.fromfile(fh, N_SLICE_SORTED * N_MOVE)
116 | fh.close()
117 | ########################################################################################################################
118 |
119 | # ################# Move table for the d_edges coordinate for transition phase 1 -> phase 2 ############################
120 |
121 | # The d_edges coordinate describes the 12!/8! = 11880 possible positions of the DR, DF, DL and DB edges. It is needed at
122 | # the end of phase 1 to set up the coordinates of phase 2
123 | # 0 <= d_edges < 11880 in phase 1, 0 <= d_edges < 1680 in phase 2, d_edges = 0 for solved cube.
124 | fname = "move_d_edges"
125 | if not path.isfile(fname):
126 | print("creating " + fname + " table...")
127 | d_edges_move = ar.array('H', [0 for i in range(N_SLICE_SORTED * N_MOVE)])
128 | for i in range(N_SLICE_SORTED):
129 | if i % 200 == 0:
130 | print('.', end='', flush=True)
131 | a.set_d_edges(i)
132 | for j in enums.Color:
133 | for k in range(3):
134 | a.edge_multiply(cb.basicMoveCube[j])
135 | d_edges_move[N_MOVE * i + 3 * j + k] = a.get_d_edges()
136 | a.edge_multiply(cb.basicMoveCube[j])
137 | fh = open(fname, "wb")
138 | d_edges_move.tofile(fh)
139 | print()
140 | else:
141 | print("loading " + fname + " table...")
142 | fh = open(fname, "rb")
143 | d_edges_move = ar.array('H')
144 | d_edges_move.fromfile(fh, N_SLICE_SORTED * N_MOVE)
145 | fh.close()
146 | ########################################################################################################################
147 |
148 | # ######################### # Move table for the edges in the U-face and D-face. #######################################
149 |
150 | # The ud_edges coordinate describes the 40320 permutations of the edges UR, UF, UL, UB, DR, DF, DL and DB in phase 2
151 | # ud_edges undefined in phase 1, 0 <= ud_edges < 40320 in phase 2, ud_edges = 0 for solved cube.
152 | fname = "move_ud_edges"
153 | if not path.isfile(fname):
154 | print("creating " + fname + " table...")
155 | ud_edges_move = ar.array('H', [0 for i in range(N_UD_EDGES * N_MOVE)])
156 | for i in range(N_UD_EDGES):
157 | if (i+1) % 600 == 0:
158 | print('.', end='', flush=True)
159 | if (i+1) % 48000 == 0:
160 | print('')
161 | a.set_ud_edges(i)
162 | for j in enums.Color:
163 | for k in range(3):
164 | a.edge_multiply(cb.basicMoveCube[j])
165 | # only R2, F2, L2 and B2 in phase 2
166 | if j in [enums.Color.R, enums.Color.F, enums.Color.L, enums.Color.B] and k != 1:
167 | continue
168 | ud_edges_move[N_MOVE * i + 3 * j + k] = a.get_ud_edges()
169 | a.edge_multiply(cb.basicMoveCube[j])
170 | fh = open(fname, "wb")
171 | ud_edges_move.tofile(fh)
172 | print()
173 | else:
174 | print("loading " + fname + " table...")
175 | fh = open(fname, "rb")
176 | ud_edges_move = ar.array('H')
177 | ud_edges_move.fromfile(fh, N_UD_EDGES * N_MOVE)
178 | fh.close()
179 | ########################################################################################################################
180 |
181 | # ############################ Move table for the corners coordinate in phase 2 ########################################
182 |
183 | # The corners coordinate describes the 8! = 40320 permutations of the corners.
184 | # 0 <= corners < 40320 defined but unused in phase 1, 0 <= corners < 40320 in phase 2, corners = 0 for solved cube
185 | fname = "move_corners"
186 | if not path.isfile(fname):
187 | print("creating " + fname + " table...")
188 | corners_move = ar.array('H', [0 for i in range(N_CORNERS * N_MOVE)])
189 | for i in range(N_CORNERS):
190 | if (i+1) % 200 == 0:
191 | print('.', end='', flush=True)
192 | if(i+1) % 16000 == 0:
193 | print('')
194 | a.set_corners(i)
195 | for j in enums.Color:
196 | for k in range(3):
197 | a.corner_multiply(cb.basicMoveCube[j])
198 | corners_move[N_MOVE * i + 3 * j + k] = a.get_corners()
199 | a.corner_multiply(cb.basicMoveCube[j])
200 | fh = open(fname, "wb")
201 | corners_move.tofile(fh)
202 | fh.close()
203 | print()
204 | else:
205 | print("loading " + fname + " table...")
206 | fh = open(fname, "rb")
207 | corners_move = ar.array('H')
208 | corners_move.fromfile(fh, N_CORNERS * N_MOVE)
209 | fh.close()
210 | ########################################################################################################################
211 |
--------------------------------------------------------------------------------
/coord.py:
--------------------------------------------------------------------------------
1 | # ##### The cube on the coordinate level. It is described by a 3-tuple of natural numbers in phase 1 and phase 2. ######
2 |
3 | from os import path
4 | import array as ar
5 |
6 | import cubie as cb
7 | import enums
8 | import moves as mv
9 | import pruning as pr
10 | import symmetries as sy
11 | from defs import N_U_EDGES_PHASE2, N_PERM_4, N_CHOOSE_8_4, N_FLIP, N_TWIST, N_UD_EDGES, N_MOVE
12 | from enums import Edge as Ed
13 |
14 | SOLVED = 0 # 0 is index of solved state (except for u_edges coordinate)
15 | u_edges_plus_d_edges_to_ud_edges = None # global variable
16 |
17 |
18 | class CoordCube:
19 | """Represent a cube on the coordinate level.
20 |
21 | In phase 1 a state is uniquely determined by the three coordinates flip, twist and slice.
22 | In phase 2 a state is uniquely determined by the three coordinates corners, ud_edges and slice_sorted.
23 | """
24 |
25 | def __init__(self, cc=None):
26 | if cc is None:
27 | self.twist = SOLVED # twist of corners
28 | self.flip = SOLVED # flip of edges
29 | self.slice_sorted = SOLVED # Position of FR, FL, BL, BR edges. Valid in phase 1 (<11880) and phase 2 (<24)
30 | # The phase 1 slice coordinate is given by slice_sorted // 24
31 |
32 | self.u_edges = 1656 # Valid in phase 1 (<11880) and phase 2 (<1680). 1656 is the index of solved u_edges.
33 | self.d_edges = SOLVED # Valid in phase 1 (<11880) and phase 2 (<1680)
34 | self.corners = SOLVED # corner permutation. Valid in phase1 and phase2
35 | self.ud_edges = SOLVED # permutation of the ud-edges. Valid only in phase 2
36 | else:
37 | self.twist = cc.get_twist()
38 | self.flip = cc.get_flip()
39 | self.slice_sorted = cc.get_slice_sorted()
40 | self.u_edges = cc.get_u_edges()
41 | self.d_edges = cc.get_d_edges()
42 | self.corners = cc.get_corners()
43 | if self.slice_sorted < N_PERM_4: # phase 2 cube
44 | self.ud_edges = cc.get_ud_edges()
45 | else:
46 | self.ud_edges = -1 # invalid
47 |
48 | # symmetry reduced flipslice coordinate used in phase 1
49 | self.flipslice_classidx = sy.flipslice_classidx[N_FLIP * (self.slice_sorted // N_PERM_4) + self.flip]
50 | self.flipslice_sym = sy.flipslice_sym[N_FLIP * (self.slice_sorted // N_PERM_4) + self.flip]
51 | self.flipslice_rep = sy.flipslice_rep[self.flipslice_classidx]
52 | # symmetry reduced corner permutation coordinate used in phase 2
53 | self.corner_classidx = sy.corner_classidx[self.corners]
54 | self.corner_sym = sy.corner_sym[self.corners]
55 | self.corner_rep = sy.corner_rep[self.corner_classidx]
56 |
57 | def __str__(self):
58 | s = '(twist: ' + str(self.twist) + ', flip: ' + str(self.flip) + ', slice: ' + str(self.slice_sorted // 24) + \
59 | ', U-edges: ' + str(self.u_edges) + ', D-edges: ' + str(self.d_edges) + ', E-edges: ' \
60 | + str(self.slice_sorted) + ', Corners: ' + str(self.corners) + ', UD-Edges : ' + str(self.ud_edges) + ')'
61 | s = s + '\n' + str(self.flipslice_classidx) + ' ' + str(self.flipslice_sym) + ' ' + str(self.flipslice_rep)
62 | s = s + '\n' + str(self.corner_classidx) + ' ' + str(self.corner_sym) + ' ' + str(self.corner_rep)
63 | return s
64 |
65 | def phase1_move(self, m):
66 | self.twist = mv.twist_move[N_MOVE * self.twist + m]
67 | self.flip = mv.flip_move[N_MOVE * self.flip + m]
68 | self.slice_sorted = mv.slice_sorted_move[N_MOVE * self.slice_sorted + m]
69 | # optional:
70 | self.u_edges = mv.u_edges_move[N_MOVE * self.u_edges + m] # u_edges and d_edges retrieve ud_edges easily
71 | self.d_edges = mv.d_edges_move[N_MOVE * self.d_edges + m] # if phase 1 is finished and phase 2 starts
72 | self.corners = mv.corners_move[N_MOVE * self.corners + m] # Is needed only in phase 2
73 |
74 | self.flipslice_classidx = sy.flipslice_classidx[N_FLIP * (self.slice_sorted // N_PERM_4) + self.flip]
75 | self.flipslice_sym = sy.flipslice_sym[N_FLIP * (self.slice_sorted // N_PERM_4) + self.flip]
76 | self.flipslice_rep = sy.flipslice_rep[self.flipslice_classidx]
77 |
78 | self.corner_classidx = self.corner_classidx = sy.corner_classidx[self.corners]
79 | self.corner_sym = sy.corner_sym[self.corners]
80 | self.corner_rep = sy.corner_rep[self.corner_classidx]
81 |
82 | def phase2_move(self, m):
83 | self.slice_sorted = mv.slice_sorted_move[N_MOVE * self.slice_sorted + m]
84 | self.corners = mv.corners_move[N_MOVE * self.corners + m]
85 | self.ud_edges = mv.ud_edges_move[N_MOVE * self.ud_edges + m]
86 |
87 | def get_depth_phase1(self):
88 | slice_ = self.slice_sorted // N_PERM_4
89 | flip = self.flip
90 | twist = self.twist
91 | flipslice = N_FLIP * slice_ + flip
92 | classidx = sy.flipslice_classidx[flipslice]
93 | sym = sy.flipslice_sym[flipslice]
94 | depth_mod3 = pr.get_flipslice_twist_depth3(N_TWIST * classidx + sy.twist_conj[(twist << 4) + sym])
95 |
96 | depth = 0
97 | while flip != SOLVED or slice_ != SOLVED or twist != SOLVED:
98 | if depth_mod3 == 0:
99 | depth_mod3 = 3
100 | for m in enums.Move:
101 | twist1 = mv.twist_move[N_MOVE * twist + m]
102 | flip1 = mv.flip_move[N_MOVE * flip + m]
103 | slice1 = mv.slice_sorted_move[N_MOVE * slice_ * N_PERM_4 + m] // N_PERM_4
104 | flipslice1 = N_FLIP * slice1 + flip1
105 | classidx1 = sy.flipslice_classidx[flipslice1]
106 | sym = sy.flipslice_sym[flipslice1]
107 | if pr.get_flipslice_twist_depth3(
108 | N_TWIST * classidx1 + sy.twist_conj[(twist1 << 4) + sym]) == depth_mod3 - 1:
109 | depth += 1
110 | twist = twist1
111 | flip = flip1
112 | slice_ = slice1
113 | depth_mod3 -= 1
114 | break
115 | return depth
116 |
117 | @staticmethod
118 | def get_depth_phase2(corners, ud_edges):
119 | # the slice coordinate is not included
120 | classidx = sy.corner_classidx[corners]
121 | sym = sy.corner_sym[corners]
122 | depth_mod3 = pr.get_corners_ud_edges_depth3(N_UD_EDGES * classidx + sy.ud_edges_conj[(ud_edges << 4) + sym])
123 | if depth_mod3 == 3: # unfilled entry, depth >= 11
124 | return 11
125 | depth = 0
126 | while corners != SOLVED or ud_edges != SOLVED:
127 | if depth_mod3 == 0:
128 | depth_mod3 = 3
129 | # only iterate phase 2 moves
130 | for m in (enums.Move.U1, enums.Move.U2, enums.Move.U3, enums.Move.R2, enums.Move.F2, enums.Move.D1,
131 | enums.Move.D2, enums.Move.D3, enums.Move.L2, enums.Move.B2):
132 | corners1 = mv.corners_move[N_MOVE * corners + m]
133 | ud_edges1 = mv.ud_edges_move[N_MOVE * ud_edges + m]
134 | classidx1 = sy.corner_classidx[corners1]
135 | sym = sy.corner_sym[corners1]
136 | if pr.get_corners_ud_edges_depth3(N_UD_EDGES * classidx1 + sy.ud_edges_conj[(ud_edges1 << 4) + sym]) == \
137 | depth_mod3 - 1:
138 | depth += 1
139 | corners = corners1
140 | ud_edges = ud_edges1
141 | depth_mod3 -= 1
142 | break
143 | return depth
144 |
145 |
146 | def create_phase2_edgemerge_table():
147 | """phase2_edgemerge retrieves the initial phase 2 ud_edges coordinate from the u_edges and d_edges coordinates."""
148 | fname = "phase2_edgemerge"
149 | global u_edges_plus_d_edges_to_ud_edges
150 | c_u = cb.CubieCube()
151 | c_d = cb.CubieCube()
152 | c_ud = cb.CubieCube()
153 | edge_u = [Ed.UR, Ed.UF, Ed.UL, Ed.UB]
154 | edge_d = [Ed.DR, Ed.DF, Ed.DL, Ed.DB]
155 | edge_ud = [Ed.UR, Ed.UF, Ed.UL, Ed.UB, Ed.DR, Ed.DF, Ed.DL, Ed.DB]
156 |
157 | if not path.isfile(fname):
158 | cnt = 0
159 | print("creating " + fname + " table...")
160 | u_edges_plus_d_edges_to_ud_edges = ar.array('H', [0 for i in range(N_U_EDGES_PHASE2 * N_PERM_4)])
161 | for i in range(N_U_EDGES_PHASE2):
162 | c_u.set_u_edges(i)
163 | for j in range(N_CHOOSE_8_4):
164 | c_d.set_d_edges(j * N_PERM_4)
165 | invalid = False
166 | for e in edge_ud:
167 | c_ud.ep[e] = -1 # invalidate edges
168 | if c_u.ep[e] in edge_u:
169 | c_ud.ep[e] = c_u.ep[e]
170 | if c_d.ep[e] in edge_d:
171 | c_ud.ep[e] = c_d.ep[e]
172 | if c_ud.ep[e] == -1:
173 | invalid = True # edge collision
174 | break
175 | if not invalid:
176 | for k in range(N_PERM_4):
177 | c_d.set_d_edges(j * N_PERM_4 + k)
178 | for e in edge_ud:
179 | if c_u.ep[e] in edge_u:
180 | c_ud.ep[e] = c_u.ep[e]
181 | if c_d.ep[e] in edge_d:
182 | c_ud.ep[e] = c_d.ep[e]
183 | u_edges_plus_d_edges_to_ud_edges[N_PERM_4 * i + k] = c_ud.get_ud_edges()
184 | cnt += 1
185 | if cnt % 2000 == 0:
186 | print('.', end='', flush=True)
187 | print()
188 | fh = open(fname, "wb")
189 | u_edges_plus_d_edges_to_ud_edges.tofile(fh)
190 | fh.close()
191 | print()
192 | else:
193 | fh = open(fname, "rb")
194 | u_edges_plus_d_edges_to_ud_edges = ar.array('H')
195 | u_edges_plus_d_edges_to_ud_edges.fromfile(fh, N_U_EDGES_PHASE2 * N_PERM_4)
196 |
197 |
198 | ########################################################################################################################
199 |
200 |
201 | create_phase2_edgemerge_table()
202 |
--------------------------------------------------------------------------------
/symmetries.py:
--------------------------------------------------------------------------------
1 | # #################### Symmetry related functions. Symmetry considerations increase the performance of the solver.######
2 |
3 | from os import path
4 | import numpy as np
5 | import array as ar
6 | import cubie as cb
7 | from defs import N_TWIST, N_SYM, N_SYM_D4h, N_FLIP, N_SLICE, N_CORNERS, N_UD_EDGES, N_MOVE, N_FLIPSLICE_CLASS, \
8 | N_CORNERS_CLASS
9 | from enums import Corner as Co, Edge as Ed, Move as Mv, BS
10 |
11 | INVALID = 65535
12 |
13 | # #################### Permutations and orientation changes of the basic symmetries ###################################
14 |
15 | # 120° clockwise rotation around the long diagonal URF-DBL
16 | cpROT_URF3 = [Co.URF, Co.DFR, Co.DLF, Co.UFL, Co.UBR, Co.DRB, Co.DBL, Co.ULB]
17 | coROT_URF3 = [1, 2, 1, 2, 2, 1, 2, 1]
18 | epROT_URF3 = [Ed.UF, Ed.FR, Ed.DF, Ed.FL, Ed.UB, Ed.BR, Ed.DB, Ed.BL, Ed.UR, Ed.DR, Ed.DL, Ed.UL]
19 | eoROT_URF3 = [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1]
20 |
21 | # 180° rotation around the axis through the F and B centers
22 | cpROT_F2 = [Co.DLF, Co.DFR, Co.DRB, Co.DBL, Co.UFL, Co.URF, Co.UBR, Co.ULB]
23 | coROT_F2 = [0, 0, 0, 0, 0, 0, 0, 0]
24 | epROT_F2 = [Ed.DL, Ed.DF, Ed.DR, Ed.DB, Ed.UL, Ed.UF, Ed.UR, Ed.UB, Ed.FL, Ed.FR, Ed.BR, Ed.BL]
25 | eoROT_F2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
26 |
27 | # 90° clockwise rotation around the axis through the U and D centers
28 | cpROT_U4 = [Co.UBR, Co.URF, Co.UFL, Co.ULB, Co.DRB, Co.DFR, Co.DLF, Co.DBL]
29 | coROT_U4 = [0, 0, 0, 0, 0, 0, 0, 0]
30 | epROT_U4 = [Ed.UB, Ed.UR, Ed.UF, Ed.UL, Ed.DB, Ed.DR, Ed.DF, Ed.DL, Ed.BR, Ed.FR, Ed.FL, Ed.BL]
31 | eoROT_U4 = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
32 |
33 | # reflection at the plane through the U, D, F, B centers
34 | cpMIRR_LR2 = [Co.UFL, Co.URF, Co.UBR, Co.ULB, Co.DLF, Co.DFR, Co.DRB, Co.DBL]
35 | coMIRR_LR2 = [3, 3, 3, 3, 3, 3, 3, 3]
36 | epMIRR_LR2 = [Ed.UL, Ed.UF, Ed.UR, Ed.UB, Ed.DL, Ed.DF, Ed.DR, Ed.DB, Ed.FL, Ed.FR, Ed.BR, Ed.BL]
37 | eoMIRR_LR2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
38 |
39 | basicSymCube = [cb.CubieCube()] * 4
40 | basicSymCube[BS.ROT_URF3] = cb.CubieCube(cpROT_URF3, coROT_URF3, epROT_URF3, eoROT_URF3)
41 | basicSymCube[BS.ROT_F2] = cb.CubieCube(cpROT_F2, coROT_F2, epROT_F2, eoROT_F2)
42 | basicSymCube[BS.ROT_U4] = cb.CubieCube(cpROT_U4, coROT_U4, epROT_U4, eoROT_U4)
43 | basicSymCube[BS.MIRR_LR2] = cb.CubieCube(cpMIRR_LR2, coMIRR_LR2, epMIRR_LR2, eoMIRR_LR2)
44 | # ######################################################################################################################
45 |
46 | # ######################################## Fill SymCube list ###########################################################
47 |
48 | # 48 CubieCubes will represent the 48 cube symmetries
49 | symCube = []
50 | cc = cb.CubieCube() # Identity cube
51 | idx = 0
52 | for urf3 in range(3):
53 | for f2 in range(2):
54 | for u4 in range(4):
55 | for lr2 in range(2):
56 | symCube.append(cb.CubieCube(cc.cp, cc.co, cc.ep, cc.eo))
57 | idx += 1
58 | cc.multiply(basicSymCube[BS.MIRR_LR2])
59 | cc.multiply(basicSymCube[BS.ROT_U4])
60 | cc.multiply(basicSymCube[BS.ROT_F2])
61 | cc.multiply(basicSymCube[BS.ROT_URF3])
62 | ########################################################################################################################
63 |
64 | # ########################################## Fill the inv_idx array ####################################################
65 |
66 | # Indices for the inverse symmetries: SymCube[inv_idx[idx]] == SymCube[idx]^(-1)
67 | inv_idx = [0] * N_SYM
68 | for j in range(N_SYM):
69 | for i in range(N_SYM):
70 | cc = cb.CubieCube(symCube[j].cp, symCube[j].co, symCube[j].ep, symCube[j].eo)
71 | cc.corner_multiply(symCube[i])
72 | if cc.cp[Co.URF] == Co.URF and cc.cp[Co.UFL] == Co.UFL and cc.cp[Co.ULB] == Co.ULB:
73 | inv_idx[j] = i
74 | break
75 | ########################################################################################################################
76 |
77 | # ################################# Generate the group table for the 48 cube symmetries ################################
78 | mult_sym = np.empty([N_SYM, N_SYM], dtype=np.uint8)
79 | for i in range(N_SYM):
80 | for j in range(N_SYM):
81 | cc = cb.CubieCube(symCube[i].cp, symCube[i].co, symCube[i].ep, symCube[i].eo)
82 | cc.multiply(symCube[j])
83 | for k in range(N_SYM):
84 | if cc == symCube[k]: # SymCube[i]*SymCube[j] == SymCube[k]
85 | mult_sym[i][j] = k
86 | break
87 | ########################################################################################################################
88 |
89 | # #### Generate the table for the conjugation of a move m by a symmetry s. conj_move[m, s] = s*m*s^-1 ##################
90 | conj_move = np.empty([N_MOVE, N_SYM], dtype=np.uint8)
91 | for s in range(N_SYM):
92 | for m in Mv:
93 | ss = cb.CubieCube(symCube[s].cp, symCube[s].co, symCube[s].ep, symCube[s].eo) # copy cube
94 | ss.multiply(cb.moveCube[m]) # s*m
95 | ss.multiply(symCube[inv_idx[s]]) # s*m*s^-1
96 | for m2 in Mv:
97 | if ss == cb.moveCube[m2]:
98 | conj_move[m][s] = m2
99 | ########################################################################################################################
100 |
101 | # ###### Generate the phase 1 table for the conjugation of the twist t by a symmetry s. twist_conj[t, s] = s*t*s^-1 ####
102 | fname = "conj_twist"
103 | if not path.isfile(fname):
104 | print('On the first run, several tables will be created. This takes from 1/2 hour (e.g. PC) to 6 hours '
105 | '(e.g. RaspberryPi3), depending on the hardware.')
106 | print("creating " + fname + " table...")
107 | twist_conj = ar.array('H', [0] * (N_TWIST * N_SYM_D4h))
108 | for t in range(N_TWIST):
109 | cc = cb.CubieCube()
110 | cc.set_twist(t)
111 | for s in range(N_SYM_D4h):
112 | ss = cb.CubieCube(symCube[s].cp, symCube[s].co, symCube[s].ep, symCube[s].eo) # copy cube
113 | ss.corner_multiply(cc) # s*t
114 | ss.corner_multiply(symCube[inv_idx[s]]) # s*t*s^-1
115 | twist_conj[N_SYM_D4h * t + s] = ss.get_twist()
116 | fh = open(fname, "wb")
117 | twist_conj.tofile(fh)
118 | else:
119 | print("loading " + fname + " table...")
120 | fh = open(fname, 'rb')
121 | twist_conj = ar.array('H')
122 | twist_conj.fromfile(fh, N_TWIST * N_SYM_D4h)
123 |
124 | fh.close()
125 | # ######################################################################################################################
126 |
127 | # #################### Generate the phase 2 table for the conjugation of the URtoDB coordinate by a symmetrie ##########
128 | fname = "conj_ud_edges"
129 | if not path.isfile(fname):
130 | print("creating " + fname + " table...")
131 | ud_edges_conj = ar.array('H', [0] * (N_UD_EDGES * N_SYM_D4h))
132 | for t in range(N_UD_EDGES):
133 | if (t + 1) % 400 == 0:
134 | print('.', end='', flush=True)
135 | if (t + 1) % 32000 == 0:
136 | print('')
137 | cc = cb.CubieCube()
138 | cc.set_ud_edges(t)
139 | for s in range(N_SYM_D4h):
140 | ss = cb.CubieCube(symCube[s].cp, symCube[s].co, symCube[s].ep, symCube[s].eo) # copy cube
141 | ss.edge_multiply(cc) # s*t
142 | ss.edge_multiply(symCube[inv_idx[s]]) # s*t*s^-1
143 | ud_edges_conj[N_SYM_D4h * t + s] = ss.get_ud_edges()
144 | print('')
145 | fh = open(fname, "wb")
146 | ud_edges_conj.tofile(fh)
147 | else:
148 | print("loading " + fname + " table...")
149 | fh = open(fname, "rb")
150 | ud_edges_conj = ar.array('H')
151 | ud_edges_conj.fromfile(fh, N_UD_EDGES * N_SYM_D4h)
152 | fh.close()
153 | # ######################################################################################################################
154 |
155 | # ############## Generate the tables to handle the symmetry reduced flip-slice coordinate in phase 1 ##################
156 | fname1 = "fs_classidx"
157 | fname2 = "fs_sym"
158 | fname3 = "fs_rep"
159 | if not (path.isfile(fname1) and path.isfile(fname2) and path.isfile(fname3)):
160 | print("creating " + "flipslice sym-tables...")
161 | flipslice_classidx = ar.array('H', [INVALID] * (N_FLIP * N_SLICE)) # idx -> classidx
162 | flipslice_sym = ar.array('B', [0] * (N_FLIP * N_SLICE)) # idx -> symmetry
163 | flipslice_rep = ar.array('L', [0] * N_FLIPSLICE_CLASS) # classidx -> idx of representant
164 |
165 | classidx = 0
166 | cc = cb.CubieCube()
167 | for slc in range(N_SLICE):
168 | cc.set_slice(slc)
169 | for flip in range(N_FLIP):
170 | cc.set_flip(flip)
171 | idx = N_FLIP * slc + flip
172 | if (idx + 1) % 4000 == 0:
173 | print('.', end='', flush=True)
174 | if (idx + 1) % 320000 == 0:
175 | print('')
176 |
177 | if flipslice_classidx[idx] == INVALID:
178 | flipslice_classidx[idx] = classidx
179 | flipslice_sym[idx] = 0
180 | flipslice_rep[classidx] = idx
181 | else:
182 | continue
183 | for s in range(N_SYM_D4h): # conjugate representant by all 16 symmetries
184 | ss = cb.CubieCube(symCube[inv_idx[s]].cp, symCube[inv_idx[s]].co, symCube[inv_idx[s]].ep,
185 | symCube[inv_idx[s]].eo) # copy cube
186 | ss.edge_multiply(cc)
187 | ss.edge_multiply(symCube[s]) # s^-1*cc*s
188 | idx_new = N_FLIP * ss.get_slice() + ss.get_flip()
189 | if flipslice_classidx[idx_new] == INVALID:
190 | flipslice_classidx[idx_new] = classidx
191 | flipslice_sym[idx_new] = s
192 | classidx += 1
193 | print('')
194 | fh = open(fname1, 'wb')
195 | flipslice_classidx.tofile(fh)
196 | fh.close()
197 | fh = open(fname2, 'wb')
198 | flipslice_sym.tofile(fh)
199 | fh.close()
200 | fh = open(fname3, 'wb')
201 | flipslice_rep.tofile(fh)
202 | fh.close()
203 |
204 | else:
205 | print("loading " + "flipslice sym-tables...")
206 |
207 | fh = open(fname1, 'rb')
208 | flipslice_classidx = ar.array('H')
209 | flipslice_classidx.fromfile(fh, N_FLIP * N_SLICE)
210 | fh.close()
211 | fh = open(fname2, 'rb')
212 | flipslice_sym = ar.array('B')
213 | flipslice_sym.fromfile(fh, N_FLIP * N_SLICE)
214 | fh.close()
215 | fh = open(fname3, 'rb')
216 | flipslice_rep = ar.array('L')
217 | flipslice_rep.fromfile(fh, N_FLIPSLICE_CLASS)
218 | fh.close()
219 | ########################################################################################################################
220 |
221 | # ############ Generate the tables to handle the symmetry reduced corner permutation coordinate in phase 2 #############
222 | fname1 = "co_classidx"
223 | fname2 = "co_sym"
224 | fname3 = "co_rep"
225 | if not (path.isfile(fname1) and path.isfile(fname2) and path.isfile(fname3)):
226 | print("creating " + "corner sym-tables...")
227 | corner_classidx = ar.array('H', [INVALID] * N_CORNERS) # idx -> classidx
228 | corner_sym = ar.array('B', [0] * N_CORNERS) # idx -> symmetry
229 | corner_rep = ar.array('H', [0] * N_CORNERS_CLASS) # classidx -> idx of representant
230 |
231 | classidx = 0
232 | cc = cb.CubieCube()
233 | for cp in range(N_CORNERS):
234 | cc.set_corners(cp)
235 | if (cp + 1) % 8000 == 0:
236 | print('.', end='', flush=True)
237 |
238 | if corner_classidx[cp] == INVALID:
239 | corner_classidx[cp] = classidx
240 | corner_sym[cp] = 0
241 | corner_rep[classidx] = cp
242 | else:
243 | continue
244 | for s in range(N_SYM_D4h): # conjugate representant by all 16 symmetries
245 | ss = cb.CubieCube(symCube[inv_idx[s]].cp, symCube[inv_idx[s]].co, symCube[inv_idx[s]].ep,
246 | symCube[inv_idx[s]].eo) # copy cube
247 | ss.corner_multiply(cc)
248 | ss.corner_multiply(symCube[s]) # s^-1*cc*s
249 | cp_new = ss.get_corners()
250 | if corner_classidx[cp_new] == INVALID:
251 | corner_classidx[cp_new] = classidx
252 | corner_sym[cp_new] = s
253 | classidx += 1
254 | print('')
255 | fh = open(fname1, 'wb')
256 | corner_classidx.tofile(fh)
257 | fh.close()
258 | fh = open(fname2, 'wb')
259 | corner_sym.tofile(fh)
260 | fh.close()
261 | fh = open(fname3, 'wb')
262 | corner_rep.tofile(fh)
263 | fh.close()
264 |
265 | else:
266 | print("loading " + "corner sym-tables...")
267 |
268 | fh = open(fname1, 'rb')
269 | corner_classidx = ar.array('H')
270 | corner_classidx.fromfile(fh,N_CORNERS)
271 | fh.close()
272 | fh = open(fname2, 'rb')
273 | corner_sym = ar.array('B')
274 | corner_sym.fromfile(fh, N_CORNERS)
275 | fh.close()
276 | fh = open(fname3, 'rb')
277 | corner_rep = ar.array('H')
278 | corner_rep.fromfile(fh, N_CORNERS_CLASS)
279 | fh.close()
280 | ########################################################################################################################
281 |
--------------------------------------------------------------------------------
/client_gui2.py:
--------------------------------------------------------------------------------
1 | # ################ A simple graphical interface which communicates with the server #####################################
2 |
3 | # While client_gui only allows to set the facelets with the mouse, this file (client_gui2) also takes input from the
4 | # webcam and includes sliders for some opencv parameters.
5 |
6 | from tkinter import *
7 | import socket
8 | import face
9 | import cubie
10 | from threading import Thread
11 | from vision2 import grab_colors
12 | import vision_params
13 |
14 |
15 | import numpy as np
16 |
17 | # ################################## some global variables and constants ###############################################
18 | DEFAULT_HOST = 'localhost'
19 | DEFAULT_PORT = '8080'
20 | width = 60 # width of a facelet in pixels
21 | facelet_id = [[[0 for col in range(3)] for row in range(3)] for fc in range(6)]
22 | colorpick_id = [0 for i in range(6)]
23 | curcol = None
24 | t = ("U", "R", "F", "D", "L", "B")
25 | cols = ("yellow", "green", "red", "white", "blue", "orange")
26 |
27 |
28 | ########################################################################################################################
29 |
30 | # ################################################ Diverse functions ###################################################
31 |
32 |
33 | def show_text(txt):
34 | """Display messages."""
35 | print(txt)
36 | display.insert(INSERT, txt)
37 | root.update_idletasks()
38 |
39 |
40 | def create_facelet_rects(a):
41 | """Initialize the facelet grid on the canvas."""
42 | offset = ((1, 0), (2, 1), (1, 1), (1, 2), (0, 1), (3, 1))
43 | for f in range(6):
44 | for row in range(3):
45 | y = 10 + offset[f][1] * 3 * a + row * a
46 | for col in range(3):
47 | x = 10 + offset[f][0] * 3 * a + col * a
48 | facelet_id[f][row][col] = canvas.create_rectangle(x, y, x + a, y + a, fill="grey")
49 | if row == 1 and col == 1:
50 | canvas.create_text(x + width // 2, y + width // 2, font=("", 14), text=t[f], state=DISABLED)
51 | for f in range(6):
52 | canvas.itemconfig(facelet_id[f][1][1], fill=cols[f])
53 |
54 |
55 | def create_colorpick_rects(a):
56 | """Initialize the "paintbox" on the canvas."""
57 | global curcol
58 | global cols
59 | for i in range(6):
60 | x = (i % 3) * (a + 5) + 7 * a
61 | y = (i // 3) * (a + 5) + 7 * a
62 | colorpick_id[i] = canvas.create_rectangle(x, y, x + a, y + a, fill=cols[i])
63 | canvas.itemconfig(colorpick_id[0], width=4)
64 | curcol = cols[0]
65 |
66 |
67 | def get_definition_string():
68 | """Generate the cube definition string from the facelet colors."""
69 | color_to_facelet = {}
70 | for i in range(6):
71 | color_to_facelet.update({canvas.itemcget(facelet_id[i][1][1], "fill"): t[i]})
72 | s = ''
73 | for f in range(6):
74 | for row in range(3):
75 | for col in range(3):
76 | s += color_to_facelet[canvas.itemcget(facelet_id[f][row][col], "fill")]
77 | return s
78 |
79 |
80 | ########################################################################################################################
81 |
82 | # ############################### Solve the displayed cube with a local or remote server ###############################
83 |
84 |
85 | def solve():
86 | """Connect to the server and return the solving maneuver."""
87 | display.delete(1.0, END) # clear output window
88 | try:
89 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
90 | except socket.error:
91 | show_text('Failed to create socket')
92 | return
93 | # host = 'f9f0b2jt6zmzyo6b.myfritz.net' # my RaspberryPi, if online
94 | host = txt_host.get(1.0, END).rstrip() # default is localhost
95 | port = int(txt_port.get(1.0, END)) # default is port 8080
96 |
97 | try:
98 | remote_ip = socket.gethostbyname(host)
99 | except socket.gaierror:
100 | show_text('Hostname could not be resolved.')
101 | return
102 | try:
103 | s.connect((remote_ip, port))
104 | except:
105 | show_text('Cannot connect to server!')
106 | return
107 | show_text('Connected with ' + remote_ip + '\n')
108 | try:
109 | defstr = get_definition_string() + '\n'
110 | except:
111 | show_text('Invalid facelet configuration.\nWrong or missing colors.')
112 | return
113 | show_text(defstr)
114 | try:
115 | s.sendall((defstr + '\n').encode())
116 | except:
117 | show_text('Cannot send cube configuration to server.')
118 | return
119 | show_text(s.recv(2048).decode())
120 |
121 |
122 | ########################################################################################################################
123 |
124 | # ################################# Functions to change the facelet colors #############################################
125 |
126 |
127 | def clean():
128 | """Restore the cube to a clean cube."""
129 | for f in range(6):
130 | for row in range(3):
131 | for col in range(3):
132 | canvas.itemconfig(facelet_id[f][row][col], fill=canvas.itemcget(facelet_id[f][1][1], "fill"))
133 |
134 |
135 | def empty():
136 | """Remove the facelet colors except the center facelets colors."""
137 | for f in range(6):
138 | for row in range(3):
139 | for col in range(3):
140 | if row != 1 or col != 1:
141 | canvas.itemconfig(facelet_id[f][row][col], fill="grey")
142 |
143 |
144 | def random():
145 | """Generate a random cube and sets the corresponding facelet colors."""
146 | cc = cubie.CubieCube()
147 | cc.randomize()
148 | fc = cc.to_facelet_cube()
149 | idx = 0
150 | for f in range(6):
151 | for row in range(3):
152 | for col in range(3):
153 | canvas.itemconfig(facelet_id[f][row][col], fill=cols[fc.f[idx]])
154 | idx += 1
155 |
156 |
157 | ########################################################################################################################
158 |
159 | # ################################### Edit the facelet colors ##########################################################
160 |
161 |
162 | def click(event):
163 | """Define how to react on left mouse clicks"""
164 | global curcol
165 | idlist = canvas.find_withtag("current")
166 | if len(idlist) > 0:
167 | if idlist[0] in colorpick_id:
168 | curcol = canvas.itemcget("current", "fill")
169 | for i in range(6):
170 | canvas.itemconfig(colorpick_id[i], width=1)
171 | canvas.itemconfig("current", width=5)
172 | else:
173 | canvas.itemconfig("current", fill=curcol)
174 |
175 |
176 | ########################################################################################################################
177 |
178 |
179 | # ######################################### functions to set the slider values #########################################
180 | def set_rgb_L(val):
181 | vision_params.rgb_L = int(val)
182 |
183 |
184 | def set_orange_L(val):
185 | vision_params.orange_L = int(val)
186 |
187 |
188 | def set_orange_H(val):
189 | vision_params.orange_H = int(val)
190 |
191 |
192 | def set_yellow_H(val):
193 | vision_params.yellow_H = int(val)
194 |
195 |
196 | def set_green_H(val):
197 | vision_params.green_H = int(val)
198 |
199 |
200 | def set_blue_H(val):
201 | vision_params.blue_H = int(val)
202 |
203 |
204 | def set_sat_W(val):
205 | vision_params.sat_W = int(val)
206 |
207 |
208 | def set_val_W(val):
209 | vision_params.val_W = int(val)
210 |
211 |
212 | def set_sigma_C(val):
213 | vision_params.sigma_C = int(val)
214 |
215 |
216 | def set_delta_C(val):
217 | vision_params.delta_C = int(val)
218 |
219 |
220 | def transfer():
221 | """Transfer the facelet colors detected by the opencv vision to the GUI editor."""
222 | if len(vision_params.face_col) == 0:
223 | return
224 | centercol = vision_params.face_col[1][1]
225 |
226 | vision_params.cube_col[centercol] = vision_params.face_col
227 | vision_params.cube_hsv[centercol] = vision_params.face_hsv
228 |
229 | dc = {}
230 | for i in range(6):
231 | dc[canvas.itemcget(facelet_id[i][1][1], "fill")] = i # map color to face number
232 | for i in range(3):
233 | for j in range(3):
234 | canvas.itemconfig(facelet_id[dc[centercol]][i][j], fill=vision_params.face_col[i][j])
235 |
236 | # ######################################################################################################################
237 |
238 | # ###################################### Generate and display the TK_widgets ##########################################
239 |
240 | root = Tk()
241 | root.wm_title("Solver Client")
242 | canvas = Canvas(root, width=12 * width + 20, height=9 * width + 20)
243 | canvas.pack()
244 | bsolve = Button(text="Solve", height=2, width=10, relief=RAISED, command=solve)
245 | bsolve_window = canvas.create_window(10 + 10.5 * width, 10 + 6.5 * width, anchor=NW, window=bsolve)
246 | bclean = Button(text="Clean", height=1, width=10, relief=RAISED, command=clean)
247 | bclean_window = canvas.create_window(10 + 10.5 * width, 10 + 7.5 * width, anchor=NW, window=bclean)
248 | bempty = Button(text="Empty", height=1, width=10, relief=RAISED, command=empty)
249 | bempty_window = canvas.create_window(10 + 10.5 * width, 10 + 8 * width, anchor=NW, window=bempty)
250 | brandom = Button(text="Random", height=1, width=10, relief=RAISED, command=random)
251 | brandom_window = canvas.create_window(10 + 10.5 * width, 10 + 8.5 * width, anchor=NW, window=brandom)
252 | display = Text(height=7, width=39)
253 | text_window = canvas.create_window(10 + 6.5 * width, 10 + .5 * width, anchor=NW, window=display)
254 | hp = Label(text=' Hostname and Port')
255 | hp_window = canvas.create_window(10 + 0 * width, 10 + 0.6 * width, anchor=NW, window=hp)
256 | txt_host = Text(height=1, width=20)
257 | txt_host_window = canvas.create_window(10 + 0 * width, 10 + 1 * width, anchor=NW, window=txt_host)
258 | txt_host.insert(INSERT, DEFAULT_HOST)
259 | txt_port = Text(height=1, width=20)
260 | txt_port_window = canvas.create_window(10 + 0 * width, 10 + 1.5 * width, anchor=NW, window=txt_port)
261 | txt_port.insert(INSERT, DEFAULT_PORT)
262 | canvas.bind("", click)
263 | create_facelet_rects(width)
264 | create_colorpick_rects(width)
265 |
266 | s_orange_L = Scale(root, from_=1, to=14, length=width * 1.4, showvalue=0, label='red-orange', orient=HORIZONTAL,
267 | command=set_orange_L)
268 | canvas.create_window(10, 12 + 6.0 * width, anchor=NW, window=s_orange_L)
269 | s_orange_L.set(vision_params.orange_L)
270 |
271 | s_orange_H = Scale(root, from_=8, to=40, length=width * 1.4, showvalue=0, label='orange-yellow', orient=HORIZONTAL,
272 | command=set_orange_H)
273 | canvas.create_window(10, 12 + 6.6 * width, anchor=NW, window=s_orange_H)
274 | s_orange_H.set(vision_params.orange_H)
275 |
276 | s_yellow_H = Scale(root, from_=31, to=80, length=width * 1.4, showvalue=0, label='yellow-green', orient=HORIZONTAL,
277 | command=set_yellow_H)
278 | canvas.create_window(10, 12 + 7.2 * width, anchor=NW, window=s_yellow_H)
279 | s_yellow_H.set(vision_params.yellow_H)
280 |
281 | s_green_H = Scale(root, from_=70, to=120, length=width * 1.4, showvalue=0, label='green-blue', orient=HORIZONTAL,
282 | command=set_green_H)
283 | canvas.create_window(10, 12 + 7.8 * width, anchor=NW, window=s_green_H)
284 | s_green_H.set(vision_params.green_H)
285 |
286 | s_blue_H = Scale(root, from_=120, to=180, length=width * 1.4, showvalue=0, label='blue-red', orient=HORIZONTAL,
287 | command=set_blue_H)
288 | canvas.create_window(10, 12 + 8.4 * width, anchor=NW, window=s_blue_H)
289 | s_blue_H.set(vision_params.blue_H)
290 |
291 | s_rgb_L = Scale(root, from_=0, to=140, length=width * 1.4, showvalue=0, label='black-filter', orient=HORIZONTAL,
292 | command=set_rgb_L)
293 | canvas.create_window(10 + width * 1.5, 12 + 6 * width, anchor=NW, window=s_rgb_L)
294 | s_rgb_L.set(vision_params.rgb_L)
295 |
296 | s_sat_W = Scale(root, from_=120, to=0, length=width * 1.4, showvalue=0, label='white-filter s', orient=HORIZONTAL,
297 | command=set_sat_W)
298 | canvas.create_window(10 + width * 1.5, 12 + 6.6 * width, anchor=NW, window=s_sat_W)
299 | s_sat_W.set(vision_params.sat_W)
300 |
301 | s_val_W = Scale(root, from_=80, to=255, length=width * 1.4, showvalue=0, label='white-filter v', orient=HORIZONTAL,
302 | command=set_val_W)
303 | canvas.create_window(10 + width * 1.5, 12 + 7.2 * width, anchor=NW, window=s_val_W)
304 | s_val_W.set(vision_params.val_W)
305 |
306 | s_sigma_C = Scale(root, from_=30, to=0, length=width * 1.4, showvalue=0, label='color-filter \u03c3', orient=HORIZONTAL,
307 | command=set_sigma_C)
308 | canvas.create_window(10 + width * 1.5, 12 + 7.8 * width, anchor=NW, window=s_sigma_C)
309 | s_sigma_C.set(vision_params.sigma_C)
310 |
311 | s_delta_C = Scale(root, from_=10, to=0, length=width * 1.4, showvalue=0, label='color-filter \u03b4', orient=HORIZONTAL,
312 | command=set_delta_C)
313 | canvas.create_window(10 + width * 1.5, 12 + 8.4 * width, anchor=NW, window=s_delta_C)
314 | s_delta_C.set(vision_params.delta_C)
315 |
316 | btransfer = Button(text="Webcam import", height=2, width=13, relief=RAISED, command=transfer)
317 | canvas.create_window(10 + 0.5 * width, 10 + 2.1 * width, anchor=NW, window=btransfer)
318 |
319 |
320 | root.mainloop()
321 |
322 | ########################################################################################################################
323 |
--------------------------------------------------------------------------------
/solver.py:
--------------------------------------------------------------------------------
1 | # ################### The SolverThread class solves implements the two phase algorithm #################################
2 | import face
3 | import threading as thr
4 | import cubie
5 | import symmetries as sy
6 | import coord
7 | import enums as en
8 | import moves as mv
9 | import pruning as pr
10 | import time
11 |
12 |
13 | class SolverThread(thr.Thread):
14 |
15 | def __init__(self, cb_cube, rot, inv, ret_length, timeout, start_time, solutions, terminated, shortest_length):
16 | """
17 | :param cb_cube: The cube to be solved in CubieCube representation
18 | :param rot: Rotates the cube 120° * rot along the long diagonal before applying the two-phase-algorithm
19 | :param inv: 0: Do not invert the cube . 1: Invert the cube before applying the two-phase-algorithm
20 | :param ret_length: If a solution with length <= ret_length is found the search stops.
21 | The most efficient way to solve a cube is to start six threads in parallel with rot = 0, 1 and 2 and
22 | inv = 0, 1. The first thread which finds a solutions sets the terminated flag which signals all other threads
23 | to teminate. On average this solves a cube about 12 times faster than solving one cube with a single thread.
24 | And this despite of Pythons GlobalInterpreterLock GIL.
25 | :param timeout: Essentially the maximal search time in seconds. Essentially because the search does not return
26 | before at least one solution has been found.
27 | :param start_time: The time the search started.
28 | :param solutions: An array with the found solutions found by the six parallel threads
29 | :param terminated: An event shared by the six threads to signal a termination request
30 | :param shortest_length: The length of the shortes solutions in the solution array
31 | """
32 | thr.Thread.__init__(self)
33 | self.cb_cube = cb_cube # CubieCube
34 | self.co_cube = None # CoordCube initialized in function run
35 | self.rot = rot
36 | self.inv = inv
37 | self.sofar_phase1 = None
38 | self.sofar_phase2 = None
39 | self.lock = thr.Lock()
40 | self.ret_length = ret_length
41 | self.timeout = timeout
42 | self.start_time = start_time
43 |
44 | self.cornersave = 0
45 |
46 | # these variables are shared by the six threads, initialized in function solve
47 | self.solutions = solutions
48 | self.terminated = terminated
49 | self.shortest_length = shortest_length
50 |
51 | def search_phase2(self, corners, ud_edges, slice_sorted, dist, togo_phase2):
52 | # ##############################################################################################################
53 | if self.terminated.is_set():
54 | return
55 | ################################################################################################################
56 | if togo_phase2 == 0 and slice_sorted == 0:
57 | self.lock.acquire() # phase 2 solved, store solution
58 | man = self.sofar_phase1 + self.sofar_phase2
59 | if len(self.solutions) == 0 or (len(self.solutions[-1]) > len(man)):
60 |
61 | if self.inv == 1: # we solved the inverse cube
62 | man = list(reversed(man))
63 | man[:] = [en.Move((m // 3) * 3 + (2 - m % 3)) for m in man] # R1->R3, R2->R2, R3->R1 etc.
64 | man[:] = [en.Move(sy.conj_move[m, 16 * self.rot]) for m in man]
65 | self.solutions.append(man)
66 | self.shortest_length[0] = len(man)
67 |
68 | if self.shortest_length[0] <= self.ret_length: # we have reached the target length
69 | self.terminated.set()
70 | self.lock.release()
71 | else:
72 | for m in en.Move:
73 | if m in [en.Move.R1, en.Move.R3, en.Move.F1, en.Move.F3,
74 | en.Move.L1, en.Move.L3, en.Move.B1, en.Move.B3]:
75 | continue
76 |
77 | if len(self.sofar_phase2) > 0:
78 | diff = self.sofar_phase2[-1] // 3 - m // 3
79 | if diff in [0, 3]: # successive moves: on same face or on same axis with wrong order
80 | continue
81 | else:
82 | if len(self.sofar_phase1) > 0:
83 | diff = self.sofar_phase1[-1] // 3 - m // 3
84 | if diff in [0, 3]: # successive moves: on same face or on same axis with wrong order
85 | continue
86 |
87 | corners_new = mv.corners_move[18 * corners + m]
88 | ud_edges_new = mv.ud_edges_move[18 * ud_edges + m]
89 | slice_sorted_new = mv.slice_sorted_move[18 * slice_sorted + m]
90 |
91 | classidx = sy.corner_classidx[corners_new]
92 | sym = sy.corner_sym[corners_new]
93 | dist_new_mod3 = pr.get_corners_ud_edges_depth3(
94 | 40320 * classidx + sy.ud_edges_conj[(ud_edges_new << 4) + sym])
95 | dist_new = pr.distance[3 * dist + dist_new_mod3]
96 | if max(dist_new, pr.cornslice_depth[24 * corners_new + slice_sorted_new]) >= togo_phase2:
97 | continue # impossible to reach solved cube in togo_phase2 - 1 moves
98 |
99 | self.sofar_phase2.append(m)
100 | self.search_phase2(corners_new, ud_edges_new, slice_sorted_new, dist_new, togo_phase2 - 1)
101 | self.sofar_phase2.pop(-1)
102 |
103 | def search(self, flip, twist, slice_sorted, dist, togo_phase1):
104 | # ##############################################################################################################
105 | if self.terminated.is_set():
106 | return
107 | ################################################################################################################
108 | if togo_phase1 == 0: # phase 1 solved
109 |
110 | if time.monotonic() > self.start_time + self.timeout and len(self.solutions) > 0:
111 | self.terminated.set()
112 |
113 | # compute initial phase 2 coordinates
114 | if self.sofar_phase1: # check if list is not empty
115 | m = self.sofar_phase1[-1]
116 | else:
117 | m = en.Move.U1 # value is irrelevant here, no phase 1 moves
118 |
119 | if m in [en.Move.R3, en.Move.F3, en.Move.L3, en.Move.B3]: # phase 1 solution come in pairs
120 | corners = mv.corners_move[18 * self.cornersave + m - 1] # apply R2, F2, L2 ord B2 on last ph1 solution
121 | else:
122 | corners = self.co_cube.corners
123 | for m in self.sofar_phase1: # get current corner configuration
124 | corners = mv.corners_move[18 * corners + m]
125 | self.cornersave = corners
126 |
127 | # new solution must be shorter and we do not use phase 2 maneuvers with length > 11 - 1 = 10
128 | togo2_limit = min(self.shortest_length[0] - len(self.sofar_phase1), 11)
129 | if pr.cornslice_depth[24 * corners + slice_sorted] >= togo2_limit: # this precheck speeds up the computation
130 | return
131 |
132 | u_edges = self.co_cube.u_edges
133 | d_edges = self.co_cube.d_edges
134 | for m in self.sofar_phase1:
135 | u_edges = mv.u_edges_move[18 * u_edges + m]
136 | d_edges = mv.d_edges_move[18 * d_edges + m]
137 | ud_edges = coord.u_edges_plus_d_edges_to_ud_edges[24 * u_edges + d_edges % 24]
138 |
139 | dist2 = self.co_cube.get_depth_phase2(corners, ud_edges)
140 | for togo2 in range(dist2, togo2_limit): # do not use more than togo2_limit - 1 moves in phase 2
141 | self.sofar_phase2 = []
142 | self.search_phase2(corners, ud_edges, slice_sorted, dist2, togo2)
143 |
144 | else:
145 | for m in en.Move:
146 | # dist = 0 means that we are already are in the subgroup H. If there are less than 5 moves left
147 | # this forces all remaining moves to be phase 2 moves. So we can forbid these at the end of phase 1
148 | # and generate these moves in phase 2.
149 | if dist == 0 and togo_phase1 < 5 and m in [en.Move.U1, en.Move.U2, en.Move.U3, en.Move.R2,
150 | en.Move.F2, en.Move.D1, en.Move.D2, en.Move.D3,
151 | en.Move.L2, en.Move.B2]:
152 | continue
153 |
154 | if len(self.sofar_phase1) > 0:
155 | diff = self.sofar_phase1[-1] // 3 - m // 3
156 | if diff in [0, 3]: # successive moves: on same face or on same axis with wrong order
157 | continue
158 |
159 | flip_new = mv.flip_move[18 * flip + m] # N_MOVE = 18
160 | twist_new = mv.twist_move[18 * twist + m]
161 | slice_sorted_new = mv.slice_sorted_move[18 * slice_sorted + m]
162 |
163 | flipslice = 2048 * (slice_sorted_new // 24) + flip_new # N_FLIP * (slice_sorted // N_PERM_4) + flip
164 | classidx = sy.flipslice_classidx[flipslice]
165 | sym = sy.flipslice_sym[flipslice]
166 | dist_new_mod3 = pr.get_flipslice_twist_depth3(2187 * classidx + sy.twist_conj[(twist_new << 4) + sym])
167 | dist_new = pr.distance[3 * dist + dist_new_mod3]
168 | if dist_new >= togo_phase1: # impossible to reach subgroup H in togo_phase1 - 1 moves
169 | continue
170 |
171 | self.sofar_phase1.append(m)
172 | self.search(flip_new, twist_new, slice_sorted_new, dist_new, togo_phase1 - 1)
173 | self.sofar_phase1.pop(-1)
174 |
175 | def run(self):
176 | cb = None
177 | if self.rot == 0: # no rotation
178 | cb = cubie.CubieCube(self.cb_cube.cp, self.cb_cube.co, self.cb_cube.ep, self.cb_cube.eo)
179 | elif self.rot == 1: # conjugation by 120° rotation
180 | cb = cubie.CubieCube(sy.symCube[32].cp, sy.symCube[32].co, sy.symCube[32].ep, sy.symCube[32].eo)
181 | cb.multiply(self.cb_cube)
182 | cb.multiply(sy.symCube[16])
183 | elif self.rot == 2: # conjugation by 240° rotation
184 | cb = cubie.CubieCube(sy.symCube[16].cp, sy.symCube[16].co, sy.symCube[16].ep, sy.symCube[16].eo)
185 | cb.multiply(self.cb_cube)
186 | cb.multiply(sy.symCube[32])
187 | if self.inv == 1: # invert cube
188 | tmp = cubie.CubieCube()
189 | cb.inv_cubie_cube(tmp)
190 | cb = tmp
191 |
192 | self.co_cube = coord.CoordCube(cb) # the rotated/inverted cube in coordinate representation
193 |
194 | dist = self.co_cube.get_depth_phase1()
195 | for togo1 in range(dist, 20): # iterative deepening, solution has at least dist moves
196 | self.sofar_phase1 = []
197 | self.search(self.co_cube.flip, self.co_cube.twist, self.co_cube.slice_sorted, dist, togo1)
198 | #################################End class SolverThread#################################################################
199 |
200 |
201 | def solve(cubestring, max_length=20, timeout=3):
202 | """Solve a cube defined by its cube definition string.
203 | :param cubestring: The format of the string is given in the Facelet class defined in the file enums.py
204 | :param max_length: The function will return if a maneuver of length <= max_length has been found
205 | :param timeout: If the function times out, the best solution found so far is returned. If there has not been found
206 | any solution yet the computation continues until a first solution appears.
207 | """
208 | fc = face.FaceCube()
209 | s = fc.from_string(cubestring)
210 | if s != cubie.CUBE_OK:
211 | return s # Error in facelet cube
212 | cc = fc.to_cubie_cube()
213 | s = cc.verify()
214 | if s != cubie.CUBE_OK:
215 | return s # Error in cubie cube
216 |
217 | my_threads = []
218 | s_time = time.monotonic()
219 |
220 | # these mutable variables are modidified by all six threads
221 | s_length = [999]
222 | solutions = []
223 | terminated = thr.Event()
224 | terminated.clear()
225 | syms = cc.symmetries()
226 | if len(list({16, 20, 24, 28} & set(syms))) > 0: # we have some rotational symmetry along a long diagonal
227 | tr = [0, 3] # so we search only one direction and the inverse
228 | else:
229 | tr = range(6) # This means search in 3 directions + inverse cube
230 | if len(list(set(range(48, 96)) & set(syms))) > 0: # we have some antisymmetry so we do not search the inverses
231 | tr = list(filter(lambda x: x < 3, tr))
232 | for i in tr:
233 | th = SolverThread(cc, i % 3, i // 3, max_length, timeout, s_time, solutions, terminated, [999])
234 | my_threads.append(th)
235 | th.start()
236 | for t in my_threads:
237 | t.join() # wait until all threads have finished
238 | s = ''
239 | if len(solutions) > 0:
240 | for m in solutions[-1]: # the last solution is the shortest
241 | s += m.name + ' '
242 | return s + '(' + str(len(s)//3) + 'f)'
243 | ########################################################################################################################
--------------------------------------------------------------------------------
/vision2.py:
--------------------------------------------------------------------------------
1 | # ######################## analyse the webcam input and retrieve the facelet colors of a single cube face ##############
2 |
3 |
4 | # pip install opencv_python-3.2.0-cp36-cp36m-win_amd64.whl
5 |
6 | import cv2
7 | import numpy as np
8 | import vision_params
9 |
10 | grid_N = 25 # number of grid-squares in vertical direction
11 |
12 |
13 | def drawgrid(img, n):
14 | """Draw grid onto the webcam output. Only used for debugging purposes."""
15 | h, w = img.shape[:2]
16 | sz = h // n
17 | border = 1 * sz
18 | for y in range(border, h - border, sz):
19 | for x in range(border, w - border, sz):
20 | cv2.rectangle(img, (x, y), (x + sz, y + sz), (0, 0, 0), 1) # plot small squares in black and white
21 | cv2.rectangle(img, (x - 1, y - 1), (x + 1 + sz, y + 1 + sz), (255, 255, 255), 1)
22 |
23 |
24 | def del_duplicates(pts):
25 | """Delete one of two potential facelet centers stored in pts if they are too close to each other."""
26 | delta = width / 12 # width is defined global in grabcolors()
27 | dele = True
28 | while dele:
29 | dele = False
30 | r = range(len(pts))
31 | for i in r:
32 | for j in r[i + 1:]:
33 | if np.linalg.norm(pts[i] - pts[j]) < delta:
34 | del pts[j]
35 | dele = True
36 | if dele:
37 | break
38 | if dele:
39 | break
40 |
41 |
42 | def medoid(pts):
43 | """The mediod is the point with the smallest summed distance from the other points.
44 | This is a candidate for the center facelet."""
45 | res = np.array([0.0, 0.0])
46 | smin = 100000
47 | for i in pts:
48 | s = 0
49 | for j in pts:
50 | s += np.linalg.norm(i - j)
51 | if s < smin:
52 | smin = s
53 | res = i
54 |
55 | return res
56 |
57 |
58 | def facelets(pts, med):
59 | """Separate the candidates into edge and corner facelets by their distance from the medoid."""
60 | ed = []
61 | co = []
62 | if med[0] == 0:
63 | return co, ed # no edgefacelets detected
64 | # find shortest distance
65 | dmin = 10000
66 | for p in pts:
67 | d = np.linalg.norm(p - med)
68 | if 1 < d < dmin:
69 | dmin = d
70 | # edgefacelets should be in a distance not more than dmin*1.3
71 | for p in pts:
72 | d = np.linalg.norm(p - med)
73 | if dmin - 1 < d < dmin * 1.3:
74 | ed.append(p)
75 | # now find the corner facelets
76 | for p in pts:
77 | d = np.linalg.norm(p - med)
78 | if dmin * 1.3 < d < dmin * 1.7:
79 | co.append(p)
80 | return co, ed
81 |
82 |
83 | def mirr_facelet(co, ed, med):
84 | """If we have detected a facelet position, the point reflection at the center also gives a facelet position.
85 | We can use this position in case the other facelet was not detected directly."""
86 | aef = []
87 | acf = []
88 | for p in ed:
89 | pa = 2 * med - p
90 | aef.append(pa)
91 | for p in co:
92 | pa = 2 * med - p
93 | acf.append(pa)
94 |
95 | # delete duplicates
96 | delta = width / 12 # width is defined global in grabcolors()
97 | for k in range(len(aef) - 1, -1, -1):
98 | for p in ed:
99 | if np.linalg.norm(aef[k] - p) < delta:
100 | del aef[k]
101 | break
102 |
103 | for k in range(len(acf) - 1, -1, -1):
104 | for p in co:
105 | if np.linalg.norm(acf[k] - p) < delta:
106 | del acf[k]
107 | break
108 |
109 | return acf, aef
110 |
111 |
112 | def display_colorname(bgrcap, p):
113 | """Display the colornames on the webcam picture."""
114 | p = p.astype(np.uint16)
115 | _, col = getcolor(p)
116 | if col in ('blue', 'green', 'red'):
117 | txtcol = (255, 255, 255)
118 | else:
119 | txtcol = (0, 0, 0)
120 | font = cv2.FONT_HERSHEY_SIMPLEX
121 | tz = cv2.getTextSize(col, font, 0.4, 1)[0]
122 | cv2.putText(
123 | bgrcap, col, tuple(p - (tz[0] // 2, -tz[1] // 2)), font, 0.4, txtcol, 1)
124 |
125 |
126 | def getcolor(p):
127 | """Decide the color of a facelet by its h value (non white) or by s and v (white)."""
128 | sz = 10
129 | p = p.astype(np.uint16)
130 | rect = hsv[p[1] - sz:p[1] + sz, p[0] - sz:p[0] + sz]
131 | median = np.sum(rect, axis=(0, 1)) / sz / sz / 4
132 | mh, ms, mv = median
133 | if ms <= vision_params.sat_W and mv >= vision_params.val_W:
134 | return median, 'white'
135 | elif vision_params.orange_L <= mh < vision_params.orange_H:
136 | return median, 'orange'
137 | elif vision_params.orange_H <= mh < vision_params.yellow_H:
138 | return median, 'yellow'
139 | elif vision_params.yellow_H <= mh < vision_params.green_H:
140 | if ms < 150:
141 | return median, 'white' # green saturation is always higher
142 | else:
143 | return median, 'green'
144 | elif vision_params.green_H <= mh < vision_params.blue_H:
145 | if ms < 150:
146 | return median, 'white' # blue saturation is always higher
147 | else:
148 | return median, 'blue'
149 | else:
150 | return median, 'red'
151 |
152 |
153 | def getcolors(co, ed, aco, aed, m):
154 | """Find the colors of the 9 facelets and decide their position on the cube face."""
155 | centers = [[m for x in range(3)] for x in range(3)]
156 | colors = [['' for x in range(3)] for x in range(3)]
157 | s = np.array([0., 0., 0.])
158 | hsvs = [[s for x in range(3)] for x in range(3)]
159 | cocents = co + aco
160 | if len(cocents) != 4:
161 | return [], []
162 | edcents = ed + aed
163 | if len(edcents) != 4:
164 | return [], []
165 | for i in cocents:
166 | if i[0] < m[0] and i[1] < m[1]:
167 | centers[0][0] = i
168 | elif i[0] > m[0] and i[1] < m[1]:
169 | centers[0][2] = i
170 | elif i[0] < m[0] and i[1] > m[1]:
171 | centers[2][0] = i
172 | elif i[0] > m[0] and i[1] > m[1]:
173 | centers[2][2] = i
174 |
175 | for i in edcents:
176 | if i[1] < centers[0][1][1]:
177 | centers[0][1] = i
178 | for i in edcents:
179 | if i[0] < centers[1][0][0]:
180 | centers[1][0] = i
181 | for i in edcents:
182 | if i[0] > centers[1][2][0]:
183 | centers[1][2] = i
184 | for i in edcents:
185 | if i[1] > centers[2][1][1]:
186 | centers[2][1] = i
187 | for x in range(3):
188 | for y in range(3):
189 | hsv_, col = getcolor(centers[x][y])
190 | colors[x][y] = col
191 | hsvs[x][y] = hsv_
192 |
193 | return hsvs, colors
194 |
195 |
196 | def find_squares(bgrcap, n):
197 | """ Find the positions of squares in the webcam picture."""
198 | global mask, color_mask, white_mask, black_mask
199 |
200 | h, s, v = cv2.split(hsv)
201 | h_sqr = np.square(h)
202 |
203 | sz = height // n
204 | border = 1 * sz
205 |
206 | varmax_edges = 20
207 |
208 | # iterate all grid squares
209 | for y in range(border, height - border, sz):
210 | for x in range(border, width - border, sz):
211 |
212 | # compute the standard deviation sigma of the hue in the square
213 | rect_h = h[y:y + sz, x:x + sz]
214 | rect_h_sqr = h_sqr[y:y + sz, x:x + sz]
215 | median_h = np.sum(rect_h) / sz / sz
216 | sqr_median_h = median_h * median_h
217 | median_h_sqr = np.sum(rect_h_sqr) / sz / sz
218 | var = median_h_sqr - sqr_median_h
219 | sigma = np.sqrt(var)
220 |
221 | delta = vision_params.delta_C
222 |
223 | # if sigma is small enough define a mask on the 3x3 square with the grid square in it's center
224 | if sigma < vision_params.sigma_W:
225 | rect3x3 = hsv[y - 1 * sz:y + 2 * sz, x - 1 * sz:x + 2 * sz]
226 | mask = cv2.inRange(rect3x3, (0, 0, vision_params.val_W),
227 | (255, vision_params.sat_W, 255))
228 | # and OR it to the white_mask
229 | white_mask[y - 1 * sz:y + 2 * sz, x - 1 * sz:x + 2 * sz] = \
230 | cv2.bitwise_or(mask, white_mask[y - 1 * sz:y + 2 * sz, x - 1 * sz:x + 2 * sz])
231 |
232 | # similar procedure for the color mask. Some issues because hues are computed modulo 180
233 | if sigma < vision_params.sigma_C:
234 | rect3x3 = h[y - 1 * sz:y + 2 * sz, x - 1 * sz:x + 2 * sz]
235 | if median_h + delta >= 180:
236 | mask = cv2.inRange(rect3x3, 0, median_h + delta - 180)
237 | mask = cv2.bitwise_or(mask, cv2.inRange(rect3x3, median_h - delta, 180))
238 | elif median_h - delta < 0:
239 | mask = cv2.inRange(rect3x3, median_h - delta + 180, 180)
240 | mask = cv2.bitwise_or(mask, cv2.inRange(rect3x3, 0, median_h + delta))
241 | else:
242 | mask = cv2.inRange(rect3x3, median_h - delta, median_h + delta)
243 | color_mask[y - 1 * sz:y + 2 * sz, x - 1 * sz:x + 2 * sz] = \
244 | cv2.bitwise_or(mask, color_mask[y - 1 * sz:y + 2 * sz, x - 1 * sz:x + 2 * sz])
245 |
246 | black_mask = cv2.inRange(bgrcap, (0, 0, 0), (vision_params.rgb_L, vision_params.rgb_L, vision_params.rgb_L))
247 | black_mask = cv2.bitwise_not(black_mask)
248 |
249 | color_mask = cv2.bitwise_and(color_mask, black_mask)
250 | color_mask = cv2.blur(color_mask, (20, 20))
251 | color_mask = cv2.inRange(color_mask, 240, 255)
252 |
253 | white_mask = cv2.bitwise_and(white_mask, black_mask)
254 | white_mask = cv2.blur(white_mask, (20, 20))
255 | white_mask = cv2.inRange(white_mask, 240, 255)
256 |
257 | itr = iter([white_mask, color_mask]) # apply white filter first!
258 |
259 | # search for squares in the white_mask and in the color_mask
260 | for j in itr:
261 | # find contours
262 | # works for OpenCV 3.2 or higher. For versions < 3.2 omit im2 in the line below.
263 | im2, contours, hierarchy = cv2.findContours(j, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
264 | for n in range(len(contours)):
265 | approx = cv2.approxPolyDP(contours[n], sz // 2, True)
266 | # if the contour cannot be approximated by a quadrangle it is not a facelet square
267 | if approx.shape[0] != 4:
268 | continue
269 | corners = approx[:, 0] # get the corners of the potential facelet square
270 |
271 | # the edges of the square should have all about the same length
272 | edges = np.array(
273 | [cv2.norm(corners[0] - corners[1], cv2.NORM_L2), cv2.norm(corners[1] - corners[2], cv2.NORM_L2),
274 | cv2.norm(corners[2] - corners[3], cv2.NORM_L2),
275 | cv2.norm(corners[3] - corners[0], cv2.NORM_L2)])
276 | edges_mean_sq = (np.sum(edges) / 4) ** 2
277 | edges_sq_mean = np.sum(np.square(edges)) / 4
278 | if edges_sq_mean - edges_mean_sq > varmax_edges:
279 | continue
280 |
281 | # cv2.drawContours(bgrcap, [approx], -1, (0, 0, 255), 8)
282 | middle = np.sum(corners, axis=0) / 4 # store the center of the potential facelet
283 | cent.append(np.asarray(middle))
284 |
285 |
286 | def grab_colors():
287 | """Find the cube in the webcam picture and grab the colors of the facelets."""
288 | global cent, width, height, hsv, color_mask, white_mask
289 | cap = cv2.VideoCapture(0)
290 | _, bgrcap = cap.read()
291 | if bgrcap is None:
292 | print('Cannot connect to webcam!')
293 | print('If you use a Raspberry Pi and no USB-webcam you have to run "sudo modprobe bvm2835-v4l2" first!')
294 | return
295 | height, width = bgrcap.shape[:2]
296 | while 1:
297 |
298 | # Take each frame
299 | _, bgrcap = cap.read() #
300 | bgrcap = cv2.blur(bgrcap, (5, 5))
301 |
302 | # now set all hue values >160 to 0. This is important since the color red often contains hue values
303 | # in this range *and* also hue values >0 and else we get a mess when we compute mean and variance
304 | hsv = cv2.cvtColor(bgrcap, cv2.COLOR_BGR2HSV)
305 | h, s, v = cv2.split(hsv) #
306 | h_mask = cv2.inRange(h, 0, 160)
307 | h = cv2.bitwise_and(h, h, mask=h_mask)
308 | hsv = cv2.merge((h, s, v)).astype(float)
309 |
310 | # define two empty masks for the white-filter and the color-filter
311 | color_mask = cv2.inRange(bgrcap, np.array([1, 1, 1]), np.array([0, 0, 0])) # mask for colors
312 | white_mask = cv2.inRange(bgrcap, np.array([1, 1, 1]), np.array([0, 0, 0])) # special mask for white
313 |
314 | cent = [] # the centers of the facelet-square candidates are stored in this global variable
315 | find_squares(bgrcap, grid_N) # find the candidates
316 | del_duplicates(cent) # delete candidates which are too close together
317 |
318 | # the medoid is the center which has the closest summed distances to the other centers
319 | # It should be the center facelet of the cube
320 | m = medoid(cent)
321 |
322 | cf, ef = facelets(cent, m) # identify the centers of the corner and edge facelets
323 |
324 | # compute the alternate corner and edges facelet centers. These are the point reflections of an already
325 | # known facelet center at the medoid center. Should some facelet center not be detected by itself it usually
326 | # still is detected in this way.
327 | acf, aef = mirr_facelet(cf, ef, m)
328 |
329 | display_colorname(bgrcap, m)
330 | for i in ef:
331 | display_colorname(bgrcap, i)
332 | for i in cf:
333 | display_colorname(bgrcap, i)
334 | for i in aef:
335 | display_colorname(bgrcap, i)
336 | for i in acf:
337 | display_colorname(bgrcap, i)
338 |
339 | # the results supplied by getcolors are used in client_gui2.py for the "Webcam import"
340 | vision_params.face_hsv, vision_params.face_col = getcolors(cf, ef, acf, aef, m)
341 |
342 | # drawgrid(bgrcap, grid_N)
343 |
344 | # show the windows
345 | cv2.imshow('color_filter mask', cv2.resize(color_mask, (width // 2, height // 2)))
346 | cv2.imshow('white_filter mask', cv2.resize(white_mask, (width // 2, height // 2)))
347 | cv2.imshow('black_filter mask', cv2.resize(black_mask, (width // 2, height // 2)))
348 | cv2.imshow('Webcam - type "x" to quit.', bgrcap)
349 |
350 | k = cv2.waitKey(5) & 0xFF
351 | if k == 120: # type x to exit
352 | break
353 |
354 |
355 | cv2.destroyAllWindows()
356 |
--------------------------------------------------------------------------------
/pruning.py:
--------------------------------------------------------------------------------
1 | # ##################### The pruning tables cut the search tree during the search. ######################################
2 | # ##################### The pruning values are stored modulo 3 which saves a lot of memory. ############################
3 |
4 | import defs
5 | import enums
6 | import moves as mv
7 | import symmetries as sy
8 | import cubie as cb
9 | from os import path
10 | import time
11 | import array as ar
12 |
13 | flipslice_twist_depth3 = None # global variables, initialized during pruning table cration
14 | corners_ud_edges_depth3 = None
15 | cornslice_depth = None
16 | edgeslice_depth = None
17 |
18 | # ####################### functions to extract or set values in the pruning tables #####################################
19 |
20 |
21 | def get_flipslice_twist_depth3(ix):
22 | """get_fst_depth3(ix) is *exactly* the number of moves % 3 to solve phase 1 of a cube with index ix"""
23 | y = flipslice_twist_depth3[ix // 16]
24 | y >>= (ix % 16) * 2
25 | return y & 3
26 |
27 |
28 | def get_corners_ud_edges_depth3(ix):
29 | """corners_ud_edges_depth3(ix) is *at least* the number of moves % 3 to solve phase 2 of a cube with index ix"""
30 | y = corners_ud_edges_depth3[ix // 16]
31 | y >>= (ix % 16) * 2
32 | return y & 3
33 |
34 |
35 | def set_flipslice_twist_depth3(ix, value):
36 | shift = (ix % 16) * 2
37 | base = ix >> 4
38 | flipslice_twist_depth3[base] &= ~(3 << shift) & 0xffffffff
39 | flipslice_twist_depth3[base] |= value << shift
40 |
41 |
42 | def set_corners_ud_edges_depth3(ix, value):
43 | shift = (ix % 16) * 2
44 | base = ix >> 4
45 | corners_ud_edges_depth3[base] &= ~(3 << shift) & 0xffffffff
46 | corners_ud_edges_depth3[base] |= value << shift
47 |
48 | ########################################################################################################################
49 |
50 |
51 | def create_phase1_prun_table():
52 | """Create/load the flipslice_twist_depth3 pruning table for phase 1."""
53 | global flipslice_twist_depth3
54 | total = defs.N_FLIPSLICE_CLASS * defs.N_TWIST
55 | fname = "phase1_prun"
56 | if not path.isfile(fname):
57 | print("creating " + fname + " table...")
58 | print('This may take half an hour or even longer, depending on the hardware.')
59 |
60 | flipslice_twist_depth3 = ar.array('L', [0xffffffff] * (total // 16 + 1))
61 | # #################### create table with the symmetries of the flipslice classes ###############################
62 | cc = cb.CubieCube()
63 | fs_sym = ar.array('H', [0] * defs.N_FLIPSLICE_CLASS)
64 | for i in range(defs.N_FLIPSLICE_CLASS):
65 | if (i + 1) % 1000 == 0:
66 | print('.', end='', flush=True)
67 | rep = sy.flipslice_rep[i]
68 | cc.set_slice(rep // defs.N_FLIP)
69 | cc.set_flip(rep % defs.N_FLIP)
70 |
71 | for s in range(defs.N_SYM_D4h):
72 | ss = cb.CubieCube(sy.symCube[s].cp, sy.symCube[s].co, sy.symCube[s].ep,
73 | sy.symCube[s].eo) # copy cube
74 | ss.edge_multiply(cc) # s*cc
75 | ss.edge_multiply(sy.symCube[sy.inv_idx[s]]) # s*cc*s^-1
76 | if ss.get_slice() == rep // defs.N_FLIP and ss.get_flip() == rep % defs.N_FLIP:
77 | fs_sym[i] |= 1 << s
78 | print()
79 | # ##################################################################################################################
80 |
81 | fs_classidx = 0 # value for solved phase 1
82 | twist = 0
83 | set_flipslice_twist_depth3(defs.N_TWIST * fs_classidx + twist, 0)
84 | done = 1
85 | depth = 0
86 | backsearch = False
87 | print('depth:', depth, 'done: ' + str(done) + '/' + str(total))
88 | while done != total:
89 | depth3 = depth % 3
90 | if depth == 9:
91 | # backwards search is faster for depth >= 9
92 | print('flipping to backwards search...')
93 | backsearch = True
94 | if depth < 8:
95 | mult = 5 # controls the output a few lines below
96 | else:
97 | mult = 1
98 | idx = 0
99 | for fs_classidx in range(defs.N_FLIPSLICE_CLASS):
100 | if (fs_classidx + 1) % (200 * mult) == 0:
101 | print('.', end='', flush=True)
102 | if (fs_classidx + 1) % (16000 * mult) == 0:
103 | print('')
104 |
105 | twist = 0
106 | while twist < defs.N_TWIST:
107 |
108 | # ########## if table entries are not populated, this is very fast: ################################
109 | if not backsearch and idx % 16 == 0 and flipslice_twist_depth3[idx // 16] == 0xffffffff \
110 | and twist < defs.N_TWIST - 16:
111 | twist += 16
112 | idx += 16
113 | continue
114 | ####################################################################################################
115 |
116 | if backsearch:
117 | match = (get_flipslice_twist_depth3(idx) == 3)
118 | else:
119 | match = (get_flipslice_twist_depth3(idx) == depth3)
120 |
121 | if match:
122 | flipslice = sy.flipslice_rep[fs_classidx]
123 | flip = flipslice % 2048 # defs.N_FLIP = 2048
124 | slice_ = flipslice >> 11 # // defs.N_FLIP
125 | for m in enums.Move:
126 | twist1 = mv.twist_move[18 * twist + m] # defs.N_MOVE = 18
127 | flip1 = mv.flip_move[18 * flip + m]
128 | slice1 = mv.slice_sorted_move[432 * slice_ + m] // 24 # defs.N_PERM_4 = 24, 18*24 = 432
129 | flipslice1 = (slice1 << 11) + flip1
130 | fs1_classidx = sy.flipslice_classidx[flipslice1]
131 | fs1_sym = sy.flipslice_sym[flipslice1]
132 | twist1 = sy.twist_conj[(twist1 << 4) + fs1_sym]
133 | idx1 = 2187 * fs1_classidx + twist1 # defs.N_TWIST = 2187
134 | if not backsearch:
135 | if get_flipslice_twist_depth3(idx1) == 3: # entry not yet filled
136 | set_flipslice_twist_depth3(idx1, (depth + 1) % 3)
137 | done += 1
138 | # ####symmetric position has eventually more than one representation ###############
139 | sym = fs_sym[fs1_classidx]
140 | if sym != 1:
141 | for j in range(1, 16):
142 | sym >>= 1
143 | if sym % 2 == 1:
144 | twist2 = sy.twist_conj[(twist1 << 4) + j]
145 | # fs2_classidx = fs1_classidx due to symmetry
146 | idx2 = 2187 * fs1_classidx + twist2
147 | if get_flipslice_twist_depth3(idx2) == 3:
148 | set_flipslice_twist_depth3(idx2, (depth + 1) % 3)
149 | done += 1
150 | ####################################################################################
151 |
152 | else: # backwards search
153 | if get_flipslice_twist_depth3(idx1) == depth3:
154 | set_flipslice_twist_depth3(idx, (depth + 1) % 3)
155 | done += 1
156 | break
157 | twist += 1
158 | idx += 1 # idx = defs.N_TWIST * fs_class + twist
159 |
160 | depth += 1
161 | print()
162 | print('depth:', depth, 'done: ' + str(done) + '/' + str(total))
163 |
164 | fh = open(fname, "wb")
165 | flipslice_twist_depth3.tofile(fh)
166 | else:
167 | print("loading " + fname + " table...")
168 | fh = open(fname, "rb")
169 | flipslice_twist_depth3 = ar.array('L')
170 | flipslice_twist_depth3.fromfile(fh, total // 16 + 1)
171 | fh.close()
172 |
173 |
174 | def create_phase2_prun_table():
175 | """Create/load the corners_ud_edges_depth3 pruning table for phase 2."""
176 | total = defs.N_CORNERS_CLASS * defs.N_UD_EDGES
177 | fname = "phase2_prun"
178 | global corners_ud_edges_depth3
179 | if not path.isfile(fname):
180 | print("creating " + fname + " table...")
181 |
182 | corners_ud_edges_depth3 = ar.array('L', [0xffffffff] * (total // 16))
183 | # ##################### create table with the symmetries of the corners classes ################################
184 | cc = cb.CubieCube()
185 | c_sym = ar.array('H', [0] * defs.N_CORNERS_CLASS)
186 | for i in range(defs.N_CORNERS_CLASS):
187 | if (i + 1) % 1000 == 0:
188 | print('.', end='', flush=True)
189 | rep = sy.corner_rep[i]
190 | cc.set_corners(rep)
191 | for s in range(defs.N_SYM_D4h):
192 | ss = cb.CubieCube(sy.symCube[s].cp, sy.symCube[s].co, sy.symCube[s].ep,
193 | sy.symCube[s].eo) # copy cube
194 | ss.corner_multiply(cc) # s*cc
195 | ss.corner_multiply(sy.symCube[sy.inv_idx[s]]) # s*cc*s^-1
196 | if ss.get_corners() == rep:
197 | c_sym[i] |= 1 << s
198 | print()
199 | ################################################################################################################
200 |
201 | c_classidx = 0 # value for solved phase 2
202 | ud_edge = 0
203 | set_corners_ud_edges_depth3(defs.N_UD_EDGES * c_classidx + ud_edge, 0)
204 | done = 1
205 | depth = 0
206 | print('depth:', depth, 'done: ' + str(done) + '/' + str(total))
207 | while depth < 10: # we fill the table only do depth 9 + 1
208 | depth3 = depth % 3
209 | idx = 0
210 | mult = 2
211 | if depth > 9:
212 | mult = 1
213 | for c_classidx in range(defs.N_CORNERS_CLASS):
214 | if (c_classidx + 1) % (20 * mult) == 0:
215 | print('.', end='', flush=True)
216 | if (c_classidx + 1) % (1600 * mult) == 0:
217 | print('')
218 |
219 | ud_edge = 0
220 | while ud_edge < defs.N_UD_EDGES:
221 |
222 | # ################ if table entries are not populated, this is very fast: ##########################
223 | if idx % 16 == 0 and corners_ud_edges_depth3[idx // 16] == 0xffffffff \
224 | and ud_edge < defs.N_UD_EDGES - 16:
225 | ud_edge += 16
226 | idx += 16
227 | continue
228 | ####################################################################################################
229 |
230 | if get_corners_ud_edges_depth3(idx) == depth3:
231 | corner = sy.corner_rep[c_classidx]
232 | # only iterate phase 2 moves
233 | for m in (enums.Move.U1, enums.Move.U2, enums.Move.U3, enums.Move.R2, enums.Move.F2,
234 | enums.Move.D1, enums.Move.D2, enums.Move.D3, enums.Move.L2, enums.Move.B2):
235 | ud_edge1 = mv.ud_edges_move[18 * ud_edge + m]
236 | corner1 = mv.corners_move[18 * corner + m]
237 | c1_classidx = sy.corner_classidx[corner1]
238 | c1_sym = sy.corner_sym[corner1]
239 | ud_edge1 = sy.ud_edges_conj[(ud_edge1 << 4) + c1_sym]
240 | idx1 = 40320 * c1_classidx + ud_edge1 # N_UD_EDGES = 40320
241 | if get_corners_ud_edges_depth3(idx1) == 3: # entry not yet filled
242 | set_corners_ud_edges_depth3(idx1, (depth + 1) % 3) # depth + 1 <= 10
243 | done += 1
244 | # ######symmetric position has eventually more than one representation #############
245 | sym = c_sym[c1_classidx]
246 | if sym != 1:
247 | for j in range(1, 16):
248 | sym >>= 1
249 | if sym % 2 == 1:
250 | ud_edge2 = sy.ud_edges_conj[(ud_edge1 << 4) + j]
251 | # c1_classidx does not change
252 | idx2 = 40320 * c1_classidx + ud_edge2
253 | if get_corners_ud_edges_depth3(idx2) == 3:
254 | set_corners_ud_edges_depth3(idx2, (depth + 1) % 3)
255 | done += 1
256 | ####################################################################################
257 |
258 | ud_edge += 1
259 | idx += 1 # idx = defs.N_UD_EDGEPERM * corner_classidx + ud_edge
260 |
261 | depth += 1
262 | print()
263 | print('depth:', depth, 'done: ' + str(done) + '/' + str(total))
264 |
265 | print('remaining unfilled entries have depth >=11')
266 | fh = open(fname, "wb")
267 | corners_ud_edges_depth3.tofile(fh)
268 | else:
269 | print("loading " + fname + " table...")
270 | fh = open(fname, "rb")
271 | corners_ud_edges_depth3 = ar.array('L')
272 | corners_ud_edges_depth3.fromfile(fh, total // 16)
273 |
274 | fh.close()
275 |
276 |
277 | def create_phase2_cornsliceprun_table():
278 | """Create/load the cornslice_depth pruning table for phase 2. With this table we do a fast precheck
279 | at the beginning of phase 2."""
280 | fname = "phase2_cornsliceprun"
281 | global cornslice_depth
282 | if not path.isfile(fname):
283 | print("creating " + fname + " table...")
284 | cornslice_depth = ar.array('b', [-1] * (defs.N_CORNERS * defs.N_PERM_4))
285 | corners = 0 # values for solved phase 2
286 | slice_ = 0
287 | cornslice_depth[defs.N_PERM_4 * corners + slice_] = 0
288 | done = 1
289 | depth = 0
290 | idx = 0
291 | while done != defs.N_CORNERS * defs.N_PERM_4:
292 | for corners in range(defs.N_CORNERS):
293 | for slice_ in range(defs.N_PERM_4):
294 | if cornslice_depth[defs.N_PERM_4 * corners + slice_] == depth:
295 | for m in (enums.Move.U1, enums.Move.U2, enums.Move.U3, enums.Move.R2, enums.Move.F2,
296 | enums.Move.D1, enums.Move.D2, enums.Move.D3, enums.Move.L2, enums.Move.B2):
297 | corners1 = mv.corners_move[18 * corners + m]
298 | slice_1 = mv.slice_sorted_move[18 * slice_ + m]
299 | idx1 = defs.N_PERM_4 * corners1 + slice_1
300 | if cornslice_depth[idx1] == -1: # entry not yet filled
301 | cornslice_depth[idx1] = depth + 1
302 | done += 1
303 | if done % 20000 == 0:
304 | print('.', end='', flush=True)
305 |
306 | depth += 1
307 | print()
308 | fh = open(fname, "wb")
309 | cornslice_depth.tofile(fh)
310 | else:
311 | print("loading " + fname + " table...")
312 | fh = open(fname, "rb")
313 | cornslice_depth = ar.array('b')
314 | cornslice_depth.fromfile(fh, defs.N_CORNERS * defs.N_PERM_4)
315 | fh.close()
316 |
317 | # array distance computes the new distance from the old_distance i and the new_distance_mod3 j. ########################
318 | # We need this array because the pruning tables only store the distances mod 3. ########################################
319 | distance = ar.array('b', [0 for i in range(60)])
320 | for i in range(20):
321 | for j in range(3):
322 | distance[3*i + j] = (i // 3) * 3 + j
323 | if i % 3 == 2 and j == 0:
324 | distance[3 * i + j] += 3
325 | elif i % 3 == 0 and j == 2:
326 | distance[3 * i + j] -= 3
327 |
328 | create_phase1_prun_table()
329 | create_phase2_prun_table()
330 | create_phase2_cornsliceprun_table()
331 |
--------------------------------------------------------------------------------
/cubie.py:
--------------------------------------------------------------------------------
1 | # ####### The cube on the cubie level is described by the permutation and orientations of corners and edges ############
2 |
3 | from defs import cornerFacelet, edgeFacelet, cornerColor, edgeColor, N_SYM
4 | from enums import Color, Corner as Co, Edge as Ed
5 | import face
6 | from misc import c_nk, rotate_left, rotate_right
7 | from random import randrange
8 |
9 |
10 | # ################## The basic six cube moves described by permutations and changes in orientation #####################
11 |
12 | # Up-move
13 | cpU = [Co.UBR, Co.URF, Co.UFL, Co.ULB, Co.DFR, Co.DLF, Co.DBL, Co.DRB]
14 | coU = [0, 0, 0, 0, 0, 0, 0, 0]
15 | epU = [Ed.UB, Ed.UR, Ed.UF, Ed.UL, Ed.DR, Ed.DF, Ed.DL, Ed.DB, Ed.FR, Ed.FL, Ed.BL, Ed.BR]
16 | eoU = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
17 |
18 | # Right-move
19 | cpR = [Co.DFR, Co.UFL, Co.ULB, Co.URF, Co.DRB, Co.DLF, Co.DBL, Co.UBR] # permutation of the corners
20 | coR = [2, 0, 0, 1, 1, 0, 0, 2] # changes of the orientations of the corners
21 | epR = [Ed.FR, Ed.UF, Ed.UL, Ed.UB, Ed.BR, Ed.DF, Ed.DL, Ed.DB, Ed.DR, Ed.FL, Ed.BL, Ed.UR] # permutation of the edges
22 | eoR = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # changes of the permutations of the edges
23 |
24 | # Front-move
25 | cpF = [Co.UFL, Co.DLF, Co.ULB, Co.UBR, Co.URF, Co.DFR, Co.DBL, Co.DRB]
26 | coF = [1, 2, 0, 0, 2, 1, 0, 0]
27 | epF = [Ed.UR, Ed.FL, Ed.UL, Ed.UB, Ed.DR, Ed.FR, Ed.DL, Ed.DB, Ed.UF, Ed.DF, Ed.BL, Ed.BR]
28 | eoF = [0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0]
29 |
30 | # Down-move
31 | cpD = [Co.URF, Co.UFL, Co.ULB, Co.UBR, Co.DLF, Co.DBL, Co.DRB, Co.DFR]
32 | coD = [0, 0, 0, 0, 0, 0, 0, 0]
33 | epD = [Ed.UR, Ed.UF, Ed.UL, Ed.UB, Ed.DF, Ed.DL, Ed.DB, Ed.DR, Ed.FR, Ed.FL, Ed.BL, Ed.BR]
34 | eoD = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
35 |
36 | # Left-move
37 | cpL = [Co.URF, Co.ULB, Co.DBL, Co.UBR, Co.DFR, Co.UFL, Co.DLF, Co.DRB]
38 | coL = [0, 1, 2, 0, 0, 2, 1, 0]
39 | epL = [Ed.UR, Ed.UF, Ed.BL, Ed.UB, Ed.DR, Ed.DF, Ed.FL, Ed.DB, Ed.FR, Ed.UL, Ed.DL, Ed.BR]
40 | eoL = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
41 |
42 | # Back-move
43 | cpB = [Co.URF, Co.UFL, Co.UBR, Co.DRB, Co.DFR, Co.DLF, Co.ULB, Co.DBL]
44 | coB = [0, 0, 1, 2, 0, 0, 2, 1]
45 | epB = [Ed.UR, Ed.UF, Ed.UL, Ed.BR, Ed.DR, Ed.DF, Ed.DL, Ed.BL, Ed.FR, Ed.FL, Ed.UB, Ed.DB]
46 | eoB = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1]
47 | ########################################################################################################################
48 |
49 | CUBE_OK = True
50 |
51 |
52 | class CubieCube:
53 | """Represent a cube on the cubie level with 8 corner cubies, 12 edge cubies and the cubie orientations.
54 |
55 | Is also used to represent:
56 | 1. the 18 cube moves
57 | 2. the 48 symmetries of the cube.
58 | """
59 | def __init__(self, cp=None, co=None, ep=None, eo=None):
60 | """
61 | Initializes corners and edges.
62 | :param cp: corner permutation
63 | :param co: corner orientation
64 | :param ep: edge permutation
65 | :param eo: edge orientation
66 | """
67 | if cp is None:
68 | self.cp = [Co(i) for i in range(8)] # You may not put this as the default two lines above!
69 | else:
70 | self.cp = cp[:]
71 | if co is None:
72 | self.co = [0]*8
73 | else:
74 | self.co = co[:]
75 | if ep is None:
76 | self.ep = [Ed(i) for i in range(12)]
77 | else:
78 | self.ep = ep[:]
79 | if eo is None:
80 | self.eo = [0] * 12
81 | else:
82 | self.eo = eo[:]
83 |
84 | def __str__(self):
85 | """Print string for a cubie cube."""
86 | s = ''
87 | for i in Co:
88 | s = s + '(' + str(self.cp[i]) + ',' + str(self.co[i]) + ')'
89 | s += '\n'
90 | for i in Ed:
91 | s = s + '(' + str(self.ep[i]) + ',' + str(self.eo[i]) + ')'
92 | return s
93 |
94 | def __eq__(self, other):
95 | """Define equality of two cubie cubes."""
96 | if self.cp == other.cp and self.co == other.co and self.ep == other.ep and self.eo == other.eo:
97 | return True
98 | else:
99 | return False
100 |
101 | def to_facelet_cube(self):
102 | """Return a facelet representation of the cube."""
103 | fc = face.FaceCube()
104 | for i in Co:
105 | j = self.cp[i] # corner j is at corner position i
106 | ori = self.co[i] # orientation of C j at position i
107 | for k in range(3):
108 | fc.f[cornerFacelet[i][(k+ori) % 3]] = cornerColor[j][k]
109 | for i in Ed:
110 | j = self.ep[i] # similar for Es
111 | ori = self.eo[i]
112 | for k in range(2):
113 | fc.f[edgeFacelet[i][(k+ori) % 2]] = edgeColor[j][k]
114 | return fc
115 |
116 | def corner_multiply(self, b):
117 | """Multiply this cubie cube with another cubie cube b, restricted to the corners. Does not change b."""
118 | c_perm = [0]*8
119 | c_ori = [0]*8
120 | ori = 0
121 | for c in Co:
122 | c_perm[c] = self.cp[b.cp[c]]
123 | ori_a = self.co[b.cp[c]]
124 | ori_b = b.co[c]
125 | if ori_a < 3 and ori_b < 3: # two regular cubes
126 | ori = ori_a + ori_b
127 | if ori >= 3:
128 | ori -= 3
129 | elif ori_a < 3 <= ori_b: # cube b is in a mirrored state
130 | ori = ori_a + ori_b
131 | if ori >= 6:
132 | ori -= 3 # the composition also is in a mirrored state
133 | elif ori_a >= 3 > ori_b: # cube a is in a mirrored state
134 | ori = ori_a - ori_b
135 | if ori < 3:
136 | ori += 3 # the composition is a mirrored cube
137 | elif ori_a >= 3 and ori_b >= 3: # if both cubes are in mirrored states
138 | ori = ori_a - ori_b
139 | if ori < 0:
140 | ori += 3 # the composition is a regular cube
141 | c_ori[c] = ori
142 | for c in Co:
143 | self.cp[c] = c_perm[c]
144 | self.co[c] = c_ori[c]
145 |
146 | def edge_multiply(self, b):
147 | """ Multiply this cubie cube with another cubiecube b, restricted to the edges. Does not change b."""
148 | e_perm = [0]*12
149 | e_ori = [0]*12
150 | for e in Ed:
151 | e_perm[e] = self.ep[b.ep[e]]
152 | e_ori[e] = (b.eo[e] + self.eo[b.ep[e]]) % 2
153 | for e in Ed:
154 | self.ep[e] = e_perm[e]
155 | self.eo[e] = e_ori[e]
156 |
157 | def multiply(self, b):
158 | self.corner_multiply(b)
159 | self.edge_multiply(b)
160 |
161 | def inv_cubie_cube(self, d):
162 | """Store the inverse of this cubie cube in d."""
163 | for e in Ed:
164 | d.ep[self.ep[e]] = e
165 | for e in Ed:
166 | d.eo[e] = self.eo[d.ep[e]]
167 |
168 | for c in Co:
169 | d.cp[self.cp[c]] = c
170 | for c in Co:
171 | ori = self.co[d.cp[c]]
172 | if ori >= 3:
173 | d.co[c] = ori
174 | else:
175 | d.co[c] = -ori
176 | if d.co[c] < 0:
177 | d.co[c] += 3
178 |
179 | def corner_parity(self):
180 | """Give the parity of the corner permutation."""
181 | s = 0
182 | for i in range(Co.DRB, Co.URF, -1):
183 | for j in range(i - 1, Co.URF - 1, -1):
184 | if self.cp[j] > self.cp[i]:
185 | s += 1
186 | return s % 2
187 |
188 | def edge_parity(self):
189 | """Give the parity of the edge permutation. A solvable cube has the same corner and edge parity."""
190 | s = 0
191 | for i in range(Ed.BR, Ed.UR, -1):
192 | for j in range(i - 1, Ed.UR - 1, -1):
193 | if self.ep[j] > self.ep[i]:
194 | s += 1
195 | return s % 2
196 |
197 | def symmetries(self):
198 | """Generate a list of the symmetries and antisymmetries of the cubie cube."""
199 | from symmetries import symCube, inv_idx # not nice here but else we have circular imports
200 | s = []
201 | d = CubieCube()
202 | for j in range(N_SYM):
203 | c = CubieCube(symCube[j].cp, symCube[j].co, symCube[j].ep, symCube[j].eo)
204 | c.multiply(self)
205 | c.multiply(symCube[inv_idx[j]])
206 | if self == c:
207 | s.append(j)
208 | c.inv_cubie_cube(d)
209 | if self == d: # then we have antisymmetry
210 | s.append(j + N_SYM)
211 | return s
212 |
213 | # ###################################### coordinates for phase 1 and 2 #################################################
214 | def get_twist(self):
215 | """Get the twist of the 8 corners. 0 <= twist < 2187 in phase 1, twist = 0 in phase 2."""
216 | ret = 0
217 | for i in range(Co.URF, Co.DRB):
218 | ret = 3 * ret + self.co[i]
219 | return ret
220 |
221 | def set_twist(self, twist):
222 | twistparity = 0
223 | for i in range(Co.DRB - 1, Co.URF - 1, -1):
224 | self.co[i] = twist % 3
225 | twistparity += self.co[i]
226 | twist //= 3
227 | self.co[Co.DRB] = ((3 - twistparity % 3) % 3)
228 |
229 | def get_flip(self):
230 | """Get the flip of the 12 edges. 0 <= flip < 2048 in phase 1, flip = 0 in phase 2."""
231 | ret = 0
232 | for i in range(Ed.UR, Ed.BR):
233 | ret = 2 * ret + self.eo[i]
234 | return ret
235 |
236 | def set_flip(self, flip):
237 | flipparity = 0
238 | for i in range(Ed.BR - 1, Ed.UR - 1, -1):
239 | self.eo[i] = flip % 2
240 | flipparity += self.eo[i]
241 | flip //= 2
242 | self.eo[Ed.BR] = ((2 - flipparity % 2) % 2)
243 |
244 | def get_slice(self):
245 | """Get the location of the UD-slice edges FR,FL,BL and BR ignoring their permutation.
246 | 0<= slice < 495 in phase 1, slice = 0 in phase 2."""
247 | a = x = 0
248 | # Compute the index a < (12 choose 4)
249 | for j in range(Ed.BR, Ed.UR - 1, -1):
250 | if Ed.FR <= self.ep[j] <= Ed.BR:
251 | a += c_nk(11 - j, x + 1)
252 | x += 1
253 | return a
254 |
255 | def set_slice(self, idx):
256 | slice_edge = list(range(Ed.FR, Ed.BR + 1))
257 | other_edge = [Ed.UR, Ed.UF, Ed.UL, Ed.UB, Ed.DR, Ed.DF, Ed.DL, Ed.DB]
258 | a = idx # Location
259 | for e in Ed:
260 | self.ep[e] = -1 # Invalidate all edge positions
261 |
262 | x = 4 # set slice edges
263 | for j in Ed:
264 | if a - c_nk(11 - j, x) >= 0:
265 | self.ep[j] = slice_edge[4 - x]
266 | a -= c_nk(11 - j, x)
267 | x -= 1
268 |
269 | x = 0 # set the remaining edges UR..DB
270 | for j in Ed:
271 | if self.ep[j] == -1:
272 | self.ep[j] = other_edge[x]
273 | x += 1
274 |
275 | def get_slice_sorted(self):
276 | """Get the permutation and location of the UD-slice edges FR,FL,BL and BR.
277 | 0 <= slice_sorted < 11880 in phase 1, 0 <= slice_sorted < 24 in phase 2, slice_sorted = 0 for solved cube."""
278 | a = x = 0
279 | edge4 = [0]*4
280 | # First compute the index a < (12 choose 4) and the permutation array perm.
281 | for j in range(Ed.BR, Ed.UR - 1, -1):
282 | if Ed.FR <= self.ep[j] <= Ed.BR:
283 | a += c_nk(11 - j, x + 1)
284 | edge4[3 - x] = self.ep[j]
285 | x += 1
286 | # Then compute the index b < 4! for the permutation in edge4
287 | b = 0
288 | for j in range(3, 0, -1):
289 | k = 0
290 | while edge4[j] != j + 8:
291 | rotate_left(edge4, 0, j)
292 | k += 1
293 | b = (j + 1)*b + k
294 | return 24*a + b
295 |
296 | def set_slice_sorted(self, idx):
297 | slice_edge = [Ed.FR, Ed.FL, Ed.BL, Ed.BR]
298 | other_edge = [Ed.UR, Ed.UF, Ed.UL, Ed.UB, Ed.DR, Ed.DF, Ed.DL, Ed.DB]
299 | b = idx % 24 # Permutation
300 | a = idx // 24 # Location
301 | for e in Ed:
302 | self.ep[e] = -1 # Invalidate all edge positions
303 |
304 | j = 1 # generate permutation from index b
305 | while j < 4:
306 | k = b % (j + 1)
307 | b //= j + 1
308 | while k > 0:
309 | rotate_right(slice_edge, 0, j)
310 | k -= 1
311 | j += 1
312 |
313 | x = 4 # set slice edges
314 | for j in Ed:
315 | if a - c_nk(11 - j, x) >= 0:
316 | self.ep[j] = slice_edge[4 - x]
317 | a -= c_nk(11 - j, x)
318 | x -= 1
319 |
320 | x = 0 # set the remaining edges UR..DB
321 | for j in Ed:
322 | if self.ep[j] == -1:
323 | self.ep[j] = other_edge[x]
324 | x += 1
325 |
326 | def get_u_edges(self):
327 | """Get the permutation and location of edges UR, UF, UL and UB.
328 | 0 <= u_edges < 11880 in phase 1, 0 <= u_edges < 1680 in phase 2, u_edges = 1656 for solved cube."""
329 | a = x = 0
330 | edge4 = [0]*4
331 | ep_mod = self.ep[:]
332 | for j in range(4):
333 | rotate_right(ep_mod, 0, 11)
334 | # First compute the index a < (12 choose 4) and the permutation array perm.
335 | for j in range(Ed.BR, Ed.UR - 1, -1):
336 | if Ed.UR <= ep_mod[j] <= Ed.UB:
337 | a += c_nk(11 - j, x + 1)
338 | edge4[3 - x] = ep_mod[j]
339 | x += 1
340 | # Then compute the index b < 4! for the permutation in edge4
341 | b = 0
342 | for j in range(3, 0, -1):
343 | k = 0
344 | while edge4[j] != j:
345 | rotate_left(edge4, 0, j)
346 | k += 1
347 | b = (j + 1)*b + k
348 | return 24*a + b
349 |
350 | def set_u_edges(self, idx):
351 | slice_edge = [Ed.UR, Ed.UF, Ed.UL, Ed.UB]
352 | other_edge = [Ed.DR, Ed.DF, Ed.DL, Ed.DB, Ed.FR, Ed.FL, Ed.BL, Ed.BR]
353 | b = idx % 24 # Permutation
354 | a = idx // 24 # Location
355 | for e in Ed:
356 | self.ep[e] = -1 # Invalidate all edge positions
357 |
358 | j = 1 # generate permutation from index b
359 | while j < 4:
360 | k = b % (j + 1)
361 | b //= j + 1
362 | while k > 0:
363 | rotate_right(slice_edge, 0, j)
364 | k -= 1
365 | j += 1
366 |
367 | x = 4 # set slice edges
368 | for j in Ed:
369 | if a - c_nk(11 - j, x) >= 0:
370 | self.ep[j] = slice_edge[4 - x]
371 | a -= c_nk(11 - j, x)
372 | x -= 1
373 |
374 | x = 0 # set the remaining edges UR..DB
375 | for j in Ed:
376 | if self.ep[j] == -1:
377 | self.ep[j] = other_edge[x]
378 | x += 1
379 | for j in range(4):
380 | rotate_left(self.ep, 0, 11)
381 |
382 | def get_d_edges(self):
383 | """Get the permutation and location of the edges DR, DF, DL and DB.
384 | 0 <= d_edges < 11880 in phase 1, 0 <= d_edges < 1680 in phase 2, d_edges = 0 for solved cube."""
385 | a = x = 0
386 | edge4 = [0] * 4
387 | ep_mod = self.ep[:]
388 | for j in range(4):
389 | rotate_right(ep_mod, 0, 11)
390 | # First compute the index a < (12 choose 4) and the permutation array perm.
391 | for j in range(Ed.BR, Ed.UR - 1, -1):
392 | if Ed.DR <= ep_mod[j] <= Ed.DB:
393 | a += c_nk(11 - j, x + 1)
394 | edge4[3 - x] = ep_mod[j]
395 | x += 1
396 | # Then compute the index b < 4! for the permutation in edge4
397 | b = 0
398 | for j in range(3, 0, -1):
399 | k = 0
400 | while edge4[j] != j + 4:
401 | rotate_left(edge4, 0, j)
402 | k += 1
403 | b = (j + 1) * b + k
404 | return 24 * a + b
405 |
406 | def set_d_edges(self, idx):
407 | slice_edge = [Ed.DR, Ed.DF, Ed.DL, Ed.DB]
408 | other_edge = [Ed.FR, Ed.FL, Ed.BL, Ed.BR, Ed.UR, Ed.UF, Ed.UL, Ed.UB]
409 | b = idx % 24 # Permutation
410 | a = idx // 24 # Location
411 | for e in Ed:
412 | self.ep[e] = -1 # Invalidate all edge positions
413 |
414 | j = 1 # generate permutation from index b
415 | while j < 4:
416 | k = b % (j + 1)
417 | b //= j + 1
418 | while k > 0:
419 | rotate_right(slice_edge, 0, j)
420 | k -= 1
421 | j += 1
422 |
423 | x = 4 # set slice edges
424 | for j in Ed:
425 | if a - c_nk(11 - j, x) >= 0:
426 | self.ep[j] = slice_edge[4 - x]
427 | a -= c_nk(11 - j, x)
428 | x -= 1
429 |
430 | x = 0 # set the remaining edges UR..DB
431 | for j in Ed:
432 | if self.ep[j] == -1:
433 | self.ep[j] = other_edge[x]
434 | x += 1
435 | for j in range(4):
436 | rotate_left(self.ep, 0, 11)
437 |
438 | def get_corners(self):
439 | """Get the permutation of the 8 corners.
440 | 0 <= corners < 40320 defined but unused in phase 1, 0 <= corners < 40320 in phase 2,
441 | corners = 0 for solved cube"""
442 | perm = list(self.cp) # duplicate cp
443 | b = 0
444 | for j in range(Co.DRB, Co.URF, -1):
445 | k = 0
446 | while perm[j] != j:
447 | rotate_left(perm, 0, j)
448 | k += 1
449 | b = (j + 1) * b + k
450 | return b
451 |
452 | def set_corners(self, idx):
453 | self.cp = [i for i in Co]
454 | for j in Co:
455 | k = idx % (j + 1)
456 | idx //= j + 1
457 | while k > 0:
458 | rotate_right(self.cp, 0, j)
459 | k -= 1
460 |
461 | def get_ud_edges(self):
462 | """Get the permutation of the 8 U and D edges.
463 | ud_edges undefined in phase 1, 0 <= ud_edges < 40320 in phase 2, ud_edges = 0 for solved cube."""
464 | perm = self.ep[0:8] # duplicate first 8 elements of ep
465 | b = 0
466 | for j in range(Ed.DB, Ed.UR, -1):
467 | k = 0
468 | while perm[j] != j:
469 | rotate_left(perm, 0, j)
470 | k += 1
471 | b = (j + 1) * b + k
472 | return b
473 |
474 | def set_ud_edges(self, idx):
475 | # positions of FR FL BL BR edges are not affected
476 | for i in list(Ed)[0:8]:
477 | self.ep[i] = i
478 | for j in list(Ed)[0:8]:
479 | k = idx % (j + 1)
480 | idx //= j + 1
481 | while k > 0:
482 | rotate_right(self.ep, 0, j)
483 | k -= 1
484 | # ###################################### end coordinates for phase 1 and 2 #############################################
485 |
486 | # ############################################ other usefull functions #################################################
487 | def randomize(self):
488 | """Generate a random cube. The probability is the same for all possible states."""
489 | def set_edges(idx):
490 | """The permutation of the 12 edges. 0 <= idx < 12!."""
491 | self.ep = [i for i in Ed]
492 | for j in Ed:
493 | k = idx % (j + 1)
494 | idx //= j + 1
495 | while k > 0:
496 | rotate_right(self.ep, 0, j)
497 | k -= 1
498 | set_edges(randrange(479001600)) # 12!
499 | p = self.edge_parity()
500 | while True:
501 | self.set_corners(randrange(40320)) # 8!
502 | if p == self.corner_parity(): # parities of edge and corner permutations must be the same
503 | break
504 | self.set_flip(randrange(2048)) # 2^11
505 | self.set_twist(randrange(2187)) # 3^7
506 |
507 | def verify(self):
508 | """Check if cubiecube is valid."""
509 | edge_count = [0]*12
510 | for i in Ed:
511 | edge_count[self.ep[i]] += 1
512 | for i in Ed:
513 | if edge_count[i] != 1:
514 | return 'Error: Some edges are undefined.'
515 |
516 | s = 0
517 | for i in Ed:
518 | s += self.eo[i]
519 | if s % 2 != 0:
520 | return 'Error: Total edge flip is wrong.'
521 |
522 | corner_count = [0] * 8
523 | for i in Co:
524 | corner_count[self.cp[i]] += 1
525 | for i in Co:
526 | if corner_count[i] != 1:
527 | return 'Error: Some corners are undefined.'
528 |
529 | s = 0
530 | for i in Co:
531 | s += self.co[i]
532 | if s % 3 != 0:
533 | return 'Error: Total corner twist is wrong.'
534 |
535 | if self.edge_parity() != self.corner_parity():
536 | return 'Error: Wrong edge and corner parity'
537 |
538 | return CUBE_OK
539 | ########################################################################################################################
540 |
541 | # ################################## these cubes represent the basic cube moves ########################################
542 | basicMoveCube = [0] * 6
543 | basicMoveCube[Color.U] = CubieCube(cpU, coU, epU, eoU)
544 | basicMoveCube[Color.R] = CubieCube(cpR, coR, epR, eoR)
545 | basicMoveCube[Color.F] = CubieCube(cpF, coF, epF, eoF)
546 | basicMoveCube[Color.D] = CubieCube(cpD, coD, epD, eoD)
547 | basicMoveCube[Color.L] = CubieCube(cpL, coL, epL, eoL)
548 | basicMoveCube[Color.B] = CubieCube(cpB, coB, epB, eoB)
549 | ########################################################################################################################
550 |
551 | # ################################# these cubes represent the all 18 cube moves ########################################
552 |
553 | moveCube = [0] * 18
554 | for c1 in Color:
555 | cc = CubieCube()
556 | for k1 in range(3):
557 | cc.multiply(basicMoveCube[c1])
558 | moveCube[3 * c1 + k1] = CubieCube(cc.cp, cc.co, cc.ep, cc.eo)
559 | ########################################################################################################################
560 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------