/**
* 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 {
Cnk,
createMove,
createPrun,
getNPerm,
getPruning,
minx,
rn,
rndPerm,
setNPerm,
} from "../lib/mathlib";
import { regScrambler } from "./scramble";
const U = 0,
R = 5,
F = 10,
L = 15,
BL = 20,
BR = 25,
DR = 30,
DL = 35,
DBL = 40,
B = 45,
DBR = 50,
D = 55;
const kiloFacelet = [
[U + 2, R + 3, F + 4],
[U + 3, F + 3, L + 4],
[U + 4, L + 3, BL + 4],
[U + 0, BL + 3, BR + 4],
[U + 1, BR + 3, R + 4],
[D + 3, B + 0, DBL + 1],
[D + 2, DBR + 0, B + 1],
[D + 1, DR + 0, DBR + 1],
[D + 0, DL + 0, DR + 1],
[D + 4, DBL + 0, DL + 1],
[F + 0, R + 2, DR + 3],
[L + 0, F + 2, DL + 3],
[BL + 0, L + 2, DBL + 3],
[BR + 0, BL + 2, B + 3],
[R + 0, BR + 2, DBR + 3],
[B + 4, BL + 1, DBL + 2],
[DBR + 4, BR + 1, B + 2],
[DR + 4, R + 1, DBR + 2],
[DL + 4, F + 1, DR + 2],
[DBL + 4, L + 1, DL + 2],
];
export class KiloCubie {
perm: number[];
twst: number[];
constructor() {
this.perm = [];
this.twst = [];
for (let i = 0; i < 20; i++) {
this.perm[i] = i;
this.twst[i] = 0;
}
}
static SOLVED = new KiloCubie();
static moveCube: KiloCubie[] = [];
static symCube: KiloCubie[] = [];
static symMult: number[][] = [];
static symMulI: number[][] = [];
static symMulM: number[][] = [];
static CombCoord: any;
toFaceCube(kFacelet?: number[][]) {
kFacelet = kFacelet || kiloFacelet;
const f = [];
for (let c = 0; c < 20; c++) {
const j = this.perm[c];
const ori = this.twst[c];
for (let n = 0; n < 3; n++) {
f[kFacelet[c][(n + ori) % 3]] = ~~(kFacelet[j][n] / 5);
}
}
return f;
}
fromFacelet(facelet: number[], kFacelet?: number[][]) {
kFacelet = kFacelet || kiloFacelet;
let count = 0;
const f = [];
for (let i = 0; i < 60; ++i) {
f[i] = facelet[i];
count += Math.pow(16, f[i]);
}
if (count != 0x555555555555) {
return -1;
}
for (let i = 0; i < 20; i++) {
for (let j = 0; j < 20; j++) {
let twst = -1;
for (let t = 0; t < 3; t++) {
if (
~~(kFacelet[j][0] / 5) == f[kFacelet[i][t]] &&
~~(kFacelet[j][1] / 5) == f[kFacelet[i][(t + 1) % 3]] &&
~~(kFacelet[j][2] / 5) == f[kFacelet[i][(t + 2) % 3]]
) {
twst = t;
break;
}
}
if (twst != -1) {
this.perm[i] = j;
this.twst[i] = twst;
}
}
}
return this;
}
hashCode() {
let ret = 0;
for (let i = 0; i < 20; i++) {
ret = 0 | (ret * 31 + this.perm[i] * 3 + this.twst[i]);
}
return ret;
}
static KiloMult(a: KiloCubie, b: KiloCubie, prod: KiloCubie) {
for (let i = 0; i < 20; i++) {
prod.perm[i] = a.perm[b.perm[i]];
prod.twst[i] = (a.twst[b.perm[i]] + b.twst[i]) % 3;
}
}
static KiloMult3(a: KiloCubie, b: KiloCubie, c: KiloCubie, prod: KiloCubie) {
for (let i = 0; i < 20; i++) {
prod.perm[i] = a.perm[b.perm[c.perm[i]]];
prod.twst[i] =
(a.twst[b.perm[c.perm[i]]] + b.twst[c.perm[i]] + c.twst[i]) % 3;
}
}
invFrom(cc: KiloCubie) {
for (let i = 0; i < 20; i++) {
this.perm[cc.perm[i]] = i;
this.twst[cc.perm[i]] = (3 - cc.twst[i]) % 3;
}
return this;
}
init(perm: number[], twst: number[]) {
this.perm = perm.slice();
this.twst = twst.slice();
return this;
}
isEqual(c: KiloCubie) {
for (let i = 0; i < 20; i++) {
if (this.perm[i] != c.perm[i] || this.twst[i] != c.twst[i]) {
return false;
}
}
return true;
}
setComb(idx: number, r?: number) {
r = r || 4;
let fill = 19;
for (let i = 19; i >= 0; i--) {
if (idx >= Cnk[i][r]) {
idx -= Cnk[i][r--];
this.perm[i] = r;
} else {
this.perm[i] = fill--;
}
this.twst[i] = 0;
}
}
getComb(r?: number) {
r = r || 4;
const thres = r;
let idxComb = 0;
let idxOri = 0;
const permR = [];
for (let i = 19; i >= 0; i--) {
if (this.perm[i] < thres) {
idxComb += Cnk[i][r--];
idxOri = idxOri * 3 + this.twst[i];
permR[r] = this.perm[i];
}
}
return [idxComb, getNPerm(permR, thres), idxOri];
}
faceletMove(face: number, pow: number, wide: number) {
const facelet = this.toFaceCube();
const state = [];
for (let i = 0; i < 12; i++) {
for (let j = 0; j < 5; j++) {
state[i * 11 + j] = facelet[i * 5 + j];
state[i * 11 + j + 5] = 0;
}
state[i * 11 + 10] = 0;
}
minx.doMove(state, face, pow, wide);
for (let i = 0; i < 12; i++) {
for (let j = 0; j < 5; j++) {
facelet[i * 5 + j] = state[i * 11 + j];
}
}
this.fromFacelet(facelet);
}
}
function createMoveCube() {
//init move
const moveCube = [];
const moveHash = [];
for (let i = 0; i < 12 * 4; i++) {
moveCube[i] = new KiloCubie();
}
for (let a = 0; a < 48; a += 4) {
moveCube[a].faceletMove(a >> 2, 1, 0);
moveHash[a] = moveCube[a].hashCode();
for (let p = 0; p < 3; p++) {
KiloCubie.KiloMult(moveCube[a + p], moveCube[a], moveCube[a + p + 1]);
moveHash[a + p + 1] = moveCube[a + p + 1].hashCode();
}
}
KiloCubie.moveCube = moveCube;
//init sym
const symCube: KiloCubie[] = [];
const symMult: number[][] = [];
const symMulI: number[][] = [];
const symMulM: number[][] = [];
const symHash: number[] = [];
const tmp = new KiloCubie();
for (let s = 0; s < 60; s++) {
symCube[s] = new KiloCubie().init(tmp.perm, tmp.twst);
symHash[s] = symCube[s].hashCode();
symMult[s] = [];
symMulI[s] = [];
tmp.faceletMove(0, 1, 1); // [U]
if (s % 5 == 4) {
// [F] or [R]
tmp.faceletMove(s % 10 == 4 ? 1 : 2, 1, 1);
}
if (s % 30 == 29) {
tmp.faceletMove(1, 2, 1);
tmp.faceletMove(2, 1, 1);
tmp.faceletMove(0, 3, 1);
}
}
for (let i = 0; i < 60; i++) {
for (let j = 0; j < 60; j++) {
KiloCubie.KiloMult(symCube[i], symCube[j], tmp);
const k = symHash.indexOf(tmp.hashCode());
symMult[i][j] = k;
symMulI[k][j] = i;
}
}
for (let s = 0; s < 60; s++) {
symMulM[s] = [];
for (let j = 0; j < 12; j++) {
KiloCubie.KiloMult3(
symCube[symMulI[0][s]],
moveCube[j * 4],
symCube[s],
tmp,
);
const k = moveHash.indexOf(tmp.hashCode());
symMulM[s][j] = k >> 2;
}
}
KiloCubie.symCube = symCube;
KiloCubie.symMult = symMult;
KiloCubie.symMulI = symMulI;
KiloCubie.symMulM = symMulM;
}
class CombCoord {
map: KiloCubie;
imap: KiloCubie;
tmp: KiloCubie;
constructor(cubieMap: number[]) {
this.map = new KiloCubie();
this.imap = new KiloCubie();
this.map.perm = cubieMap.slice();
for (let i = 0; i < 20; i++) {
if (cubieMap.indexOf(i) == -1) {
this.map.perm.push(i);
}
}
this.imap.invFrom(this.map);
this.tmp = new KiloCubie();
}
get(cc: KiloCubie, r?: number) {
KiloCubie.KiloMult3(this.imap, cc, this.map, this.tmp);
return this.tmp.getComb(r);
}
set(cc: KiloCubie, idx: number, r?: number) {
this.tmp.setComb(idx, r);
KiloCubie.KiloMult3(this.map, this.tmp, this.imap, cc);
}
}
KiloCubie.CombCoord = CombCoord;
const perm4Mult: number[][] = [];
const perm4MulT: number[][] = [];
const perm4TT: number[][] = [];
const perm3Mult = [];
const perm3MulT = [];
const perm3TT = [];
const ckmv: number[] = [];
const urfMove = [1, 2, 0, 5, 10, 6, 3, 4, 9, 11, 7, 8];
const y2Move = [0, 3, 4, 5, 1, 2, 8, 9, 10, 6, 7, 11];
const yMove = [0, 2, 3, 4, 5, 1, 7, 8, 9, 10, 6, 11];
function comb4FullMove(moveTable: number[][][], idx: number, move: number) {
let slice = ~~(idx / 81 / 24);
let perm = ~~(idx / 81) % 24;
let twst = idx % 81;
const val = moveTable[move][slice];
slice = val[0];
perm = perm4Mult[perm][val[1]];
twst = perm4TT[perm4MulT[val[1]][twst]][val[2]];
return slice * 81 * 24 + perm * 81 + twst;
}
function comb3FullMove(moveTable: number[][][], idx: number, move: number) {
let slice = ~~(idx / 27 / 6);
let perm = ~~(idx / 27) % 6;
let twst = idx % 27;
const val = moveTable[move][slice];
slice = val[0];
perm = perm4Mult[perm][val[1]];
twst = perm4TT[perm4MulT[val[1]][twst * 3] / 3][val[2]];
return slice * 27 * 6 + perm * 27 + twst;
}
let isInit = false;
function init() {
if (isInit) return;
isInit = true;
const tt = performance.now();
createMoveCube();
function setTwst4(arr: number[], idx: number) {
for (let k = 0; k < 4; k++) {
arr[k] = idx % 3;
idx = ~~(idx / 3);
}
}
function getTwst4(arr: number[]) {
let idx = 0;
for (let k = 3; k >= 0; k--) {
idx = idx * 3 + arr[k];
}
return idx;
}
const perm1: number[] = [];
const perm2: number[] = [];
const perm3: number[] = [];
for (let i = 0; i < 24; i++) {
perm4Mult[i] = [];
setNPerm(perm1, i, 4);
for (let j = 0; j < 24; j++) {
setNPerm(perm2, j, 4);
for (let k = 0; k < 4; k++) {
perm3[k] = perm1[perm2[k]];
}
perm4Mult[i][j] = getNPerm(perm3, 4);
}
}
for (let j = 0; j < 24; j++) {
perm4MulT[j] = [];
setNPerm(perm2, j, 4);
for (let i = 0; i < 81; i++) {
setTwst4(perm1, i);
for (let k = 0; k < 4; k++) {
perm3[k] = perm1[perm2[k]];
}
perm4MulT[j][i] = getTwst4(perm3);
}
}
for (let j = 0; j < 81; j++) {
perm4TT[j] = [];
setTwst4(perm2, j);
for (let i = 0; i < 81; i++) {
setTwst4(perm1, i);
for (let k = 0; k < 4; k++) {
perm3[k] = (perm1[k] + perm2[k]) % 3;
}
perm4TT[j][i] = getTwst4(perm3);
}
}
const tmp1 = new KiloCubie();
const tmp2 = new KiloCubie();
for (let m1 = 0; m1 < 12; m1++) {
ckmv[m1] = 1 << m1;
for (let m2 = 0; m2 < m1; m2++) {
KiloCubie.KiloMult(
KiloCubie.moveCube[m1 * 4],
KiloCubie.moveCube[m2 * 4],
tmp1,
);
KiloCubie.KiloMult(
KiloCubie.moveCube[m2 * 4],
KiloCubie.moveCube[m1 * 4],
tmp2,
);
if (tmp1.isEqual(tmp2)) {
ckmv[m1] |= 1 << m2;
}
}
}
initPhase1();
initPhase2();
initPhase3();
}
const Phase1Move: any[] = [];
const Phase2Move: any[] = [];
const Phase3Move: any[] = [];
const Phase1Prun: number[] = [];
const Phase2Prun: number[] = [];
const Phase3Prun: number[] = [];
let phase1Coord: CombCoord;
let phase2Coord: CombCoord;
let phase3Coord: CombCoord;
function initPhase1() {
phase1Coord = new CombCoord([5, 6, 7, 8, 9]);
const tmp1 = new KiloCubie();
const tmp2 = new KiloCubie();
createMove(
Phase1Move,
1140,
function (idx: number, move: number) {
phase1Coord.set(tmp1, idx, 3);
KiloCubie.KiloMult(tmp1, KiloCubie.moveCube[move * 4], tmp2);
return phase1Coord.get(tmp2, 3);
},
12,
);
createPrun(
Phase1Prun,
0,
1140 * 27 * 6,
8,
comb3FullMove.bind(null, Phase1Move),
12,
4,
5,
);
}
function initPhase2() {
phase2Coord = new CombCoord([
13, 15, 16, 0, 1, 2, 3, 4, 10, 11, 12, 14, 17, 18, 19,
]);
const tmp1 = new KiloCubie();
const tmp2 = new KiloCubie();
createMove(
Phase2Move,
455,
function (idx: number, move: number) {
phase2Coord.set(tmp1, idx, 3);
KiloCubie.KiloMult(tmp1, KiloCubie.moveCube[move * 4], tmp2);
return phase2Coord.get(tmp2, 3);
},
6,
);
createPrun(
Phase2Prun,
0,
455 * 27 * 6,
8,
comb3FullMove.bind(null, Phase2Move),
6,
4,
4,
);
}
function initPhase3() {
phase3Coord = new CombCoord([0, 1, 2, 3, 4, 10, 11, 14, 17, 18]);
const tmp1 = new KiloCubie();
const tmp2 = new KiloCubie();
createMove(
Phase3Move,
210,
function (idx: number, move: number) {
phase3Coord.set(tmp1, idx);
KiloCubie.KiloMult(tmp1, KiloCubie.moveCube[move * 4], tmp2);
return phase3Coord.get(tmp2);
},
3,
);
createPrun(
Phase3Prun,
0,
210 * 81 * 24,
14,
comb4FullMove.bind(null, Phase3Move),
3,
4,
6,
);
}
function idaSearch(
idx: any,
isSolved: Function,
getPrun: Function,
doMove: Function,
N_AXIS: number,
maxl: number,
lm: number,
sol: any[],
) {
if (maxl == 0) {
return isSolved(idx);
} else if (getPrun(idx) > maxl) {
return false;
}
for (let axis = 0; axis < N_AXIS; axis++) {
if ((ckmv[lm] >> axis) & 1) {
continue;
}
let idx1 = idx;
for (let pow = 0; pow < 4; pow++) {
idx1 = doMove(idx1, axis);
if (idx1 == null) {
break;
}
if (
idaSearch(idx1, isSolved, getPrun, doMove, N_AXIS, maxl - 1, axis, sol)
) {
sol.push([axis, pow]);
// sol.push(["U", "R", "F", "L", "BL", "BR", "DR", "DL", "DBL", "B", "DBR", "D"][axis] + ["", "2", "2'", "'"][pow]);
return true;
}
}
}
return false;
}
function solve(
idx: number[],
isSolved: Function,
getPrun: Function,
doMove: Function,
N_AXIS: number,
maxl: number,
) {
const sol: any[] = [];
for (let l = 0; l <= maxl; l++) {
if (idaSearch(idx, isSolved, getPrun, doMove, N_AXIS, l, -1, sol)) {
break;
}
}
sol.reverse();
return sol;
}
// function solveMulti(idxs, isSolved, getPrun, doMove, N_AXIS, maxl) {
// let sol = [];
// let s = 0;
// out: for (let l = 0; l <= maxl; l++) {
// for (s = 0; s < idxs.length; s++) {
// if (idaSearch(idxs[s], isSolved, getPrun, doMove, N_AXIS, l, -1, sol)) {
// break out;
// }
// }
// }
// sol.reverse();
// return [s, sol];
// }
function move2str(moves: number[][]) {
const ret = [];
for (let i = 0; i < moves.length; i++) {
ret.push(
["U", "R", "F", "L", "BL", "BR", "DR", "DL", "DBL", "B", "DBR", "D"][
moves[i][0]
] + ["", "2", "2'", "'"][moves[i][1]],
);
}
return ret.join(" ");
}
function solveKiloCubie(cc: KiloCubie) {
init();
const kc0 = new KiloCubie();
const kc1 = new KiloCubie();
kc0.init(cc.perm, cc.twst);
let idx;
//phase1
const doPhase1Move = comb3FullMove.bind(null, Phase1Move);
let val0 = phase1Coord.get(kc0, 3);
KiloCubie.KiloMult3(
KiloCubie.symCube[KiloCubie.symMulI[0][2]],
kc0,
KiloCubie.symCube[2],
kc1,
);
let val1 = phase1Coord.get(kc1, 3);
idx = [
val0[0] * 27 * 6 + val0[1] * 27 + val0[2],
val1[0] * 27 * 6 + val1[1] * 27 + val1[2],
];
let tt = +new Date();
const sol1 = solve(
idx,
function (idx: number[]) {
return idx[0] == 0 && idx[1] == 0;
},
function (idx: number[]) {
return Math.max(
getPruning(Phase1Prun, idx[0]),
getPruning(Phase1Prun, idx[1]),
);
},
function (idx: number[], move: number) {
const idx1 = [
doPhase1Move(idx[0], move),
doPhase1Move(idx[1], y2Move[move]),
];
if (idx1[0] == idx[0] && idx1[1] == idx[1]) {
return null;
}
return idx1;
},
12,
9,
);
for (let i = 0; i < sol1.length; i++) {
const move = sol1[i];
KiloCubie.KiloMult(kc0, KiloCubie.moveCube[move[0] * 4 + move[1]], kc1);
kc0.init(kc1.perm, kc1.twst);
}
//phase2
const doPhase2Move = comb3FullMove.bind(null, Phase2Move);
val0 = phase2Coord.get(kc0, 3);
KiloCubie.KiloMult3(
KiloCubie.symCube[KiloCubie.symMulI[0][1]],
kc0,
KiloCubie.symCube[1],
kc1,
);
val1 = phase2Coord.get(kc1, 3);
idx = [
val0[0] * 27 * 6 + val0[1] * 27 + val0[2],
val1[0] * 27 * 6 + val1[1] * 27 + val1[2],
];
tt = +new Date();
const sol2 = solve(
idx,
function (idx: number[]) {
return idx[0] == 0 && idx[1] == 0;
},
function (idx: number[]) {
return Math.max(
getPruning(Phase2Prun, idx[0]),
getPruning(Phase2Prun, idx[1]),
);
},
function (idx: number[], move: number) {
const idx1 = [
doPhase2Move(idx[0], move),
doPhase2Move(idx[1], yMove[move]),
];
if (idx1[0] == idx[0] && idx1[1] == idx[1]) {
return null;
}
return idx1;
},
6,
14,
);
for (let i = 0; i < sol2.length; i++) {
const move = sol2[i];
KiloCubie.KiloMult(kc0, KiloCubie.moveCube[move[0] * 4 + move[1]], kc1);
kc0.init(kc1.perm, kc1.twst);
}
//phase3
const doPhase3Move = comb4FullMove.bind(null, Phase3Move);
val0 = phase3Coord.get(kc0);
KiloCubie.KiloMult3(
KiloCubie.symCube[KiloCubie.symMulI[0][6]],
kc0,
KiloCubie.symCube[6],
kc1,
);
val1 = phase3Coord.get(kc1);
KiloCubie.KiloMult3(
KiloCubie.symCube[KiloCubie.symMulI[0][29]],
kc0,
KiloCubie.symCube[29],
kc1,
);
const val2 = phase3Coord.get(kc1);
idx = [
val0[0] * 81 * 24 + val0[1] * 81 + val0[2],
val1[0] * 81 * 24 + val1[1] * 81 + val1[2],
val2[0] * 81 * 24 + val2[1] * 81 + val2[2],
];
tt = +new Date();
const sol3 = solve(
idx,
function (idx: number[]) {
return idx[0] == 0 && idx[1] == 0 && idx[2] == 0;
},
function (idx: number[]) {
return Math.max(
getPruning(Phase3Prun, idx[0]),
getPruning(Phase3Prun, idx[1]),
getPruning(Phase3Prun, idx[2]),
);
},
function (idx: number[], move: number) {
return [
doPhase3Move(idx[0], move),
doPhase3Move(idx[1], (move + 1) % 3),
doPhase3Move(idx[2], (move + 2) % 3),
];
},
3,
14,
);
return move2str(Array.prototype.concat(sol1, sol2, sol3));
}
function checkSolver() {
init();
const kc0 = new KiloCubie();
const kc1 = new KiloCubie();
const gen = [];
for (let i = 0; i < 200; i++) {
const move = rn(12);
gen.push([move, 0]);
KiloCubie.KiloMult(kc0, KiloCubie.moveCube[move * 4], kc1);
kc0.init(kc1.perm, kc1.twst);
}
return move2str(gen) + " " + solveKiloCubie(kc0);
}
function getScramble() {
init();
const cc = new KiloCubie();
cc.perm = rndPerm(20, true);
let chksum = 60;
for (let i = 0; i < 19; i++) {
const t = rn(3);
cc.twst[i] = t;
chksum -= t;
}
cc.twst[19] = chksum % 3;
return solveKiloCubie(cc);
}
regScrambler("klmso", getScramble);