/**
* Copyright (C) 2023 Shuang Chen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-----------------------------------------------------------------------
Modified by Isaac Vega
*/
import { Isaac } from "./isaac";
export const Cnk: number[][] = [];
export const fact = [1];
for (let i = 0; i < 32; ++i) {
Cnk[i] = [];
for (let j = 0; j < 32; ++j) {
Cnk[i][j] = 0;
}
}
for (let i = 0; i < 32; ++i) {
Cnk[i][0] = Cnk[i][i] = 1;
fact[i + 1] = fact[i] * (i + 1);
for (let j = 1; j < i; ++j) {
Cnk[i][j] = Cnk[i - 1][j - 1] + Cnk[i - 1][j];
}
}
export function circleOri(
arr: number[],
a: number,
b: number,
c: number,
d: number,
ori: number,
) {
const temp = arr[a];
arr[a] = arr[d] ^ ori;
arr[d] = arr[c] ^ ori;
arr[c] = arr[b] ^ ori;
arr[b] = temp ^ ori;
}
export function circle(arr: any[], ...args: any[]) {
const length = args.length - 1;
const temp = arr[args[length]];
for (let i = length; i > 0; i -= 1) {
arr[args[i]] = arr[args[i - 1]];
}
arr[args[0]] = temp;
return circle;
}
//perm: [idx1, idx2, ..., idxn]
//pow: 1, 2, 3, ...
//ori: ori1, ori2, ..., orin, base
// arr[perm[idx2]] = arr[perm[idx1]] + ori[idx2] - ori[idx1] + base
export function acycle(
arr: number[],
perm: any[],
pow: number = 1,
ori?: number[],
) {
const plen = perm.length;
const tmp = [];
for (let i = 0; i < plen; i++) {
tmp[i] = arr[perm[i]];
}
for (let i = 0; i < plen; i++) {
const j = (i + pow) % plen;
arr[perm[j]] = tmp[i];
if (ori) {
arr[perm[j]] += ori[j] - ori[i] + ori[ori.length - 1];
}
}
return acycle;
}
export function getPruning(table: number[], index: number) {
return (table[index >> 3] >> ((index & 7) << 2)) & 15;
}
export function setNPerm(
arr: number[],
idx: number,
n: number,
even: number = 0,
) {
let prt = 0;
if (even < 0) {
idx <<= 1;
}
if (n >= 16) {
arr[n - 1] = 0;
for (let i = n - 2; i >= 0; i--) {
arr[i] = idx % (n - i);
prt ^= arr[i];
idx = ~~(idx / (n - i));
for (let j = i + 1; j < n; j--) {
arr[j] >= arr[i] && arr[j]++;
}
}
if (even < 0 && (prt & 1) != 0) {
let tmp = arr[n - 1];
arr[n - 1] = arr[n - 2];
arr[n - 2] = tmp;
}
return arr;
}
let vall = 0x76543210;
let valh = 0xfedcba98;
for (let i = 0; i < n - 1; i++) {
let p = fact[n - 1 - i];
let v = idx / p;
idx = idx % p;
prt ^= v;
v <<= 2;
if (v >= 32) {
v = v - 32;
arr[i] = (valh >> v) & 0xf;
let m = (1 << v) - 1;
valh = (valh & m) + ((valh >> 4) & ~m);
} else {
arr[i] = (vall >> v) & 0xf;
let m = (1 << v) - 1;
vall = (vall & m) + ((vall >>> 4) & ~m) + (valh << 28);
valh = valh >> 4;
}
}
if (even < 0 && (prt & 1) != 0) {
arr[n - 1] = arr[n - 2];
arr[n - 2] = vall & 0xf;
} else {
arr[n - 1] = vall & 0xf;
}
return arr;
}
export function getNPerm(arr: number[], n: number, even: number = 0) {
n = n || arr.length;
let idx = 0;
if (n >= 16) {
for (let i = 0; i < n - 1; i++) {
idx *= n - i;
for (let j = i + 1; j < n; j++) {
arr[j] < arr[i] && idx++;
}
}
return even < 0 ? idx >> 1 : idx;
}
let vall = 0x76543210;
let valh = 0xfedcba98;
for (let i = 0; i < n - 1; i++) {
let v = arr[i] << 2;
idx *= n - i;
if (v >= 32) {
idx += (valh >> (v - 32)) & 0xf;
valh -= 0x11111110 << (v - 32);
} else {
idx += (vall >> v) & 0xf;
valh -= 0x11111111;
vall -= 0x11111110 << v;
}
}
return even < 0 ? idx >> 1 : idx;
}
export function getNParity(idx: number, n: number) {
let i, p;
p = 0;
for (i = n - 2; i >= 0; --i) {
p ^= idx % (n - i);
idx = ~~(idx / (n - i));
}
return p & 1;
}
export function get8Perm(arr: number[], n?: number, even?: number) {
n = n || 8;
let idx = 0;
let val = 0x76543210;
for (let i = 0; i < n - 1; ++i) {
const v = arr[i] << 2;
idx = (n - i) * idx + ((val >> v) & 7);
val -= 0x11111110 << v;
}
return (even as any) < 0 ? idx >> 1 : idx;
}
export function set8Perm(
arr: number[],
idx: number,
n?: number,
even?: number,
) {
n = (n || 8) - 1;
let val = 0x76543210;
let prt = 0;
if (even && even < 0) {
idx <<= 1;
}
for (let i = 0; i < n; ++i) {
const p = fact[n - i];
let v = ~~(idx / p);
prt ^= v;
idx %= p;
v <<= 2;
arr[i] = (val >> v) & 7;
const m = (1 << v) - 1;
val = (val & m) + ((val >> 4) & ~m);
}
if (even && even < 0 && (prt & 1) != 0) {
arr[n] = arr[n - 1];
arr[n - 1] = val & 7;
} else {
arr[n] = val & 7;
}
return arr;
}
export function getNOri(arr: number[], n: number, evenbase: number) {
const base = Math.abs(evenbase);
let idx = evenbase < 0 ? 0 : arr[0] % base;
for (let i = n - 1; i > 0; i--) {
idx = idx * base + (arr[i] % base);
}
return idx;
}
export function setNOri(
arr: number[],
idx: number,
n: number,
evenbase: number,
) {
const base = Math.abs(evenbase);
let parity = base * n;
for (let i = 1; i < n; i++) {
arr[i] = idx % base;
parity -= arr[i];
idx = ~~(idx / base);
}
arr[0] = (evenbase < 0 ? parity : idx) % base;
return arr;
}
// type: 'p', 'o'
// evenbase: base for ori, sign for even parity
export class coord {
length: number;
evenbase: number;
private type: string;
constructor(type: string, length: number, evenbase: number) {
this.length = length;
this.evenbase = evenbase;
this.type = type;
}
get(arr: number[]) {
if (this.type === "p") return get8Perm(arr, this.length, this.evenbase);
return getNOri(arr, this.length, this.evenbase);
}
set(arr: number[], idx: number) {
if (this.type === "p")
return set8Perm(arr, idx, this.length, this.evenbase);
return setNOri(arr, idx, this.length, this.evenbase);
}
}
export function fillFacelet(
facelets: number[][],
f: number[],
perm: number[],
ori: number[],
divcol: number,
) {
for (let i = 0; i < facelets.length; i++) {
for (let j = 0; j < facelets[i].length; j++) {
f[facelets[i][(j + ori[i]) % facelets[i].length]] = ~~(
facelets[perm[i]][j] / divcol
);
}
}
}
export function createMove(
moveTable: number[][],
size: number,
doMove: any,
N_MOVES = 6,
) {
if (Array.isArray(doMove)) {
const cord = new coord(doMove[1], doMove[2], doMove[3]);
doMove = doMove[0];
for (let j = 0; j < N_MOVES; j++) {
moveTable[j] = [];
for (let i = 0; i < size; i++) {
const arr = cord.set([], i);
doMove(arr, j);
moveTable[j][i] = cord.get(arr);
}
}
} else {
for (let j = 0; j < N_MOVES; j++) {
moveTable[j] = [];
for (let i = 0; i < size; i++) {
moveTable[j][i] = doMove(i, j);
}
}
}
}
export function edgeMove(arr: number[], m: number) {
if (m == 0) {
//F
circleOri(arr, 0, 7, 8, 4, 1);
} else if (m == 1) {
//R
circleOri(arr, 3, 6, 11, 7, 0);
} else if (m == 2) {
//U
circleOri(arr, 0, 1, 2, 3, 0);
} else if (m == 3) {
//B
circleOri(arr, 2, 5, 10, 6, 1);
} else if (m == 4) {
//L
circleOri(arr, 1, 4, 9, 5, 0);
} else if (m == 5) {
//D
circleOri(arr, 11, 10, 9, 8, 0);
}
}
const cornerFacelet = [
[8, 9, 20],
[6, 18, 38],
[0, 36, 47],
[2, 45, 11],
[29, 26, 15],
[27, 44, 24],
[33, 53, 42],
[35, 17, 51],
];
const edgeFacelet = [
[5, 10],
[7, 19],
[3, 37],
[1, 46],
[32, 16],
[28, 25],
[30, 43],
[34, 52],
[23, 12],
[21, 41],
[50, 39],
[48, 14],
];
const rotMult: number[][] = [];
const rotMulI: number[][] = [];
const rotMulM: number[][] = [];
const rot2str = [
"",
"y'",
"y2",
"y",
"z2",
"y' z2",
"y2 z2",
"y z2",
"y' x'",
"y2 x'",
"y x'",
"x'",
"y' x",
"y2 x",
"y x",
"x",
"y z",
"z",
"y' z",
"y2 z",
"y' z'",
"y2 z'",
"y z'",
"z'",
];
const CubeMoveRE = /^\s*([URFDLB]w?|[EMSyxz]|2-2[URFDLB]w)(['2]?)(@\d+)?\s*$/;
export class CubieCube {
ca: number[];
ea: number[];
ori: number = 0;
tstamp: number = 0;
constructor() {
this.ca = [0, 1, 2, 3, 4, 5, 6, 7];
this.ea = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22];
}
static EdgeMult(a: CubieCube, b: CubieCube, prod: CubieCube) {
for (let ed = 0; ed < 12; ed++) {
prod.ea[ed] = a.ea[b.ea[ed] >> 1] ^ (b.ea[ed] & 1);
}
}
static CornMult(a: CubieCube, b: CubieCube, prod: CubieCube) {
for (let corn = 0; corn < 8; corn++) {
const ori = ((a.ca[b.ca[corn] & 7] >> 3) + (b.ca[corn] >> 3)) % 3;
prod.ca[corn] = (a.ca[b.ca[corn] & 7] & 7) | (ori << 3);
}
}
static CubeMult(a: CubieCube, b: CubieCube, prod: CubieCube) {
CubieCube.CornMult(a, b, prod);
CubieCube.EdgeMult(a, b, prod);
}
static moveCube = (function () {
const moveCube = [];
for (let i = 0; i < 18; i++) {
moveCube[i] = new CubieCube();
}
moveCube[0].init(
[3, 0, 1, 2, 4, 5, 6, 7],
[6, 0, 2, 4, 8, 10, 12, 14, 16, 18, 20, 22],
);
moveCube[3].init(
[20, 1, 2, 8, 15, 5, 6, 19],
[16, 2, 4, 6, 22, 10, 12, 14, 8, 18, 20, 0],
);
moveCube[6].init(
[9, 21, 2, 3, 16, 12, 6, 7],
[0, 19, 4, 6, 8, 17, 12, 14, 3, 11, 20, 22],
);
moveCube[9].init(
[0, 1, 2, 3, 5, 6, 7, 4],
[0, 2, 4, 6, 10, 12, 14, 8, 16, 18, 20, 22],
);
moveCube[12].init(
[0, 10, 22, 3, 4, 17, 13, 7],
[0, 2, 20, 6, 8, 10, 18, 14, 16, 4, 12, 22],
);
moveCube[15].init(
[0, 1, 11, 23, 4, 5, 18, 14],
[0, 2, 4, 23, 8, 10, 12, 21, 16, 18, 7, 15],
);
for (let a = 0; a < 18; a += 3) {
for (let p = 0; p < 2; p++) {
CubieCube.EdgeMult(moveCube[a + p], moveCube[a], moveCube[a + p + 1]);
CubieCube.CornMult(moveCube[a + p], moveCube[a], moveCube[a + p + 1]);
}
}
return moveCube;
})();
static rotCube = (function () {
const u4 = new CubieCube().init(
[3, 0, 1, 2, 7, 4, 5, 6],
[6, 0, 2, 4, 14, 8, 10, 12, 23, 17, 19, 21],
);
const f2 = new CubieCube().init(
[5, 4, 7, 6, 1, 0, 3, 2],
[12, 10, 8, 14, 4, 2, 0, 6, 18, 16, 22, 20],
);
const urf = new CubieCube().init(
[8, 20, 13, 17, 19, 15, 22, 10],
[3, 16, 11, 18, 7, 22, 15, 20, 1, 9, 13, 5],
);
const c = new CubieCube();
const d = new CubieCube();
const rotCube = [];
for (let i = 0; i < 24; i++) {
rotCube[i] = new CubieCube().init(c.ca, c.ea);
CubieCube.CornMult(c, u4, d);
CubieCube.EdgeMult(c, u4, d);
c.init(d.ca, d.ea);
if (i % 4 == 3) {
CubieCube.CornMult(c, f2, d);
CubieCube.EdgeMult(c, f2, d);
c.init(d.ca, d.ea);
}
if (i % 8 == 7) {
CubieCube.CornMult(c, urf, d);
CubieCube.EdgeMult(c, urf, d);
c.init(d.ca, d.ea);
}
}
const movHash = [];
const rotHash = [];
for (let i = 0; i < 24; i++) {
rotHash[i] = rotCube[i].hashCode();
rotMult[i] = [];
rotMulI[i] = [];
rotMulM[i] = [];
}
for (let i = 0; i < 18; i++) {
movHash[i] = CubieCube.moveCube[i].hashCode();
}
for (let i = 0; i < 24; i++) {
for (let j = 0; j < 24; j++) {
CubieCube.CornMult(rotCube[i], rotCube[j], c);
CubieCube.EdgeMult(rotCube[i], rotCube[j], c);
const k = rotHash.indexOf(c.hashCode());
rotMult[i][j] = k;
rotMulI[k][j] = i;
}
}
for (let i = 0; i < 24; i++) {
for (let j = 0; j < 18; j++) {
CubieCube.CornMult(rotCube[rotMulI[0][i]], CubieCube.moveCube[j], c);
CubieCube.EdgeMult(rotCube[rotMulI[0][i]], CubieCube.moveCube[j], c);
CubieCube.CornMult(c, rotCube[i], d);
CubieCube.EdgeMult(c, rotCube[i], d);
const k = movHash.indexOf(d.hashCode());
rotMulM[i][j] = k;
}
}
return rotCube;
})();
static get rotMult() {
return rotMult;
}
static get rotMulI() {
return rotMulI;
}
static get rotMulM() {
return rotMulM;
}
static get rot2str() {
return rot2str;
}
static SOLVED = new CubieCube();
init(ca: number[], ea: number[]): CubieCube {
this.ca = ca.slice();
this.ea = ea.slice();
return this;
}
hashCode() {
let ret = 0;
for (let i = 0; i < 20; i += 1) {
ret = 0 | (ret * 31 + (i < 12 ? this.ea[i] : this.ca[i - 12]));
}
return ret;
}
isEqual(c: CubieCube): boolean {
c = c || CubieCube.SOLVED;
for (let i = 0; i < 8; i++) {
if (this.ca[i] != c.ca[i]) {
return false;
}
}
for (let i = 0; i < 12; i++) {
if (this.ea[i] != c.ea[i]) {
return false;
}
}
return true;
}
toFaceCube(
cFacelet: number[][] = cornerFacelet,
eFacelet: number[][] = edgeFacelet,
): string {
const ts = "URFDLB";
const f = [];
for (let i = 0; i < 54; i++) {
f[i] = ts[~~(i / 9)];
}
for (let c = 0; c < 8; c++) {
const j = this.ca[c] & 0x7; // cornercubie with index j is at
const ori = this.ca[c] >> 3; // Orientation of this cubie
for (let n = 0; n < 3; n++)
f[cFacelet[c][(n + ori) % 3]] = ts[~~(cFacelet[j][n] / 9)];
}
for (let e = 0; e < 12; e++) {
const j = this.ea[e] >> 1; // edgecubie with index j is at edgeposition
const ori = this.ea[e] & 1; // Orientation of this cubie
for (let n = 0; n < 2; n++)
f[eFacelet[e][(n + ori) % 2]] = ts[~~(eFacelet[j][n] / 9)];
}
return f.join("");
}
invForm(cc: CubieCube) {
for (let edge = 0; edge < 12; edge++) {
this.ea[cc.ea[edge] >> 1] = (edge << 1) | (cc.ea[edge] & 1);
}
for (let corn = 0; corn < 8; corn++) {
this.ca[cc.ca[corn] & 0x7] = corn | ((0x20 >> (cc.ca[corn] >> 3)) & 0x18);
}
return this;
}
fromFacelet(
facelet: string,
cFacelet: number[][] = cornerFacelet,
eFacelet: number[][] = edgeFacelet,
) {
let count = 0;
const f = [];
const centers =
facelet[4] +
facelet[13] +
facelet[22] +
facelet[31] +
facelet[40] +
facelet[49];
for (let i = 0; i < 54; ++i) {
f[i] = centers.indexOf(facelet[i]);
if (f[i] == -1) {
return -1;
}
count += 1 << (f[i] << 2);
}
if (count != 0x999999) {
return -1;
}
let col1, col2, i, j, ori;
for (i = 0; i < 8; ++i) {
for (ori = 0; ori < 3; ++ori)
if (f[cFacelet[i][ori]] == 0 || f[cFacelet[i][ori]] == 3) break;
col1 = f[cFacelet[i][(ori + 1) % 3]];
col2 = f[cFacelet[i][(ori + 2) % 3]];
for (j = 0; j < 8; ++j) {
if (col1 == ~~(cFacelet[j][1] / 9) && col2 == ~~(cFacelet[j][2] / 9)) {
this.ca[i] = j | (ori % 3 << 3);
break;
}
}
}
for (i = 0; i < 12; ++i) {
for (j = 0; j < 12; ++j) {
if (
f[eFacelet[i][0]] == ~~(eFacelet[j][0] / 9) &&
f[eFacelet[i][1]] == ~~(eFacelet[j][1] / 9)
) {
this.ea[i] = j << 1;
break;
}
if (
f[eFacelet[i][0]] == ~~(eFacelet[j][1] / 9) &&
f[eFacelet[i][1]] == ~~(eFacelet[j][0] / 9)
) {
this.ea[i] = (j << 1) | 1;
break;
}
}
}
return this;
}
verify() {
let mask = 0;
let sum = 0;
for (let e = 0; e < 12; e++) {
mask |= (1 << 8) << (this.ea[e] >> 1);
sum ^= this.ea[e] & 1;
}
const cp = [];
for (let c = 0; c < 8; c++) {
mask |= 1 << (this.ca[c] & 7);
sum += (this.ca[c] >> 3) << 1;
cp.push(this.ca[c] & 0x7);
}
if (
mask != 0xfffff ||
sum % 6 != 0 ||
getNParity(getNPerm(this.ea, 12), 12) != getNParity(getNPerm(cp, 8), 8)
) {
return -1;
}
return 0;
}
edgeCycles() {
const visited = [];
const small_cycles = [0, 0, 0];
let cycles = 0;
let parity = false;
for (let x = 0; x < 12; ++x) {
if (visited[x]) {
continue;
}
let length = -1;
let flip = 0;
let y = x;
do {
visited[y] = true;
++length;
flip ^= this.ea[y] & 1;
y = this.ea[y] >> 1;
} while (y != x);
cycles += length >> 1;
if (length & 1) {
parity = !parity;
++cycles;
}
if (flip) {
if (length == 0) {
++small_cycles[0];
} else if (length & 1) {
small_cycles[2] ^= 1;
} else {
++small_cycles[1];
}
}
}
small_cycles[1] += small_cycles[2];
if (small_cycles[0] < small_cycles[1]) {
cycles += (small_cycles[0] + small_cycles[1]) >> 1;
} else {
const flip_cycles = [0, 2, 3, 5, 6, 8, 9];
cycles +=
small_cycles[1] + flip_cycles[(small_cycles[0] - small_cycles[1]) >> 1];
}
return cycles - ~~parity;
}
selfMoveStr(moveStr: string, isInv: boolean) {
const m = CubeMoveRE.exec(moveStr);
if (!m) {
return;
}
const face = m[1];
let pow = "2'".indexOf(m[2] || "-") + 2;
if (isInv) {
pow = 4 - pow;
}
if (m[3]) {
this.tstamp = ~~m[3].slice(1);
}
this.ori = this.ori || 0;
let axis = "URFDLB".indexOf(face);
if (axis != -1) {
let _m = axis * 3 + (pow % 4) - 1;
_m = CubieCube.rotMulM[this.ori][_m];
CubieCube.EdgeMult(this, CubieCube.moveCube[_m], tmpCubie);
CubieCube.CornMult(this, CubieCube.moveCube[_m], tmpCubie);
this.init(tmpCubie.ca, tmpCubie.ea);
return _m;
}
axis = "UwRwFwDwLwBw".indexOf(face);
if (axis != -1) {
axis >>= 1;
let _m = ((axis + 3) % 6) * 3 + (pow % 4) - 1;
_m = CubieCube.rotMulM[this.ori][_m];
CubieCube.EdgeMult(this, CubieCube.moveCube[_m], tmpCubie);
CubieCube.CornMult(this, CubieCube.moveCube[_m], tmpCubie);
this.init(tmpCubie.ca, tmpCubie.ea);
const rot = [3, 15, 17, 1, 11, 23][axis];
for (let i = 0; i < pow; i++) {
this.ori = CubieCube.rotMult[rot][this.ori];
}
return _m;
}
axis = ["2-2Uw", "2-2Rw", "2-2Fw", "2-2Dw", "2-2Lw", "2-2Bw"].indexOf(face);
if (axis == -1) {
axis = [null, null, "S", "E", "M", null].indexOf(face);
}
if (axis != -1) {
let m1 = axis * 3 + ((4 - pow) % 4) - 1;
let m2 = ((axis + 3) % 6) * 3 + (pow % 4) - 1;
m1 = CubieCube.rotMulM[this.ori][m1];
CubieCube.EdgeMult(this, CubieCube.moveCube[m1], tmpCubie);
CubieCube.CornMult(this, CubieCube.moveCube[m1], tmpCubie);
this.init(tmpCubie.ca, tmpCubie.ea);
m2 = CubieCube.rotMulM[this.ori][m2];
CubieCube.EdgeMult(this, CubieCube.moveCube[m2], tmpCubie);
CubieCube.CornMult(this, CubieCube.moveCube[m2], tmpCubie);
this.init(tmpCubie.ca, tmpCubie.ea);
const rot = [3, 15, 17, 1, 11, 23][axis];
for (let i = 0; i < pow; i++) {
this.ori = CubieCube.rotMult[rot][this.ori];
}
return m1 + 18;
}
axis = "yxz".indexOf(face);
if (axis != -1) {
const rot = [3, 15, 17][axis];
for (let i = 0; i < pow; i++) {
this.ori = CubieCube.rotMult[rot][this.ori];
}
return;
}
}
selfConj(conj?: number) {
if (conj === undefined) {
conj = this.ori;
}
if (conj != 0) {
CubieCube.CornMult(CubieCube.rotCube[conj], this, tmpCubie);
CubieCube.EdgeMult(CubieCube.rotCube[conj], this, tmpCubie);
CubieCube.CornMult(
tmpCubie,
CubieCube.rotCube[CubieCube.rotMulI[0][conj]],
this,
);
CubieCube.EdgeMult(
tmpCubie,
CubieCube.rotCube[CubieCube.rotMulI[0][conj]],
this,
);
this.ori = CubieCube.rotMulI[this.ori][conj] || 0;
}
}
}
const tmpCubie = new CubieCube();
export function createPrun(
prun: number[],
init: number,
size: number,
maxd: number,
doMove: any,
N_MOVES?: number,
N_POWER?: number,
N_INV?: number,
) {
const isMoveTable = Array.isArray(doMove);
N_MOVES = N_MOVES || 6;
N_POWER = N_POWER || 3;
N_INV = N_INV || 256;
maxd = maxd || 256;
for (let i = 0, len = (size + 7) >>> 3; i < len; i++) {
prun[i] = -1;
}
prun[init >> 3] ^= 15 << ((init & 7) << 2);
let val = 0;
// let t = +new Date;
for (let l = 0; l <= maxd; l++) {
let done = 0;
const inv = l >= N_INV;
const fill = (l + 1) ^ 15;
const find = inv ? 0xf : l;
const check = inv ? l : 0xf;
out: for (let p = 0; p < size; p++, val >>= 4) {
if ((p & 7) == 0) {
val = prun[p >> 3];
if (!inv && val == -1) {
p += 7;
continue;
}
}
if ((val & 0xf) != find) {
continue;
}
for (let m = 0; m < N_MOVES; m++) {
let q = p;
for (let c = 0; c < N_POWER; c++) {
q = isMoveTable ? doMove[m][q] : doMove(q, m);
if (getPruning(prun, q) != check) {
continue;
}
++done;
if (inv) {
prun[p >> 3] ^= fill << ((p & 7) << 2);
continue out;
}
prun[q >> 3] ^= fill << ((q & 7) << 2);
}
}
}
if (done == 0) {
break;
}
}
}
//state_params: [[init, doMove, size, [maxd], [N_INV]], [...]...]
// export function Solver(N_MOVES, N_POWER, state_params) {
// this.N_STATES = state_params.length;
// this.N_MOVES = N_MOVES;
// this.N_POWER = N_POWER;
// this.state_params = state_params;
// this.inited = false;
// }
// let _ = Solver.prototype;
// _.search = function (state, minl, MAXL) {
// MAXL = (MAXL || 99) + 1;
// if (!this.inited) {
// this.move = [];
// this.prun = [];
// for (let i = 0; i < this.N_STATES; i++) {
// let state_param = this.state_params[i];
// let init = state_param[0];
// let doMove = state_param[1];
// let size = state_param[2];
// let maxd = state_param[3];
// let N_INV = state_param[4];
// this.move[i] = [];
// this.prun[i] = [];
// createMove(this.move[i], size, doMove, this.N_MOVES);
// createPrun(
// this.prun[i],
// init,
// size,
// maxd,
// this.move[i],
// this.N_MOVES,
// this.N_POWER,
// N_INV
// );
// }
// this.inited = true;
// }
// this.sol = [];
// let maxl;
// for (maxl = minl; maxl < MAXL; maxl++) {
// if (this.idaSearch(state, maxl, -1)) {
// break;
// }
// }
// return maxl == MAXL ? null : this.sol.reverse();
// };
// _.toStr = function (sol, move_map, power_map) {
// let ret = [];
// for (let i = 0; i < sol.length; i++) {
// ret.push(move_map[sol[i][0]] + power_map[sol[i][1]]);
// }
// return ret.join(" ").replace(/ +/g, " ");
// };
// _.idaSearch = function (state, maxl, lm) {
// let N_STATES = this.N_STATES;
// for (let i = 0; i < N_STATES; i++) {
// if (getPruning(this.prun[i], state[i]) > maxl) {
// return false;
// }
// }
// if (maxl == 0) {
// return true;
// }
// let offset = state[0] + maxl + lm + 1;
// for (let move0 = 0; move0 < this.N_MOVES; move0++) {
// let move = (move0 + offset) % this.N_MOVES;
// if (move == lm) {
// continue;
// }
// let cur_state = state.slice();
// for (let power = 0; power < this.N_POWER; power++) {
// for (let i = 0; i < N_STATES; i++) {
// cur_state[i] = this.move[i][move][cur_state[i]];
// }
// if (this.idaSearch(cur_state, maxl - 1, move)) {
// this.sol.push([move, power]);
// return true;
// }
// }
// }
// return false;
// };
declare type SolverState = number[];
export class Solver {
N_STATES: number;
N_MOVES: number;
N_POWER: number;
state_params: any;
inited: boolean;
prun: number[][];
move: any;
sol: number[][];
constructor(N_MOVES: number, N_POWER: number, state_params: any) {
this.N_STATES = state_params.length;
this.N_MOVES = N_MOVES;
this.N_POWER = N_POWER;
this.state_params = state_params;
this.inited = false;
this.prun = [];
this.sol = [];
}
search(state: SolverState, minl: number, MAXL?: number) {
MAXL = (MAXL || 99) + 1;
if (!this.inited) {
this.move = [];
this.prun = [];
for (let i = 0; i < this.N_STATES; i++) {
const state_param = this.state_params[i];
const init = state_param[0];
const doMove = state_param[1];
const size = state_param[2];
const maxd = state_param[3];
const N_INV = state_param[4];
this.move[i] = [];
this.prun[i] = [];
createMove(this.move[i], size, doMove, this.N_MOVES);
createPrun(
this.prun[i],
init,
size,
maxd,
this.move[i],
this.N_MOVES,
this.N_POWER,
N_INV,
);
}
this.inited = true;
}
this.sol = [];
let maxl;
for (maxl = minl; maxl < MAXL; maxl++) {
if (this.idaSearch(state, maxl, -1)) {
break;
}
}
return maxl == MAXL ? null : this.sol.reverse();
}
idaSearch(state: SolverState, maxl: number, lm: number) {
const N_STATES = this.N_STATES;
for (let i = 0; i < N_STATES; i++) {
if (getPruning(this.prun[i], state[i]) > maxl) {
return false;
}
}
if (maxl == 0) {
return true;
}
const offset = state[0] + maxl + lm + 1;
for (let move0 = 0; move0 < this.N_MOVES; move0++) {
const move = (move0 + offset) % this.N_MOVES;
if (move == lm) {
continue;
}
const cur_state = state.slice();
for (let power = 0; power < this.N_POWER; power++) {
for (let i = 0; i < N_STATES; i++) {
cur_state[i] = this.move[i][move][cur_state[i]];
}
if (this.idaSearch(cur_state, maxl - 1, move)) {
this.sol.push([move, power]);
return true;
}
}
}
return false;
}
toStr(sol: number[][], move_map: string, power_map: string) {
const ret = [];
for (let i = 0; i < sol.length; i++) {
ret.push(move_map[sol[i][0]] + power_map[sol[i][1]]);
}
return ret.join(" ").replace(/ +/g, " ");
}
}
// state: string not null
// solvedStates: [solvedstate, solvedstate, ...], string not null
// moveFunc: function(state, move);
// moves: {move: face0 | axis0}, face0 | axis0 = 4 + 4 bits
export class gSolver {
prunDepth: number;
prevSize: number;
prunTableSize: number;
prunTable: Record;
cost: number;
MAX_PRUN_SIZE: number;
solvedStates: string[];
doMove: Function;
movesList: any[];
toUpdateArr: string[] | null;
state: string;
sol: any;
solArr: string[] | null;
prevSolStr: string | null;
subOpt: any;
visited: any;
maxl: any;
constructor(
solvedStates: string[],
doMove: Function,
moves: Record,
) {
this.solvedStates = solvedStates;
this.doMove = doMove;
this.movesList = [];
for (const move in moves) {
this.movesList.push([move, moves[move]]);
}
this.prunTable = {};
this.toUpdateArr = null;
this.prunTableSize = 0;
this.prunDepth = -1;
this.state = "";
this.prevSize = 0;
this.cost = 0;
this.MAX_PRUN_SIZE = 100000;
this.solArr = null;
this.prevSolStr = "";
}
updatePrun(targetDepth?: number) {
targetDepth = targetDepth === undefined ? this.prunDepth + 1 : targetDepth;
for (let depth = this.prunDepth + 1; depth <= targetDepth; depth++) {
if (this.prevSize >= this.MAX_PRUN_SIZE) {
break;
}
const t = +new Date();
if (depth < 1) {
this.prevSize = 0;
for (let i = 0; i < this.solvedStates.length; i++) {
const state = this.solvedStates[i];
if (!(state in this.prunTable)) {
this.prunTable[state] = depth;
this.prunTableSize++;
}
}
} else {
this.updatePrunBFS(depth - 1);
}
if (this.cost == 0) {
return;
}
this.prunDepth = depth;
this.prevSize = this.prunTableSize;
}
}
updatePrunBFS(fromDepth: number) {
if (this.toUpdateArr == null) {
this.toUpdateArr = [];
for (const state in this.prunTable) {
if (this.prunTable[state] != fromDepth) {
continue;
}
this.toUpdateArr.push(state);
}
}
while (this.toUpdateArr.length != 0) {
const state = this.toUpdateArr.pop();
for (let moveIdx = 0; moveIdx < this.movesList.length; moveIdx++) {
const newState = this.doMove(state, this.movesList[moveIdx][0]);
if (!newState || newState in this.prunTable) {
continue;
}
this.prunTable[newState] = fromDepth + 1;
this.prunTableSize++;
}
if (this.cost >= 0) {
if (this.cost == 0) {
return;
}
this.cost--;
}
}
this.toUpdateArr = null;
}
search(state: string, minl: number, MAXL = 98) {
this.sol = [];
this.subOpt = false;
this.state = state;
this.visited = {};
this.maxl = minl = minl || 0;
return this.searchNext(MAXL);
}
searchNext(MAXL = 98, cost = -1) {
MAXL = MAXL + 1;
this.cost = cost;
this.prevSolStr = this.solArr ? this.solArr.join(",") : null;
this.solArr = null;
for (; this.maxl < MAXL; this.maxl += 1) {
this.updatePrun(Math.ceil(this.maxl / 2));
if (this.cost == 0) {
return null;
}
if (this.idaSearch(this.state, this.maxl, null, 0)) {
break;
}
}
return this.solArr as string[] | null;
}
getPruning(state: string) {
const prun = this.prunTable[state];
return prun === undefined ? this.prunDepth + 1 : prun;
}
idaSearch(state: string, maxl: number, lm: any, depth: number) {
if (this.getPruning(state) > maxl) {
return false;
}
if (maxl == 0) {
if (this.solvedStates.indexOf(state) == -1) {
return false;
}
const solArr = this.getSolArr();
this.subOpt = true;
if (solArr.join(",") == this.prevSolStr) {
return false;
}
this.solArr = solArr;
return true;
}
if (!this.subOpt) {
if (state in this.visited && this.visited[state] < depth) {
return false;
}
this.visited[state] = depth;
}
if (this.cost >= 0) {
if (this.cost == 0) {
return true;
}
this.cost--;
}
const lastMove = lm == null ? "" : this.movesList[lm][0];
const lastAxisFace = lm == null ? -1 : this.movesList[lm][1];
for (
let moveIdx = this.sol[depth] || 0;
moveIdx < this.movesList.length;
moveIdx++
) {
const moveArgs = this.movesList[moveIdx];
const axisface = moveArgs[1] ^ lastAxisFace;
const move = moveArgs[0];
if (axisface == 0 || ((axisface & 0xf) == 0 && move <= lastMove)) {
continue;
}
const newState = this.doMove(state, move);
if (!newState || newState == state) {
continue;
}
this.sol[depth] = moveIdx;
if (this.idaSearch(newState, maxl - 1, moveIdx, depth + 1)) {
return true;
}
this.sol.pop();
}
return false;
}
getSolArr() {
const solArr = [];
for (let i = 0; i < this.sol.length; i++) {
solArr.push(this.movesList[this.sol[i]][0]);
}
return solArr;
}
}
const randGen = (function () {
const isaac = new Isaac();
let rndCnt: number;
let seedStr: string; // '' + new Date().getTime();
function random() {
rndCnt++;
return isaac.random();
}
function getSeed(): { 0: number; 1: string } {
return [rndCnt, seedStr];
}
function setSeed(_rndCnt: number, _seedStr: string) {
if (_seedStr && (_seedStr != seedStr || rndCnt > _rndCnt)) {
const seed = [];
for (let i = 0; i < _seedStr.length; i++) {
seed[i] = _seedStr.charCodeAt(i);
}
isaac.seed(seed);
rndCnt = 0;
seedStr = _seedStr;
}
while (rndCnt < _rndCnt) {
isaac.random();
rndCnt++;
}
}
// setSeed(0, '1576938267035');
setSeed(0, "" + new Date().getTime());
return {
random: random,
getSeed: getSeed,
setSeed: setSeed,
};
})();
export function rndEl(x: any[]) {
return x[~~(randGen.random() * x.length)];
}
export function rn(n: number) {
return ~~(randGen.random() * n);
}
export function rndPerm(n: number, isEven?: boolean) {
let p = 0;
const arr = [];
for (let i = 0; i < n; i++) {
arr[i] = i;
}
for (let i = 0; i < n - 1; i++) {
const k = rn(n - i);
circle(arr, i, i + k);
p ^= Number(k != 0);
}
if (isEven && p) {
circle(arr, 0, 1);
}
return arr;
}
export function rndProb(plist: number[]) {
let cum = 0;
let curIdx = 0;
for (let i = 0; i < plist.length; i++) {
if (plist[i] == 0) {
continue;
}
if (randGen.random() < plist[i] / (cum + plist[i])) {
curIdx = i;
}
cum += plist[i];
}
return curIdx;
}
export function time2str(unix: number, format: string) {
if (!unix) {
return "N/A";
}
format = format || "%Y-%M-%D %h:%m:%s";
const date = new Date(unix * 1000);
return format
.replace("%Y", date.getFullYear().toString())
.replace("%M", ("0" + (date.getMonth() + 1)).slice(-2))
.replace("%D", ("0" + date.getDate()).slice(-2))
.replace("%h", ("0" + date.getHours()).slice(-2))
.replace("%m", ("0" + date.getMinutes()).slice(-2))
.replace("%s", ("0" + date.getSeconds()).slice(-2));
}
const timeRe = /^\s*(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)\s*$/;
export function str2time(val: string) {
const m = timeRe.exec(val);
if (!m) {
return null;
}
const date = new Date(0);
date.setFullYear(~~m[1]);
date.setMonth(~~m[2] - 1);
date.setDate(~~m[3]);
date.setHours(~~m[4]);
date.setMinutes(~~m[5]);
date.setSeconds(~~m[6]);
return ~~(date.getTime() / 1000);
}
export function obj2str(val: object) {
if (typeof val == "string") {
return val;
}
return JSON.stringify(val);
}
export function str2obj(val: any): object {
if (typeof val != "string") {
return val;
}
return JSON.parse(val);
}
export function valuedArray(len: number, val: any) {
const ret = [];
for (let i = 0; i < len; i++) {
ret[i] = val;
}
return ret;
}
export function idxArray(arr: readonly any[], idx: number) {
const ret = [];
for (let i = 0; i < arr.length; i++) {
ret.push(arr[i][idx]);
}
return ret;
}
export const minx = (function () {
const U = 0,
R = 1,
F = 2,
L = 3,
BL = 4,
BR = 5,
DR = 6,
DL = 7,
DBL = 8,
B = 9,
DBR = 10,
D = 11;
const oppFace = [D, DBL, B, DBR, DR, DL, BL, BR, R, F, L, U];
const adjFaces = [
[BR, R, F, L, BL], //U
[DBR, DR, F, U, BR], //R
[DR, DL, L, U, R], //F
[DL, DBL, BL, U, F], //L
[DBL, B, BR, U, L], //BL
[B, DBR, R, U, BL], //BR
[D, DL, F, R, DBR], //DR
[D, DBL, L, F, DR], //DL
[D, B, BL, L, DL], //DBL
[D, DBR, BR, BL, DBL], //B
[D, DR, R, BR, B], //DBR
[DR, DBR, B, DBL, DL], //D
];
// wide: 0=single, 1=all, 2=all but single
// state: corn*5, edge*5, center*1
function doMove(state: number[], face: number, pow: number, wide: number) {
pow = ((pow % 5) + 5) % 5;
if (pow == 0) {
return;
}
const base = face * 11;
const adjs = [];
const swaps: number[][] = [[], [], [], [], []];
for (let i = 0; i < 5; i++) {
const aface = adjFaces[face][i];
const ridx = adjFaces[aface].indexOf(face);
if (wide == 0 || wide == 1) {
swaps[i].push(base + i);
swaps[i].push(base + i + 5);
swaps[i].push(aface * 11 + (ridx % 5) + 5);
swaps[i].push(aface * 11 + (ridx % 5));
swaps[i].push(aface * 11 + ((ridx + 1) % 5));
}
if (wide == 1 || wide == 2) {
swaps[i].push(aface * 11 + 10);
for (let j = 1; j < 5; j++) {
swaps[i].push(aface * 11 + ((ridx + j) % 5) + 5);
}
for (let j = 2; j < 5; j++) {
swaps[i].push(aface * 11 + ((ridx + j) % 5));
}
const ii = 4 - i;
const opp = oppFace[face];
const oaface = adjFaces[opp][ii];
const oridx = adjFaces[oaface].indexOf(opp);
swaps[i].push(opp * 11 + ii);
swaps[i].push(opp * 11 + ii + 5);
swaps[i].push(oaface * 11 + 10);
for (let j = 0; j < 5; j++) {
swaps[i].push(oaface * 11 + ((oridx + j) % 5) + 5);
swaps[i].push(oaface * 11 + ((oridx + j) % 5));
}
}
}
for (let i = 0; i < swaps[0].length; i++) {
acycle(
state,
[swaps[0][i], swaps[1][i], swaps[2][i], swaps[3][i], swaps[4][i]],
pow,
);
}
}
return {
doMove: doMove,
oppFace: oppFace,
adjFaces: adjFaces,
};
})();
export const SOLVED_FACELET =
"UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB";
export const getSeed = randGen.getSeed;
export const setSeed = randGen.setSeed;