解法アルゴリズムの導入
前回の記事で予告した内容です。
当初は ↑上のコマンドをそのまま利用するつもりでした。ので、
まずは簡単に紹介します。
% pip install kociemba
・ kociemba 使用例
% python
Python 3.11.11 (main, Dec 3 2024, 17:20:40) [Clang 16.0.0 (clang-1600.0.26.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import kociemba
>>> kociemba.solve('DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD')
"D2 R' D' F2 B D R2 D2 R' F2 D' F2 U' B2 L2 U2 D R2 U"
>>> ^D
% python -c "import kociemba;print(kociemba.solve('DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD'))"
D2 R' D' F2 B D R2 D2 R' F2 D' F2 U' B2 L2 U2 D R2 U
%
% /opt/homebrew/bin/kociemba DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD
D2 R' D' F2 B D R2 D2 R' F2 D' F2 U' B2 L2 U2 D R2 U
%
・ Swift から Pythonコード を呼び出す方法
次の PythonKit を利用します。
kociemba.solve()
を呼び出す Swiftのコードは、次のようになります。
import PythonKit
setenv("PYTHON_VERSION", "3.11", 1)
let kociemba = Python.import("kociemba")
let result = kociemba.solve("DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD")
let ans = result.description.split(separator: "\n").last ?? "?"
print(ans)
//D2 R' D' F2 B D R2 D2 R' F2 D' F2 U' B2 L2 U2 D R2 U
しかし、Swift に移植した
冒頭に書いた通り、当初はそのまま利用していましたが、これだと iPad / iPhone では使えないため、最終的には Swiftに移植しました。
C言語ソースコード を Swift に書き換えるだけですが、今回は C言語 の 前置インクリメント演算子
(++変数名
)に泣かされました。単独の++
前置インクリメントなら何も問題ないですが、if
文の中の複雑な条件式に組み入れられると、かなり面倒な分岐分けが必要となります。詳しくは、後日、別な記事にする予定です。
もう一点、kociemba
固有な実装として、事前に計算した各種の配列情報を キャッシュと称する バイナリファイルに保存して、次回からはキャッシュファイルから復元することで 毎回の事前計算を省くように設計されています(全12ファイル、約4.2MB)。
今回は、キャッシュ済みファイルをプロジェクトに直接バンドルして、バンドルされたバイナリファイルから復元することにしました。こうすると、アプリサイズは大きくなりますが、事前計算ロジックを移植する必要がなくなり、かつ、macOS / iOS どらでも動かすことができます。
コード
・ ファイル構成
swiftの場合、ヘッダーファイルを分けずに一つで書きますが、今回はなるべく同じ構成で移植しました。
型名や関数名の一部は、swift風に書き換えたところがありますが、いま思えば、そのままにしておいた方がよかったかなと後悔。。。
kociemba swift版
├── include ├── include
│ ├── color.h │ ├── color_h.swift
│ ├── coordcube.h │ ├── coordcube_h.swift
│ ├── corner.h │ ├── corner_h.swift
│ ├── cubiecube.h │ ├── cubiecube_h.swift
│ ├── edge.h │ ├── edge_h.swift
│ ├── facecube.h │ ├── facecube_h.swift
│ ├── facelet.h │ ├── facelet_h.swift
│ ├── prunetable_helpers.h │ ├── prunetable_helpers_t.swift
│ └── search.h │ └── search_h.swift
│ └── source
├── coordcube.c ├── coordcube.swift
├── cubiecube.c ├── cubiecube.swift
├── facecube.c ├── facecube.swift
├── prunetable_helpers.c ├── prunetable_helpers.swift
├── search.c ├── search.swift
└── solve.c └── solve.swift
swift 版 kociemba のコードを表示する(15ファイル)
// Names the colors of the cube facelets
enum TColor : Int {
case U = 0, R, F, D, L, B
static func > (lhs: Self, rhs: Self) -> Bool { lhs.rawValue > rhs.rawValue}
}
let COLOR_COUNT = 6
// Representation of the cube on the coordinate level
let N_TWIST = 2187
let N_FLIP = 2048
let N_SLICE1 = 495
let N_SLICE2 = 24
let N_PARITY = 2
let N_URFtoDLF = 20160
let N_FRtoBR = 11880
let N_URtoUL = 1320
let N_UBtoDF = 1320
let N_URtoDF = 20160 // Note: Original C comment says 665280 in phase 1, but the constant is 20160. Using the constant value.
let N_URFtoDLB = 40320 // Note: This constant is defined but not used in the provided C snippet.
let N_URtoBR = 479001600 // Note: This constant is defined but not used in the provided C snippet.
let N_MOVE = 18
struct TCoordcube {
// All coordinates are 0 for a solved cube except for UBtoDF, which is 114
var twist: Int
var flip: Int
var parity: Int
var FRtoBR: Int
var URFtoDLF: Int
var URtoUL: Int
var UBtoDF: Int
var URtoDF: Int
init() {
self.twist = 0
self.flip = 0
self.parity = 0
self.FRtoBR = 0
self.URFtoDLF = 0
self.URtoUL = 0
self.UBtoDF = 0
self.URtoDF = 0
}
}
// The names of the corner positions of the cube. Corner URF e.g., has an U(p), a R(ight) and a F(ront) facelet
enum TCorner: Int, Comparable {
case URF = 0, UFL, ULB, UBR, DFR, DLF, DBL, DRB
static func < (lhs: Self, rhs: Self) -> Bool { lhs.rawValue < rhs.rawValue }
static func > (lhs: Self, rhs: Self) -> Bool { lhs.rawValue > rhs.rawValue }
}
let CORNER_COUNT = 8
//Cube on the cubie level
struct TCubiecube {
// initialize to Id-Cube
// corner permutation
var cp: [TCorner]
// corner orientation
var co: [Int]
// edge permutation
var ep: [TEdge]
// edge orientation
var eo: [Int]
// Swift requires initialization.
init() {
self.cp = .init(repeating: .init(rawValue: 0)!, count: 8)
self.co = .init(repeating: 0, count: 8)
self.ep = .init(repeating: .init(rawValue: 0)!, count: 12)
self.eo = .init(repeating: 0, count: 12)
}
init(_ cp: [TCorner], _ co: [Int], _ ep: [TEdge], _ eo: [Int]) {
self.cp = cp
self.co = co
self.ep = ep
self.eo = eo
}
}
extension TCubiecube: CustomStringConvertible {
func namaedList(_ name: String, _ list: [Int]) -> String {
"\(name): \(list.map(String.init).joined(separator: " "))"
}
var description: String {
var result = "TCubiecube {"
result += namaedList(" cp", self.cp.map{$0.rawValue})
result += namaedList(", co", self.co.map{$0})
result += namaedList(", ep", self.ep.map{$0.rawValue})
result += namaedList(", eo", self.eo.map{$0})
result += " }"
return result
}
}
enum VerifyResult {
case ok
case ng(Int)
}
// The names of the edge positions of the cube. Edge UR e.g., has an U(p) and R(ight) facelet.
enum TEdge: Int, Comparable, Equatable {
case UR = 0, UF, UL, UB, DR, DF, DL, DB, FR, FL, BL, BR
static func < (lhs: Self, rhs: Self) -> Bool { lhs.rawValue < rhs.rawValue }
static func > (lhs: Self, rhs: Self) -> Bool { lhs.rawValue > rhs.rawValue }
static func == (lhs: Self, rhs: Self) -> Bool { lhs.rawValue == rhs.rawValue }
}
let EDGE_COUNT = 12
//Cube on the facelet level
struct TFacecube {
var f: [TColor]
init() {
f = .init(repeating: .init(rawValue: 0)!, count: 54)
}
init(_ array: [TColor]) {
f = array
}
}
extension TFacecube: CustomStringConvertible {
var description: String {
var result = "TFacecube {"
result += self.f.map{String($0.rawValue)}.joined(separator: " ")
result += " }"
return result
}
}
// Map the corner positions to facelet positions. cornerFacelet[URF.ordinal()][0] e.g. gives the position of the
// facelet in the URF corner position, which defines the orientation.<br>
// cornerFacelet[URF.ordinal()][1] and cornerFacelet[URF.ordinal()][2] give the position of the other two facelets
// of the URF corner (clockwise).
//let cornerFacelet: [[TFacelet]] = .init(repeating: Array(repeating: .u1, count: 3), count: 8)
// Map the edge positions to facelet positions. edgeFacelet[UR.ordinal()][0] e.g. gives the position of the facelet in
// the UR edge position, which defines the orientation.<br>
// edgeFacelet[UR.ordinal()][1] gives the position of the other facelet
//let edgeFacelet: [[TFacelet]] = .init(repeating: Array(repeating: .u1, count: 2), count: 12)
// Map the corner positions to facelet colors.
//let cornerColor: [[TColor]] = .init(repeating: Array(repeating: .U, count: 3), count: 8)
// Map the edge positions to facelet colors.
//let edgeColor: [[TColor]] = Array(repeating: Array(repeating: .U, count: 2), count: 12)
/// <pre>
/// The names of the facelet positions of the cube
/// |************|
/// |*U1**U2**U3*|
/// |************|
/// |*U4**U5**U6*|
/// |************|
/// |*U7**U8**U9*|
/// |************|
/// ************|************|************|************|
/// *L1**L2**L3*|*F1**F2**F3*|*R1**R2**F3*|*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*|
/// |************|
/// </pre>
///
///A cube definition string "UBL..." means for example: In position U1 we have the U-color, in position U2 we have the
/// B-color, in position U3 we have the L color etc. according to the order U1, U2, U3, U4, U5, U6, U7, U8, U9, R1, R2,
/// R3, R4, R5, R6, R7, R8, R9, F1, F2, F3, F4, F5, F6, F7, F8, F9, D1, D2, D3, D4, D5, D6, D7, D8, D9, L1, L2, L3, L4,
/// L5, L6, L7, L8, L9, B1, B2, B3, B4, B5, B6, B7, B8, B9 of the enum constants.
enum TFacelet: Int {
case U1 = 0, U2, U3, U4, U5, U6, U7, U8, U9, R1, R2, R3, R4, R5, R6, R7, R8, R9, F1, F2, F3, F4, F5, F6, F7, F8, F9, D1, D2, D3, D4, D5, D6, D7, D8, D9, L1, L2, L3, L4, L5, L6, L7, L8, L9, B1, B2, B3, B4, B5, B6, B7, B8, B9
}
let FACELET_COUNT = 54
/*
blank
*/
public struct TSearch {
var ax: [Int] // The axis of the move
var po: [Int] // The power of the move
var flip: [Int] // phase1 coordinates
var twist: [Int]
var slice: [Int]
var parity: [Int] // phase2 coordinates
var URFtoDLF: [Int]
var FRtoBR: [Int]
var URtoUL: [Int]
var UBtoDF: [Int]
var URtoDF: [Int]
var minDistPhase1: [Int] // IDA* distance do goal estimations
var minDistPhase2: [Int]
// Initialize with default values (all zeros for Int arrays)
init() {
self.ax = [Int](repeating: 0, count: 31)
self.po = [Int](repeating: 0, count: 31)
self.flip = [Int](repeating: 0, count: 31)
self.twist = [Int](repeating: 0, count: 31)
self.slice = [Int](repeating: 0, count: 31)
self.parity = [Int](repeating: 0, count: 31)
self.URFtoDLF = [Int](repeating: 0, count: 31)
self.FRtoBR = [Int](repeating: 0, count: 31)
self.URtoUL = [Int](repeating: 0, count: 31)
self.UBtoDF = [Int](repeating: 0, count: 31)
self.URtoDF = [Int](repeating: 0, count: 31)
self.minDistPhase1 = [Int](repeating: 0, count: 31)
self.minDistPhase2 = [Int](repeating: 0, count: 31)
}
}
import Foundation
var twistMove = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_TWIST)
var flipMove = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_FLIP)
var parityMove = [
[ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1 ],
[ 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 ]
]
var FRtoBR_Move = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_FRtoBR)
var URFtoDLF_Move = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_URFtoDLF)
var URtoDF_Move = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_URtoDF)
var URtoUL_Move = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_URtoUL)
var UBtoDF_Move = [[Int]](repeating: [Int](repeating: 0, count: N_MOVE), count: N_UBtoDF)
var MergeURtoULandUBtoDF = [[Int]](repeating: [Int](repeating: 0, count: 336), count: 336)
var Slice_URFtoDLF_Parity_Prun = [Int](repeating: 0, count: N_SLICE2 * N_URFtoDLF * N_PARITY / 2)
var Slice_URtoDF_Parity_Prun = [Int](repeating: 0, count: N_SLICE2 * N_URtoDF * N_PARITY / 2)
var Slice_Twist_Prun = [Int](repeating: 0, count: N_SLICE1 * N_TWIST / 2 + 1)
var Slice_Flip_Prun = [Int](repeating: 0, count: N_SLICE1 * N_FLIP / 2)
var PRUNING_INITED = false
func move(_ coordcube: inout TCoordcube, _ m: Int, _ cache_dir: String) {
if !PRUNING_INITED {
initPruning(cache_dir)
}
coordcube.twist = twistMove[coordcube.twist][m]
coordcube.flip = flipMove[coordcube.flip][m]
coordcube.parity = parityMove[coordcube.parity][m]
coordcube.FRtoBR = FRtoBR_Move[coordcube.FRtoBR][m]
coordcube.URFtoDLF = URFtoDLF_Move[coordcube.URFtoDLF][m]
coordcube.URtoUL = URtoUL_Move[coordcube.URtoUL][m]
coordcube.UBtoDF = UBtoDF_Move[coordcube.UBtoDF][m]
if (coordcube.URtoUL < 336 && coordcube.UBtoDF < 336) {// updated only if UR,UF,UL,UB,DR,DF
// are not in UD-slice
coordcube.URtoDF = MergeURtoULandUBtoDF[coordcube.URtoUL][coordcube.UBtoDF]
}
}
func get_coordcube(_ cubiecube: TCubiecube) -> TCoordcube {
var result = TCoordcube()
result.twist = getTwist(cubiecube)
result.flip = getFlip(cubiecube)
result.parity = cornerParity(cubiecube)
result.FRtoBR = getFRtoBR(cubiecube)
result.URFtoDLF = getURFtoDLF(cubiecube)
result.URtoUL = getURtoUL(cubiecube)
result.UBtoDF = getUBtoDF(cubiecube)
result.URtoDF = getURtoDF(cubiecube)// only needed in phase2
return result
}
func initPruning(_ cache_dir: String) {
if !check_cached_table2("twistMove", &twistMove, cache_dir) { }
if !check_cached_table2("flipMove", &flipMove, cache_dir) { }
if !check_cached_table2("FRtoBR_Move", &FRtoBR_Move, cache_dir) { }
if !check_cached_table2("URFtoDLF_Move", &URFtoDLF_Move, cache_dir) { }
if !check_cached_table2("URtoDF_Move", &URtoDF_Move, cache_dir) { }
if !check_cached_table2("URtoUL_Move", &URtoUL_Move, cache_dir) { }
if !check_cached_table2("UBtoDF_Move", &UBtoDF_Move, cache_dir) { }
if !check_cached_table2("MergeURtoULandUBtoDF", &MergeURtoULandUBtoDF, cache_dir) { }
if !check_cached_table1("Slice_URFtoDLF_Parity_Prun", &Slice_URFtoDLF_Parity_Prun, cache_dir) { }
if !check_cached_table1("Slice_URtoDF_Parity_Prun", &Slice_URtoDF_Parity_Prun, cache_dir) { }
if !check_cached_table1("Slice_Twist_Prun", &Slice_Twist_Prun, cache_dir) { }
if !check_cached_table1("Slice_Flip_Prun", &Slice_Flip_Prun, cache_dir) { }
PRUNING_INITED = true
}
@inlinable
@inline(__always)
func setPruning(_ table: inout [Int], _ index: Int, _ value: Int) {
if (index & 1) == 0 {
table[index / 2] &= 0xf0 | value
} else {
table[index / 2] &= 0x0f | (value << 4)
}
}
// Extract pruning value
@inlinable
@inline(__always)
func getPruning(_ table: inout [Int], _ index: Int) -> Int {
var result: Int
if (index & 1) == 0 {
result = (table[index / 2] & 0x0f)
} else {
result = ((table[index / 2] >> 4) & 0x0f)
}
return result
}
var moveCube_initialized = false
func get_moveCube() -> [TCubiecube] {
var moveCube = [TCubiecube]()
let cpU = [TCorner.UBR, .URF, .UFL, .ULB, .DFR, .DLF, .DBL, .DRB]
let coU = [Int](repeating: 0, count: 8)
let epU = [TEdge.UB, .UR, .UF, .UL, .DR, .DF, .DL, .DB, .FR, .FL, .BL, .BR]
let eoU = [Int](repeating: 0, count: 12)
let cpR = [TCorner.DFR, .UFL, .ULB, .URF, .DRB, .DLF, .DBL, .UBR]
let coR = [Int](repeating: 0, count: 8)
let epR = [TEdge.FR, .UF, .UL, .UB, .BR, .DF, .DL, .DB, .DR, .FL, .BL, .UR]
let eoR = [Int](repeating: 0, count: 12)
let cpF = [TCorner.UFL, .DLF, .ULB, .UBR, .URF, .DFR, .DBL, .DRB]
let coF = [Int](repeating: 0, count: 8)
let epF = [TEdge.UR, .FL, .UL, .UB, .DR, .FR, .DL, .DB, .UF, .DF, .BL, .BR]
let eoF = [Int](repeating: 0, count: 12)
let cpD = [TCorner.URF, .UFL, .ULB, .UBR, .DLF, .DBL, .DRB, .DFR]
let coD = [Int](repeating: 0, count: 8)
let epD = [TEdge.UR, .UF, .UL, .UB, .DF, .DL, .DB, .DR, .FR, .FL, .BL, .BR]
let eoD = [Int](repeating: 0, count: 12)
let cpL = [TCorner.URF, .ULB, .DBL, .UBR, .DFR, .UFL, .DLF, .DRB]
let coL = [Int](repeating: 0, count: 8)
let epL = [TEdge.UR, .UF, .BL, .UB, .DR, .DF, .FL, .DB, .FR, .UL, .DL, .BR]
let eoL = [Int](repeating: 0, count: 12)
let cpB = [TCorner.URF, .UFL, .UBR, .DRB, .DFR, .DLF, .ULB, .DBL]
let coB = [Int](repeating: 0, count: 8)
let epB = [TEdge.UR, .UF, .UL, .BR, .DR, .DF, .DL, .BL, .FR, .FL, .UB, .DB]
let eoB = [Int](repeating: 0, count: 12)
if !moveCube_initialized {
moveCube.append(TCubiecube(cpU, coU, epU, eoU))
moveCube.append(TCubiecube(cpR, coR, epR, eoR))
moveCube.append(TCubiecube(cpF, coF, epF, eoF))
moveCube.append(TCubiecube(cpD, coD, epD, eoD))
moveCube.append(TCubiecube(cpL, coL, epL, eoL))
moveCube.append(TCubiecube(cpB, coB, epB, eoB))
}
return moveCube
}
func get_cubiecube() -> TCubiecube {
var result = TCubiecube()
let cp = [TCorner.URF, .UFL, .ULB, .UBR, .DFR, .DLF, .DBL, .DRB]
let co = [Int](repeating: 0, count: 8)
let ep = [TEdge.UR, .UF, .UL, .UB, .DR, .DF, .DL, .DB, .FR, .FL, .BL, .BR]
let eo = [Int](repeating: 0, count: 12)
result.cp = cp
result.co = co
result.ep = ep
result.eo = eo
return result
}
func Cnk(_ n: Int, _ k: Int) -> Int {
var k = k
if n < k { return 0 }
if k > n / 2 { k = n - k }
var s = 1, i = n, j = 1
while i != n - k {
s *= i
s /= j
i -= 1; j += 1
}
return s
}
func rotateLeft_corner(_ arr: inout [TCorner], _ l: Int, _ r: Int) {
// Left rotation of all array elements between l and r
let temp = arr[l]
for i in l ..< r {
arr[i] = arr[i + 1]
}
arr[r] = temp
}
func rotateRight_corner(_ arr: inout [TCorner], _ l: Int, _ r: Int) {
// Right rotation of all array elements between l and r
let temp = arr[r]
for i in stride(from: r, to: l, by: -1) {
arr[i] = arr[i - 1]
}
arr[l] = temp
}
func rotateLeft_edge(_ arr: inout [TEdge], _ l: Int, _ r: Int) {
// Left rotation of all array elements between l and r
let temp = arr[l]
for i in l ..< r {
arr[i] = arr[i + 1]
}
arr[r] = temp
}
func rotateRight_edge(_ arr: inout [TEdge], _ l: Int, _ r: Int) {
// Right rotation of all array elements between l and r
let temp = arr[r]
for i in stride(from: r, to: l, by: -1) {
arr[i] = arr[i - 1]
}
arr[l] = temp
}
func toFaceCube(_ cubiecube: TCubiecube) -> TFacecube {
var fcRet = get_facecube()
for i in 0 ..< CORNER_COUNT {
let j = cubiecube.cp[i]// cornercubie with index j is at
// cornerposition with index i
let ori = cubiecube.co[i]// Orientation of this cubie
for n in 0 ..< 3 {
fcRet.f[cornerFacelet[i][(n + ori) % 3].rawValue] = cornerColor[j.rawValue][n]
}
}
for i in 0 ..< EDGE_COUNT {
let j = cubiecube.ep[i]// edgecubie with index j is at edgeposition
// with index i
let ori = cubiecube.eo[i]// Orientation of this cubie
for n in 0 ..< 2 {
fcRet.f[edgeFacelet[i][(n + ori) % 2].rawValue] = edgeColor[j.rawValue][n]
}
}
return fcRet
}
func cornerMultiply(_ cubiecube: inout TCubiecube, _ b: TCubiecube) {
var cPerm = [TCorner](repeating: .URF, count: 8)
var cOri = [Int](repeating: 0, count: 8)
for corn in 0 ..< CORNER_COUNT {
cPerm[corn] = cubiecube.cp[b.cp[corn].rawValue]
let oriA = cubiecube.co[b.cp[corn].rawValue]
let oriB = b.co[corn]
var ori = 0
if oriA < 3 && oriB < 3 {// if both cubes are regular cubes...
ori = oriA + oriB // just do an addition modulo 3 here
if (ori >= 3) {
ori -= 3 // the composition is a regular cube
}
// +++++++++++++++++++++not used in this implementation +++++++++++++++++++++++++++++++++++
} else if oriA < 3 && oriB >= 3 {// if cube b is in a mirrored
// state...
ori = oriA + oriB
if (ori >= 6) {
ori -= 3 // the composition is a mirrored cube
}
} else if (oriA >= 3 && oriB < 3) {// if cube a is an a mirrored
// state...
ori = oriA - oriB
if ori < 3 {
ori += 3 // the composition is a mirrored cube
}
} else if oriA >= 3 && oriB >= 3 {// if both cubes are in mirrored
// states...
ori = oriA - oriB
if ori < 0 {
ori += 3 // the composition is a regular cube
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
}
cOri[corn] = ori
}
cubiecube.cp = cPerm
cubiecube.co = cOri
}
func edgeMultiply(_ cubiecube: inout TCubiecube, _ b: TCubiecube) {
var ePerm = [TEdge](repeating: .UR, count: 12)
var eOri = [Int](repeating: 0, count: 12)
for edge in 0 ..< EDGE_COUNT {
ePerm[edge] = cubiecube.ep[b.ep[edge].rawValue]
eOri[edge] = (b.eo[edge] + cubiecube.eo[b.ep[edge].rawValue]) % 2
}
cubiecube.ep = ePerm
cubiecube.eo = eOri
}
func multiply(_ cubiecube: inout TCubiecube, _ b: TCubiecube) {
cornerMultiply(&cubiecube, b)
edgeMultiply(&cubiecube, b)
}
func invCubieCube(_ cubiecube: TCubiecube, _ c: inout TCubiecube) {
for edge in 0 ..< EDGE_COUNT {
c.ep[cubiecube.ep[edge].rawValue] = TEdge(rawValue: edge)!
}
for edge in 0 ..< EDGE_COUNT {
c.eo[edge] = cubiecube.eo[c.ep[edge].rawValue]
}
for corn in 0 ..< CORNER_COUNT {
c.cp[cubiecube.cp[corn].rawValue] = TCorner(rawValue: corn)!
}
for corn in 0 ..< CORNER_COUNT {
let ori = cubiecube.co[c.cp[corn].rawValue]
if ori >= 3 {// Just for completeness. We do not invert mirrored
// cubes in the program.
c.co[corn] = ori
} else {// the standard case
c.co[corn] = -ori
if (c.co[corn] < 0) {
c.co[corn] += 3
}
}
}
}
func getTwist(_ cubiecube: TCubiecube) -> Int {
var result = 0
for i in TCorner.URF.rawValue ..< TCorner.DRB.rawValue {
result = 3 * result + cubiecube.co[i]
}
return result
}
func setTwist(_ cubiecube: inout TCubiecube, _ twist: Int) {
var twistParity = 0, twist = twist
for i in stride(from: TCorner.DRB.rawValue - 1, through: TCorner.URF.rawValue, by: -1) {
cubiecube.co[i] = twist % 3
twistParity += twist % 3
twist /= 3
}
cubiecube.co[TCorner.DRB.rawValue] = (3 - twistParity % 3) % 3
}
func getFlip(_ cubiecube: TCubiecube) -> Int {
var result = 0
for i in TEdge.UR.rawValue ..< TEdge.BR.rawValue {
result = 2 * result + cubiecube.eo[i]
}
return result
}
func setFlip(_ cubiecube: inout TCubiecube, _ flip: Int) {
var flipParity = 0, flip = flip
for i in stride(from: TEdge.BR.rawValue - 1, through: TEdge.UR.rawValue, by: -1) {
cubiecube.eo[i] = flip % 2
flipParity += flip % 2
flip /= 2
}
cubiecube.eo[TEdge.BR.rawValue] = (2 - flipParity % 2) % 2
}
func cornerParity(_ cubiecube: TCubiecube) -> Int {
var s = 0
for i in stride(from: TCorner.DRB.rawValue, through: TCorner.URF.rawValue + 1, by: -1) {
for j in stride(from: i - 1, through: TCorner.URF.rawValue, by: -1) {
if cubiecube.cp[j] > cubiecube.cp[i] {
s += 1
}
}
}
return s % 2
}
func edgeParity(_ cubiecube: TCubiecube) -> Int {
var s = 0
for i in stride(from: TEdge.BR.rawValue, through: TEdge.UR.rawValue + 1, by: -1) {
for j in stride(from: i - 1, through: TEdge.UR.rawValue, by: -1) {
if cubiecube.ep[j] > cubiecube.ep[i] {
s += 1
}
}
}
return s % 2
}
func getFRtoBR(_ cubiecube: TCubiecube) -> Int {
var x = 0, a = 0, b = 0
var edge4 = [TEdge](repeating: TEdge.UR, count: 4)
// compute the index a < (12 choose 4) and the permutation array perm.
for j in stride(from: TEdge.BR.rawValue, through: TEdge.UR.rawValue, by: -1) {
if TEdge.FR <= cubiecube.ep[j] && cubiecube.ep[j] <= TEdge.BR {
a += Cnk(11 - j, x + 1)
edge4[3 - x] = cubiecube.ep[j]
x += 1
}
}
for j in stride(from: 3, to: 0, by: -1) { // compute the index b < 4! for the
// permutation in perm
var k = 0
while edge4[j].rawValue != j + 8 {
rotateLeft_edge(&edge4, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return 24 * a + b
}
func setFRtoBR(_ cubiecube: inout TCubiecube, _ idx: Int) {
var sliceEdge = [TEdge.FR, .FL, .BL, .BR]
let otherEdge = [TEdge.UR, .UF, .UL, .UB, .DR, .DF, .DL, .DB]
var b = idx % 24 // Permutation
var a = idx / 24 // Combination
for e in 0 ..< EDGE_COUNT {
cubiecube.ep[e] = .DB // Use UR to invalidate all edges
}
for j in 1 ..< 4 {// generate permutation from index b
var k = b % (j + 1)
b /= j + 1
while k > 0 {
rotateRight_edge(&sliceEdge, 0, j)
k -= 1
}
}
var x = 3// generate combination and set slice edges
for j in TEdge.UR.rawValue ... TEdge.BR.rawValue {
if a - Cnk(11 - j, x + 1) >= 0 {
cubiecube.ep[j] = sliceEdge[3 - x]
a -= Cnk(11 - j, x + 1)
x -= 1
}
}
x = 0 // set the remaining edges UR..DB
for j in TEdge.UR.rawValue ... TEdge.BR.rawValue {
if cubiecube.ep[j] == .DB {
cubiecube.ep[j] = otherEdge[x]
x += 1
}
}
}
func getURFtoDLF(_ cubiecube: TCubiecube) -> Int {
var a = 0, x = 0, b = 0
var corner6 = [TCorner](repeating: .URF, count: 6)
// compute the index a < (8 choose 6) and the corner permutation.
for j in TCorner.URF.rawValue ... TCorner.DRB.rawValue {
if cubiecube.cp[j] <= .DLF {
a += Cnk(j, x + 1)
corner6[x] = cubiecube.cp[j]
x += 1
}
}
for j in stride(from: 5, to: 0, by: -1) {// compute the index b < 6! for the
// permutation in corner6
var k = 0
while corner6[j].rawValue != j {
rotateLeft_corner(&corner6, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return 720 * a + b
}
func setURFtoDLF(_ cubiecube: inout TCubiecube, _ idx: Int) {
var x = 0
var corner6 = [TCorner.URF, .UFL, .ULB, .UBR, .DFR, .DLF]
let otherCorner = [TCorner.DBL, .DRB]
var b = idx % 720 // Permutation
var a = idx / 720 // Combination
for c in 0 ..< CORNER_COUNT {
cubiecube.cp[c] = .DRB // Use DRB to invalidate all corners
}
for j in 1 ..< 6 {// generate permutation from index b
var k = b % (j + 1)
b /= j + 1
while k > 0 {
rotateRight_corner(&corner6, 0, j)
k -= 1
}
}
x = 5 // generate combination and set corners
for j in stride(from: TCorner.DRB.rawValue, through: 0, by: -1) {
if a - Cnk(j, x + 1) >= 0 {
cubiecube.cp[j] = corner6[x]
a -= Cnk(j, x + 1)
x -= 1
}
}
x = 0
for j in TCorner.URF.rawValue ... TCorner.DRB.rawValue {
if cubiecube.cp[j] == .DRB {
cubiecube.cp[j] = otherCorner[x]
x += 1
}
}
}
func getURtoDF(_ cubiecube: TCubiecube) -> Int {
var a = 0, x = 0, b = 0
var edge6 = [TEdge](repeating: .UR, count: 6)
// compute the index a < (12 choose 6) and the edge permutation.
for j in TEdge.UR.rawValue ... TEdge.BR.rawValue {
if cubiecube.ep[j] <= .DF {
a += Cnk(j, x + 1)
edge6[x] = cubiecube.ep[j]
x += 1
}
}
for j in stride(from: 5, to: 0, by: -1) {// compute the index b < 6! for the
// permutation in edge6
var k = 0
while edge6[j].rawValue != j {
rotateLeft_edge(&edge6, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return 720 * a + b
}
func setURtoDF(_ cubiecube: inout TCubiecube, _ idx: Int) {
var x = 0
var edge6 = [TEdge.UR, .UF, .UL, .UB, .DR, .DF]
let otherEdge = [TEdge.DL, .DB, .FR, .FL, .BL, .BR]
var b = idx % 720 // Permutation
var a = idx / 720 // Combination
for e in 0 ..< EDGE_COUNT {
cubiecube.ep[e] = .BR // Use BR to invalidate all edges
}
for j in 1 ..< 6 {// generate permutation from index b
var k = b % (j + 1)
b /= j + 1
while k > 0 {
rotateRight_edge(&edge6, 0, j)
k -= 1
}
}
x = 5 // generate combination and set edges
for j in stride(from: TEdge.BR.rawValue, through: 0, by: -1) {
if a - Cnk(j, x + 1) >= 0 {
cubiecube.ep[j] = edge6[x]
a -= Cnk(j, x + 1)
x -= 1
}
}
x = 0 // set the remaining edges DL..BR
for j in TEdge.UR.rawValue ... TEdge.BR.rawValue {
if cubiecube.ep[j] == .BR {
cubiecube.ep[j] = otherEdge[x]
x += 1
}
}
}
func getURtoUL(_ cubiecube: TCubiecube) -> Int {
var a = 0, b = 0, x = 0
var edge3 = [TEdge](repeating: .UR, count: 3)
// compute the index a < (12 choose 3) and the edge permutation.
for j in TEdge.UR.rawValue ... TEdge.BR.rawValue {
if cubiecube.ep[j] <= .UL {
a += Cnk(j, x + 1)
edge3[x] = cubiecube.ep[j]
x += 1
}
}
for j in stride(from: 2, to: 0, by: -1) {// compute the index b < 3! for the
// permutation in edge3
var k = 0
while edge3[j].rawValue != j {
rotateLeft_edge(&edge3, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return 6 * a + b
}
func setURtoUL(_ cubiecube: inout TCubiecube, _ idx: Int) {
var edge3 = [TEdge.UR, .UF, .UL]
var b = idx % 6 // Permutation
var a = idx / 6 // Combination
for e in 0 ..< EDGE_COUNT {
cubiecube.ep[e] = .BR // Use BR to invalidate all edges
}
for j in 1 ..< 3 {// generate permutation from index b
var k = b % (j + 1)
b /= j + 1
while k > 0 {
rotateRight_edge(&edge3, 0, j)
k -= 1
}
}
var x = 2// generate combination and set edges
for j in stride(from: TEdge.BR.rawValue, through: 0, by: -1) {
if a - Cnk(j, x + 1) >= 0 {
cubiecube.ep[j] = edge3[x]
a -= Cnk(j, x + 1)
x -= 1
}
}
}
func getUBtoDF(_ cubiecube: TCubiecube) -> Int {
var a = 0, x = 0, b = 0
var edge3 = [TEdge](repeating: .UR, count: 3)
// compute the index a < (12 choose 3) and the edge permutation.
for j in TEdge.UR.rawValue ... TEdge.BR.rawValue {
if .UB <= cubiecube.ep[j] && cubiecube.ep[j] <= .DF {
a += Cnk(j, x + 1)
edge3[x] = cubiecube.ep[j]
x += 1
}
}
for j in stride(from: 2, to: 0, by: -1) {// compute the index b < 3! for the
// permutation in edge3
var k = 0
while edge3[j].rawValue != TEdge.UB.rawValue + j {
rotateLeft_edge(&edge3, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return 6 * a + b
}
func setUBtoDF(_ cubiecube: inout TCubiecube, _ idx: Int) {
var x = 0
var edge3 = [TEdge.UB, .DR, .DF]
var b = idx % 6 // Permutation
var a = idx / 6 // Combination
for e in 0 ..< EDGE_COUNT {
cubiecube.ep[e] = .BR// Use BR to invalidate all edges
}
for j in 1 ..< 3 {// generate permutation from index b
var k = b % (j + 1)
b /= j + 1
while k > 0 {
rotateRight_edge(&edge3, 0, j)
k -= 1
}
}
x = 2// generate combination and set edges
for j in stride(from: TEdge.BR.rawValue, through: 0, by: -1) {
if a - Cnk(j, x + 1) >= 0 {
cubiecube.ep[j] = edge3[x]
a -= Cnk(j, x + 1)
x -= 1
}
}
}
func getURFtoDLB(_ cubiecube: TCubiecube) -> Int {
var perm = cubiecube.cp
var b = 0
for j in stride(from: 7, to: 0, by: -1) {// compute the index b < 8! for the permutation in perm
var k = 0
while perm[j].rawValue != j {
rotateLeft_corner(&perm, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return b
}
func setURFtoDLB(_ cubiecube: inout TCubiecube, _ idx: Int) {
var idx = idx
var perm = [TCorner.URF, .UFL, .ULB, .UBR, .DFR, .DLF, .DBL, .DRB]
var x = 7 // set corners
for j in 1 ..< 8 {
var k = idx % (j + 1)
idx /= j + 1
while k > 0 {
rotateRight_corner(&perm, 0, j)
k -= 1
}
}
for j in stride(from: 7, through: 0, by: -1) {
cubiecube.cp[j] = perm[x]
x -= 1
}
}
func getURtoBR(_ cubiecube: TCubiecube) -> Int {
var perm = cubiecube.ep
var b = 0
for j in stride(from: 11, to: 0, by: -1) {// compute the index b < 12! for the permutation in perm
var k = 0
while perm[j].rawValue != j {
rotateLeft_edge(&perm, 0, j)
k += 1
}
b = (j + 1) * b + k
}
return b
}
func setURtoBR(_ cubiecube: inout TCubiecube, _ idx: Int) {
var idx = idx
var perm = [TEdge.UR, .UF, .UL, .UB, .DR, .DF, .DL, .DB, .FR, .FL, .BL, .BR]
var x = 11// set edges
for j in 1 ..< 12 {
var k = idx % (j + 1)
idx /= j + 1
while k > 0 {
rotateRight_edge(&perm, 0, j)
k -= 1
}
}
for j in stride(from: 11, through: 0, by: -1) {
cubiecube.ep[j] = perm[x]
x -= 1
}
}
func verify(_ cubiecube: TCubiecube) -> VerifyResult {
var edgeCount = [Int](repeating: 0, count: 12)
var cornerCount = [Int](repeating: 0, count: 8)
for e in 0 ..< EDGE_COUNT {
edgeCount[cubiecube.ep[e].rawValue] += 1
}
if !edgeCount.allSatisfy({ $0 == 1 }) {
return VerifyResult.ng(-2)
}
if cubiecube.eo.sum() % 2 != 0 {
return VerifyResult.ng(-3)
}
for c in 0 ..< CORNER_COUNT {
cornerCount[cubiecube.cp[c].rawValue] += 1
}
if !cornerCount.allSatisfy({ $0 == 1 }) {
return VerifyResult.ng(-4) // missing corners
}
if cubiecube.co.sum() % 3 != 0 {
return VerifyResult.ng(-5) // twisted corner
}
if ((edgeParity(cubiecube) ^ cornerParity(cubiecube)) != 0) {
return VerifyResult.ng(-6) // parity error
}
return VerifyResult.ok // cube ok
}
func getURtoDF_standalone(_ idx1: Int, _ idx2: Int) -> Int {
var a = get_cubiecube()
var b = get_cubiecube()
setURtoUL(&a, idx1)
setUBtoDF(&b, idx2)
for i in 0 ..< 8 {
if a.ep[i] != .BR {
if b.ep[i] != .BR {// collision
return -1
} else {
b.ep[i] = a.ep[i]
}
}
}
let result = getURtoDF(b)
return result
}
let cornerFacelet: [[TFacelet]] = [ [ .U9, .R1, .F3 ], [ .U7, .F1, .L3 ], [ .U1, .L1, .B3 ], [ .U3, .B1, .R3 ],
[ .D3, .F9, .R7 ], [ .D1, .L9, .F7 ], [ .D7, .B9, .L7 ], [ .D9, .R9, .B7 ] ]
let edgeFacelet: [[TFacelet]] = [ [ .U6, .R2 ], [ .U8, .F2 ], [ .U4, .L2 ], [ .U2, .B2 ], [ .D6, .R8 ], [ .D2, .F8 ],
[ .D4, .L8 ], [ .D8, .B8 ], [ .F6, .R4 ], [ .F4, .L6 ], [ .B6, .L4 ], [ .B4, .R6 ] ]
let cornerColor: [[TColor]] = [ [ .U, .R, .F ], [ .U, .F, .L ], [ .U, .L, .B ], [ .U, .B, .R ], [ .D, .F, .R ], [ .D, .L, .F ],
[ .D, .B, .L ], [ .D, .R, .B ] ]
let edgeColor: [[TColor]] = [ [ .U, .R ], [ .U, .F ], [ .U, .L ], [ .U, .B ], [ .D, .R ], [ .D, .F ], [ .D, .L ], [ .D, .B ],
[ .F, .R ], [ .F, .L ], [ .B, .L ], [ .B, .R ] ]
func get_facecube() -> TFacecube {
let result = TFacecube( [.U, .U, .U, .U, .U, .U, .U, .U, .U, .R, .R, .R, .R, .R, .R, .R, .R, .R, .F, .F, .F, .F, .F, .F, .F, .F, .F, .D, .D, .D, .D, .D, .D, .D, .D, .D, .L, .L, .L, .L, .L, .L, .L, .L, .L, .B, .B, .B, .B, .B, .B, .B, .B, .B] )
return result
}
func get_facecube_fromstring(_ cubeString: String) -> TFacecube {
var result = [TColor]()
for c in cubeString {
switch c {
case "U": result.append(.U)
case "R": result.append(.R)
case "F": result.append(.F)
case "D": result.append(.D)
case "L": result.append(.L)
case "B": result.append(.B)
default: break
}
}
return TFacecube(result)
}
func to_String(_ facecube: TFacecube) -> String {
var result: String = ""
for f in facecube.f {
switch(f) {
case .U: result.append("U")
case .R: result.append("R")
case .F: result.append("F")
case .D: result.append("D")
case .L: result.append("L")
case .B: result.append("B")
}
}
return result
}
func toCubieCube(_ facecube: TFacecube) -> TCubiecube {
var ccRet = TCubiecube()
for i in 0 ..< 8 {
ccRet.cp[i] = .URF // invalidate corners
}
for i in 0 ..< 12 {
ccRet.ep[i] = .UR // and edges
}
for i in 0 ..< CORNER_COUNT {
// get the colors of the cubie at corner i, starting with U/D
for ori in 0 ..< 3 {
if facecube.f[cornerFacelet[i][ori].rawValue].rawValue == TColor.U.rawValue || facecube.f[cornerFacelet[i][ori].rawValue].rawValue == TColor.D.rawValue {
let col1 = facecube.f[cornerFacelet[i][(ori + 1) % 3].rawValue]
let col2 = facecube.f[cornerFacelet[i][(ori + 2) % 3].rawValue]
for j in 0 ..< EDGE_COUNT {
if col1 == cornerColor[j][1] && col2 == cornerColor[j][2] {
// in cornerposition i we have cornercubie j
ccRet.cp[i] = TCorner(rawValue: j)!
ccRet.co[i] = ori % 3
break
}
}
break
}
}
}
for i in 0 ..< EDGE_COUNT {
for j in 0 ..< EDGE_COUNT {
if facecube.f[edgeFacelet[i][0].rawValue] == edgeColor[j][0]
&& facecube.f[edgeFacelet[i][1].rawValue] == edgeColor[j][1] {
ccRet.ep[i] = TEdge(rawValue: j)!
ccRet.eo[i] = 0
break
}
if facecube.f[edgeFacelet[i][0].rawValue] == edgeColor[j][1]
&& facecube.f[edgeFacelet[i][1].rawValue] == edgeColor[j][0] {
ccRet.ep[i] = TEdge(rawValue: j)!
ccRet.eo[i] = 1
break
}
}
}
return ccRet
}
import Foundation
typealias OneDimension = [Int]
typealias TwoDimension = [[Int]]
func check_cached_table1(_ name: String, _ buf: inout OneDimension, _ cache_dir: String) -> Bool {
var result = true
if let path = Bundle.main.path(forResource: name, ofType: nil) {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
if data.count == buf.count {
buf = data.map { Int($0) }
} else {
print("Cache table1 \(name) size unmatch. data: \(data.count), buf: \(buf.count)")
result = false
}
}
} else {
print("Cache table1 \(name) was not found. ERROR!")
result = false
}
return result
}
func check_cached_table2(_ name: String, _ buf: inout TwoDimension, _ cache_dir: String) -> Bool {
var result = true
if let path = Bundle.main.path(forResource: name, ofType: nil) {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
let height = buf.count
let width = data.count / 2 / height
if data.count / 2 == height * buf[0].count {
var p = 0
for h in 0 ..< height {
for w in 0 ..< width {
buf[h][w] = Int(data[p]) + (Int(data[p + 1]) << 8) //for litte endian
p += 2
}
}
} else {
print("Cache table2 \(name) size unmatch. data: \(data.count / 2), buf: \(height * buf[0].count)")
result = false
}
}
} else {
print("Cache table2 \(name) was not found. ERROR!")
result = false
}
return result
}
import Foundation
@inlinable
@inline(__always)
func preAdd<T: BinaryInteger>(_ array: inout [T], _ index: Int, _ value: T) -> T {
array[index] += value
return array[index]
}
extension Array where Element: BinaryInteger {
@inlinable
@inline(__always)
func sum() -> Element { self.reduce(0, +) }
}
func solutionToString(_ search: TSearch, _ length: Int, _ depthPhase1: Int) -> String {
var s = ""
for i in 0 ..< length {
switch search.ax[i] {
case 0: s.append("U")
case 1: s.append("R")
case 2: s.append("F")
case 3: s.append("D")
case 4: s.append("L")
case 5: s.append("B")
default: break
}
switch search.po[i] {
case 1: s.append(" ")
case 2: s.append("2 ")
case 3: s.append("' ")
default: break
}
if (i == depthPhase1 - 1) {
s.append(". ")
}
}
return String(s)
}
func solution(_ facelets: String, maxDepth: Int, timeOut: TimeInterval, useSeparator: Bool, _ cache_dir: String) -> String? {
var search = TSearch()
var count = [Int](repeating: 0, count: 6)
if !PRUNING_INITED {
initPruning(cache_dir)
}
for c in facelets {
switch c {
case "U": count[TColor.U.rawValue] += 1
case "R": count[TColor.R.rawValue] += 1
case "F": count[TColor.F.rawValue] += 1
case "D": count[TColor.D.rawValue] += 1
case "L": count[TColor.L.rawValue] += 1
case "B": count[TColor.B.rawValue] += 1
default: break
}
}
if !count.allSatisfy({ $0 == 9 }) {
return nil
}
let fc = get_facecube_fromstring(facelets)
let cc = toCubieCube(fc)
if case .ng(let error) = verify(cc) {
print("verify error: \(error)")
return nil
}
// // +++++++++++++++++++++++ initialization +++++++++++++++++++++++++++++++++
let c = get_coordcube(cc)
search.po[0] = 0
search.ax[0] = 0
search.flip[0] = c.flip
search.twist[0] = c.twist
search.parity[0] = c.parity
search.slice[0] = c.FRtoBR / 24
search.URFtoDLF[0] = c.URFtoDLF
search.FRtoBR[0] = c.FRtoBR
search.URtoUL[0] = c.URtoUL
search.UBtoDF[0] = c.UBtoDF
search.minDistPhase1[1] = 1// else failure for depth=1, n=0
var mv = 0, n = 0
var busy = false
var depthPhase1 = 1
let tStart = Date.now
// +++++++++++++++++++ Main loop ++++++++++++++++++++++++++++++++++++++++++
repeat { // Equivalent to do-while(1)
repeat { // Equivalent to do-while(busy)
if (depthPhase1 - n > search.minDistPhase1[n + 1]) && !busy {
if search.ax[n] == 0 || search.ax[n] == 3 { // Initialize next move
n += 1
search.ax[n] = 1
} else {
n += 1
search.ax[n] = 0
}
search.po[n] = 1
} else if preAdd(&search.po, n, 1) > 3 {
repeat { // increment axis
if preAdd(&search.ax, n, 1) > 5 {
if -tStart.timeIntervalSinceNow > timeOut {
return nil
}
if n == 0 {
if depthPhase1 >= maxDepth {
return nil
} else {
depthPhase1 += 1
search.ax[n] = 0
search.po[n] = 1
busy = false
break // Break from inner do-while (increment axis)
}
} else {
n -= 1
busy = true
break // Break from inner do-while (increment axis)
}
} else {
search.po[n] = 1
busy = false
}
} while n != 0 && (search.ax[n - 1] == search.ax[n] || search.ax[n - 1] - 3 == search.ax[n])
} else {
busy = false
}
} while busy
// +++++++++++++ compute new coordinates and new minDistPhase1 ++++++++++
// if minDistPhase1 =0, the H subgroup is reached
mv = 3 * search.ax[n] + search.po[n] - 1
search.flip[n + 1] = flipMove[Int(search.flip[n])][mv]
search.twist[n + 1] = twistMove[Int(search.twist[n])][mv]
search.slice[n + 1] = FRtoBR_Move[Int(search.slice[n] * 24)][mv] / 24
search.minDistPhase1[n + 1] = max(
getPruning(&Slice_Flip_Prun, N_SLICE1 * search.flip[n + 1] + search.slice[n + 1]),
getPruning(&Slice_Twist_Prun, N_SLICE1 * search.twist[n + 1] + search.slice[n + 1])
)
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//print(String(format:"n:%d depthPhase1:%d", n, depthPhase1))
if search.minDistPhase1[n + 1] == 0 && n >= depthPhase1 - 5 {
search.minDistPhase1[n + 1] = 10// instead of 10 any value >5 is possible
let s = (n == depthPhase1 - 1) ? totalDepth(&search, depthPhase1, maxDepth) : 0
if n == depthPhase1 - 1 && s >= 0 {
if s == depthPhase1
|| (search.ax[depthPhase1 - 1] != search.ax[depthPhase1]
&& search.ax[depthPhase1 - 1] != search.ax[depthPhase1] + 3) {
let result =
if useSeparator {
solutionToString(search, s, depthPhase1)
} else {
solutionToString(search, s, -1)
}
return result
}
}
}
} while true
}
func totalDepth(_ search: inout TSearch, _ depthPhase1: Int, _ maxDepth: Int) -> Int {
let maxDepthPhase2 = min(10, maxDepth - depthPhase1)// Allow only max 10 moves in phase2
for i in 0 ..< depthPhase1 {
let mv = 3 * search.ax[i] + search.po[i] - 1
//print(String(format: "%d %d %d %d", i, mv, search.ax[i], search.po[i]))
search.URFtoDLF[i + 1] = URFtoDLF_Move[search.URFtoDLF[i]][mv]
search.FRtoBR[i + 1] = FRtoBR_Move[search.FRtoBR[i]][mv]
search.parity[i + 1] = parityMove[search.parity[i]][mv]
}
let d1 = getPruning(&Slice_URFtoDLF_Parity_Prun,
(N_SLICE2 * search.URFtoDLF[depthPhase1] + search.FRtoBR[depthPhase1]) * 2
+ search.parity[depthPhase1])
if d1 > maxDepthPhase2 {
return -1
}
for i in 0 ..< depthPhase1 {
let mv = 3 * search.ax[i] + search.po[i] - 1
search.URtoUL[i + 1] = URtoUL_Move[Int(search.URtoUL[i])][mv]
search.UBtoDF[i + 1] = UBtoDF_Move[Int(search.UBtoDF[i])][mv]
}
search.URtoDF[depthPhase1] = MergeURtoULandUBtoDF[Int(search.URtoUL[depthPhase1])][Int(search.UBtoDF[depthPhase1])]
let d2 = getPruning(&Slice_URtoDF_Parity_Prun,
(N_SLICE2 * search.URtoDF[depthPhase1] + search.FRtoBR[depthPhase1]) * 2
+ search.parity[depthPhase1])
if d2 > maxDepthPhase2 {
return -1
}
search.minDistPhase2[depthPhase1] = max(d1, d2)
if search.minDistPhase2[depthPhase1] == 0 { // already solved
return depthPhase1
}
// now set up search
var depthPhase2 = 1
var n = depthPhase1
var busy = false
search.po[depthPhase1] = 0
search.ax[depthPhase1] = 0
search.minDistPhase2[n + 1] = 1// else failure for depthPhase2=1, n=0
// +++++++++++++++++++ end initialization ++++++++++++++++++++++++++++++++
repeat { // Equivalent to do-while
repeat { // Equivalent to do-while(busy)
if (depthPhase1 + depthPhase2 - n > search.minDistPhase2[n + 1]) && !busy {
if search.ax[n] == 0 || search.ax[n] == 3 { // Initialize next move
n += 1
search.ax[n] = 1
search.po[n] = 2
} else {
n += 1
search.ax[n] = 0
search.po[n] = 1
}
} else if (search.ax[n] == 0 || search.ax[n] == 3) ? (preAdd(&search.po, n, 1) > 3) : (preAdd(&search.po, n, 2) > 3) {
repeat { // Equivalent to do-while
if preAdd(&search.ax, n, 1) > 5 {
if n == depthPhase1 {
if depthPhase2 >= maxDepthPhase2 {
return -1
} else {
depthPhase2 += 1
search.ax[n] = 0
search.po[n] = 1
busy = false
break // Break from inner do-while (increment axis)
}
} else {
n -= 1
busy = true
break // Break from inner do-while (increment axis)
}
} else {
if search.ax[n] == 0 || search.ax[n] == 3 {
search.po[n] = 1
} else {
search.po[n] = 2
}
busy = false
}
} while n != depthPhase1 && (search.ax[n - 1] == search.ax[n] || search.ax[n - 1] - 3 == search.ax[n])
} else {
busy = false
}
} while busy
// +++++++++++++ compute new coordinates and new minDist ++++++++++
let mv = 3 * search.ax[n] + search.po[n] - 1
search.URFtoDLF[n + 1] = URFtoDLF_Move[search.URFtoDLF[n]][mv]
search.FRtoBR[n + 1] = FRtoBR_Move[search.FRtoBR[n]][mv]
search.parity[n + 1] = parityMove[search.parity[n]][mv]
search.URtoDF[n + 1] = URtoDF_Move[search.URtoDF[n]][mv]
search.minDistPhase2[n + 1] = max(
getPruning(&Slice_URtoDF_Parity_Prun, (N_SLICE2 * search.URtoDF[n + 1] + search.FRtoBR[n + 1]) * 2 + search.parity[n + 1]),
getPruning(&Slice_URFtoDLF_Parity_Prun, (N_SLICE2 * search.URFtoDLF[n + 1] + search.FRtoBR[n + 1]) * 2 + search.parity[n + 1]))
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
} while search.minDistPhase2[n + 1] != 0
return depthPhase1 + depthPhase2
}
func patternize(_ facelets: String, _ pattern: String) -> String {
var fc = TFacecube()
let start_fc = get_facecube_fromstring(facelets)
let pattern_fc = get_facecube_fromstring(pattern)
let start_cc = toCubieCube(start_fc)
let pattern_cc = toCubieCube(pattern_fc)
var inv_pattern_cc = get_cubiecube()
invCubieCube(pattern_cc, &inv_pattern_cc)
multiply(&inv_pattern_cc, start_cc)
fc = toFaceCube(inv_pattern_cc)
return to_String(fc)
}
func kociemba(_ argv1: String, _ argv2: String?) -> String? {
var facelets = argv1
if let argv2 {
facelets = patternize(facelets, argv2)
//print("facelets: \(facelets)")
}
if let result = solution(facelets, maxDepth: 24, timeOut: 1000, useSeparator: false, "cache") {
return result
}
return nil
}
import SwiftUI
struct ContentView: View {
@State var facelets = "DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD"
@State var pattern = ""
@State var solution = ""
@State var message = ""
var body: some View {
VStack {
HStack {
Text("facelets: ")
TextField("facelets", text: $facelets)
}
HStack {
Text("pattern: ")
TextField("option", text: $pattern)
}
Button("resolve", action: {
let start = Date.now
if let result = kociemba(facelets, pattern.isEmpty ? nil : pattern) {
let duration = -start.timeIntervalSinceNow
solution = result
print(result)
message = String(format: "duration: %.3f seconds", duration)
} else {
solution = "Unsolvable cube!"
print("Unsolvable cube!")
}
})
.padding(.bottom, 20)
HStack {
Text("solution: ")
Text(solution)
Spacer()
}
HStack {
Text(message)
Spacer()
}
}
.padding()
}
}
#Preview {
ContentView()
}
iPhone13 mini
の実機で、1回目はキャッシュファイルの読み込みが走るので 0.4秒ほどかかりますが、2回目以降は瞬殺です。
% time kociemba "DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD"
D2 R' D' F2 B D R2 D2 R' F2 D' F2 U' B2 L2 U2 D R2 U
kociemba 0.02s user 0.01s system 88% cpu 0.032 total
Macと変わらない性能。
次回の記事で、展開図と3D表示を同期して回転するようにして、今回の解法コードを組み込んでアプリを完成させます。
以上