0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

4. ルービックキューブ 解法コマンド (kociemba)

Posted at

解法アルゴリズムの導入


前回の記事で予告した内容です。

当初は ↑上のコマンドをそのまま利用するつもりでした。ので、
まずは簡単に紹介します。

インストール
% pip install kociemba

・ kociemba 使用例

REPL実行
% 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

1ライナー実行
% 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ファイル)
color_h.swift
// 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
coordcube_h.swift
// 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
    }
}
corner_h.swift
// 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
cubiecube_h.swift
//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)
}
edge_h.swift
// 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
facecube_h.swift
//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)
facelet_h.swift
/// <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
prunetable_helpers_t.swift
/*
 blank
 */
search_h.swift
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)
    }
}
coordcube.swift
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
}
cubiecube.swift
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
}
facecube.swift
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
}
prunetable_helpers.swift
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
}
search.swift
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)
}
solve.swift
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
}
ContentView(テストアプリ)
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()
}

iphone0.png

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表示を同期して回転するようにして、今回の解法コードを組み込んでアプリを完成させます。



以上



0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?