一个解二阶魔方的api(An api for Pocket Cube).
npm install pocketcube
import { Rubik } from 'pocketcube';
const rubik = new Rubik(0);
rubik.action(Rubik.R[0]);
rubik.action(Rubik.U[2], Rubik.Rubik.B[1]);
rubik.action(`RF2U'`);
解魔方:
import { Rubik } from 'pocketcube';
const rubik = new Rubik();
rubik.action(`RF2U'B2LBD2L'`);
rubik.solve(); // DFDR'F'R2F2D2
rubik.solve(0); // URFU'R'F2R2U2
new Rubik().action(`D2RF'U2B'`).solve(0); // FR2FU'F2
见Wikipedia/Pocket_Cube#Permutations(zh:维基百科/二阶魔方#变化).
8
个角块的位置均可进行任意互换(8!
种状态), 如果以一个角块不动作为参考角块, 其他7
个角块都能任意转换方向(即37
种状态)(注: 这里指的转换方向, 或者说翻转, 是指一个角块从例如白-红-绿
变成绿-白-红
但是一次翻转一定会翻转到3
个角块). 如果在空间中旋转则不计算方向不同而状态相同的魔方, 实际上的准确状态数还应除以8
. 所以二阶魔方的总状态数为:
$${\frac{8!\ 3^{7}}{24}}=7!\ 3^{6}=3,674,160$$
本项目中的状态与Wiki中的有些许不同: 在空间中旋转不忽略方向不同而状态相同的魔方.
即总状态数为:
为了给所有状态一个固定的"id
", 这里定义了8
个角块的位置与8
个角块的旋转的排列(状态)到状态数的映射.
对于一个初始魔方, 使用x
y
z
三个自由度定义角块的编号. 偏向右手的块其x
值规定为1
(偏向左手的块其x
值规定为0
), 位于底层的块其y
值规定为1
(位于顶层的块其y
值规定为0
), 朝向前的块其z
值规定为1
(朝向后的块其z
值规定为0
). 并以x
y
z
顺序转换为一个二进制数.
对于一个初始魔方, 每个角块只考虑其的一个面, 这里取顶面与底面, 并记作A
面. 对于任意魔方, 角块的旋转量可以用三个值表示. 若此角块的A
面位于顶面或底面, 其旋转量记为0
; A
面若以魔方的几何中心到此角块的顶点为旋转轴顺时针(反方向)旋转$\frac{2}{3}\pi$后位于顶面或底面, 则记为1
; A
面若以魔方的几何中心到此角块的顶点为旋转轴逆时针(正方向)旋转$\frac{2}{3}\pi$后位于顶面或底面, 则记为2
初始魔方的编号与旋转量为[[0, 1, 2, 3, 4, 5, 6, 7], [0, 0, 0, 0, 0, 0, 0, 0]]
.
将初始魔方用R
公式旋转后的编号与旋转量为[[0, 5, 2, 1, 4, 7, 6, 3], [0, 1, 0, 2, 0, 2, 0, 1]]
.
将编号与旋转量用一个方法转换为状态数, 保证任意魔方有且仅有唯一的状态数:
const list = [8];
position = T.slice(0, -1).reduceRight((p, c) => p * 3 + c, C.reduceRight((p, c, i) => {
const o = list.findIndex((v) => c < v);
list.splice(o, 0, c);
return p * (8 - i) + o;
}, 0));
C = [], T = [];
T.push((3 - Array.from({ length: 7 }).reduce((p, c) => {
T.push(c = position % 3);
position = ~~(position / 3);
return p + c;
}, 0) % 3) % 3);
Array.from({ length: 8 }).forEach(function (_, i) {
T.push(this.splice(position % (8 - i), 1)[0]);
position = ~~(position / (8 - i));
}, Array.from({ length: 8 }).map((v, i) => i));
初始魔方的状态值为0
.
将初始魔方用"R"
公式旋转后的状态值为77350359
.
类似矩阵乘法.
C2 = [], T2 = [];
C1.forEach((n, i) => {
C2[i] = C0[n];
T2[i] = (T0[n] + T1[i]) % 3;
});
定义X_0
Y_0
Z_0
, 状态值分别为36822425
51565086
17954269
.
存在集合C
, 满足:
C
中所有元素被称为基本状态.
记C_0
为状态数为0
的魔方, 显然其为C
的元素.
用类似的方法定义X
:
等效的定义:
Y
Z
类似.
P
Q
若满足以下:
则表示P
为Q
的逆, 或Q
为P
的逆:
复合的逆:
逆的逆:
定义R_0
, 其状态值为77350359
.
定义R
, 方法与X
类似.
定义L
:
定义U
:
定义F
B
D
:
...
定义T
为X
Y
Z
R
U
F
L
D
B
的并.
T
中所有元素被称为基本旋转.
记_P
为P
的镜像.
定义:
复合的镜像:
镜像的镜像:
逆的镜像:
P
Q
若满足以下:
则表示P
全等于Q
.
P
Q
若满足以下:
则表示P
相似于Q
.
初始化一个魔方.
使用position
创建一个Rubik
.
position
是Rubik
实例的状态值.
position
是一个小于88179840
的非负number
, 默认值为0
.
返回当前状态值, 值为小于88179840
的非负number
.
此方法将返回新的Rubik
实例, 新的Rubik
的状态值将与原Rubik
的状态值相同.
此方法将返回当前状态的逆.
此方法将返回当前状态的镜像.
执行旋转或复合旋转(状态).
const rubik = new Rubik();
rubik.action(`R`);
rubik.action(`U2`, `B`);
rubik.action(`RU'RF2R'UR'`);
rubik.action(new Rubik(123), `RU'RF2R'UR'`,`U2`);
rubik.action(new Rubik().action(Rubik.R[0]));
检测是否复原.
new Rubik().action(`R`).isReinstated(); // false
new Rubik().action(`R`).action(`R'`).isReinstated(); // true
new Rubik().action(`XY2`).isReinstated(); // true
获取位置i
的值, 与编号和旋转量有关.
C[i] * 3 + T[i];
返回值为一个Generator
, 生成相似于当前状态(S
)的状态.
const rubik = new Rubik(/* ... */);
for (const { rubik: _rubik, image, inverse, base, coordinate } of rubik.similar(15)) {
// ...
}
n
: congruent=1; image=2; inverse=4; image&inverse=8;
若p
不为空, 则只输出位置p
上值为c
的状态, c
默认为0
.
返回值为一个Generator
, 生成全等于当前状态(S
)的状态.
解魔方.
new Rubik().action(`R'`).solve(); // R
new Rubik().action(`FU'BU'RU'F2DR'BRU'L'UR2`).solve(); // R'B2R'U2BR'B2R'B2
new Rubik().action(`FU'BU'RU'F2DR'BRU'L'UR2`).solve(0); // R'F2R'F2UF'U2F'R2
new Rubik().action(`FU'BU'RU'F2DR'BRU'L'UR2`).solve(-1); // L'D2L'D2BD'L2D'B2
new Rubik().action(`FU'BU'RU'F2DR'BRU'L'UR2`).solve(0b00000010110); // R'B2L'F2DF'U2F'U2
t
为整数时, 按位以0
: [R
U
F
], 1
: [L
D
B
], 分配公式字母.