-
Notifications
You must be signed in to change notification settings - Fork 6
/
cubepos.w
2506 lines (2277 loc) · 85.6 KB
/
cubepos.w
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\def\mod{\mathop{mod}}
@s vector template
@s map template
@s void int
@* Introduction.
This simple class represents positions of a 3x3x3 Rubik's cube
efficiently (both in space and time). It is the basis of almost every
program I have written for the cube over the past few years.
@ Preface of header file.
We use a common technique to protect against multiple inclusion and
thus redeclaration, by starting the main include file with a
conditional that checks whether the file has already been included.
We then include the standard headers that we need. We also flatten
the namespace so vector and other components are in our namespace
for convenience.
@(cubepos.h@>=
#ifndef CUBEPOS_H
#define CUBEPOS_H
#include <cstring>
#include <cstdlib>
#include <stddef.h>
#include <vector>
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#include <intrin.h>
inline int ffs1(int v){unsigned long r;_BitScanForward(&r, v);return(int)r;}
#else
inline int ffs1(int v){return ffs(v)-1;}
#include <sys/time.h>
#endif
using namespace std ;
@ Distance metric.
Early in computer cubing history, two primary move metrics were
defined. The {\it half-turn metric} counts every quarter turn and
every half turn of a face as a single move; this was adopted by most
west-coast researchers. The {\it quarter-turn metric} counts a half
turn as two moves; this was adopted by east-coast researchers. The
quarter-turn metric has some nice properties; for instance, odd
positions are exactly those reachable by an odd number of moves.
Nonetheless, the half-turn metric is the more common metric,
corresponding more closely to what most people think of as a ``move''.
We also include the {\it slice-turn metric} where moving a center
slice also counts as a move, and the {\it axial-turn metric} where
moving any pair of opposite faces in any way is legal.
This class supports four metrics. One of the preprocessor symbols
|HALF|, |QUARTER|, |SLICE|, or |AXIAL| must be defined when
this file is included, and
this defines the operation of the class. We make this a compile-time
directive for efficiency. Supporting both through clever templates
is certainly possible, but too complicated for the gain in
convenience.
@(cubepos.h@>=
#ifdef HALF
#ifdef SLICE
#error "Can't define HALF and SLICE"
#endif
#ifdef QUARTER
#error "Can't define HALF and SLICE"
#endif
#ifdef AXIAL
#error "Can't define HALF and AXIAL"
#endif
#else
#ifdef SLICE
#ifdef QUARTER
#error "Can't define SLICE and QUARTER"
#endif
#ifdef AXIAL
#error "Can't define SLICE and AXIAL"
#endif
#else
#ifdef QUARTER
#ifdef AXIAL
#error "Can't define SLICE and AXIAL"
#endif
#else
#ifndef AXIAL
#error "Please define one of HALF, SLICE, QUARTER, or AXIAL"
#endif
#endif
#endif
#endif
@ Common constants.
The following constants are used throughout the class and by other
programs that use this. The |M| constant is the number of
automorphisms of the cube induced by rotation and reflection; these
automorphisms themselves form a group, commonly called $M$.
@(cubepos.h@>=
#ifdef HALF
const int NMOVES = 18 ;
const int TWISTS = 3 ;
#endif
#ifdef QUARTER
const int NMOVES = 12 ;
const int TWISTS = 2 ;
#endif
#ifdef SLICE
const int NMOVES = 27 ;
const int TWISTS = 3 ;
#endif
#ifdef AXIAL
const int NMOVES = 45 ;
const int TWISTS = 3 ;
#endif
const int FACES = 6 ;
const int M = 48 ;
const int CUBIES = 24 ;
@ Class declaration.
Before the class, we declare a public shared copy of the solved cube
(the identity of the group); for convenience, we do not make it a
static member object but instead make it a global. Then we define the
class. We break it into four components for convenience. Note that
we keep the data representation public; we trust users of this class
not to abuse this privilege. Many things are simplified by direct
access to the data. We also provide a slot for general utility
functions, like error reporting, time reporting, and random number
generation.
@s cubepos int
@s moveseq int
@(cubepos.h@>=
extern const class cubepos identity_cube ; @/
@<Global utility declarations@> @;
class cubepos {
public: @/
@<Public method declarations of cubepos@> @;
@<Static data declarations of cubepos@> @;
@<Data representation of cubepos@> @;
} ;
@<Static initialization hack@> @;
#endif
@* Representation and numbering.
The Rubik's cube has twenty movable cubies that move around the six
face centers. If we do not consider rotations of the whole cube,
those twenty cubies are the only pieces that move. Whole-cube
rotations are not generally considered ``moves'' so we will ignore
these for the moment.
@ Representing the corners.
We represent the corners and edges separately. Let us start with
the corners. We number the corners 0 to 7 starting with the top
layer; the back left corner is 0, then the back right corner is
1, then the front left corner is 2, and the front right corner
is 3. We number the corners on the bottom layer in the same order,
assigning them 4, 5, 6, and 7.
\vskip\baselineskip
\hbox to \hsize{\hfil
\vbox{\halign{
\strut\vrule height 15pt depth 7pt
\hbox to 20pt{\hfil#\hfil}\vrule&\hbox to 20pt{\hfil#\hfil}\vrule
&\hbox to 20pt{\hfil#\hfil}\vrule\cr
\noalign{\hrule}
0&&1\cr
\noalign{\hrule}
&&\cr
\noalign{\hrule}
2&&3\cr
\noalign{\hrule}
\omit\span\omit\span\omit\vbox to 16pt{}\hfil Top Layer\hfil\cr
}}
\hskip 40pt
\vbox{\halign{
\strut\vrule height 15pt depth 7pt
\hbox to 20pt{\hfil#\hfil}\vrule&\hbox to 20pt{\hfil#\hfil}\vrule
&\hbox to 20pt{\hfil#\hfil}\vrule\cr
\noalign{\hrule}
&&\cr
\noalign{\hrule}
&&\cr
\noalign{\hrule}
&&\cr
\noalign{\hrule}
\omit\span\omit\span\omit\vbox to 16pt{}\hfil Middle Layer\hfil\cr
}}
\hskip 40pt
\vbox{\halign{
\strut\vrule height 15pt depth 7pt
\hbox to 20pt{\hfil#\hfil}\vrule&\hbox to 20pt{\hfil#\hfil}\vrule
&\hbox to 20pt{\hfil#\hfil}\vrule\cr
\noalign{\hrule}
4&&5\cr
\noalign{\hrule}
&&\cr
\noalign{\hrule}
6&&7\cr
\noalign{\hrule}
\omit\span\omit\span\omit\vbox to 16pt{}\hfil Bottom Layer\hfil\cr
}}\hfil}
Each cubie can be in one of the eight slots, and it can have up to
three distinct orientations, for a total of 24 distinct states (this
is |CUBIES|). We therefore use an |unsigned char| to hold these
values. The low three bits of the value in |c[i]| indicates the slot
where corner cube |i| is. The next two bits give the corner twist of
that cubie; 0 indicates no twist, 1 indicates a clockwise twist, and 2
indicates a counterclockwise twist. We will define our corner twist
convention later.
@<Data representation of cubepos@>=
unsigned char c[8] ;
@ Representing the edge cubies.
The edges are represented in a similar fashion to the corners.
There are twelve edges.
\vskip\baselineskip
\hbox to \hsize{\hfil
\vbox{\halign{
\strut\vrule height 15pt depth 7pt
\hbox to 20pt{\hfil#\hfil}\vrule&\hbox to 20pt{\hfil#\hfil}\vrule
&\hbox to 20pt{\hfil#\hfil}\vrule\cr
\noalign{\hrule}
&0&\cr
\noalign{\hrule}
1&&2\cr
\noalign{\hrule}
&3&\cr
\noalign{\hrule}
\omit\span\omit\span\omit\vbox to 16pt{}\hfil Top Layer\hfil\cr
}}
\hskip 40pt
\vbox{\halign{
\strut\vrule height 15pt depth 7pt
\hbox to 20pt{\hfil#\hfil}\vrule&\hbox to 20pt{\hfil#\hfil}\vrule
&\hbox to 20pt{\hfil#\hfil}\vrule\cr
\noalign{\hrule}
4&&5\cr
\noalign{\hrule}
&&\cr
\noalign{\hrule}
6&&7\cr
\noalign{\hrule}
\omit\span\omit\span\omit\vbox to 16pt{}\hfil Middle Layer\hfil\cr
}}
\hskip 40pt
\vbox{\halign{
\strut\vrule height 15pt depth 7pt
\hbox to 20pt{\hfil#\hfil}\vrule&\hbox to 20pt{\hfil#\hfil}\vrule
&\hbox to 20pt{\hfil#\hfil}\vrule\cr
\noalign{\hrule}
&8&\cr
\noalign{\hrule}
9&&10\cr
\noalign{\hrule}
&11&\cr
\noalign{\hrule}
\omit\span\omit\span\omit\vbox to 16pt{}\hfil Bottom Layer\hfil\cr
}}\hfil}
Each edge can be in one of twelve slots, and it can have only two
orientations, for a total again of 24 possible states. The low bit of
the value in |e[i]| indicates whether edge |i| is flipped or not; a 0
indicates it is not, and a 1 indicates it is. The next four bits
indicate the edge slot that edge |i| currently resides in. We will
define our edge flip convention later.
@<Data representation of cubepos@>=
unsigned char e[12] ;
@ We need methods to compare two |cubepos| objects for equality and
for total ordering. We use |memcmp| to do the work; the |g++|
compiler (at least) uses efficient intrinsics for this.
@<Public...@>=
inline bool operator<(const cubepos &cp) const {
return memcmp(this, &cp, sizeof(cp)) < 0 ;
}
inline bool operator==(const cubepos &cp) const {
return memcmp(this, &cp, sizeof(cp))==0 ;
}
inline bool operator!=(const cubepos &cp) const {
return memcmp(this, &cp, sizeof(cp))!=0 ;
}
@ Convenience methods and arrays.
We provide these four inline functions to make it convenient to go to
and from separate permutation/orientation to combined cubie values,
and from cubie values to permutations and orientations. We also provide
fast routines that lets us add the orientation (only) from one
cubieval to another cubieval (for both corners and edges).
Finally, we throw in the initialization declaration.
@<Public method declarations of cubepos@>=
static inline int edge_perm(int cubieval) { return cubieval >> 1 ; }
static inline int edge_ori(int cubieval) { return cubieval & 1 ; }
static inline int corner_perm(int cubieval) { return cubieval & 7 ; }
static inline int corner_ori(int cubieval) { return cubieval >> 3 ; }
static inline int edge_flip(int cubieval) { return cubieval ^ 1 ; }
static inline int edge_val(int perm, int ori) { return perm * 2 + ori ; }
static inline int corner_val(int perm, int ori) { return ori * 8 + perm ; }
static inline int edge_ori_add(int cv1, int cv2) { return cv1 ^ edge_ori(cv2) ; }
static inline int corner_ori_add(int cv1, int cv2) \
{ return mod24[cv1 + (cv2 & 0x18)] ; }
static inline int corner_ori_sub(int cv1, int cv2) \
{ return cv1 + corner_ori_neg_strip[cv2] ; }
static void init() ;
@ We need to put the static data into the C++ file; it's rather
annoying that we need to both declare and define these arrays. We
also provide an initialization routine that follows the declarations
and any other utility functions we need.
@<cubepos.cpp@>=
#include <iostream>
#include "cubepos.h"
#include <math.h>
#include <random>
mt19937 rng ;
@<Static data instantiations@> @;
@<Local routines for cubepos@> @;
void cubepos::init() {
static int initialized = 0 ;
if (initialized)
return ;
initialized = 1 ;
@<Initialization of cubepos@> @;
}
@ Corner orientation changes.
Frequently we need to change the orientation of a cubie. This
is easy to do for edges (just flip the low-order bit) but for
corners it is slightly more difficult. We introduce the following
arrays to allow changing corner orientations without performing
a modulo or division operation.
@<Static data decl...@>=
static unsigned char corner_ori_inc[CUBIES], corner_ori_dec[CUBIES],
corner_ori_neg_strip[CUBIES], mod24[2*CUBIES] ;
@ We need to declare these in the C++ file. We also declare our
identity cube here.
@<Static data inst...@>=
const cubepos identity_cube(0,0,0) ;
unsigned char cubepos::corner_ori_inc[CUBIES],
cubepos::corner_ori_dec[CUBIES],
cubepos::corner_ori_neg_strip[CUBIES], cubepos::mod24[2*CUBIES] ;
@ Initialization of these is straightforward.
@<Initializ...@>=
for (int i=0; i<CUBIES; i++) {
int perm = corner_perm(i) ;
int ori = corner_ori(i) ;
corner_ori_inc[i] = corner_val(perm, (ori + 1) % 3) ;
corner_ori_dec[i] = corner_val(perm, (ori + 2) % 3) ;
corner_ori_neg_strip[i] = corner_val(0, (3 - ori) % 3) ;
mod24[i] = mod24[i+CUBIES] = i ;
}
@ The constructor.
There are two constructors. The first one is both the default
constructor and the copy constructor; if no argument is provided,
it just makes a copy of the identity cube. The second constructor
(with three int arguments all of which are ignored) initializes
the current cube more slowly (it does not depend on the identity
cube being initialized) and, if the full class itself has not
been initialized, initializes the class as well. This second
constructor is only used for static objects.
@<Public method declarations of cubepos@>=
inline cubepos(const cubepos &cp=identity_cube) { *this = cp ; }
cubepos(int,int,int) ;
@ The static initialization hack.
We declare an instance of |cubepos| using this second constructor
here, so it appears in every compilation unit that uses |cubepos|, and
thus we work around the {\it static initialization fiasco} that makes
C++ so dangerous. We also declare the method that will do the work of
initializing all the static members of the class.
@<Static initialization hack@>=
static cubepos cubepos_initialization_hack(1,2,3) ;
@ Initializing the identity cube.
Based on the data structure definitions we have given so far, we can
write the constructor that initializes the identity position here. We
also throw in a call to the main class initialization routine, which
ensures the class is initialized for any compilation unit that
includes this header.
@(cubepos.cpp@>=
cubepos::cubepos(int,int,int) {
for (int i=0; i<8; i++)
c[i] = corner_val(i, 0) ;
for (int i=0; i<12; i++)
e[i] = edge_val(i, 0) ;
init() ;
}
@ Face numbering.
When we talk about faces of the cube, normally we refer to the color
of the face, which, on the 3x3x3 cube, is given by the center cubie,
since the center cubie cannot be displaced by normal moves. Rather
than adopt a particular color scheme, it is conventional in
mathematics about the cube to simply label each face by its spatial
location; thus, we have the Up and Down faces, the Left and Right
faces, and the Front and Back faces. These are normally abbreviated
U, F, R, D, B, and L. We number the faces so that we can use face
indices to index into arrays, and so we can number the moves which are
all twists of faces. The numbering we choose is arbitrary, but if we
choose it to have certain properties it can simplify later code. The
numbering we adopt is based on the most regular ``unfolding'' of the
cube we could find. The order we adopt is U, F, R, D, B, L, to which
we assign the ordinals 0 to 5. This ordering has the following
properties:
1. The opposite face of face $i$ is $i+3\mod |FACES|$
2. Every three cyclically consecutive faces in this ordering
join at a single corner. This defines six of the eight
corners; the other two are defined by the odd-numbered
and the even-numbered faces, respectively.
3. Every pair of faces whose ordinals do not differ by 3
(mod |FACES|) defines an edge.
@<Static data decl...@>=
static char faces[FACES] ;
#ifdef SLICE
static char movefaces[FACES+3] ;
#endif
#ifdef AXIAL
static char movefaces[FACES+9] ;
#endif
@ We initialize the face array here.
@<Static data inst...@>=
char cubepos::faces[FACES] = {'U','F','R','D','B','L'} ;
#ifdef SLICE
char cubepos::movefaces[FACES+3] = {'U','F','R','D','B','L','I','J','K'} ;
#endif
#ifdef AXIAL
char cubepos::movefaces[FACES+9] = {'U','F','R','D','B','L',
'A','C','E','G','H','I',
'J','K','M'} ;
#endif
@ Move numbering.
Once we've numbered the faces, numbering the moves is straightforward.
We order the twists in the order (clockwise, half turn,
counterclockwise) based on the idea that a clockwise twist is the
basic move, and a half turn is that move squared (or done twice) and a
counterclockwise turn is that move done three times. Of course, for
the quarter turn metric, we do not permit the half turn (and thus do
not count it in the indexing).
We will represent a clockwise turn of the U face by the move U1 (or,
where there is no ambiguity, just U). A half-turn is represented by
U2, and a counterclockwise turn by U3. The normal convention for a
counterclockwise turn is U', but I prefer U3 for simplicity. When
discussing these moves in a mathematical context we may use $U$,
$U^2$, and $U^{-1}$, respectively.
For slice turns, we want to use the standard moves E, M, and S.
But we do not include the centers in our representation. So we
cheat; for the slice turn metric, we instead introduce I, J, and K,
where I1 is the same as U1D3, J1 is the same as F1B3, and K1 is
the same as R1L3, and so on. A later postprocessing step can
turn a sequence of these moves of opposite faces into the
corresponding cube moves (with any necessary whole-cube rotations).
At this point many programmers would fill the namespace with symbolic
names for each of the moves. At this point, I don't believe that
carries any benefit.
The move routine has this signature:
@<Public method declarations of cubepos@>=
void move(int mov) ;
@ For the quarter turn metric, we support the concept of double
moves for faces F, R, B, and L as ``extended moves''. This makes
some code to come a lot easier, when dealing with the Kociemba
subgroup in the quarter turn metric. We number these moves after
the normal moves, and we introduce a new constant here that
gives us the count of normal moves plus extended moves.
@<Global utility...@>=
#ifdef QUARTER
const int NMOVES_EXT = NMOVES + 4 ;
#else
const int NMOVES_EXT = NMOVES ;
#endif
@* The first move routine.
We are ready now to write our first move routine. Since we are
storing the location and orientation for each cubie, it is
straightforward to determine how a particular move will affect each
cubie. We can encode this information into a pair of small arrays,
one for corners and one for edges. Note that the impact of a move
does not depend on which corner or which edge it is, just the
current location and orientation, so we can use the same array for
all cubies.
@<Static data decl...@>=
static unsigned char edge_trans[NMOVES_EXT][CUBIES],
corner_trans[NMOVES_EXT][CUBIES] ;
@ Here is the data itself.
The two arrays sum to only 864 bytes, and the values for each move are
contiguous (within each array) so this is cache-friendly.
@<Static data inst...@>=
unsigned char cubepos::edge_trans[NMOVES_EXT][CUBIES],
cubepos::corner_trans[NMOVES_EXT][CUBIES] ;
@ Performing the move.
Performing the move itself is simple; we just apply the arrays above
to each cubie. We grab a pointer to the array more as a coding
shorthand than as a hint to the compiler. We manually unroll the
loop; we want to ensure the compiler gives us one short quick function
body with no branches or loops; this routine could be called many
trillions of times. Even though this code looks relatively long, it's
branch-free, cache-friendly, and appears to execute extremely fast on
modern processors such as the Intel i7-920.
@(cubepos.cpp@>=
void cubepos::move(int mov) {
const unsigned char *p = corner_trans[mov] ;
c[0] = p[c[0]] ; c[1] = p[c[1]] ; c[2] = p[c[2]] ;
c[3] = p[c[3]] ; c[4] = p[c[4]] ; c[5] = p[c[5]] ;
c[6] = p[c[6]] ; c[7] = p[c[7]] ;
p = edge_trans[mov] ;
e[0] = p[e[0]] ; e[1] = p[e[1]] ; e[2] = p[e[2]] ;
e[3] = p[e[3]] ; e[4] = p[e[4]] ; e[5] = p[e[5]] ;
e[6] = p[e[6]] ; e[7] = p[e[7]] ; e[8] = p[e[8]] ;
e[9] = p[e[9]] ; e[10] = p[e[10]] ; e[11] = p[e[11]] ;
}
@ Edge permutation.
We need to now fill in the |edge_trans| and |corner_trans| arrays.
Based on our cubie numbering, we can build an array listing the slots
that are affected by a clockwise twist of each face, in the order of
the moves, based on our slot numbering convention and move numbering
convention. A clockwise twist of the first face (U) moves the cubie
from slot 0 into slot 2, from slot 2 into slot 3, from slot 3 into
slot 1, and from slot 1 into slot 0. This is represented by the
permutation written as $(0,2,3,1)$, and this comprises the first
element of the following array. The rest are filled in similarly.
@<Static data inst...@>=
static const unsigned char edge_twist_perm[FACES][4] = {
{ 0, 2, 3, 1 },
{ 3, 7, 11, 6 },
{ 2, 5, 10, 7 },
{ 9, 11, 10, 8 },
{ 0, 4, 8, 5 },
{ 1, 6, 9, 4 }
} ;
@ Corner permutation.
We can do the same thing for the corner permutation. A quarter twist
of the U face moves the corner in slot 0 to slot 1, from slot 1 to
slot 3, from slot 3 to slot 2, and from slot 2 to slot 0. This
permutation is $(0,1,3,2)$, and it's the first entry in the array
below. This array is carefully constructed so the first two slots are
always from the U face (assuming any slots are), which simplifies
some later code.
@<Static data inst...@>=
static const unsigned char corner_twist_perm[FACES][4] = {
{ 0, 1, 3, 2 },
{ 2, 3, 7, 6 },
{ 3, 1, 5, 7 },
{ 4, 6, 7, 5 },
{ 1, 0, 4, 5 },
{ 0, 2, 6, 4 }
} ;
@ Edge orientation convention.
Now we consider the orientation aspects of moves. When we say a
corner is twisted, or an edge is flipped, that makes sense when the
cubie is in its solved position. But what does it mean for a cubie to
be twisted or flipped when it is in some other slot?
Let us start by considering edge flip. Consider the edge cubie whose
home location is the intersection of the U and F faces (we can call
this cubie UF). If we permit only the moves U, F, D, and B (and
half-twists and counterclockwise twists), it is straightforward to see
that whenever the cubie UF is in the U or D face, its U facelet (the
sticker colored the same as the center cubie on the U face) is always
on the U or D face, and never on one of the F, R, B, or L faces.
Further, when the UF cubie is in the middle layer, its U facelet is
always on the L or R face. In other words, there is only a single
orientation for each cubie in each slot if we start from the solved
position and perform only combinations of the moves U, F, D, and B.
If we further permit R and L moves, however, this is no longer true.
In particular, the move sequence F1R1U1 brings the UF cubie back to
the UF slot, but now the U facelet is in the front face.
We can thus define an edge orientation convention as follows. Only
the four moves R1, R3, L1, and L3 modify the edge orientation of any
cubie as the cubie moves along slots. All other moves preserve
the edge orientation.
There are a number of alternative edge orientation conventions. We
can use any pair of opposite faces instead of R and L above. Or,
interestingly, we can simply state that every quarter move flips
the edge orientation of every involved edge cubie. This last
convention has more symmetry than the one we adopt, but for reasons
that come from certain specific use of this class, we reject these
alternative orientation conventions and use the one defined here.
@<Static data inst...@>=
static const unsigned char edge_change[FACES] = { 0, 0, 1, 0, 0, 1 } ;
@ Corner orientation convention.
Corner orientation is similar, but there are three possible
orientations for every cubie, not just two. Note that every cubie has
a U or D facelet; this permits a straightforward orientation
convention based on simple examination. If the U or D facelet is in
the U or D face, we declare the cubie to be properly oriented (an
orientation of 0). If twisting the cubie (when looking towards the
center of the cube from that cubie) counterclockwise brings it into
the oriented state, then we consider the cubie to be oriented
clockwise, or +1. If twisting the cubie clockwise brings it into the
oriented state, we consider the cubie to be oriented counterclockwise,
or +2 (which is $--1\mod 3$).
From this definition, it is clear that no move of the U or D faces
will change the orientation of any corner cubie. A quarter twist of
any other face that leaves a particular corner cubie in the same U or
D face that it started from will effect a clockwise twist on that
cubie. A quarter twist that moves a corner cube from the U face to
the D face, or from the D face to the U face, will effect a
counterclockwise twist on that cubie. This can be summarized in the
following array. Note that we use the information that the
|corner_twist_perm| array above always starts with two U face slots
before listing two D face slots; thus, the transition corresponding to
elements 0 and 2 preserve the U or D face of a cubie, while the
elements for 1 and 3 move a cubie from the U face to the D face or
vice versa.
@<Static data inst...@>=
static const unsigned char corner_change[FACES][4] = {
{ 0, 0, 0, 0 },
{ 1, 2, 1, 2 },
{ 1, 2, 1, 2 },
{ 0, 0, 0, 0 },
{ 1, 2, 1, 2 },
{ 1, 2, 1, 2 },
} ;
@ Making the move table.
At this point we have all the data we need to fill in the
|corner_trans| and |edge_trans| arrays. This initialization routine
combines information from the four static arrays above, along with the
bit assignment for the cubie information we have defined. First we
fill in default unchanged values for all entries.
@<Initializ...@>=
for (int m=0; m<NMOVES_EXT; m++)
for (int c=0; c<CUBIES; c++) {
edge_trans[m][c] = c ;
corner_trans[m][c] = c ;
}
@ Next we modify the ones affected by the twists, according to the
|corner_twist_perm| and |edge_twist_perm| arrays. For every move, we
figure out if it is a quarter move or not, and calculate the increment
in the permutation arrays (1 for clockwise, 2 for half turns, and 3
for counterclockwise). For both corners and edges, we only change the
orientation if it is a quarter move, and if so, according to the
|edge_change| and |corner_change| arrays, respectively.
@<Initializ...@>=
for (int f=0; f<FACES; f++)
for (int t=0; t<3; t++) {
#ifdef QUARTER
int m = -1 ;
if (t == 1) {
if (f != 0 && f != 3) {
m = NMOVES + f - 1 ;
if (f >= 3)
m-- ;
}
} else {
m = f * TWISTS + (t >> 1) ;
}
#else
int m = f * TWISTS + t ;
#endif
int isquarter = (t == 0 || t == 2) ;
int perminc = t + 1 ;
if (m < 0)
continue ;
for (int i=0; i<4; i++) {
int ii = (i + perminc) % 4 ;
for (int o=0; o<2; o++) {
int oo = o ; // new orientation
if (isquarter)
oo ^= edge_change[f] ;
edge_trans[m][edge_val(edge_twist_perm[f][i], o)] =
edge_val(edge_twist_perm[f][ii], oo) ;
}
for (int o=0; o<3; o++) {
int oo = o ; // new orientation
if (isquarter)
oo = (corner_change[f][i] + oo) % 3 ;
corner_trans[m][corner_val(corner_twist_perm[f][i], o)] =
corner_val(corner_twist_perm[f][ii], oo) ;
}
}
}
@ For slice turns, we take advantage of the fact that the slice
moves are just combinations of the non-slice moves to calculate
the transition tables for these.
@<Initializ...@>=
#ifdef SLICE
for (int f=0; f<3; f++)
for (int t=0; t<TWISTS; t++) {
int m1 = f * TWISTS + t ;
int m2 = (f + 3) * TWISTS + (TWISTS - 1 - t) ;
int m3 = m1 + FACES * TWISTS ;
for (int c=0; c<CUBIES; c++) {
if (edge_trans[m1][c] != c)
edge_trans[m3][c] = edge_trans[m1][c] ;
if (edge_trans[m2][c] != c)
edge_trans[m3][c] = edge_trans[m2][c] ;
if (corner_trans[m1][c] != c)
corner_trans[m3][c] = corner_trans[m1][c] ;
if (corner_trans[m2][c] != c)
corner_trans[m3][c] = corner_trans[m2][c] ;
}
}
#endif
#ifdef AXIAL
int movectr = TWISTS * FACES ;
for (int t2=0; t2<TWISTS; t2++)
for (int f=0; f<3; f++)
for (int t1=0; t1<TWISTS; t1++) {
int m1 = f * TWISTS + t1 ;
int m2 = (f + 3) * TWISTS + t2 ;
int m3 = movectr++ ;
for (int c=0; c<CUBIES; c++) {
if (edge_trans[m1][c] != c)
edge_trans[m3][c] = edge_trans[m1][c] ;
if (edge_trans[m2][c] != c)
edge_trans[m3][c] = edge_trans[m2][c] ;
if (corner_trans[m1][c] != c)
corner_trans[m3][c] = corner_trans[m1][c] ;
if (corner_trans[m2][c] != c)
corner_trans[m3][c] = corner_trans[m2][c] ;
}
}
#endif
@* Inverse positions.
What we have so far makes a useful and powerful class. But there are
a lot of interesting and useful operations we can provide that reflect
the group nature of Rubik's cube. In particular, the inverse operation
is very important.
Every move sequence leads to a particular position from the solved
cube. Just like each move has an inverse move, each position has an
inverse position and each sequence has an inverse sequence.
For moves, we declare, instantiate, and initialize the inverse move.
Halfturn moves are their own inverse; the inverse of a clockwise move
is a counterclockwise move, and vice versa.
We use a typedef for a movesequence to make the code clearer and
to help eliminate the template syntax that trips up cweave.
@<Global utility...@>=
typedef vector<int> moveseq ;
@ First we declare the public methods to invert a move, move sequence,
and position. Since inverting a position will be so common, we
require that the destination be passed in by reference (which
eliminates any confusion on the part of the programmer as to when
an object will be created or destroyed).
@<Public method...@>=
static int invert_move(int mv) { return inv_move[mv] ; }
static moveseq invert_sequence(const moveseq &sequence) ;
void invert_into(cubepos &dst) const ;
@ To invert moves, we need a quick static array.
@<Static data decl...@>=
static unsigned char inv_move[NMOVES_EXT] ;
@ We add instantiate this array.
@<Static data inst...@>=
unsigned char cubepos::inv_move[NMOVES_EXT] ;
@ Initialization of this array is straightforward. We simply
negate the twist component.
@<Initializ...@>=
for (int i=0; i<NMOVES; i++)
inv_move[i] = TWISTS * (i / TWISTS) + (NMOVES - i - 1) % TWISTS ;
#ifdef QUARTER
for (int i=NMOVES; i<NMOVES_EXT; i++)
inv_move[i] = i ;
#endif
#ifdef AXIAL
for (int t2=0; t2<3; t2++)
for (int f=0; f<3; f++)
for (int t1=0; t1<3; t1++)
inv_move[FACES*TWISTS+t2*9+f*3+t1] =
FACES*TWISTS+(TWISTS-t2-1)*9+f*3+(TWISTS-t1-1) ;
#endif
@ To invert a sequence, we reverse it and invert each move. This
routine returns a new vector; we do not anticipate it being called
frequently since it is usually more convenient to just invert a
position.
@(cubepos.cpp@>=
moveseq cubepos::invert_sequence(const moveseq &seq) {
unsigned int len = seq.size() ;
moveseq r(len) ;
for (unsigned int i=0; i<len; i++)
r[len-i-1] = invert_move(seq[i]) ;
return r ;
}
@ Now we get into one of the more entertaining routines: inverting a
position. If we ignore orientation, then the cube is just a
combination of two permutations. Inverting a permutation stored in
array |a| to an array |b| is easy; just set |b[a[i]] = i| for |i in
0..n-1|. Let us consider what happens to orientations.
Let us consider edges first. Just like permutations can be composed,
cube positions in Rubik's cube can be composed (indeed, this is much
of what it means for the cube to be a group). Thus, when we speak of
a position that has cubie number 3 at slot 5, we can also treat it as
an operation that moves the cubie that was at slot 3 to slot 5, just
as we can for permutations. Consider the permutation $(0,3,1)$, which
means the cubie at position 0 is moved to position 3, the cubie at
position 3 is moved to position 1, and the cubie at position 1 is
moved to position 0. The inverse permutation is clearly $(0,1,3)$.
Let us extend the permutation notation with $+$ to indicate an edge
flip or corner twist, and $-$ to indicate a counterclockwise corner
twist. Thus the position (looking only at edges for the moment)
$(0,3+,1)$ means that the cubie at slot 0 is moved to slot 3 and
flipped, the cubie at slot 3 is moved to slot 1, and the cubie at slot
1 is moved to slot 0.
With groups, one way to calculate the inverse is to repeately
calculate powers of an element until you get back to the identity
element; the position immediately preceding the identity element in
this sequence is the inverse of the original position. Let us do this
with the $(0,3+,1)$ position. Applying it to itself yields
$(0,1+,3+)$. Applying it again gives $(0+),(1+),(3+)$ (that is, all
cubies are in their original positions but flipped). Continuing, we
obtain $(0+,3,1+)$, $(0+,1,3)$, and finally $1$ (that is, the identity
permutation). Thus, the order of this position is 6, and the inverse
of this position is $(0+,1,3)$.
We can also perform this inversion by simple inspection. If a
position takes a cubie from slot $i$ to slot $j$ and flips it in the
process, then the inverse position must take the cubie in slot $j$ and
move it to slot $i$, unflipping it in the process.
This allows us to write our inversion routine very easily.
@<Local routines...@>=
void cubepos::invert_into(cubepos &dst) const {
for (int i=0; i<8; i++) {
int cval = c[i] ;
dst.c[corner_perm(cval)] = corner_ori_sub(i, cval) ;
}
for (int i=0; i<12; i++) {
int cval = e[i] ;
dst.e[edge_perm(cval)] = edge_val(i, edge_ori(cval)) ;
}
}
@* The second move routine.
Now, a move sequence applied to the identity cube yields a position,
and applying the inverse sequence yields the inverse position. We can
think of a position as corresponding to an implicit move sequence that
leads to that position, and performing a move on the position extends
that sequence on the right by that move. What about extending the
sequence on the left; can we perform that operation?
Alternatively, our class as we have presented it so far contains two
arrays |c| and |e| that, for each corner and edge cubie, indicates
what slot it is in and what orientation it has in that slot. Another
representation might instead use those arrays to indicate for each
slot, what cubie is in the slot and what orientation it has.
Interestingly, these two distinct representations are exactly
inverses of each other. Using the |invert_into| method, we can
convert a position from one representation to the other.
Can we write a move routine that treats the position in the second
fashion? Yes we can, and it is fairly simple to do. We will call
this routine |movepc| because it is a move routine that treats the
representation as a slot (position) to cubie map (where our default
presentation so far has considered it as a cubie to slot map).
@<Public method...@>=
void movepc(int mov) ;
@ One advantage of representing the cube with a slot to cubie map is,
for each move, we only need to update the specific eight slots that
are modified by a particular move. In our cubie to slot
representation, we cannot easily take such a shortcut. This advantage
is reduced by the need to branch based on the move number itself,
however; which slots are affected by a move depends on the move. It
is possible to use a table and indirection to eliminate the switch,
but half moves and quarter moves still must be distinguished in some
way, so you can't eliminate it all.
Moves on the up and down face preserve both corner and edge
orientations of the cubies, so they just move cubie values from
slot to slot. We define macros to do a swap and a four-cycle.
The first argument to these macros is the array to modify; the
remaining arguments are indices into this array.
@(cubepos.cpp@>=
#define ROT2(cc,a,b) {unsigned char t=cc[a];cc[a]=cc[b];cc[b]=t;}
#define ROT4(cc,a,b,c,d) {unsigned char t=cc[d];cc[d]=cc[c];cc[c]=cc[b]; cc[b]=cc[a];cc[a]=t;}
#define ROT22(cc,a,b,c,d) ROT2(cc,a,c) ROT2(cc,b,d)
@ Some moves change the orientation of edges. Some moves change the
orientation of corners. Looking at the definition of the
|corner_change| array, we note that the moves that change the
orientations of the corners are all the same; the even corners are
incremented and the odd ones are decremented, so we only need a single
preprocessor macro.
@(cubepos.cpp@>=
#define EDGE4FLIP(a,b,c,d) {unsigned char t=e[d];e[d]=edge_flip(e[c]);\
e[c]=edge_flip(e[b]); e[b]=edge_flip(e[a]);e[a]=edge_flip(t);}
#define CORNER4FLIP(a,b,cc,d) {unsigned char t=c[d];c[d]=corner_ori_inc[c[cc]];\
c[cc]=corner_ori_dec[c[b]];c[b]=corner_ori_inc[c[a]];c[a]=corner_ori_dec[t];}
@ With these macros, we are ready to write the routine; just a big
switch statement, then a bunch of constants indicating which slots are
affected. We do need to separate the halfturn from the quarter turn
case. All of these constants are tedious to look up so it's imperative
we test them exhaustively; luckily, the redundancy from the |move|
routine and the relationship between |move| and |movepc| makes it easy
to test. Note how the slice turn moves are just combinations of the
other moves.
@(cubepos.cpp@>=
void cubepos::movepc(int mov) {
switch(mov) {
#ifdef QUARTER
case 0: ROT4(e,0,2,3,1); ROT4(c,0,1,3,2); break ;
case 1: ROT4(e,1,3,2,0); ROT4(c,2,3,1,0); break ;
case 2: ROT4(e,3,7,11,6); CORNER4FLIP(3,7,6,2); break ;
case 3: ROT4(e,6,11,7,3); CORNER4FLIP(3,2,6,7); break ;
case 4: EDGE4FLIP(2,5,10,7); CORNER4FLIP(1,5,7,3); break ;
case 5: EDGE4FLIP(7,10,5,2); CORNER4FLIP(1,3,7,5); break ;
case 6: ROT4(e,9,11,10,8); ROT4(c,4,6,7,5); break ;
case 7: ROT4(e,8,10,11,9); ROT4(c,5,7,6,4); break ;
case 8: ROT4(e,0,4,8,5); CORNER4FLIP(0,4,5,1); break ;
case 9: ROT4(e,5,8,4,0); CORNER4FLIP(0,1,5,4); break ;
case 10: EDGE4FLIP(1,6,9,4); CORNER4FLIP(2,6,4,0); break ;
case 11: EDGE4FLIP(4,9,6,1); CORNER4FLIP(2,0,4,6); break ;
case 12: ROT22(e,3,7,11,6); ROT22(c,2,3,7,6); break ;
case 13: ROT22(e,2,5,10,7); ROT22(c,3,1,5,7); break ;
case 14: ROT22(e,0,4,8,5); ROT22(c,1,0,4,5); break ;
case 15: ROT22(e,1,6,9,4); ROT22(c,0,2,6,4); break ;
#else
case 0: ROT4(e,0,2,3,1); ROT4(c,0,1,3,2); break ;
case 1: ROT22(e,0,2,3,1); ROT22(c,0,1,3,2); break ;
case 2: ROT4(e,1,3,2,0); ROT4(c,2,3,1,0); break ;
case 3: ROT4(e,3,7,11,6); CORNER4FLIP(3,7,6,2); break ;
case 4: ROT22(e,3,7,11,6); ROT22(c,2,3,7,6); break ;
case 5: ROT4(e,6,11,7,3); CORNER4FLIP(3,2,6,7); break ;
case 6: EDGE4FLIP(2,5,10,7); CORNER4FLIP(1,5,7,3); break ;
case 7: ROT22(e,2,5,10,7); ROT22(c,3,1,5,7); break ;
case 8: EDGE4FLIP(7,10,5,2); CORNER4FLIP(1,3,7,5); break ;
case 9: ROT4(e,9,11,10,8); ROT4(c,4,6,7,5); break ;
case 10: ROT22(e,9,11,10,8); ROT22(c,4,6,7,5); break ;
case 11: ROT4(e,8,10,11,9); ROT4(c,5,7,6,4); break ;
case 12: ROT4(e,0,4,8,5); CORNER4FLIP(0,4,5,1); break ;
case 13: ROT22(e,0,4,8,5); ROT22(c,1,0,4,5); break ;
case 14: ROT4(e,5,8,4,0); CORNER4FLIP(0,1,5,4); break ;
case 15: EDGE4FLIP(1,6,9,4); CORNER4FLIP(2,6,4,0); break ;
case 16: ROT22(e,1,6,9,4); ROT22(c,0,2,6,4); break ;
case 17: EDGE4FLIP(4,9,6,1); CORNER4FLIP(2,0,4,6); break ;
#ifdef SLICE
case 18: ROT4(e,0,2,3,1); ROT4(c,0,1,3,2);
ROT4(e,8,10,11,9); ROT4(c,5,7,6,4); break ;
case 19: ROT22(e,0,2,3,1); ROT22(c,0,1,3,2);