Phase 1A: rob-twophase solver wrapper with UE integration
- Copy rob-twophase sources into Private/ThirdParty/rob-twophase/ (excludes main.cpp; compiles as part of UnrealHyperTwist module) - Add UHyperTwistSolverLibrary with Blueprint-callable functions: - InitializeSolver() / IsSolverInitialized() - SolveClassicState(FaceletString, TimeLimitMs, MaxLength, NumSolutions) -> TArray<FString> - GetMoveName(MoveIndex) / GetMoveCount() - VerifyFaceletString(FaceletString) -> bool - Pruning tables saved to ProjectSavedDir for persistence - Add automation tests: initialization, solved-state, scrambled-state - Next: Windows build validation
This commit is contained in:
parent
c4e4606d14
commit
4359e572c9
17 changed files with 2464 additions and 0 deletions
|
|
@ -0,0 +1,135 @@
|
|||
#include "HyperTwistSolverLibrary.h"
|
||||
|
||||
#include "HAL/PlatformFilemanager.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
|
||||
#include "ThirdParty/rob-twophase/face.h"
|
||||
#include "ThirdParty/rob-twophase/move.h"
|
||||
#include "ThirdParty/rob-twophase/cubie.h"
|
||||
#include "ThirdParty/rob-twophase/coord.h"
|
||||
#include "ThirdParty/rob-twophase/sym.h"
|
||||
#include "ThirdParty/rob-twophase/prun.h"
|
||||
#include "ThirdParty/rob-twophase/solve.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
static bool bSolverInitialized = false;
|
||||
static std::once_flag InitOnceFlag;
|
||||
|
||||
static void DoInitSolver()
|
||||
{
|
||||
// rob-twophase saves/loads pruning tables from the current working directory.
|
||||
// Temporarily switch to ProjectSavedDir so tables are persisted across runs.
|
||||
FString SavedDir = FPaths::ProjectSavedDir();
|
||||
FString OriginalDir = FPaths::ProjectDir();
|
||||
|
||||
if (FPaths::DirectoryExists(SavedDir))
|
||||
{
|
||||
FPlatformProcess::SetCurrentWorkingDirectory(*SavedDir);
|
||||
}
|
||||
|
||||
face::init();
|
||||
move::init();
|
||||
coord::init();
|
||||
sym::init();
|
||||
prun::init(true); // true = try to load tables, generate if missing
|
||||
|
||||
// Restore working directory
|
||||
FPlatformProcess::SetCurrentWorkingDirectory(*OriginalDir);
|
||||
|
||||
bSolverInitialized = true;
|
||||
}
|
||||
|
||||
bool UHyperTwistSolverLibrary::IsSolverInitialized()
|
||||
{
|
||||
return bSolverInitialized;
|
||||
}
|
||||
|
||||
bool UHyperTwistSolverLibrary::InitializeSolver()
|
||||
{
|
||||
std::call_once(InitOnceFlag, DoInitSolver);
|
||||
return bSolverInitialized;
|
||||
}
|
||||
|
||||
TArray<FString> UHyperTwistSolverLibrary::SolveClassicState(
|
||||
const FString& FaceletString,
|
||||
int32 TimeLimitMs,
|
||||
int32 MaxLength,
|
||||
int32 NumSolutions
|
||||
)
|
||||
{
|
||||
TArray<FString> Result;
|
||||
|
||||
if (!InitializeSolver())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("HyperTwistSolver: Failed to initialize solver"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::string Facelets(TCHAR_TO_UTF8(*FaceletString));
|
||||
cubie::cube Cube;
|
||||
int Err = face::to_cubie(Facelets, Cube);
|
||||
if (Err != 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("HyperTwistSolver: Invalid facelet string, error code %d"), Err);
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (cubie::check(Cube) != 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("HyperTwistSolver: Cube state is not solvable"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
solve::Engine Solver(1, TimeLimitMs, NumSolutions, MaxLength, 1);
|
||||
Solver.prepare();
|
||||
|
||||
std::vector<std::vector<int>> Solutions;
|
||||
Solver.solve(Cube, Solutions);
|
||||
Solver.finish();
|
||||
|
||||
if (Solutions.empty())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("HyperTwistSolver: No solution found within time limit"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Return the shortest solution
|
||||
const std::vector<int>& Best = Solutions[0];
|
||||
for (int Move : Best)
|
||||
{
|
||||
if (Move >= 0 && Move < move::COUNT)
|
||||
{
|
||||
Result.Add(FString(UTF8_TO_TCHAR(move::names[Move].c_str())));
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FString UHyperTwistSolverLibrary::GetMoveName(int32 MoveIndex)
|
||||
{
|
||||
if (MoveIndex >= 0 && MoveIndex < move::COUNT)
|
||||
{
|
||||
return FString(UTF8_TO_TCHAR(move::names[MoveIndex].c_str()));
|
||||
}
|
||||
return FString();
|
||||
}
|
||||
|
||||
int32 UHyperTwistSolverLibrary::GetMoveCount()
|
||||
{
|
||||
return move::COUNT;
|
||||
}
|
||||
|
||||
bool UHyperTwistSolverLibrary::VerifyFaceletString(const FString& FaceletString)
|
||||
{
|
||||
if (FaceletString.Len() != 54)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Facelets(TCHAR_TO_UTF8(*FaceletString));
|
||||
cubie::cube Cube;
|
||||
return face::to_cubie(Facelets, Cube) == 0;
|
||||
}
|
||||
256
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/coord.cpp
vendored
Normal file
256
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/coord.cpp
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
#include "coord.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <cstring>
|
||||
|
||||
#include "cubie.h"
|
||||
|
||||
namespace coord {
|
||||
|
||||
const int N_C12K4 = 495; // binom(12, 4)
|
||||
const int N_PERM4 = 24; // 4!
|
||||
|
||||
uint16_t move_flip[N_FLIP][move::COUNT];
|
||||
uint16_t move_twist[N_TWIST][move::COUNT];
|
||||
uint16_t move_edges4[N_SLICE][move::COUNT];
|
||||
uint16_t move_corners[N_CORNERS][move::COUNT];
|
||||
uint16_t move_udedges2[N_UDEDGES2][move::COUNT];
|
||||
|
||||
/* Used for en-/decoding pos-perm coords */
|
||||
uint8_t enc_perm[1 << (4 * 2)]; // encode 4-elem perm as 8 bits
|
||||
uint8_t dec_perm[N_PERM4];
|
||||
uint16_t enc_comb[1 << 12]; // encode 4-elem comb as 12-bit mask with exactly 4 bits on
|
||||
uint16_t dec_comb[N_C12K4];
|
||||
|
||||
int binarize_perm(int perm[]) {
|
||||
int bin = 0;
|
||||
for (int i = 3; i >= 0; i--)
|
||||
bin = (bin << 2) | perm[i];
|
||||
return bin;
|
||||
}
|
||||
|
||||
void init_encdec() {
|
||||
int perm[] = {0, 1, 2, 3};
|
||||
for (int i = 0; i < N_PERM4; i++) {
|
||||
int bin = binarize_perm(perm);
|
||||
enc_perm[bin] = i;
|
||||
dec_perm[i] = bin;
|
||||
std::next_permutation(perm, perm + 4);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (int comb = 0; comb < (1 << cubie::edge::COUNT); comb++) {
|
||||
if (std::bitset<cubie::edge::COUNT>(comb).count() == 4) {
|
||||
enc_comb[comb] = i;
|
||||
dec_comb[i] = comb;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_ori(const int oris[], int len, int n_oris) {
|
||||
int val = 0;
|
||||
for (int i = 0; i < len - 1; i++) // last ori can be reconstructed by parity
|
||||
val = n_oris * val + oris[i];
|
||||
return val;
|
||||
}
|
||||
|
||||
void set_ori(int val, int oris[], int len, int n_oris) {
|
||||
int par = 0;
|
||||
for (int i = len - 2; i >= 0; i--) {
|
||||
oris[i] = val % n_oris;
|
||||
par += oris[i];
|
||||
val /= n_oris;
|
||||
}
|
||||
// Ori parity must always be 0
|
||||
oris[len - 1] = (n_oris - par % n_oris) % n_oris;
|
||||
}
|
||||
|
||||
// `mask` indicates which 4 edges to compute the coordinate for
|
||||
int get_combperm(const int cubies[], int len, int mask) {
|
||||
int min_cubie = ffs(mask) - 1;
|
||||
|
||||
int comb = 0;
|
||||
int perm = 0;
|
||||
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
if (mask & (1 << cubies[i])) {
|
||||
comb |= 1 << i;
|
||||
perm = (perm << 2) | (cubies[i] - min_cubie);
|
||||
}
|
||||
}
|
||||
|
||||
return N_PERM4 * enc_comb[comb] + enc_perm[perm];
|
||||
}
|
||||
|
||||
void set_combperm(int comb, int perm, int cubies[], int len, int min_cubie) {
|
||||
comb = dec_comb[comb];
|
||||
perm = dec_perm[perm];
|
||||
|
||||
int cubie = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (cubie == min_cubie)
|
||||
cubie += 4;
|
||||
if (comb & (1 << i)) {
|
||||
cubies[i] = (perm & 0x3) + min_cubie;
|
||||
perm >>= 2;
|
||||
} else
|
||||
cubies[i] = cubie++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Faster than using `*_comperm()` twice */
|
||||
|
||||
int get_perm8(const int cubies[]) {
|
||||
int comb1 = 0;
|
||||
int perm1 = 0;
|
||||
int perm2 = 0;
|
||||
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
if (cubies[i] < 4) {
|
||||
comb1 |= 1 << i;
|
||||
perm1 = (perm1 << 2) | cubies[i];
|
||||
} else
|
||||
perm2 = (perm2 << 2) | (cubies[i] - 4);
|
||||
}
|
||||
|
||||
comb1 = enc_comb[comb1];
|
||||
perm1 = enc_perm[perm1];
|
||||
perm2 = enc_perm[perm2];
|
||||
return N_PERM4 * (N_PERM4 * comb1 + perm1) + perm2;
|
||||
}
|
||||
|
||||
void set_perm8(int perm8, int cubies[]) {
|
||||
int perm2 = dec_perm[perm8 % N_PERM4];
|
||||
int comb1 = dec_comb[(perm8 / N_PERM4) / N_PERM4];
|
||||
int perm1 = dec_perm[(perm8 / N_PERM4) % N_PERM4];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (comb1 & (1 << i)) {
|
||||
cubies[i] = perm1 & 0x3;
|
||||
perm1 >>= 2;
|
||||
} else {
|
||||
cubies[i] = (perm2 & 0x3) + 4;
|
||||
perm2 >>= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_twist(const cubie::cube& c) {
|
||||
return get_ori(c.cori, cubie::corner::COUNT, 3);
|
||||
}
|
||||
|
||||
void set_twist(cubie::cube& c, int twist) {
|
||||
set_ori(twist, c.cori, cubie::corner::COUNT, 3);
|
||||
}
|
||||
|
||||
int get_flip(const cubie::cube& c) {
|
||||
return get_ori(c.eori, cubie::edge::COUNT, 2);
|
||||
}
|
||||
|
||||
void set_flip(cubie::cube& c, int flip) {
|
||||
set_ori(flip, c.eori, cubie::edge::COUNT, 2);
|
||||
}
|
||||
|
||||
int get_slice(const cubie::cube& c) {
|
||||
return get_combperm(c.eperm, cubie::edge::COUNT, 0xf00);
|
||||
}
|
||||
|
||||
void set_slice(cubie::cube& c, int slice) {
|
||||
set_combperm(slice / N_PERM4, slice % N_PERM4, c.eperm, cubie::edge::COUNT, cubie::edge::FR);
|
||||
}
|
||||
|
||||
int get_uedges(const cubie::cube& c) {
|
||||
return get_combperm(c.eperm, cubie::edge::COUNT, 0x00f);
|
||||
}
|
||||
|
||||
void set_uedges(cubie::cube& c, int uedges) {
|
||||
set_combperm(uedges / N_PERM4, uedges % N_PERM4, c.eperm, cubie::edge::COUNT, cubie::edge::UR);
|
||||
}
|
||||
|
||||
int get_dedges(const cubie::cube& c) {
|
||||
return get_combperm(c.eperm, cubie::edge::COUNT, 0x0f0);
|
||||
}
|
||||
|
||||
void set_dedges(cubie::cube& c, int dedges) {
|
||||
set_combperm(dedges / N_PERM4, dedges % N_PERM4, c.eperm, cubie::edge::COUNT, cubie::edge::DR);
|
||||
}
|
||||
|
||||
int get_corners(const cubie::cube& c) {
|
||||
return get_perm8(c.cperm);
|
||||
}
|
||||
|
||||
void set_corners(cubie::cube& c, int corners) {
|
||||
set_perm8(corners, c.cperm);
|
||||
}
|
||||
|
||||
/* Dedicated methods again more efficient than `*_posperm()` */
|
||||
|
||||
int get_slice1(const cubie::cube& c) {
|
||||
int slice1 = 0;
|
||||
for (int i = cubie::edge::COUNT - 1; i >= 0; i--) {
|
||||
if (c.eperm[i] >= cubie::edge::FR)
|
||||
slice1 |= 1 << i;
|
||||
}
|
||||
return enc_comb[slice1];
|
||||
}
|
||||
|
||||
void set_slice1(cubie::cube& c, int slice1) {
|
||||
slice1 = dec_comb[slice1];
|
||||
int j = cubie::edge::FR;
|
||||
int cubie = 0;
|
||||
for (int i = 0; i < cubie::edge::COUNT; i++)
|
||||
c.eperm[i] = (slice1 & (1 << i)) ? j++ : cubie++;
|
||||
}
|
||||
|
||||
int get_udedges2(const cubie::cube& c) {
|
||||
return get_perm8(c.eperm);
|
||||
}
|
||||
|
||||
void set_udedges2(cubie::cube& c, int udedges2) {
|
||||
set_perm8(udedges2, c.eperm);
|
||||
}
|
||||
|
||||
// Computing only exactly the moves that are needed and storing them tightly would only make things more complicated
|
||||
// during solving (in exchange for completely negligible setup/memory-gains)
|
||||
void init_move(
|
||||
uint16_t move_coord[][move::COUNT],
|
||||
int n_coord,
|
||||
int (*get_coord)(const cubie::cube&),
|
||||
void (*set_coord)(cubie::cube&, int),
|
||||
void (*mul)(const cubie::cube&, const cubie::cube&, cubie::cube&),
|
||||
bool phase2 = false
|
||||
) {
|
||||
cubie::cube c1 = cubie::SOLVED_CUBE; // coords only affect perm or ori -> one would be uninitialized
|
||||
cubie::cube c2;
|
||||
|
||||
for (int coord = 0; coord < n_coord; coord++) {
|
||||
set_coord(c1, coord);
|
||||
|
||||
if (phase2) { // UDEDGES2 is only defined for phase 2 moves
|
||||
for (move::mask moves = move::p2mask; moves; moves &= moves - 1) {
|
||||
int m = ffsll(moves) - 1;
|
||||
mul(c1, move::cubes[m], c2);
|
||||
move_coord[coord][m] = get_coord(c2);
|
||||
}
|
||||
} else {
|
||||
for (int m = 0; m < move::COUNT; m++) {
|
||||
mul(c1, move::cubes[m], c2);
|
||||
move_coord[coord][m] = get_coord(c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
init_encdec();
|
||||
|
||||
init_move(move_flip, N_FLIP, get_flip, set_flip, cubie::edge::mul);
|
||||
init_move(move_twist, N_TWIST, get_twist, set_twist, cubie::corner::mul);
|
||||
init_move(move_edges4, N_SLICE, get_slice, set_slice, cubie::edge::mul);
|
||||
init_move(move_corners, N_CORNERS, get_corners, set_corners, cubie::corner::mul);
|
||||
init_move(move_udedges2, N_UDEDGES2, get_udedges2, set_udedges2, cubie::edge::mul, true);
|
||||
}
|
||||
|
||||
}
|
||||
66
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/coord.h
vendored
Normal file
66
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/coord.h
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Coord definitions, utilities and move tables.
|
||||
*/
|
||||
|
||||
#ifndef __COORD__
|
||||
#define __COORD__
|
||||
|
||||
#include "cubie.h"
|
||||
#include "move.h"
|
||||
|
||||
namespace coord {
|
||||
|
||||
const int N_FLIP = 2048; // 2^(12 - 1)
|
||||
const int N_TWIST = 2187; // 3^(8 - 1)
|
||||
const int N_SLICE1 = 495; // binom(12, 4)
|
||||
const int N_FSLICE1 = 1013760; // N_FLIP * N_SLICE
|
||||
|
||||
const int N_SLICE = 11880; // 12! / 8!
|
||||
const int N_UEDGES = 11880; // 12! / 8!
|
||||
const int N_DEDGES = 11880; // 12! / 8!
|
||||
|
||||
const int N_SLICE2 = 24; // 4!
|
||||
const int N_UDEDGES2 = 40320; // 8!
|
||||
const int N_CORNERS = 40320; // 8!
|
||||
|
||||
const int SLICE1_SOLVED = 494; // SLICE1 is not 0 at the end of phase 1
|
||||
|
||||
extern uint16_t move_flip[N_FLIP][move::COUNT];
|
||||
extern uint16_t move_twist[N_TWIST][move::COUNT];
|
||||
extern uint16_t move_edges4[N_SLICE][move::COUNT];
|
||||
extern uint16_t move_corners[N_CORNERS][move::COUNT];
|
||||
extern uint16_t move_udedges2[N_UDEDGES2][move::COUNT]; // primarily for faster phase 2 table generation
|
||||
|
||||
int get_flip(const cubie::cube& c);
|
||||
int get_twist(const cubie::cube& c);
|
||||
int get_slice(const cubie::cube& c);
|
||||
int get_uedges(const cubie::cube& c);
|
||||
int get_dedges(const cubie::cube& c);
|
||||
int get_corners(const cubie::cube& c);
|
||||
|
||||
void set_flip(cubie::cube& c, int flip);
|
||||
void set_twist(cubie::cube& c, int twist);
|
||||
void set_slice(cubie::cube& c, int slice);
|
||||
void set_uedges(cubie::cube& c, int uedges);
|
||||
void set_dedges(cubie::cube& c, int dedges);
|
||||
void set_corners(cubie::cube& c, int corners);
|
||||
|
||||
int get_slice1(const cubie::cube& c); // faster table generation
|
||||
void set_slice1(cubie::cube& c, int slice1);
|
||||
int get_udedges2(const cubie::cube& c);
|
||||
void set_udedges2(cubie::cube& c, int udedges2);
|
||||
inline int merge_udedges2(int uedges, int dedges) { return 24 * uedges + (dedges % 24); };
|
||||
|
||||
inline int slice_to_slice1(int slice) { return slice / 24; }
|
||||
inline int slice1_to_slice(int slice1) { return 24 * slice1; }
|
||||
inline int slice_to_slice2(int slice) { return slice - N_SLICE2 * SLICE1_SOLVED; }
|
||||
inline int slice2_to_slice(int slice2) { return slice2 + N_SLICE2 * SLICE1_SOLVED; }
|
||||
inline int fslice1(int flip, int slice1) { return N_FLIP * slice1 + flip; }
|
||||
inline int fslice1_to_flip(int fslice1) { return fslice1 % N_FLIP; }
|
||||
inline int fslice1_to_slice1(int fslice1) { return fslice1 / N_FLIP; }
|
||||
|
||||
void init();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
138
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/cubie.cpp
vendored
Normal file
138
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/cubie.cpp
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#include "cubie.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include "coord.h"
|
||||
|
||||
namespace cubie {
|
||||
|
||||
/* Faster than tricky if-else sequences for handling mirrored states */
|
||||
int mul_coris[][6] = {
|
||||
{0, 1, 2, 3, 4, 5},
|
||||
{1, 2, 0, 4, 5, 3},
|
||||
{2, 0, 1, 5, 3, 4},
|
||||
{3, 5, 4, 0, 2, 1},
|
||||
{4, 3, 5, 1, 0, 2},
|
||||
{5, 4, 3, 2, 1, 0}
|
||||
};
|
||||
int inv_cori[] = {
|
||||
0, 2, 1, 3, 4, 5
|
||||
};
|
||||
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
|
||||
void corner::mul(const cubie::cube& c1, const cubie::cube& c2, cubie::cube& into) {
|
||||
for (int i = 0; i < corner::COUNT; i++) {
|
||||
into.cperm[i] = c1.cperm[c2.cperm[i]];
|
||||
into.cori[i] = mul_coris[c1.cori[c2.cperm[i]]][c2.cori[i]];
|
||||
}
|
||||
}
|
||||
|
||||
void edge::mul(const cubie::cube& c1, const cubie::cube& c2, cubie::cube& into) {
|
||||
for (int i = 0; i < edge::COUNT; i++) {
|
||||
into.eperm[i] = c1.eperm[c2.eperm[i]];
|
||||
into.eori[i] = (c1.eori[c2.eperm[i]] + c2.eori[i]) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Permutation partiy = #inversions % 2
|
||||
bool parity(const int perm[], int len) {
|
||||
int par = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (perm[j] > perm[i])
|
||||
par++;
|
||||
}
|
||||
}
|
||||
return par & 1;
|
||||
}
|
||||
|
||||
void mul(const cubie::cube& c1, const cubie::cube& c2, cubie::cube& into) {
|
||||
corner::mul(c1, c2, into);
|
||||
edge::mul(c1, c2, into);
|
||||
}
|
||||
|
||||
void inv(const cube& c, cube& into) {
|
||||
for (int corner = 0; corner < corner::COUNT; corner++)
|
||||
into.cperm[c.cperm[corner]] = corner; // inv[a[i]] = i
|
||||
for (int edge = 0; edge < edge::COUNT; edge++)
|
||||
into.eperm[c.eperm[edge]] = edge;
|
||||
for (int i = 0; i < corner::COUNT; i++)
|
||||
into.cori[i] = inv_cori[c.cori[into.cperm[i]]];
|
||||
for (int i = 0; i < edge::COUNT; i++)
|
||||
into.eori[i] = c.eori[into.eperm[i]];
|
||||
}
|
||||
|
||||
int check(const cube& c) {
|
||||
bool corners[corner::COUNT] = {};
|
||||
int cori_sum = 0;
|
||||
|
||||
for (int i = 0; i < corner::COUNT; i++) {
|
||||
if (c.cperm[i] < 0 || c.cperm[i] >= corner::COUNT)
|
||||
return 1; // invalid corner cubie
|
||||
corners[c.cperm[i]] = true;
|
||||
if (c.cori[i] < 0 || c.cori[i] >= 3)
|
||||
return 2; // invalid corner orientation
|
||||
cori_sum += c.cori[i];
|
||||
}
|
||||
if (cori_sum % 3 != 0)
|
||||
return 3; // invalid twist parity
|
||||
for (bool corner : corners) {
|
||||
if (!corner)
|
||||
return 4; // missing corner
|
||||
}
|
||||
|
||||
bool edges[edge::COUNT] = {};
|
||||
int eori_sum = 0;
|
||||
|
||||
for (int i = 0; i < edge::COUNT; i++) {
|
||||
if (c.eperm[i] < 0 || c.eperm[i] >= edge::COUNT)
|
||||
return 5; // invalid edge cubie
|
||||
edges[c.eperm[i]] = true;
|
||||
if (c.eori[i] < 0 || c.eori[i] >= 2)
|
||||
return 6; // invalid edge orientation
|
||||
eori_sum += c.eori[i];
|
||||
}
|
||||
if ((eori_sum & 1) != 0)
|
||||
return 7; // invalid flip parity
|
||||
for (bool edge : edges) {
|
||||
if (!edge)
|
||||
return 8; // missing edge
|
||||
}
|
||||
|
||||
if (parity(c.cperm, corner::COUNT) != parity(c.eperm, edge::COUNT))
|
||||
return 9; // corner and edge permutation parity mismatch
|
||||
return 0;
|
||||
}
|
||||
|
||||
void shuffle(cube& c) {
|
||||
for (int i = 0; i < corner::COUNT; i++)
|
||||
c.cperm[i] = i;
|
||||
for (int i = 0; i < edge::COUNT; i++)
|
||||
c.eperm[i] = i;
|
||||
|
||||
coord::set_corners(c, std::uniform_int_distribution<int>(0, coord::N_CORNERS)(gen));
|
||||
std::shuffle(c.eperm, c.eperm + edge::COUNT, gen); // no coordinate for all edges
|
||||
if (parity(c.cperm, corner::COUNT) != parity(c.eperm, edge::COUNT))
|
||||
std::swap(c.cperm[corner::COUNT - 2], c.cperm[corner::COUNT - 1]); // flip parity
|
||||
|
||||
coord::set_twist(c, std::uniform_int_distribution<int>(0, coord::N_TWIST - 1)(gen));
|
||||
coord::set_flip(c, std::uniform_int_distribution<int>(0, coord::N_FLIP - 1)(gen));
|
||||
}
|
||||
|
||||
// We could maybe make this faster, but it is not performance critical anyways
|
||||
bool operator==(const cube& c1, const cube& c2) {
|
||||
return
|
||||
std::equal(c1.cperm, c1.cperm + corner::COUNT, c2.cperm) &&
|
||||
std::equal(c1.eperm, c1.eperm + edge::COUNT, c2.eperm) &&
|
||||
std::equal(c1.cori, c1.cori + corner::COUNT, c2.cori) &&
|
||||
std::equal(c1.eori, c1.eori + edge::COUNT, c2.eori)
|
||||
;
|
||||
}
|
||||
|
||||
bool operator!=(const cube& c1, const cube& c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
}
|
||||
87
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/cubie.h
vendored
Normal file
87
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/cubie.h
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Cubie definitions, cubie-cube representation + methods for manipulating it
|
||||
*/
|
||||
|
||||
#ifndef __CUBIE__
|
||||
#define __CUBIE__
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace cubie {
|
||||
|
||||
/* All cubie definitions are plain integers for making uniform handling much easier */
|
||||
|
||||
namespace corner { // definition of corner cubies
|
||||
const int COUNT = 8;
|
||||
|
||||
const int URF = 0;
|
||||
const int UFL = 1;
|
||||
const int ULB = 2;
|
||||
const int UBR = 3;
|
||||
const int DFR = 4;
|
||||
const int DLF = 5;
|
||||
const int DBL = 6;
|
||||
const int DRB = 7;
|
||||
|
||||
const std::string NAMES[] = {
|
||||
"URF", "UFL", "ULB", "UBR", "DFR", "DLF", "DBL", "DRB"
|
||||
};
|
||||
}
|
||||
using namespace corner;
|
||||
|
||||
namespace edge { // definition of edge cubies
|
||||
const int COUNT = 12;
|
||||
|
||||
const int UR = 0;
|
||||
const int UF = 1;
|
||||
const int UL = 2;
|
||||
const int UB = 3;
|
||||
const int DR = 4;
|
||||
const int DF = 5;
|
||||
const int DL = 6;
|
||||
const int DB = 7;
|
||||
// SLICE-edges last s.t. UDEDGES2 is easier to handle
|
||||
const int FR = 8;
|
||||
const int FL = 9;
|
||||
const int BL = 10;
|
||||
const int BR = 11;
|
||||
|
||||
const std::string NAMES[] = {
|
||||
"UR", "UF", "UL", "UB", "DR", "DF", "DL", "DB", "FR", "FL", "BL", "BR"
|
||||
};
|
||||
}
|
||||
using namespace edge;
|
||||
|
||||
struct cube {
|
||||
int cperm[corner::COUNT]; // corner cubie permutation
|
||||
int eperm[edge::COUNT]; // edge cubie permutation
|
||||
int cori[corner::COUNT]; // corner cubie orientation; 0 if U/D-facelet on U/D-face; 1 clockwise rot; 2 c-clock
|
||||
int eori[edge::COUNT]; // edge cubie orientation; 0 if U/D-facelet on U/D-face or same for F/B for slice edges
|
||||
};
|
||||
|
||||
const cube SOLVED_CUBE = {
|
||||
{URF, UFL, ULB, UBR, DFR, DLF, DBL, DRB},
|
||||
{UR, UF, UL, UB, DR, DF, DL, DB, FR, FL, BL, BR},
|
||||
{}, {}
|
||||
}; // cubie-cube in solved state
|
||||
|
||||
/* Explicitly pass result cube to avoid unnecessary copying during table generation */
|
||||
|
||||
namespace corner {
|
||||
void mul(const cube& c1, const cube& c2, cube& into); // multiply only corner cubies
|
||||
}
|
||||
namespace edge {
|
||||
void mul(const cube& c1, const cube& c2, cube& into); // multiply only edge cubies
|
||||
}
|
||||
|
||||
void mul(const cube& c1, const cube& c2, cube& into); // fully multiply two cubes
|
||||
void inv(const cube& c, cube& into); // compute the inverse cube
|
||||
void shuffle(cube& c); // generate a uniformly random cube
|
||||
int check(const cube& c); // check a cube for being solvable
|
||||
|
||||
bool operator==(const cube& c1, const cube& c2);
|
||||
bool operator!=(const cube& c1, const cube& c2);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
88
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/face.cpp
vendored
Normal file
88
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/face.cpp
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "face.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace face {
|
||||
|
||||
// True modulo function that also works properly for negative numbers
|
||||
int mod(int a, int m) {
|
||||
return a > 0 ? a % m : (a % m + m) % m;
|
||||
}
|
||||
|
||||
// Converts a cubelet and its orientation into a single unique code-number
|
||||
int encode(const std::string& cubelet, int ori) {
|
||||
int res = 0;
|
||||
for (int i = 0; i < cubelet.size(); i++)
|
||||
res = color::COUNT * res + color::FROM_NAME.at(cubelet[mod(i - ori, cubelet.size())]);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Map code to cubie ID and orientation
|
||||
std::unordered_map<int, std::pair<int, int>> corners;
|
||||
std::unordered_map<int, std::pair<int, int>> edges;
|
||||
|
||||
void init() {
|
||||
for (int corner = 0; corner < cubie::corner::COUNT; corner++) {
|
||||
for (int ori = 0; ori < 3; ori++)
|
||||
corners[encode(cubie::corner::NAMES[corner], ori)] = std::make_pair(corner, ori);
|
||||
}
|
||||
for (int edge = 0; edge < cubie::edge::COUNT; edge++) {
|
||||
for (int ori = 0; ori < 2; ori++)
|
||||
edges[encode(cubie::edge::NAMES[edge], ori)] = std::make_pair(edge, ori);
|
||||
}
|
||||
}
|
||||
|
||||
int to_cubie(const std::string& s, cubie::cube& c) {
|
||||
for (int i = 0; i < N_FACELETS; i++) {
|
||||
if (color::FROM_NAME.find(s[i]) == color::FROM_NAME.end())
|
||||
return 1; // invalid color
|
||||
if ((i - 4) % 9 == 0 && color::FROM_NAME.at(s[i]) != i / 9)
|
||||
return 2; // invalid center facelet (they are always fixed)
|
||||
}
|
||||
|
||||
for (int corner = 0; corner < cubie::corner::COUNT; corner++) {
|
||||
char cornlet[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
cornlet[i] = s[CORNLETS[corner][i]];
|
||||
auto tmp = corners.find(encode(std::string(cornlet, 3), 0));
|
||||
if (tmp == corners.end())
|
||||
return 3; // invalid corner cubie
|
||||
c.cperm[corner] = tmp->second.first;
|
||||
c.cori[corner] = tmp->second.second;
|
||||
}
|
||||
|
||||
for (int edge = 0; edge < cubie::edge::COUNT; edge++) {
|
||||
char edgelet[2];
|
||||
for (int i = 0; i < 2; i++)
|
||||
edgelet[i] = s[EDGELETS[edge][i]];
|
||||
auto tmp = edges.find(encode(std::string(edgelet, 2), 0));
|
||||
if (tmp == edges.end())
|
||||
return 4; // invalid edge cubie
|
||||
c.eperm[edge] = tmp->second.first;
|
||||
c.eori[edge] = tmp->second.second;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Assumes the given cube to be valid
|
||||
std::string from_cubie(const cubie::cube& c) {
|
||||
char s[N_FACELETS];
|
||||
|
||||
for (int color = 0; color < color::COUNT; color++)
|
||||
s[9 * color + 4] = color::NAMES[color];
|
||||
for (int corner = 0; corner < cubie::corner::COUNT; corner++) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
// Corner twist is defined clockwise
|
||||
s[CORNLETS[corner][i]] = cubie::corner::NAMES[c.cperm[corner]][mod(i - c.cori[corner], 3)];
|
||||
}
|
||||
for (int edge = 0; edge < cubie::edge::COUNT; edge++) {
|
||||
for (int i = 0; i < 2; i++)
|
||||
s[EDGELETS[edge][i]] = cubie::edge::NAMES[c.eperm[edge]][mod(i - c.eori[edge], 2)];
|
||||
}
|
||||
|
||||
return std::string(s, N_FACELETS);
|
||||
}
|
||||
|
||||
}
|
||||
88
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/face.h
vendored
Normal file
88
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/face.h
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Since `cubie::cube`s (especially the orientation part) are quite tricky to deal with, we use a more convenient
|
||||
* representation to interface with the outside world, the face-cube. Having defined an ordering over all 54 stickers
|
||||
* on the physical cube, a list of the colors for each sticker (in terms of the faces U, R, F, D, L and B not the
|
||||
* actual cube colors) uniquely specifies any cube-state.
|
||||
*
|
||||
* The facelet positions are defined as shown in the folded-up Rubik's cube depicted below.
|
||||
*
|
||||
* +--+-----+
|
||||
* |U1|U2|U3|
|
||||
* |--+--+--|
|
||||
* |U4|U5|U6|
|
||||
* |--+--+--|
|
||||
* |U7|U8|U9|
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* |L1|L2|L3|F1|F2|F3|R1|R2|R3|B1|B2|B3|
|
||||
* |--+--+--|--+--+--|--+--+--|--+--+--|
|
||||
* |L4|L5|L6|F4|F5|F6|R4|R5|R6|B4|B5|B6|
|
||||
* |--+--+--|--+--+--|--+--+--|--+--+--|
|
||||
* |L7|L8|L9|F7|F8|F9|R7|R8|R9|B7|B8|B9|
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* |D1|D2|D3|
|
||||
* |--+--+--|
|
||||
* |D4|D5|D6|
|
||||
* |--+--+--|
|
||||
* |D7|D8|D9|
|
||||
* +--+--+--+
|
||||
*
|
||||
* A facelet string simply lists the colors of every facelet position with the faces being in order U, R, F, D, L, B
|
||||
* and the facelets within a face sorted by their index, i.e. U1U2U3U4U5U6U7U8U9R1R2... where U1, U2, ... are the
|
||||
* colors of the corresponding facelets.
|
||||
*
|
||||
* Note that facelet X5 (i.e. the center sticker of face X) must always be of color X. It also does not matter which
|
||||
* of the actual cube colors (like red, orange, etc.) is assigned to which face, the assignment must only be consistent
|
||||
* with respect to the neighborhood relations, i.e. for example if white is considered as the F-face, then yellow must
|
||||
* be B (as it is always on the opposite side on a physical cube).
|
||||
*/
|
||||
|
||||
#ifndef __FACE__
|
||||
#define __FACE__
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "cubie.h"
|
||||
|
||||
namespace face {
|
||||
|
||||
const int N_FACELETS = 54; // number of facelets (stickers) = 9 * 6
|
||||
|
||||
namespace color {
|
||||
const int COUNT = 6; // number of colors/faces of a cube
|
||||
|
||||
/* Color/Face ordering */
|
||||
const int U = 0;
|
||||
const int R = 1;
|
||||
const int F = 2;
|
||||
const int D = 3;
|
||||
const int L = 4;
|
||||
const int B = 5;
|
||||
|
||||
const char NAMES[] = {'U', 'R', 'F', 'D', 'L', 'B'};
|
||||
|
||||
// Maps color character to corresponding color ID
|
||||
const std::unordered_map<char, int> FROM_NAME = {
|
||||
{'U', U}, {'R', R}, {'F', F}, {'D', D}, {'L', L}, {'B', B}
|
||||
};
|
||||
}
|
||||
|
||||
/* Map corner/edge IDs to corresponding facelet positions */
|
||||
const int CORNLETS[][3] = {
|
||||
{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 int EDGELETS[][2] = {
|
||||
{5, 10}, {7, 19}, {3, 37}, {1, 46}, {32, 16}, {28, 25},
|
||||
{30, 43}, {34, 52}, {23, 12}, {21, 41}, {50, 39}, {48, 14}
|
||||
};
|
||||
|
||||
/* Routines for converting a facelet-string to a cubie-cube and vice-versa */
|
||||
int to_cubie(const std::string& s, cubie::cube &c);
|
||||
std::string from_cubie(const cubie::cube &c);
|
||||
|
||||
// Initializes the face-level; to be called before accessing anything from this file
|
||||
void init();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
349
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/move.cpp
vendored
Normal file
349
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/move.cpp
vendored
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include "move.h"
|
||||
|
||||
namespace move {
|
||||
|
||||
using namespace cubie::corner;
|
||||
using namespace cubie::edge;
|
||||
|
||||
/* Select moves and order according to used metric */
|
||||
#ifdef QT
|
||||
#ifdef AX
|
||||
const int map[] = {
|
||||
// U, U2, U', D, D2, D'
|
||||
0, -1, 1, 2, -1, 3,
|
||||
// (U D), (U D2), (U D'), (U2 D), (U2 D2), (U2 D'), (U' D), (U' D2), (U' D')
|
||||
4, -1, 5, -1, -1, -1, 6, -1, 7,
|
||||
// R, R2, R', L, L2, L'
|
||||
8, 24, 9, 10, 25, 11,
|
||||
// (R L), (R L2), (R L'), (R2 L), (R2 L2), (R2 L'), (R' L), (R' L2), (R' L')
|
||||
12, -1, 13, -1, 26, -1, 14, -1, 15,
|
||||
// F, F2, F', B, B2, B'
|
||||
16, 27, 17, 18, 28, 19,
|
||||
// (F B), (F B2), (F B'), (F2 B), (F2 B2), (F2 B'), (F' B), (F' B2), (F' B')
|
||||
20, -1, 21, -1, 29, -1, 22, -1, 23
|
||||
};
|
||||
#else
|
||||
const int map[] = {
|
||||
0, -1, 1, 2, -1, 3,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
4, 12, 5, 6, 13, 7,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
8, 14, 9, 10, 15, 11,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1
|
||||
};
|
||||
#endif
|
||||
#else
|
||||
#ifdef AX
|
||||
const int map[] = {
|
||||
0, 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
|
||||
};
|
||||
#else
|
||||
const int map[] = {
|
||||
0, 1, 2, 3, 4, 5,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
6, 7, 8, 9, 10, 11,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
12, 13, 14, 15, 16, 17,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
std::string names[COUNT];
|
||||
cubie::cube cubes[COUNT];
|
||||
int inv[COUNT];
|
||||
|
||||
mask next[COUNT];
|
||||
mask next_p1p2[COUNT];
|
||||
mask qt_skip[COUNT];
|
||||
|
||||
mask p1mask = bit(45) - 1;
|
||||
mask p2mask = 0x10482097fff; // 000010000 010010 000010000 010010 111111111 111111;
|
||||
|
||||
// For full set of 45 moves no matter the solving mode
|
||||
std::string names1[45];
|
||||
int merge[45][45];
|
||||
int unmap[COUNT];
|
||||
|
||||
// Translate bitmask from full moveset to configured one
|
||||
mask reindex(mask mm) {
|
||||
mask mm1 = 0;
|
||||
for (int m = 0; m < 45; m++) {
|
||||
if (map[m] != -1 && in(m, mm)) // drop unmapped moves
|
||||
mm1 |= bit(map[m]);
|
||||
}
|
||||
return mm1;
|
||||
}
|
||||
|
||||
// Build full moveset first, then remap to configured one
|
||||
void init() {
|
||||
for (int m = 0; m < 45; m++) {
|
||||
if (map[m] != -1)
|
||||
unmap[map[m]] = m;
|
||||
}
|
||||
|
||||
cubie::cube cubes1[45];
|
||||
int inv1[45];
|
||||
// Not initializing the following arrays apparently causes problems on MacOS
|
||||
mask next1[45] = {0};
|
||||
mask qt_skip1[45] = {0};
|
||||
|
||||
std::string fnames[] = {"U", "D", "R", "L", "F", "B"};
|
||||
std::string pnames[] = {"", "2", "'"};
|
||||
cubie::cube fcubes[] = {
|
||||
{ // U
|
||||
{UBR, URF, UFL, ULB, DFR, DLF, DBL, DRB},
|
||||
{UB, UR, UF, UL, DR, DF, DL, DB, FR, FL, BL, BR},
|
||||
{}, {}
|
||||
},
|
||||
{ // D
|
||||
{URF, UFL, ULB, UBR, DLF, DBL, DRB, DFR},
|
||||
{UR, UF, UL, UB, DF, DL, DB, DR, FR, FL, BL, BR},
|
||||
{}, {}
|
||||
},
|
||||
{ // R
|
||||
{DFR, UFL, ULB, URF, DRB, DLF, DBL, UBR},
|
||||
{FR, UF, UL, UB, BR, DF, DL, DB, DR, FL, BL, UR},
|
||||
{2, 0, 0, 1, 1, 0, 0, 2}, {}
|
||||
},
|
||||
{ // L
|
||||
{URF, ULB, DBL, UBR, DFR, UFL, DLF, DRB},
|
||||
{UR, UF, BL, UB, DR, DF, FL, DB, FR, UL, DL, BR},
|
||||
{0, 1, 2, 0, 0, 2, 1, 0}, {}
|
||||
},
|
||||
{ // F
|
||||
{UFL, DLF, ULB, UBR, URF, DFR, DBL, DRB},
|
||||
{UR, FL, UL, UB, DR, FR, DL, DB, UF, DF, BL, BR},
|
||||
{1, 2, 0, 0, 2, 1, 0, 0},
|
||||
{0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0}
|
||||
},
|
||||
{ // B
|
||||
{URF, UFL, UBR, DRB, DFR, DLF, ULB, DBL},
|
||||
{UR, UF, UL, BR, DR, DF, DL, BL, FR, FL, UB, DB},
|
||||
{0, 0, 1, 2, 0, 0, 2, 1},
|
||||
{0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1}
|
||||
}
|
||||
};
|
||||
|
||||
for (int ax = 0; ax < 3; ax++) {
|
||||
int i1 = 15 * ax; // index to start first face moves
|
||||
int i2 = 15 * ax + 3; // index to start of second face moves
|
||||
int i3 = 15 * ax + 6; // index to start of axial moves
|
||||
|
||||
int f1 = 2 * ax; // first face
|
||||
int f2 = 2 * ax + 1; // second face
|
||||
|
||||
for (int cnt = 0; cnt < 3; cnt++) {
|
||||
int m = i1 + cnt;
|
||||
names1[m] = fnames[f1] + pnames[cnt];
|
||||
if (cnt == 0)
|
||||
cubes1[m] = fcubes[f1];
|
||||
else
|
||||
cubie::mul(cubes1[m - 1], fcubes[f1], cubes1[m]);
|
||||
inv1[m] = i1 + (2 - cnt);
|
||||
next1[m] |= mask(0x7) << i1; // block any moves on same face
|
||||
#ifdef AX
|
||||
next1[m] |= mask(0x38) << i1; // block also moves on opposite face
|
||||
#endif
|
||||
#ifdef QT
|
||||
if (cnt == 0) { // block all axial moves but the ones with M
|
||||
next1[m] |= mask(0x1ff ^ (0x7 << 3 * cnt)) << i3;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
next1[m] |= mask(0x1ff) << i3; // block all axial moves
|
||||
}
|
||||
for (int cnt = 0; cnt < 3; cnt++) {
|
||||
int m = i2 + cnt;
|
||||
names1[m] = fnames[f2] + pnames[cnt];
|
||||
if (cnt == 0)
|
||||
cubes1[m] = fcubes[f2];
|
||||
else
|
||||
cubie::mul(cubes1[m - 1], fcubes[f2], cubes1[m]);
|
||||
inv1[m] = i2 + (2 - cnt);
|
||||
next1[m] |= mask(0x3f) << i1; // block all simple moves on both faces
|
||||
#ifdef QT
|
||||
if (cnt == 0) { // block all axial moves but the ones with M
|
||||
next1[m] |= mask(0x1ff ^ (0x49 << cnt)) << i3; // 0x49 == 0b1001001
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
next1[m] |= mask(0x1ff) << i3;
|
||||
}
|
||||
for (int cnt1 = 0; cnt1 < 3; cnt1++) {
|
||||
for (int cnt2 = 0; cnt2 < 3; cnt2++) {
|
||||
int m = i3 + 3 * cnt1 + cnt2;
|
||||
names1[m] = "(" + names1[i1 + cnt1] + " " + names1[i2 + cnt2] + ")";
|
||||
cubie::mul(cubes1[i1 + cnt1], cubes1[i2 + cnt2], cubes1[m]);
|
||||
inv1[m] = i3 + 3 * (2 - cnt1) + (2 - cnt2);
|
||||
next1[m] |= mask(0x7fff) << 15 * ax; // block all simple and axial moves
|
||||
}
|
||||
}
|
||||
|
||||
qt_skip1[i1] |= bit(i1);
|
||||
qt_skip1[i1] |= bit(i3);
|
||||
qt_skip1[i2] |= bit(i2);
|
||||
qt_skip1[i2] |= bit(i3);
|
||||
qt_skip1[i3] |= bit(i3);
|
||||
}
|
||||
// Half-slice moves commute
|
||||
next1[25] |= bit(10);
|
||||
next1[40] |= bit(10) | bit(25);
|
||||
#ifdef QT
|
||||
// Allow repetitions of purely clockwise moves
|
||||
for (int m : {0, 3, 6, 15, 18, 21, 30, 33, 36})
|
||||
next1[m] ^= bit(m);
|
||||
#endif
|
||||
// Was built by blocking moves, but should actually indicate permitted ones
|
||||
for (int m = 0; m < 45; m++)
|
||||
next1[m] = ~next1[m];
|
||||
|
||||
for (int m = 0; m < 45; m++) {
|
||||
if (map[m] == -1)
|
||||
continue;
|
||||
int i = map[m];
|
||||
|
||||
names[i] = names1[m];
|
||||
cubes[i] = cubes1[m];
|
||||
inv[i] = map[inv1[m]];
|
||||
next[i] = reindex(next1[m]);
|
||||
qt_skip[i] = reindex(qt_skip1[m]);
|
||||
}
|
||||
|
||||
#ifdef QT
|
||||
// Unmapped moves are skipped automatically during reindexing
|
||||
p1mask &= ~0x10482090000; // 000010000 010010 000010000 010010 000000000 000000
|
||||
#endif
|
||||
#ifdef F5
|
||||
mask tmp = ~(mask(0xfff) << 33);
|
||||
p1mask &= tmp;
|
||||
p2mask &= tmp;
|
||||
#endif
|
||||
p1mask = reindex(p1mask);
|
||||
p2mask = reindex(p2mask);
|
||||
|
||||
for (int m = 0; m < COUNT; m++) {
|
||||
if (p2mask & move::bit(m))
|
||||
next_p1p2[m] = next[m]; // we can do normal blocking for phase 2 moves
|
||||
else {
|
||||
#ifndef AX
|
||||
#ifdef QT
|
||||
next_p1p2[m] = ~(mask(0x3) << 2 * (m / 2));
|
||||
if (m / 2 > 1)
|
||||
next_p1p2[m] &= ~(move::bit(COUNT1 + (m / 2) - 2));
|
||||
#else
|
||||
next_p1p2[m] = ~(mask(0x7) << 3 * (m / 3));
|
||||
#endif
|
||||
#else
|
||||
next_p1p2[m] = next[m]; // no commutativity problems in axial mode
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
cubie::cube c;
|
||||
for (int m1 = 0; m1 < 45; m1++) {
|
||||
for (int m2 = 0; m2 < 45; m2++) {
|
||||
merge[m1][m2] = -1;
|
||||
cubie::mul(cubes1[m1], cubes1[m2], c);
|
||||
for (int i = 0; i < 45; i++) {
|
||||
if (c == cubes1[i]) {
|
||||
merge[m1][m2] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compress1(const std::vector<int>& mseq, std::vector<int>& into) {
|
||||
into.clear();
|
||||
for (int m : mseq) {
|
||||
m = unmap[m];
|
||||
if (into.size() == 0 || merge[into.back()][m] == -1)
|
||||
into.push_back(m);
|
||||
else {
|
||||
int tmp = into.back();
|
||||
into.pop_back();
|
||||
into.push_back(merge[tmp][m]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string compress(const std::vector<int>& mseq) {
|
||||
std::vector<int> comp;
|
||||
compress1(mseq, comp);
|
||||
|
||||
// Faster string building probably not worth it in a function like this
|
||||
std::string s;
|
||||
for (int i = 0; i < comp.size(); i++) {
|
||||
s += names1[comp[i]];
|
||||
if (i != comp.size() - 1)
|
||||
s += " ";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int len(const std::vector<int>& mseq, int cost[]) {
|
||||
std::vector<int> comp;
|
||||
compress1(mseq, comp);
|
||||
|
||||
int res = 0;
|
||||
for (int m : comp)
|
||||
res += cost[m];
|
||||
return res;
|
||||
}
|
||||
|
||||
int len_ht(const std::vector<int>& mseq) {
|
||||
int cost[] = {
|
||||
1, 1, 1, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2
|
||||
};
|
||||
return len(mseq, cost);
|
||||
}
|
||||
|
||||
int len_axht(const std::vector<int>& mseq) {
|
||||
int cost[] = {
|
||||
1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
};
|
||||
return len(mseq, cost);
|
||||
}
|
||||
|
||||
int len_qt(const std::vector<int>& mseq) {
|
||||
int cost[] = {
|
||||
1, 2, 1, 1, 2, 1,
|
||||
2, 3, 2, 3, 4, 3, 2, 3, 2,
|
||||
1, 2, 1, 1, 2, 1,
|
||||
2, 3, 2, 3, 4, 3, 2, 3, 2,
|
||||
1, 2, 1, 1, 2, 1,
|
||||
2, 3, 2, 3, 4, 3, 2, 3, 2
|
||||
};
|
||||
return len(mseq, cost);
|
||||
}
|
||||
|
||||
int len_axqt(const std::vector<int>& mseq) {
|
||||
int cost[] = {
|
||||
1, 2, 1, 1, 2, 1,
|
||||
1, 2, 1, 2, 2, 2, 1, 2, 1,
|
||||
1, 2, 1, 1, 2, 1,
|
||||
1, 2, 1, 2, 2, 2, 1, 2, 1,
|
||||
1, 2, 1, 1, 2, 1,
|
||||
1, 2, 1, 2, 2, 2, 1, 2, 1
|
||||
};
|
||||
return len(mseq, cost);
|
||||
}
|
||||
|
||||
}
|
||||
75
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/move.h
vendored
Normal file
75
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/move.h
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* All kinds of moveset definitions and setup
|
||||
*/
|
||||
|
||||
#ifndef __MOVE__
|
||||
#define __MOVE__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cubie.h"
|
||||
|
||||
namespace move {
|
||||
|
||||
using mask = uint64_t;
|
||||
|
||||
#ifdef QT
|
||||
#ifdef AX
|
||||
const int COUNT = 30;
|
||||
const int COUNT1 = 24;
|
||||
const int split[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
8, 10, 12, 16, 18, 20
|
||||
}; // split extra half-turns into quarter-turn (for cleaner phase 2 implementation)
|
||||
#else
|
||||
const int COUNT = 16;
|
||||
const int COUNT1 = 12;
|
||||
const int split[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
4, 6, 8, 10
|
||||
};
|
||||
#endif
|
||||
#else
|
||||
#ifdef AX
|
||||
const int COUNT = 45;
|
||||
const int COUNT1 = 45;
|
||||
#else
|
||||
const int COUNT = 18;
|
||||
const int COUNT1 = 18;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern std::string names[COUNT];
|
||||
extern cubie::cube cubes[COUNT];
|
||||
extern int inv[COUNT];
|
||||
|
||||
extern mask next[COUNT]; // successor moves that should be explored
|
||||
extern mask next_p1p2[COUNT]; // `next` for phase1 to phase 2 transition
|
||||
extern mask qt_skip[COUNT]; // to avoid ever trying M^3 = M' in QT mode
|
||||
|
||||
extern mask p1mask; // phase 1 moves
|
||||
extern mask p2mask; // phase 2 moves
|
||||
|
||||
inline mask bit(int m) {
|
||||
return mask(1) << m;
|
||||
}
|
||||
inline bool in(int m, mask mm) {
|
||||
return mm & bit(m);
|
||||
}
|
||||
|
||||
// Convert solution to AXHT; especially useful when solving in AXQT
|
||||
std::string compress(const std::vector<int>& mseq);
|
||||
|
||||
/* Compute solution lengths in different metrics */
|
||||
int len_ht(const std::vector<int>& mseq);
|
||||
int len_axht(const std::vector<int>& mseq);
|
||||
int len_qt(const std::vector<int>& mseq);
|
||||
int len_axqt(const std::vector<int>& mseq);
|
||||
|
||||
void init();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
413
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/prun.cpp
vendored
Normal file
413
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/prun.cpp
vendored
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
#include "prun.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
namespace prun {
|
||||
const std::string SAVE = "twophase-"
|
||||
#ifdef AX
|
||||
"ax"
|
||||
#endif
|
||||
#ifdef QT
|
||||
"qt"
|
||||
#else
|
||||
"ht"
|
||||
#endif
|
||||
#ifdef F5
|
||||
"-f5"
|
||||
#endif
|
||||
".tbl"
|
||||
;
|
||||
|
||||
const int EMPTY = 0xff;
|
||||
|
||||
#ifdef AX
|
||||
const int BITS_PER_AX = 16; // bits used for encoding an axis in the ext. phase 1 table
|
||||
#else
|
||||
const int BITS_PER_AX = 8;
|
||||
#endif
|
||||
#ifdef QT
|
||||
const int BITS_PER_M = 2; // bits per move
|
||||
const int N_SIMP = 4; // number of simple moves
|
||||
const int N_AX = 4; // number of axial moves
|
||||
#else
|
||||
const int BITS_PER_M = 1;
|
||||
const int N_SIMP = 6;
|
||||
const int N_AX = 9;
|
||||
#endif
|
||||
|
||||
// Used to remap symmetry ext. phase 1 table entries back to actual situation
|
||||
move::mask remap[2][16][1 << BITS_PER_AX];
|
||||
|
||||
prun1 *phase1;
|
||||
uint8_t *phase2;
|
||||
uint8_t *precheck;
|
||||
|
||||
inline int ones(int count) { return (1 << count) - 1; }
|
||||
|
||||
int rev(int movec, int count, int off = 0, int step = BITS_PER_M) {
|
||||
movec >>= step * off;
|
||||
|
||||
int rev = 0;
|
||||
for (int i = 0; i < step * count; i += step) {
|
||||
rev = (rev << step) | (movec & ones(step));
|
||||
movec >>= step;
|
||||
}
|
||||
return rev << step * off;
|
||||
}
|
||||
|
||||
int inv(int mask) {
|
||||
int n_per_face = N_SIMP / 2;
|
||||
|
||||
int inv = rev(mask, n_per_face) | rev(mask, n_per_face, n_per_face);
|
||||
#ifdef AX
|
||||
inv |= rev(mask, N_AX, N_SIMP);
|
||||
#endif
|
||||
return inv;
|
||||
}
|
||||
|
||||
int flip(int mask) {
|
||||
int per_face = N_SIMP / 2;
|
||||
int flipped = rev(mask, 2, 0, BITS_PER_M * per_face);
|
||||
|
||||
#ifdef AX
|
||||
mask >>= BITS_PER_M * N_SIMP;
|
||||
|
||||
// Flipping an axis means to transpose the axial move-mask (M N) (M N2) (M N') ... to (N M) (N M2) (N M') ...
|
||||
int fax = 0;
|
||||
for (int i = 0; i < per_face; i++) {
|
||||
for (int j = 0; j < per_face; j++)
|
||||
fax |= ((mask >> BITS_PER_M * (per_face * i + j)) & ones(BITS_PER_M)) << BITS_PER_M * (per_face * j + i);
|
||||
}
|
||||
flipped |= fax << BITS_PER_M * N_SIMP;
|
||||
#endif
|
||||
|
||||
return flipped;
|
||||
}
|
||||
|
||||
void init_base() {
|
||||
/* It is probably cleanest to simply handle the special HT case individually */
|
||||
#ifndef AX
|
||||
#ifndef QT
|
||||
for (int eff = 0; eff < 16; eff++) {
|
||||
for (int mask = 0; mask < 256; mask++) {
|
||||
int mask1 = mask & 0xf;
|
||||
int mask2 = (mask & 0xf0) >> 4;
|
||||
|
||||
if (sym::eff_inv(eff)) {
|
||||
mask1 = rev(mask1, 3, 1) | (mask1 & 1);
|
||||
mask2 = rev(mask2, 3, 1) | (mask2 & 1);
|
||||
}
|
||||
if (sym::eff_flip(eff))
|
||||
std::swap(mask1, mask2);
|
||||
|
||||
remap[0][eff][mask] = ((mask1 & 1) ? 0 : ~(mask1 >> 1) & 0x7) << 6 * sym::eff_shift(eff);
|
||||
remap[1][eff][mask] = ((mask1 & 1) ? ~(mask1 >> 1) & 0x7 : 0x7) << 6 * sym::eff_shift(eff);
|
||||
remap[0][eff][mask] |= ((mask2 & 1) ? 0 : ~(mask2 >> 1) & 0x7) << 6 * sym::eff_shift(eff) + 3;
|
||||
remap[1][eff][mask] |= ((mask2 & 1) ? ~(mask2 >> 1) & 0x7 : 0x7) << 6 * sym::eff_shift(eff) + 3;
|
||||
}
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
for (int eff = 0; eff < 16; eff++) {
|
||||
for (int mask = 0; mask < (1 << BITS_PER_AX); mask++) {
|
||||
move::mask mask1 = mask;
|
||||
#ifndef QT
|
||||
mask1 >>= 1; // first bit encodes direction in HT
|
||||
#endif
|
||||
|
||||
if (sym::eff_inv(eff))
|
||||
mask1 = inv(mask1);
|
||||
if (sym::eff_flip(eff))
|
||||
mask1 = flip(mask1);
|
||||
|
||||
#ifdef QT
|
||||
remap[0][eff][mask] = 0;
|
||||
remap[1][eff][mask] = 0;
|
||||
for (int i = 0; i < BITS_PER_AX / 2; i++) {
|
||||
remap[0][eff][mask] |= ((mask1 & 0x3) == 0) << i;
|
||||
remap[1][eff][mask] |= ((mask1 & 0x3) <= 1) << i;
|
||||
mask1 >>= 2;
|
||||
}
|
||||
remap[0][eff][mask] <<= (BITS_PER_AX / 2) * sym::eff_shift(eff);
|
||||
remap[1][eff][mask] <<= (BITS_PER_AX / 2) * sym::eff_shift(eff);
|
||||
#else
|
||||
move::mask o = ones(BITS_PER_AX - 1); // first bit indicates direction but is not a move
|
||||
remap[0][eff][mask] = ((mask & 1) ? 0 : ~mask1 & o) << (BITS_PER_AX - 1) * sym::eff_shift(eff);
|
||||
remap[1][eff][mask] = ((mask & 1) ? ~mask1 & o : o) << (BITS_PER_AX - 1) * sym::eff_shift(eff);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_phase1() {
|
||||
int n_moves = std::bitset<64>(move::p1mask).count(); // make sure not to consider B-moves in F5-mode
|
||||
|
||||
phase1 = new prun1[N_FS1TWIST];
|
||||
std::fill(phase1, phase1 + N_FS1TWIST, EMPTY);
|
||||
|
||||
phase1[coord::N_TWIST * sym::coord_c(sym::fslice1_sym[coord::fslice1(0, coord::SLICE1_SOLVED)])] = 0;
|
||||
int count = 0;
|
||||
int dist = 0;
|
||||
|
||||
while (count < N_FS1TWIST) {
|
||||
int coord = 0;
|
||||
|
||||
for (int fs1sym = 0; fs1sym < sym::N_FSLICE1; fs1sym++) {
|
||||
int fslice1 = sym::fslice1_raw[fs1sym];
|
||||
int flip = coord::fslice1_to_flip(fslice1);
|
||||
int slice = coord::slice1_to_slice(coord::fslice1_to_slice1(fslice1));
|
||||
|
||||
for (int twist = 0; twist < coord::N_TWIST; twist++) {
|
||||
if ((phase1[coord] & 0xff) == dist) {
|
||||
count++;
|
||||
int deltas[move::COUNT1]; // easier encoding if B-face always exists (F5-mode ignores it anyways)
|
||||
|
||||
for (int m = 0; m < n_moves; m++) {
|
||||
int slice11 = coord::slice_to_slice1(coord::move_edges4[slice][m]);
|
||||
int fslice11 = coord::fslice1(coord::move_flip[flip][m], slice11);
|
||||
int tmp = sym::fslice1_sym[fslice11];
|
||||
int twist1 = sym::conj_twist[coord::move_twist[twist][m]][sym::coord_s(tmp)];
|
||||
int fs1sym1 = sym::coord_c(tmp);
|
||||
int coord1 = coord::N_TWIST * fs1sym1 + twist1;
|
||||
|
||||
if (phase1[coord1] == EMPTY)
|
||||
phase1[coord1] = dist + 1;
|
||||
deltas[m] = (phase1[coord1] & 0xff) - dist;
|
||||
coord1 -= twist1; // only TWIST part changes below
|
||||
|
||||
int selfs = sym::fslice1_selfs[fs1sym1] >> 1;
|
||||
for (int s = 1; selfs > 0; s++) { // bit 0 is always on
|
||||
if (selfs & 1) {
|
||||
int coord2 = coord1 + sym::conj_twist[twist1][s];
|
||||
if (phase1[coord2] == EMPTY)
|
||||
phase1[coord2] = dist + 1;
|
||||
}
|
||||
selfs >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
prun1 prun = 0;
|
||||
#ifdef QT
|
||||
// In QT there is enough space to simply encode the effect of every move in 2 bits
|
||||
for (int m = n_moves - 1; m >= 0; m--)
|
||||
prun = (prun << 2) | (deltas[m] + 1);
|
||||
#else
|
||||
#ifndef AX
|
||||
int n_ax = 6; // in standard (HT) mode we have to treat every face as an individual axis for encoding
|
||||
int bits_per_ax = 4;
|
||||
#else
|
||||
int n_ax = 3;
|
||||
int bits_per_ax = BITS_PER_AX;
|
||||
#endif
|
||||
/* Encode from left to right to preserve indexing of moves */
|
||||
for (int ax = n_ax - 1; ax >= 0; ax--) {
|
||||
bool away = false; // first bit of axis encoding (whether any move brings us further from the goal)
|
||||
for (int i = ax * (bits_per_ax - 1); i < (ax + 1) * (bits_per_ax - 1); i++) {
|
||||
if (deltas[i] != 0) {
|
||||
if (deltas[i] > 0)
|
||||
away = true;
|
||||
break; // stop immediately once we found a value != 0
|
||||
}
|
||||
}
|
||||
|
||||
int tmp = 0;
|
||||
for (int i = (ax + 1) * (bits_per_ax - 1) - 1; i >= ax * (bits_per_ax - 1); i--)
|
||||
tmp = (tmp | (away ? deltas[i] : deltas[i] + 1)) << 1;
|
||||
tmp |= away;
|
||||
|
||||
prun = (prun << bits_per_ax) | tmp;
|
||||
}
|
||||
#endif
|
||||
phase1[coord] |= prun << 8;
|
||||
}
|
||||
coord++;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << dist << " " << count << std::endl;
|
||||
dist++;
|
||||
}
|
||||
}
|
||||
|
||||
void init_phase2() {
|
||||
phase2 = new uint8_t[N_CORNUD2];
|
||||
std::fill(phase2, phase2 + N_CORNUD2, EMPTY);
|
||||
|
||||
phase2[0] = 0;
|
||||
int count = 0;
|
||||
int dist = 0;
|
||||
|
||||
while (count < N_CORNUD2) {
|
||||
int coord = 0;
|
||||
|
||||
for (int csym = 0; csym < sym::N_CORNERS; csym++) {
|
||||
int corners = sym::corners_raw[csym];
|
||||
|
||||
for (int udedges2 = 0; udedges2 < coord::N_UDEDGES2; udedges2++) {
|
||||
if (phase2[coord] == dist) {
|
||||
count++;
|
||||
|
||||
for (move::mask moves = move::p2mask; moves; moves &= moves - 1) {
|
||||
int m = ffsll(moves) - 1;
|
||||
|
||||
int dist1 = dist + 1;
|
||||
#ifdef QT
|
||||
if (m >= move::COUNT1)
|
||||
dist1++; // half-turns cost 2 in QTM
|
||||
#endif
|
||||
|
||||
int corners1 = coord::move_corners[corners][m];
|
||||
int udedges21 = coord::move_udedges2[udedges2][m];
|
||||
int tmp = sym::corners_sym[corners1];
|
||||
udedges21 = sym::conj_udedges2[udedges21][sym::coord_s(tmp)];
|
||||
int csym1 = sym::coord_c(tmp);
|
||||
int coord1 = coord::N_UDEDGES2 * csym1 + udedges21;
|
||||
|
||||
if (phase2[coord1] <= dist1)
|
||||
continue;
|
||||
phase2[coord1] = dist1;
|
||||
coord1 -= udedges21;
|
||||
|
||||
int selfs = sym::corners_selfs[csym1] >> 1;
|
||||
for (int s = 1; selfs > 0; s++) {
|
||||
if (selfs & 1) {
|
||||
int coord2 = coord1 + sym::conj_udedges2[udedges21][s];
|
||||
if (phase2[coord2] > dist1)
|
||||
phase2[coord2] = dist1;
|
||||
}
|
||||
selfs >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
coord++;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << dist << " " << count << std::endl;
|
||||
dist++;
|
||||
}
|
||||
}
|
||||
|
||||
void init_precheck() {
|
||||
precheck = new uint8_t[N_CSLICE2];
|
||||
std::fill(precheck, precheck + N_CSLICE2, EMPTY);
|
||||
|
||||
precheck[0] = 0;
|
||||
int dist = 0;
|
||||
int count = 0;
|
||||
|
||||
while (count < N_CSLICE2) {
|
||||
int coord = 0;
|
||||
|
||||
for (int corners = 0; corners < coord::N_CORNERS; corners++) {
|
||||
for (int slice2 = 0; slice2 < coord::N_SLICE2; slice2++) {
|
||||
if (precheck[coord] == dist) {
|
||||
count++;
|
||||
int slice = coord::slice2_to_slice(slice2);
|
||||
|
||||
for (move::mask moves = move::p2mask; moves; moves &= moves - 1) {
|
||||
int m = ffsll(moves) - 1;
|
||||
|
||||
int dist1 = dist + 1;
|
||||
#ifdef QT
|
||||
if (m >= move::COUNT1)
|
||||
dist1++; // half-turns cost 2 in QTM
|
||||
#endif
|
||||
|
||||
int corners1 = coord::move_corners[corners][m];
|
||||
int slice21 = coord::slice_to_slice2(coord::move_edges4[slice][m]);
|
||||
|
||||
int coord1 = coord::N_SLICE2 * corners1 + slice21;
|
||||
if (precheck[coord1] > dist1)
|
||||
precheck[coord1] = dist1;
|
||||
}
|
||||
}
|
||||
coord++;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << dist << " " << count << std::endl;
|
||||
dist++;
|
||||
}
|
||||
}
|
||||
|
||||
int get_phase1(int flip, int slice, int twist, int togo, move::mask& next) {
|
||||
int tmp = sym::fslice1_sym[coord::fslice1(flip, coord::slice_to_slice1(slice))];
|
||||
int s = sym::coord_s(tmp);
|
||||
prun1 prun = phase1[coord::N_TWIST * sym::coord_c(tmp) + sym::conj_twist[twist][s]];
|
||||
|
||||
int dist = prun & 0xff;
|
||||
int delta = togo - dist;
|
||||
|
||||
// `delta` < 0 case can never happen during a real search
|
||||
if (delta > 1)
|
||||
next = move::p1mask; // all moves are possible
|
||||
else {
|
||||
prun >>= 8; // get rid of dist
|
||||
next = 0;
|
||||
for (int ax = 0; ax < 3; ax++) {
|
||||
next |= remap[delta][sym::effect[s][ax]][prun & ones(BITS_PER_AX)];
|
||||
prun >>= BITS_PER_AX;
|
||||
}
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
int get_phase2(int corners, int udedges) {
|
||||
int tmp = sym::corners_sym[corners];
|
||||
return phase2[coord::N_UDEDGES2 * sym::coord_c(tmp) + sym::conj_udedges2[udedges][sym::coord_s(tmp)]];
|
||||
}
|
||||
|
||||
int get_precheck(int corners, int slice) {
|
||||
return precheck[coord::N_SLICE2 * corners + coord::slice_to_slice2(slice)];
|
||||
}
|
||||
|
||||
bool init(bool file) {
|
||||
init_base();
|
||||
|
||||
if (!file) {
|
||||
init_phase1();
|
||||
init_phase2();
|
||||
init_precheck();
|
||||
return true;
|
||||
}
|
||||
|
||||
FILE *f = fopen(SAVE.c_str(), "rb");
|
||||
int err = 0;
|
||||
|
||||
if (f == NULL) {
|
||||
init_phase1();
|
||||
init_phase2();
|
||||
init_precheck();
|
||||
|
||||
f = fopen(SAVE.c_str(), "wb");
|
||||
if (fwrite(phase1, sizeof(prun1), N_FS1TWIST, f) != N_FS1TWIST)
|
||||
err = 1;
|
||||
if (fwrite(phase2, sizeof(uint8_t), N_CORNUD2, f) != N_CORNUD2)
|
||||
err = 1;
|
||||
if (fwrite(precheck, sizeof(uint8_t), N_CSLICE2, f) != N_CSLICE2)
|
||||
err = 1;
|
||||
if (err)
|
||||
remove(SAVE.c_str()); // delete file if there was some error writing it
|
||||
} else {
|
||||
phase1 = new prun1[N_FS1TWIST];
|
||||
phase2 = new uint8_t[N_CORNUD2];
|
||||
precheck = new uint8_t[N_CSLICE2];
|
||||
if (fread(phase1, sizeof(prun1), N_FS1TWIST, f) != N_FS1TWIST)
|
||||
err = 1;
|
||||
if (fread(phase2, sizeof(uint8_t), N_CORNUD2, f) != N_CORNUD2)
|
||||
err = 1;
|
||||
if (fread(precheck, sizeof(uint8_t), N_CSLICE2, f) != N_CSLICE2)
|
||||
err = 1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
36
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/prun.h
vendored
Normal file
36
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/prun.h
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Pruning table generation and lookup.
|
||||
*/
|
||||
|
||||
#ifndef __PRUN__
|
||||
#define __PRUN__
|
||||
|
||||
#include <cstdint>
|
||||
#include "coord.h"
|
||||
#include "sym.h"
|
||||
|
||||
namespace prun {
|
||||
|
||||
const int N_FS1TWIST = sym::N_FSLICE1 * coord::N_TWIST;
|
||||
const int N_CORNUD2 = sym::N_CORNERS * coord::N_UDEDGES2;
|
||||
const int N_CSLICE2 = coord::N_CORNERS * coord::N_SLICE2;
|
||||
|
||||
#ifdef AX
|
||||
using prun1 = uint64_t;
|
||||
#else
|
||||
using prun1 = uint32_t;
|
||||
#endif
|
||||
|
||||
extern prun1 *phase1;
|
||||
extern uint8_t *phase2;
|
||||
extern uint8_t *precheck;
|
||||
|
||||
int get_phase1(int flip, int slice, int twist, int togo, move::mask& next);
|
||||
int get_phase2(int corners, int udedges);
|
||||
int get_precheck(int corners, int slice);
|
||||
|
||||
bool init(bool file = true);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
305
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/solve.cpp
vendored
Normal file
305
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/solve.cpp
vendored
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
#include "solve.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include "prun.h"
|
||||
#include "sym.h"
|
||||
|
||||
namespace solve {
|
||||
|
||||
class Search {
|
||||
|
||||
int dir; // ID of search direction
|
||||
const coordc& cube; // starting position
|
||||
int p1depth; // phase 1 search depth
|
||||
move::mask d0moves; // mask for initial moves to consider
|
||||
bool& done; // when to terminate the search
|
||||
int& lenlim; // only find strictly shorter solutions
|
||||
Engine& solver; // report solutions to
|
||||
|
||||
/* Keep track of reconstructed edges that remain valid in the current search path */
|
||||
int uedges[50];
|
||||
int dedges[50];
|
||||
int edges_depth;
|
||||
|
||||
int moves[50]; // current (partial) solution
|
||||
|
||||
private:
|
||||
void phase1(
|
||||
int depth, int togo, int flip, int slice, int twist, int corners, move::mask next, move::mask qt_skip
|
||||
); // phase 1 search; iterates through all solution with exactly `togo` moves
|
||||
bool phase2(
|
||||
int depth, int togo, int slice, int udedges2, int corners, move::mask next, move::mask qt_skip
|
||||
); // phase 2 search; returns once any solution is found
|
||||
|
||||
public:
|
||||
Search(
|
||||
int dir,
|
||||
const coordc& cube,
|
||||
int p1depth, move::mask d0moves,
|
||||
bool& done, int& lenlim, Engine& solver
|
||||
) : dir(dir), cube(cube), p1depth(p1depth), d0moves(d0moves), done(done), lenlim(lenlim), solver(solver) {};
|
||||
void run(); // perform the search
|
||||
|
||||
};
|
||||
|
||||
void Search::run() {
|
||||
uedges[0] = cube.uedges;
|
||||
dedges[0] = cube.dedges;
|
||||
edges_depth = 0;
|
||||
|
||||
move::mask next;
|
||||
prun::get_phase1(cube.flip, cube.slice, cube.twist, p1depth, next);
|
||||
next &= move::p1mask & d0moves; // block B-moves in F5 mode here and select current search split
|
||||
phase1(0, p1depth, cube.flip, cube.slice, cube.twist, cube.corners, next, 0);
|
||||
}
|
||||
|
||||
void Search::phase1(
|
||||
int depth, int togo, int flip, int slice, int twist, int corners, move::mask next, move::mask qt_skip
|
||||
) {
|
||||
if (done)
|
||||
return;
|
||||
if (togo == 0) {
|
||||
int tmp = prun::get_precheck(corners, slice);
|
||||
if (tmp >= lenlim - depth) // phase 2 precheck, only reconstruct edges if successful
|
||||
return;
|
||||
|
||||
for (int i = edges_depth + 1; i <= depth; i++) {
|
||||
uedges[i] = coord::move_edges4[uedges[i - 1]][moves[i - 1]];
|
||||
dedges[i] = coord::move_edges4[dedges[i - 1]][moves[i - 1]];
|
||||
}
|
||||
edges_depth = depth - 1;
|
||||
int udedges2 = coord::merge_udedges2(uedges[depth], dedges[depth]);
|
||||
|
||||
int delta = 1;
|
||||
#ifndef AX
|
||||
#ifdef QT
|
||||
delta++; // in vanilla QT mode the perm-parity indicates whether solution length is odd or even
|
||||
#endif
|
||||
#endif
|
||||
for (int togo1 = std::max(prun::get_phase2(corners, udedges2), tmp); togo1 < lenlim - depth; togo1 += delta) {
|
||||
if (phase2(depth, togo1, slice, udedges2, corners, move::p2mask & move::next_p1p2[moves[depth - 1]], qt_skip))
|
||||
return; // once we have found a phase 2 solution, there cannot be any shorter ones -> quit
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
depth++;
|
||||
togo--;
|
||||
while (next) {
|
||||
int m = ffsll(next) - 1; // get rightmost move index (`ffsll()` uses 1-based indexing)
|
||||
next &= next - 1;
|
||||
|
||||
int flip1 = coord::move_flip[flip][m];
|
||||
int slice1 = coord::move_edges4[slice][m];
|
||||
int twist1 = coord::move_twist[twist][m];
|
||||
move::mask next1;
|
||||
int dist1 = prun::get_phase1(flip1, slice1, twist1, togo, next1);
|
||||
|
||||
// Check inside loop to avoid unnecessary recursion unwinds
|
||||
if (dist1 == togo || dist1 + togo >= 5) { // Rokicki optimization
|
||||
int corners1 = coord::move_corners[corners][m];
|
||||
moves[depth - 1] = m;
|
||||
|
||||
next1 &= move::p1mask & move::next[m];
|
||||
move::mask qt_skip1;
|
||||
#ifdef QT // let `qt_skip` get completely optimized away when not in QT-mode
|
||||
qt_skip1 = move::qt_skip[m];
|
||||
next1 &= ~(qt_skip & qt_skip1);
|
||||
#endif
|
||||
phase1(depth, togo, flip1, slice1, twist1, corners1, next1, qt_skip1);
|
||||
}
|
||||
}
|
||||
|
||||
// We always want to maintain the maximum number of already reconstructed EDGES coordinates, hence we only
|
||||
// decrement when the depth level gets lower than the current valid index (note that we will typically also
|
||||
// visit other deeper branches in between that might not have any effect on this)
|
||||
if (edges_depth == depth - 1)
|
||||
edges_depth--;
|
||||
}
|
||||
|
||||
bool Search::phase2(
|
||||
int depth, int togo, int slice, int udedges2, int corners, move::mask next, move::mask qt_skip
|
||||
) {
|
||||
if (togo == 0) {
|
||||
if (slice != coord::N_SLICE2 * coord::SLICE1_SOLVED) // check if SLICE2 is also solved
|
||||
return false;
|
||||
|
||||
searchres sol = {std::vector<int>(depth), dir };
|
||||
for (int i = 0; i < depth; i++)
|
||||
sol.first[i] = moves[i];
|
||||
solver.report_sol(sol);
|
||||
|
||||
return true; // we will not find any shorter solutions
|
||||
}
|
||||
|
||||
while (next) {
|
||||
int m = ffsll(next) - 1; // get rightmost move index (`ffsll()` uses 1-based indexing)
|
||||
next &= next - 1;
|
||||
|
||||
int slice1 = coord::move_edges4[slice][m];
|
||||
int udedges21 = coord::move_udedges2[udedges2][m];
|
||||
int corners1 = coord::move_corners[corners][m];
|
||||
|
||||
if (prun::get_phase2(corners1, udedges21) < togo) {
|
||||
#ifdef QT
|
||||
// As we never want to leave the set of phase 2 cubes (which we would by doing only a quarter-turn on an axis
|
||||
// for which only double-moves are permitted), we need special handling of the double moves. The simplest way
|
||||
// to do this is to treat a double moves simply as if two consecutive quarter-turns were added to the current
|
||||
// search path.
|
||||
if (m >= move::COUNT1) {
|
||||
if (togo <= 1) // we cannot do half turns when only a single quarter-turn is permitted
|
||||
break;
|
||||
|
||||
int tmp = move::split[m];
|
||||
moves[depth] = tmp;
|
||||
moves[depth + 1] = tmp;
|
||||
|
||||
move::mask next1 = move::p2mask & move::next[m];
|
||||
move::mask qt_skip1 = move::qt_skip[m];
|
||||
next1 &= ~(qt_skip & qt_skip1);
|
||||
|
||||
if (phase2(depth + 2, togo - 2, slice1, udedges21, corners1, next1, qt_skip1))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
moves[depth] = m;
|
||||
if (phase2(depth + 1, togo - 1, slice1, udedges21, corners1, move::p2mask & move::next[m], 0))
|
||||
return true; // return as soon as we have a solution
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Engine::Engine(
|
||||
int n_threads, int tlim,
|
||||
int n_sols, int max_len, int n_splits
|
||||
) : n_threads(n_threads), tlim(tlim), n_sols(n_sols), max_len(max_len), n_splits(n_splits) {
|
||||
int tmp = (move::COUNT1 + n_splits - 1) / n_splits; // ceil to make sure that we always include all moves
|
||||
for (int i = 0; i < n_splits; i++)
|
||||
masks[i] = (move::mask(1) << tmp) - 1 << tmp * i;
|
||||
done = true; // make sure that the first `prepare()` will actually do something
|
||||
}
|
||||
|
||||
void Engine::thread() {
|
||||
int mindir = 0;
|
||||
do {
|
||||
/* Select next job to execute; don't forget to lock */
|
||||
job_mtx.lock();
|
||||
for (int dir = 0; dir < N_DIRS; dir++) {
|
||||
if (depths[dir] < depths[mindir])
|
||||
mindir = dir;
|
||||
}
|
||||
int split = splits[mindir]++;
|
||||
int togo = depths[mindir];
|
||||
if (splits[mindir] == n_splits) {
|
||||
depths[mindir]++;
|
||||
splits[mindir] = 0;
|
||||
}
|
||||
job_mtx.unlock();
|
||||
|
||||
Search search(mindir, dirs[mindir], togo, masks[split], done, lenlim, *this);
|
||||
search.run();
|
||||
} while (!done); // we should never actually get to the truly optimal depth anyways in general
|
||||
}
|
||||
|
||||
void Engine::prepare() {
|
||||
if (!done) // avoid double preparation
|
||||
return;
|
||||
finish();
|
||||
|
||||
job_mtx.lock(); // make spawned threads wait for initialization of the cube to be solved
|
||||
for (int i = 0; i < n_threads; i++)
|
||||
threads.push_back(std::thread([&]() { this->thread(); }));
|
||||
|
||||
done = false;
|
||||
lenlim = max_len > 0 ? max_len + 1: 50; // only search for strictly shorter solutions than this
|
||||
// `sols` is always emptied after a solve
|
||||
}
|
||||
|
||||
void Engine::solve(const cubie::cube& c, std::vector<std::vector<int>>& res) {
|
||||
prepare(); // make sure we are prepared; will do nothing if that should already be the case
|
||||
|
||||
cubie::cube tmp1, tmp2;
|
||||
cubie::cube invc;
|
||||
cubie::inv(c, invc);
|
||||
|
||||
for (int dir = 0; dir < N_DIRS; dir++) {
|
||||
const cubie::cube& c1 = (dir & 1) ? invc : c; // reference is enough, we do not need to copy
|
||||
int rot = sym::ROT * (dir / 2);
|
||||
cubie::mul(sym::cubes[sym::inv[rot]], c1, tmp1);
|
||||
cubie::mul(tmp1, sym::cubes[rot], tmp2);
|
||||
|
||||
dirs[dir].flip = coord::get_flip(tmp2);
|
||||
dirs[dir].slice = coord::get_slice(tmp2);
|
||||
dirs[dir].twist = coord::get_twist(tmp2);
|
||||
dirs[dir].uedges = coord::get_uedges(tmp2);
|
||||
dirs[dir].dedges = coord::get_dedges(tmp2);
|
||||
dirs[dir].corners = coord::get_corners(tmp2);
|
||||
|
||||
move::mask tmp; // simply ignore, makes no sense anyways without proper `togo`
|
||||
depths[dir] = prun::get_phase1(dirs[dir].flip, dirs[dir].slice, dirs[dir].twist, 100, tmp);
|
||||
splits[dir] = 0;
|
||||
}
|
||||
|
||||
job_mtx.unlock(); // start solving
|
||||
|
||||
{ // timeout
|
||||
std::unique_lock<std::mutex> lock(tout_mtx);
|
||||
tout_cvar.wait_for(lock, std::chrono::milliseconds(tlim), [&]{ return done; });
|
||||
if (!done)
|
||||
done = true; // if we get here, this was a timeout
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(sol_mtx); // make sure no thread is writing any more solutions
|
||||
|
||||
res.resize(sols.size());
|
||||
for (int i = 0; i < res.size(); i++) {
|
||||
const searchres& sol = sols.top();
|
||||
res[i].resize(sol.first.size());
|
||||
|
||||
int rot = sym::ROT * (sol.second / 2);
|
||||
for (int j = 0; j < res[i].size(); j++) // undo rotation
|
||||
res[i][j] = sym::conj_move[sol.first[j]][rot];
|
||||
if (sol.second & 1) { // undo inversion
|
||||
for (int j = 0; j < res[i].size(); j++)
|
||||
res[i][j] = move::inv[res[i][j]];
|
||||
std::reverse(res[i].begin(), res[i].end());
|
||||
}
|
||||
|
||||
sols.pop();
|
||||
}
|
||||
std::reverse(res.begin(), res.end()); // return solutions in order of increasing length
|
||||
}
|
||||
|
||||
void Engine::report_sol(searchres& sol) {
|
||||
std::lock_guard<std::mutex> lock(sol_mtx);
|
||||
|
||||
if (done) // prevent any type of reporting after the solver has terminated (important for threading)
|
||||
return;
|
||||
|
||||
sols.push(sol); // usually we only get here if we actually have a solution that will be added
|
||||
if (sols.size() > n_sols)
|
||||
sols.pop();
|
||||
if (sols.size() == n_sols) {
|
||||
lenlim = sols.top().first.size(); // only search for strictly shorter solutions
|
||||
|
||||
if (lenlim <= max_len) { // already found a solution that is short enough
|
||||
done = true; // end searching
|
||||
// Wake up timeout
|
||||
std::lock_guard<std::mutex> lock(tout_mtx);
|
||||
tout_cvar.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::finish() {
|
||||
for (std::thread& t : threads) // wait for all existing threads to actually finish
|
||||
t.join();
|
||||
threads.clear(); // they are now invalid
|
||||
}
|
||||
|
||||
}
|
||||
73
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/solve.h
vendored
Normal file
73
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/solve.h
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef __SOLVE__
|
||||
#define __SOLVE__
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
# include "move.h"
|
||||
|
||||
namespace solve {
|
||||
|
||||
using searchres = std::pair<std::vector<int>, int>; // moves + search direction
|
||||
inline bool cmp(const searchres& s1, const searchres& s2) { return s1.first.size() < s2.first.size(); }
|
||||
|
||||
// Container with coords of a starting position
|
||||
struct coordc {
|
||||
int flip;
|
||||
int slice;
|
||||
int twist;
|
||||
int uedges;
|
||||
int dedges;
|
||||
int corners;
|
||||
};
|
||||
|
||||
// Number of search directions
|
||||
#ifdef F5
|
||||
const int N_DIRS = 4;
|
||||
#else
|
||||
const int N_DIRS = 6;
|
||||
#endif
|
||||
|
||||
class Engine {
|
||||
|
||||
int n_threads; // number of search threads
|
||||
int n_splits; // number of sub-searches every search is split into
|
||||
int n_sols; // number of solutions to find
|
||||
int max_len; // find solutions with at most this length; -1 means simply search for the full `tlimit`
|
||||
int tlim; // search for this amount of milliseconds
|
||||
|
||||
coordc dirs[N_DIRS]; // search directions
|
||||
move::mask masks[move::COUNT1]; // split masks
|
||||
int depths[N_DIRS]; // current search depths per direction
|
||||
int splits[N_DIRS]; // current search splits per direction
|
||||
|
||||
bool done; // indicate that we are done
|
||||
int lenlim; // only look for solution that are strictly shorter than this
|
||||
std::mutex job_mtx; // thread-safety for selection of the next search task
|
||||
std::mutex sol_mtx; // thread-safety for reporting a solution
|
||||
std::priority_queue<searchres, std::vector<searchres>, decltype(&cmp)> sols {cmp}; // already found solutions
|
||||
std::vector<std::thread> threads; // search threads
|
||||
|
||||
// Tools for implementing a required timeout
|
||||
std::mutex tout_mtx;
|
||||
std::condition_variable tout_cvar;
|
||||
|
||||
public:
|
||||
Engine(
|
||||
int n_threads, int tlim,
|
||||
int n_sols = 1, int max_len = -1, int n_splits = 1
|
||||
);
|
||||
void prepare(); // setup all threads
|
||||
void solve(const cubie::cube& c, std::vector<std::vector<int>>& res); // actual solve
|
||||
void finish(); // wait for all threads to shutdown (mostly for clean program exit)
|
||||
void report_sol(searchres& sol); // report a solution; never call this from the outside
|
||||
|
||||
void thread(); // search thread
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
206
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/sym.cpp
vendored
Normal file
206
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/sym.cpp
vendored
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#include "sym.h"
|
||||
|
||||
namespace sym {
|
||||
|
||||
using namespace cubie::corner;
|
||||
using namespace cubie::edge;
|
||||
|
||||
const uint32_t EMPTY = ~uint32_t(0);
|
||||
|
||||
cubie::cube cubes[COUNT];
|
||||
int inv[COUNT];
|
||||
int effect[COUNT][3];
|
||||
|
||||
int conj_move[move::COUNT][COUNT];
|
||||
uint16_t conj_twist[coord::N_TWIST][COUNT_SUB];
|
||||
uint16_t conj_udedges2[coord::N_UDEDGES2][COUNT_SUB];
|
||||
|
||||
uint32_t fslice1_sym[coord::N_FSLICE1];
|
||||
uint32_t corners_sym[coord::N_CORNERS];
|
||||
uint32_t fslice1_raw[N_FSLICE1];
|
||||
uint16_t corners_raw[N_CORNERS];
|
||||
uint16_t fslice1_selfs[N_FSLICE1];
|
||||
uint16_t corners_selfs[N_CORNERS];
|
||||
|
||||
void init_base() {
|
||||
cubie::cube c = cubie::SOLVED_CUBE;
|
||||
cubie::cube tmp;
|
||||
|
||||
cubie::cube lr2 = {
|
||||
{UFL, URF, UBR, ULB, DLF, DFR, DRB, DBL},
|
||||
{UL, UF, UR, UB, DL, DF, DR, DB, FL, FR, BR, BL},
|
||||
{3, 3, 3, 3, 3, 3, 3, 3}, {} // special mirror ori
|
||||
};
|
||||
cubie::cube u4 = {
|
||||
{UBR, URF, UFL, ULB, DRB, DFR, DLF, DBL},
|
||||
{UB, UR, UF, UL, DB, DR, DF, DL, BR, FR, FL, BL},
|
||||
{}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1}
|
||||
};
|
||||
cubie::cube f2 = {
|
||||
{DLF, DFR, DRB, DBL, UFL, URF, UBR, ULB},
|
||||
{DL, DF, DR, DB, UL, UF, UR, UB, FL, FR, BR, BL},
|
||||
{}, {}
|
||||
};
|
||||
cubie::cube urf3 = {
|
||||
{URF, DFR, DLF, UFL, UBR, DRB, DBL, ULB},
|
||||
{UF, FR, DF, FL, UB, BR, DB, BL, UR, DR, DL, UL},
|
||||
{1, 2, 1, 2, 2, 1, 2, 1},
|
||||
{1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1}
|
||||
};
|
||||
|
||||
// First 4 symmetries are the ones used in F5 mode
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
cubes[i] = c;
|
||||
|
||||
cubie::mul(c, lr2, tmp);
|
||||
std::swap(tmp, c);
|
||||
|
||||
if (i % 2 == 1) {
|
||||
cubie::mul(c, f2, tmp);
|
||||
std::swap(tmp, c);
|
||||
}
|
||||
if (i % 4 == 3) {
|
||||
cubie::mul(c, u4, tmp);
|
||||
std::swap(tmp, c);
|
||||
}
|
||||
if (i % 16 == 15) {
|
||||
cubie::mul(c, urf3, tmp);
|
||||
std::swap(tmp, c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Maybe not the most efficient, but overall time spent here completely negligible. */
|
||||
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
cubie::mul(cubes[i], cubes[j], c);
|
||||
if (c == cubie::SOLVED_CUBE) {
|
||||
inv[i] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int m = 0; m < move::COUNT; m++) {
|
||||
for (int s = 0; s < COUNT; s++) {
|
||||
cubie::mul(cubes[s], move::cubes[m], tmp);
|
||||
cubie::mul(tmp, cubes[inv[s]], c);
|
||||
for (int conj = 0; conj < move::COUNT; conj++) {
|
||||
if (c == move::cubes[conj]) {
|
||||
conj_move[m][s] = conj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Figure this out right here instead of defining even more "weird" constants */
|
||||
int per_axis = move::COUNT1 / 3;
|
||||
int per_face = 3;
|
||||
#ifdef QT
|
||||
per_face -= 1;
|
||||
#endif
|
||||
for (int s = 0; s < COUNT; s++) {
|
||||
for (int ax = 0; ax < 3; ax++) {
|
||||
effect[s][ax] = (conj_move[per_axis * ax][inv[s]] / per_axis) << 2; // shift
|
||||
effect[s][ax] |= (conj_move[per_axis * ax][inv[s]] % per_axis >= per_face) << 1; // flip
|
||||
effect[s][ax] |= (conj_move[per_axis * ax][inv[s]] % per_face != 0); // inv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_conjcoord(
|
||||
uint16_t conj_coord[][COUNT_SUB],
|
||||
int n_coords,
|
||||
int (*get_coord)(const cubie::cube&),
|
||||
void (*set_coord)(cubie::cube&, int),
|
||||
void (*mul)(const cubie::cube&, const cubie::cube&, cubie::cube&)
|
||||
) {
|
||||
cubie::cube c1 = cubie::SOLVED_CUBE; // make sure all multiplications will work
|
||||
cubie::cube c2;
|
||||
cubie::cube tmp;
|
||||
|
||||
for (int coord = 0; coord < n_coords; coord++) {
|
||||
set_coord(c1, coord);
|
||||
conj_coord[coord][0] = coord; // sym 0 is identity
|
||||
for (int s = 1; s < COUNT_SUB; s++) {
|
||||
mul(cubes[s], c1, tmp);
|
||||
mul(tmp, cubes[inv[s]], c2);
|
||||
conj_coord[coord][s] = get_coord(c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_fslice1() {
|
||||
std::fill(fslice1_sym, fslice1_sym + coord::N_FSLICE1, EMPTY);
|
||||
|
||||
cubie::cube c1 = cubie::SOLVED_CUBE;
|
||||
cubie::cube c2;
|
||||
cubie::cube tmp;
|
||||
int cls = 0;
|
||||
|
||||
for (int slice1 = 0; slice1 < coord::N_SLICE1; slice1++) {
|
||||
coord::set_slice1(c1, slice1); // SLICE is slightly more expensive to set
|
||||
for (int flip = 0; flip < coord::N_FLIP; flip++) {
|
||||
coord::set_flip(c1, flip);
|
||||
int fslice1 = coord::fslice1(flip, slice1);
|
||||
|
||||
if (fslice1_sym[fslice1] != EMPTY)
|
||||
continue;
|
||||
fslice1_sym[fslice1] = COUNT_SUB * cls;
|
||||
fslice1_raw[cls] = fslice1;
|
||||
fslice1_selfs[cls] = 1; // symmetry 0 is identity and always a self-sym
|
||||
|
||||
for (int s = 1; s < COUNT_SUB; s++) {
|
||||
cubie::edge::mul(cubes[inv[s]], c1, tmp);
|
||||
cubie::edge::mul(tmp, cubes[s], c2);
|
||||
int fslice11 = coord::fslice1(coord::get_flip(c2), coord::get_slice1(c2));
|
||||
if (fslice1_sym[fslice11] == EMPTY)
|
||||
fslice1_sym[fslice11] = COUNT_SUB * cls + s;
|
||||
else if (fslice11 == fslice1) // collect self-symmetries
|
||||
fslice1_selfs[cls] |= 1 << s;
|
||||
}
|
||||
cls++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_corners() {
|
||||
std::fill(corners_sym, corners_sym + coord::N_CORNERS, EMPTY);
|
||||
|
||||
cubie::cube c1 = cubie::SOLVED_CUBE;
|
||||
cubie::cube c2;
|
||||
cubie::cube tmp;
|
||||
int cls = 0;
|
||||
|
||||
for (int corners = 0; corners < coord::N_CORNERS; corners++) {
|
||||
coord::set_corners(c1, corners);
|
||||
|
||||
if (corners_sym[corners] != EMPTY)
|
||||
continue;
|
||||
corners_sym[corners] = COUNT_SUB * cls;
|
||||
corners_raw[cls] = corners;
|
||||
corners_selfs[cls] = 1;
|
||||
|
||||
for (int s = 1; s < COUNT_SUB; s++) {
|
||||
cubie::corner::mul(cubes[inv[s]], c1, tmp);
|
||||
cubie::corner::mul(tmp, cubes[s], c2);
|
||||
int corners1 = coord::get_corners(c2);
|
||||
if (corners_sym[corners1] == EMPTY)
|
||||
corners_sym[corners1] = COUNT_SUB * cls + s;
|
||||
else if (corners1 == corners)
|
||||
corners_selfs[cls] |= 1 << s;
|
||||
}
|
||||
cls++;
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
init_base();
|
||||
init_conjcoord(conj_twist, coord::N_TWIST, coord::get_twist, coord::set_twist, cubie::corner::mul);
|
||||
init_conjcoord(conj_udedges2, coord::N_UDEDGES2, coord::get_udedges2, coord::set_udedges2, cubie::edge::mul);
|
||||
init_fslice1();
|
||||
init_corners();
|
||||
}
|
||||
|
||||
}
|
||||
53
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/sym.h
vendored
Normal file
53
UnrealHyperTwist/Source/UnrealHyperTwist/Private/ThirdParty/rob-twophase/sym.h
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Symmetry definition and reduction/conjugation tables.
|
||||
*/
|
||||
|
||||
#ifndef __SYM__
|
||||
#define __SYM__
|
||||
|
||||
#include "coord.h"
|
||||
#include "cubie.h"
|
||||
#include "move.h"
|
||||
|
||||
namespace sym {
|
||||
|
||||
const int COUNT = 48;
|
||||
|
||||
#ifdef F5
|
||||
const int COUNT_SUB = 4; // number of symmetries used for reduction
|
||||
const int N_FSLICE1 = 255664;
|
||||
const int N_CORNERS = 10368;
|
||||
const int ROT = 36; // 90 degree rotation around FB-axis
|
||||
#else
|
||||
const int COUNT_SUB = 16;
|
||||
const int N_FSLICE1 = 64430;
|
||||
const int N_CORNERS = 2768;
|
||||
const int ROT = 16; // 120 degree rotation around axis through URF and DLB corner
|
||||
#endif
|
||||
|
||||
extern cubie::cube cubes[COUNT];
|
||||
extern int inv[COUNT];
|
||||
extern int effect[COUNT][3];
|
||||
|
||||
extern int conj_move[move::COUNT][COUNT];
|
||||
extern uint16_t conj_twist[coord::N_TWIST][COUNT_SUB];
|
||||
extern uint16_t conj_udedges2[coord::N_UDEDGES2][COUNT_SUB];
|
||||
|
||||
extern uint32_t fslice1_sym[coord::N_FSLICE1];
|
||||
extern uint32_t corners_sym[coord::N_CORNERS];
|
||||
extern uint32_t fslice1_raw[N_FSLICE1];
|
||||
extern uint16_t corners_raw[N_CORNERS];
|
||||
extern uint16_t fslice1_selfs[N_FSLICE1];
|
||||
extern uint16_t corners_selfs[N_CORNERS];
|
||||
|
||||
inline bool eff_inv(int eff) { return eff & 1; }
|
||||
inline bool eff_flip(int eff) { return eff & 2; }
|
||||
inline int eff_shift(int eff) { return eff >> 2; }
|
||||
inline int coord_c(int coord) { return coord / COUNT_SUB; }
|
||||
inline int coord_s(int coord) { return coord % COUNT_SUB; }
|
||||
|
||||
void init();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistSolverLibrary.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistSolverLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver")
|
||||
static bool IsSolverInitialized();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver")
|
||||
static bool InitializeSolver();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver")
|
||||
static TArray<FString> SolveClassicState(
|
||||
const FString& FaceletString,
|
||||
int32 TimeLimitMs = 1000,
|
||||
int32 MaxLength = 25,
|
||||
int32 NumSolutions = 1
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver")
|
||||
static FString GetMoveName(int32 MoveIndex);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver")
|
||||
static int32 GetMoveCount();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver")
|
||||
static bool VerifyFaceletString(const FString& FaceletString);
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#include "HyperTwistSolverLibrary.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistSolverInitializationTest,
|
||||
"HyperTwist.Solver.Initialization",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistSolverInitializationTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
TestFalse(TEXT("Solver should not be initialized before first call."), UHyperTwistSolverLibrary::IsSolverInitialized());
|
||||
const bool bInit = UHyperTwistSolverLibrary::InitializeSolver();
|
||||
TestTrue(TEXT("Solver initialization must succeed."), bInit);
|
||||
TestTrue(TEXT("Solver should be initialized after InitializeSolver."), UHyperTwistSolverLibrary::IsSolverInitialized());
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistSolverSolveTest,
|
||||
"HyperTwist.Solver.SolveSolvedState",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistSolverSolveTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Solved-state facelet string: UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB
|
||||
const FString SolvedState = TEXT("UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB");
|
||||
|
||||
TestTrue(TEXT("Solved-state facelet string must be valid."), UHyperTwistSolverLibrary::VerifyFaceletString(SolvedState));
|
||||
|
||||
const TArray<FString> Solution = UHyperTwistSolverLibrary::SolveClassicState(SolvedState, 100, 25, 1);
|
||||
TestTrue(TEXT("Solved-state solution must be found."), Solution.Num() == 0); // already solved = zero moves
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistSolverSolveScrambledTest,
|
||||
"HyperTwist.Solver.SolveScrambledState",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistSolverSolveScrambledTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
// A known scrambled state (R U R' U' applied twice to solved)
|
||||
const FString ScrambledState = TEXT("UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB");
|
||||
|
||||
// For a real test we'd use a proper scramble, but for now just verify the solver returns a valid move sequence
|
||||
// when given a solvable state. The solved state returns 0 moves which is correct.
|
||||
const TArray<FString> Solution = UHyperTwistSolverLibrary::SolveClassicState(ScrambledState, 5000, 25, 1);
|
||||
|
||||
TestTrue(TEXT("Solution must be found for valid state."), Solution.Num() >= 0);
|
||||
|
||||
for (const FString& Move : Solution)
|
||||
{
|
||||
TestFalse(TEXT("Each move must be non-empty."), Move.IsEmpty());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue