2. ルービックキューブ 回転記号による回転を実装

https://qiita.com/nak435/items/00e4d8877532c7d9d287

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

# 英語で

ルービックキューブ の「回転記号」は、`Rubik's Cube Move Notation`のようです。直訳すると「移動表記」。

他にも、
・面の回転（U, F', R2 など）は、`Face Turns`
・2層同時回転（Dw, Bw など）は、`Wide Moves`
・キューブの持ち方を変える（X, Y' など）は、`Cube Rotations`
・真ん中の層を回転させる（M, E', S など）は、`Slice Moves`

また、逆回転を示す記号`'`を『プライム(prime)』と呼び、2層同時回転を示す記号`w`を『ワイド(wide)』と呼ぶそうです。


# 回転記号

回転記号は、次のサイトの内容に準じます。

https://tribox.com/3x3x3/solution/notation/

この記号を、前回の[`rotate`](https://qiita.com/nak435/items/00e4d8877532c7d9d287#rotate)関数に対応させると、下表のとおりとなります。

|	回転記号	|	rotate(xyz, layer, direction)	|
|	:-:	|	:-	|
|	U	|	rotate(1, 0, 0)	|
|	U'	|	rotate(1, 0, 1)	|
|	U2	|	rotate(1, 0, 0); rotate(1, 0, 0)	|
|	Uw	|	rotate(1, 0, 0); rotate(1, 1, 0)	|
|	F	|	rotate(2, 0, 0)	|
|	F'	|	rotate(2, 0, 1)	|
|	F2	|	rotate(2, 0, 0); rotate(2, 0, 0)	|
|	Fw	|	rotate(2, 0, 0); rotate(2, 1, 0)	|
|	R	|	rotate(0, 2, 0)	|
|	R'	|	rotate(0, 2, 1)	|
|	R2	|	rotate(0, 2, 0); rotate(0, 2, 0)	|
|	Rw	|	rotate(0, 2, 0); rotate(0, 1, 0)	|
|	D	|	rotate(1, 2, 1)	|
|	D'	|	rotate(1, 2, 0)	|
|	D2	|	rotate(1, 2, 1); rotate(1, 2, 1)	|
|	Dw	|	rotate(1, 2, 1); rotate(1, 1, 1)	|
|	B	|	rotate(2, 2, 1)	|
|	B'	|	rotate(2, 2, 0)	|
|	B2	|	rotate(2, 2, 1); rotate(2, 2, 1)	|
|	Bw	|	rotate(2, 2, 1); rotate(2, 1, 1)	|
|	L	|	rotate(0, 0, 1)	|
|	L'	|	rotate(0, 0, 0)	|
|	L2	|	rotate(0, 0, 1); rotate(0, 0, 1)	|
|	Lw	|	rotate(0, 0, 1); rotate(0, 1, 1)	|
|	M	|	rotate(0, 1, 1)	|
|	M'	|	rotate(0, 1, 0)	|
|	E	|	rotate(1, 1, 1)	|
|	E'	|	rotate(1, 1, 0)	|
|	S	|	rotate(2, 1, 0)	|
|	S'	|	rotate(2, 1, 1)	|
|	X	|	rotate(0, 0, 0); rotate(0, 1, 0); rotate(0, 2, 0)	|
|	X'	|	rotate(0, 0, 1); rotate(0, 1, 1); rotate(0, 2, 1)	|
|	Y	|	rotate(1, 0, 0); rotate(1, 1, 0); rotate(1, 2, 0)	|
|	Y'	|	rotate(1, 0, 1); rotate(1, 1, 1); rotate(1, 2, 1)	|
|	Z	|	rotate(2, 0, 0); rotate(2, 1, 0); rotate(2, 2, 0)	|
|	Z'	|	rotate(2, 0, 1); rotate(2, 1, 1); rotate(2, 2, 1)	|

なお、180°回転を示す記号`2`に『プライム(`'`)』を組み合わせる`L2'`や、`Lw'`のような記号もあるようですが、今回は実装しません。



# ルービックキューブ Managerクラス を新設

上記のように、一つの回転記号で複数の回転動作を必要とすることや、`Undo`,`Redo`を実装することを考えると、クラス化しておくと使い勝手が良さそうです。

```swift:Rubik's cube Manager class
final class RCManager: ObservableObject {
    @MainActor static let share = RCManager()
    @Published var rcData: RCData = RCDataInitialPattern
    private init() { }
    
    func reset() { rcData = RCDataInitialPattern }
    func rotate(_ xyz: Int, _ layer: Int, _ direction: Int) {
        //xyz       0:x, 1:y, 2:z
        //layer     0~2
        //direction 0:cw, 1:ccw
        var cube = rcData
        switch (xyz, direction) {
            // 前回と同じため省略。完全なコードは最後に示す
        }
        rcData = cube
    }
    
    //Rubik's Cube Move Notation
    func rotate(_ notation: String) {
        let moves: Array<(xyz: Int, layer: Int, direction: Int)> =
        switch notation {
            case "U":  [(1, 0, 0)]
            case "U'": [(1, 0, 1)]
            case "U2": [(1, 0, 0), (1, 0, 0)]
            case "Uw": [(1, 0, 0), (1, 1, 0)]
            case "F":  [(2, 0, 0)]
            case "F'": [(2, 0, 1)]
            case "F2": [(2, 0, 0), (2, 0, 0)]
            case "Fw": [(2, 0, 0), (2, 1, 0)]
            case "R":  [(0, 2, 0)]
            case "R'": [(0, 2, 1)]
            case "R2": [(0, 2, 0), (0, 2, 0)]
            case "Rw": [(0, 2, 0), (0, 1, 0)]
            case "D":  [(1, 2, 1)]
            case "D'": [(1, 2, 0)]
            case "D2": [(1, 2, 1), (1, 2, 1)]
            case "Dw": [(1, 2, 1), (1, 1, 1)]
            case "B":  [(2, 2, 1)]
            case "B'": [(2, 2, 0)]
            case "B2": [(2, 2, 1), (2, 2, 1)]
            case "Bw": [(2, 2, 1), (2, 1, 1)]
            case "L":  [(0, 0, 1)]
            case "L'": [(0, 0, 0)]
            case "L2": [(0, 0, 1), (0, 0, 1)]
            case "Lw": [(0, 0, 1), (0, 1, 1)]
            case "M":  [(0, 1, 1)]
            case "M'": [(0, 1, 0)]
            case "E":  [(1, 1, 1)]
            case "E'": [(1, 1, 0)]
            case "S":  [(2, 1, 0)]
            case "S'": [(2, 1, 1)]
            case "X":  [(0, 0, 0), (0, 1, 0), (0, 2, 0)]
            case "X'": [(0, 0, 1), (0, 1, 1), (0, 2, 1)]
            case "Y":  [(1, 0, 0), (1, 1, 0), (1, 2, 0)]
            case "Y'": [(1, 0, 1), (1, 1, 1), (1, 2, 1)]
            case "Z":  [(2, 0, 0), (2, 1, 0), (2, 2, 0)]
            case "Z'": [(2, 0, 1), (2, 1, 1), (2, 2, 1)]
            default: []
        }
        if moves.isEmpty { assertionFailure("invalid rotate (notation:\(notation)") }
        
        moves.forEach{ rotate($0.xyz, $0.layer, $0.direction) }
    }       
}
```


## Playground で確認

`RCManager`版をPlayground で確認します。

```swift
import PlaygroundSupport
import SwiftUI

struct SubView: View {
    @EnvironmentObject var rcManager: RCManager
    var body: some View {
        RCUnfoldedDiagramView(size: CGFloat(40), rcData: $rcManager.rcData)
        Spacer()
        HStack {
            Button("U", action: { rcManager.rotate("U") })
            Button("F", action: { rcManager.rotate("F") })
            Button("R", action: { rcManager.rotate("R") })
            Button("D", action: { rcManager.rotate("D") })
            Button("B", action: { rcManager.rotate("B") })
            Button("L", action: { rcManager.rotate("L") })
        }
        HStack {
            Button("U'", action: { rcManager.rotate("U'") })
            Button("F'", action: { rcManager.rotate("F'") })
            Button("R'", action: { rcManager.rotate("R'") })
            Button("D'", action: { rcManager.rotate("D'") })
            Button("B'", action: { rcManager.rotate("B'") })
            Button("L'", action: { rcManager.rotate("L'") })
        }
        HStack {
            Button("U2", action: { rcManager.rotate("U2") })
            Button("F2", action: { rcManager.rotate("F2") })
            Button("R2", action: { rcManager.rotate("R2") })
            Button("D2", action: { rcManager.rotate("D2") })
            Button("B2", action: { rcManager.rotate("B2") })
            Button("L2", action: { rcManager.rotate("L2") })
        }
        HStack {
            Button("Uw", action: { rcManager.rotate("Uw") })
            Button("Fw", action: { rcManager.rotate("Fw") })
            Button("Rw", action: { rcManager.rotate("Rw") })
            Button("Dw", action: { rcManager.rotate("Dw") })
            Button("Bw", action: { rcManager.rotate("Bw") })
            Button("Lw", action: { rcManager.rotate("Lw") })
        }
        HStack {
            Button("M" , action: { rcManager.rotate("M" ) })
            Button("M'", action: { rcManager.rotate("M'") })
            Button("E" , action: { rcManager.rotate("E" ) })
            Button("E'", action: { rcManager.rotate("E'") })
            Button("S" , action: { rcManager.rotate("S" ) })
            Button("S'", action: { rcManager.rotate("S'") })
        }
        HStack {
            Button("X" , action: { rcManager.rotate("X" ) })
            Button("X'", action: { rcManager.rotate("X'") })
            Button("Y" , action: { rcManager.rotate("Y" ) })
            Button("Y'", action: { rcManager.rotate("Y'") })
            Button("Z" , action: { rcManager.rotate("Z" ) })
            Button("Z'", action: { rcManager.rotate("Z'") })
        }
        Button("Reset", action: { rcManager.reset() })
    }
}

struct ContentView: View {
    var body: some View {
        SubView()
            .environmentObject(RCManager.share)
    }
}

PlaygroundPage.current.setLiveView(ContentView())
```

![liveView2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/f65fd76e-9225-40c7-aa59-1e8267dc67cb.png)



# `Undo` `Redo`を実装

シンプルに[`UndoManager`](https://developer.apple.com/documentation/foundation/undomanager/)を使って実装します。

`RCManager`クラスに`UndoManager`のインスタンスを保持して、`rotate`するときに`registerUndo`で`undo`動作を登録します。同様に、`undo`動作時は`redo`動作を登録します。

・`RCManager`変更箇所

`UndoManager`のインスタンス追加

```diff_swift:RCManager
final class RCManager: ObservableObject {
    @MainActor static let share = RCManager()
    @Published var rcData: RCData = RCDataInitialPattern
+   let undoManager = UndoManager()
    private init() { }
```

・`rotate`変更箇所

`undo`動作、`redo`動作を登録

```diff_swift:RCManager
    func rotate(_ notation: String) {
        let moves: Array<(xyz: Int, layer: Int, direction: Int)> =
        switch notation {
//途中省略
        }
        if moves.isEmpty { assertionFailure("invalid rotate (notation:\(notation)") }
        
-       moves.forEach{ rotate($0.xyz, $0.layer, $0.direction) }
+       rotate_undoredo(moves, undo: false)
    }
+   func rotate_undoredo(_ moves: Array<(xyz: Int, target: Int, direction: Int)>, undo: Bool) {
+       if undo {
+           let undo = moves.reversed().map { ($0.xyz, $0.target, 1 - $0.direction) }
+           undo.forEach{ rotate($0.0, $0.1, $0.2) }
+       } else {
+           moves.forEach{ rotate($0.xyz, $0.target, $0.direction) }
+       }
+       undoManager.registerUndo(withTarget: self) { unownedSelf in
+           unownedSelf.rotate_undoredo(moves, undo: !undo)
+       }
+   }
```


・`RCManager`拡張を追加

`undo`,`redo`関数、他を定義

```swift:RCManager
extension RCManager {
    var canUndo: Bool { undoManager.canUndo }
    var canRedo: Bool { undoManager.canRedo }
    func undo() { if canUndo { undoManager.undo() }}
    func redo() { if canRedo { undoManager.redo() }}
}
```

・`SubView`変更箇所

`undo`,`redo`ボタンを追加

```diff_swift:SubView
+        HStack {
+            Button("Undo", action: { rcManager.undo() }).disabled(!rcManager.canUndo)
+                .padding(.trailing, 60)
+            Button("Redo", action: { rcManager.redo() }).disabled(!rcManager.canRedo)
+        }

         Button("Reset", action: { rcManager.rcData = RCDataInitialPattern })
```

![liveView3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/b40a1b19-4259-4d09-bef5-19b8393bebaa.png)





# Playground 確認

前回記事参照のコードも含め、すべてのコードを示します。
加えて、初期状態から30回ランダムに面を回転させる`Scramble`も実装しました。

<details><summary><b><i>コードを表示する（480行）</i></b></summary>

```swift
import PlaygroundSupport
import SwiftUI

//Rubik's Cube face
typealias RCFace = [[Int]] //3x3

//Rubik's Cube data
typealias RCData = [RCFace] //6x3x3

//initial pattern
let RCDataInitialPattern = [
    [ //Up
        [ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8],
    ],
    [ //Left
        [ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17],
    ],
    [ //Front
        [18, 19, 20],
        [21, 22, 23],
        [24, 25, 26],
    ],
    [ //Right
        [27, 28, 29],
        [30, 31, 32],
        [33, 34, 35],
    ],
    [ //Back
        [36, 37, 38],
        [39, 40, 41],
        [42, 43, 44],
    ],
    [ //Down
        [45, 46, 47],
        [48, 49, 50],
        [51, 52, 53],
    ],
]

//Rubik's cube - world color scheme
extension UIColor {
    static let rcWhite  = UIColor(r: 255, g: 255, b: 255) //U
    static let rcOrange = UIColor(r: 247, g: 147, b:  30) //L
    static let rcGreen  = UIColor(r:   0, g: 168, b:   0) //F
    static let rcRed    = UIColor(r: 255, g:  42, b:   0) //R
    static let rcBlue   = UIColor(r:   0, g: 101, b: 255) //B
    static let rcYellow = UIColor(r: 255, g: 246, b:  26) //D
}

#if os(macOS)
typealias UIColor = NSColor
#endif

extension UIColor {
    //specifies an RGB values 0 ~ 255
    convenience init(r: Int, g: Int, b: Int) {
        self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: 1)
    }
}

extension Color {
    static func RCColor(_ index: Int) -> Color {
        //                    Up        Right   Front     Down       Left       Back
        let colors = [UIColor.rcWhite, .rcRed, .rcGreen, .rcYellow, .rcOrange, .rcBlue]
        return Color(uiColor: colors[index])
    }
    static func RCColorPosition(_ index: Int) -> Color {
        //                   U  L  F  R  B  D
        let ColorPosition = [0, 4, 2, 1, 5, 3]
        return RCColor(ColorPosition[index])
    }
    
#if os(macOS)
    init(uiColor: UIColor) {
        self.init(uiColor)
    }
#endif
}

struct RCUnfoldedDiagramView: View {
    let size: CGFloat
    @Binding var rcData: RCData
    var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 0) {
                //part1
                ForEach(-1 ..< 1, id: \.self) { face in
                    VStack(spacing: 0) {
                        ForEach(0 ..< 3, id: \.self) { row in
                            HStack(spacing: 0) {
                                ForEach(0 ..< 3, id: \.self) { col in
                                    CubePieceView(number: rcData[0][row][col], size: size, isDummy: face == -1)
                                }
                            }
                        }
                    }
                }
                Spacer()
            }
            HStack(spacing: 0) {
                //part2
                ForEach(1 ..< 5, id: \.self) { face in
                    VStack(spacing: 0) {
                        ForEach(0 ..< 3, id: \.self) { row in
                            HStack(spacing: 0) {
                                ForEach(0 ..< 3, id: \.self) { col in
                                    CubePieceView(number: rcData[face][row][col], size: size, isDummy: false)
                                }
                            }
                        }
                    }
                }
                Spacer()
            }
            HStack(spacing: 0) {
                //part3
                ForEach(4 ..< 6, id: \.self) { face in
                    VStack(spacing: 0) {
                        ForEach(0 ..< 3, id: \.self) { row in
                            HStack(spacing: 0) {
                                ForEach(0 ..< 3, id: \.self) { col in
                                    CubePieceView(number: rcData[face][row][col], size: size, isDummy: face == 4)
                                }
                            }
                        }
                    }
                }
                Spacer()
            }
            Spacer()
        }
        .padding()
    }
    struct CubePieceView: View {
        let number: Int, size: CGFloat, isDummy: Bool
        var body: some View {
            ZStack {
                if !isDummy {
                    RoundedRectangle(cornerRadius: 4)
                        .fill(Color.RCColorPosition(number / 9))
                        .stroke(.black, lineWidth: 2)
                    Text("\(number + 1)")
                        .foregroundColor(.black)
                } else {
                    EmptyView()
                }
            }
            .frame(width: size, height: size)
        }
    }
}

extension RCFace {
    //clockwise turn
    var cw: Self {
        var result = self
        for row in 0 ..< self.count {
            for col in 0 ..< self[row].count {
                result[col][self[row].count - 1 - row] = self[row][col]
            }
        }
        return result
    }
    //counterclockwise turn
    var ccw: Self {
        var result = self
        for row in 0 ..< self.count {
            for col in 0 ..< self[row].count {
                result[self[row].count - 1 - col][row] = self[row][col]
            }
        }
        return result
    }
}

final class RCManager: ObservableObject {
    @MainActor static let share = RCManager()
    @Published var rcData: RCData = RCDataInitialPattern
    let undoManager = UndoManager()
    private init() { undoManager.groupsByEvent = false }
    
    func reset() {
        rcData = RCDataInitialPattern
        undoManager.removeAllActions()
    }
    func rotate(_ xyz: Int, _ target: Int, _ direction: Int) {
        //xyz       0:x, 1:y, 2:z
        //target    0~2
        //direction 0:cw, 1:ccw
        var cube = rcData
        switch (xyz, direction) {
            case (0, 0): //x cw
                if target == 0 {
                    cube[1] = cube[1].ccw
                } else if target == 2 {
                    cube[3] = cube[3].cw
                }
                let v0 = cube[0]
                for r in 0 ..< 3 {
                    cube[0][r][target] = cube[2][r][target]
                }
                for r in 0 ..< 3 {
                    cube[2][2 - r][target] = cube[5][2 - r][target]
                }
                for r in 0 ..< 3 {
                    cube[5][r][target] = cube[4][2 - r][2 - target]
                }
                for r in 0 ..< 3 {
                    cube[4][r][2 - target] = v0[2 - r][target]
                }
                
            case (0, 1): //x ccw
                if target == 0 {
                    cube[1] = cube[1].cw
                } else if target == 2 {
                    cube[3] = cube[3].ccw
                }
                let v0 = cube[0]
                for r in 0 ..< 3 {
                    cube[0][r][target] = cube[4][2 - r][2 - target]
                }
                for r in 0 ..< 3 {
                    cube[4][r][2 - target] = cube[5][2 - r][target]
                }
                for r in 0 ..< 3 {
                    cube[5][r][target] = cube[2][r][target]
                }
                for r in 0 ..< 3 {
                    cube[2][2 - r][target] = v0[2 - r][target]
                }
                
            case (1, 0): //y cw
                if target == 0 {
                    cube[0] = cube[0].cw
                } else if target == 2 {
                    cube[5] = cube[5].ccw
                }
                let v2 = cube[2]
                for r in 0 ..< 3 {
                    cube[2][target][r] = cube[3][target][r]
                }
                for r in 0 ..< 3 {
                    cube[3][target][r] = cube[4][target][r]
                }
                for r in 0 ..< 3 {
                    cube[4][target][r] = cube[1][target][r]
                }
                for r in 0 ..< 3 {
                    cube[1][target][r] = v2[target][r]
                }
                
            case (1, 1): //y ccw
                if target == 0 {
                    cube[0] = cube[0].ccw
                } else if target == 2 {
                    cube[5] = cube[5].cw
                }
                let v2 = cube[2]
                for r in 0 ..< 3 {
                    cube[2][target][r] = cube[1][target][r]
                }
                for r in 0 ..< 3 {
                    cube[1][target][r] = cube[4][target][r]
                }
                for r in 0 ..< 3 {
                    cube[4][target][r] = cube[3][target][r]
                }
                for r in 0 ..< 3 {
                    cube[3][target][r] = v2[target][r]
                }
                
            case (2, 0): //z cw
                if target == 0 {
                    cube[2] = cube[2].cw
                } else if target == 2 {
                    cube[4] = cube[4].ccw
                }
                let v0 = cube[0]
                for r in 0 ..< 3 {
                    cube[0][2 - target][2 - r] = cube[1][r][2 - target]
                }
                for r in 0 ..< 3 {
                    cube[1][2 - r][2 - target] = cube[5][target][2 - r]
                }
                for r in 0 ..< 3 {
                    cube[5][target][r] = cube[3][2 - r][target]
                }
                for r in 0 ..< 3 {
                    cube[3][r][target] = v0[2 - target][r]
                }
                
            case (2, 1): //z ccw
                if target == 0 {
                    cube[2] = cube[2].ccw
                } else if target == 2 {
                    cube[4] = cube[4].cw
                }
                let v0 = cube[0]
                for r in 0 ..< 3 {
                    cube[0][2 - target][r] = cube[3][r][target]
                }
                for r in 0 ..< 3 {
                    cube[3][r][target] = cube[5][target][2 - r]
                }
                for r in 0 ..< 3 {
                    cube[5][target][r] = cube[1][r][2 - target]
                }
                for r in 0 ..< 3 {
                    cube[1][2 - r][2 - target] = v0[2 - target][r]
                }
                
            default:
                assertionFailure("invalid rotate (xyz:\(xyz), target:\(target), dir:\(direction))")
                break
        }
        rcData = cube
    }
    
    //Rubik's Cube Move Notation
    func rotate(_ notation: String) {
        let move: Array<(xyz: Int, target: Int, direction: Int)> =
        switch notation {
            case "U":  [(1, 0, 0)]
            case "U'": [(1, 0, 1)]
            case "U2": [(1, 0, 0), (1, 0, 0)]
            case "Uw": [(1, 0, 0), (1, 1, 0)]
            case "F":  [(2, 0, 0)]
            case "F'": [(2, 0, 1)]
            case "F2": [(2, 0, 0), (2, 0, 0)]
            case "Fw": [(2, 0, 0), (2, 1, 0)]
            case "R":  [(0, 2, 0)]
            case "R'": [(0, 2, 1)]
            case "R2": [(0, 2, 0), (0, 2, 0)]
            case "Rw": [(0, 2, 0), (0, 1, 0)]
            case "D":  [(1, 2, 1)]
            case "D'": [(1, 2, 0)]
            case "D2": [(1, 2, 1), (1, 2, 1)]
            case "Dw": [(1, 2, 1), (1, 1, 1)]
            case "B":  [(2, 2, 1)]
            case "B'": [(2, 2, 0)]
            case "B2": [(2, 2, 1), (2, 2, 1)]
            case "Bw": [(2, 2, 1), (2, 1, 1)]
            case "L":  [(0, 0, 1)]
            case "L'": [(0, 0, 0)]
            case "L2": [(0, 0, 1), (0, 0, 1)]
            case "Lw": [(0, 0, 1), (0, 1, 1)]
            case "M":  [(0, 1, 1)]
            case "M'": [(0, 1, 0)]
            case "E":  [(1, 1, 1)]
            case "E'": [(1, 1, 0)]
            case "S":  [(2, 1, 0)]
            case "S'": [(2, 1, 1)]
            case "X":  [(0, 0, 1), (0, 1, 1), (0, 2, 1)]
            case "X'": [(0, 0, 0), (0, 1, 0), (0, 2, 0)]
            case "Y":  [(1, 0, 0), (1, 1, 0), (1, 2, 0)]
            case "Y'": [(1, 0, 1), (1, 1, 1), (1, 2, 1)]
            case "Z":  [(2, 0, 0), (2, 1, 0), (2, 2, 0)]
            case "Z'": [(2, 0, 1), (2, 1, 1), (2, 2, 1)]
            default: []
        }
        if move.isEmpty { assertionFailure("invalid rotate (notation:\(notation)") }
        
        rotate_undoredo(move, undo: false)
    }
    func rotate_undoredo(_ moves: Array<(xyz: Int, target: Int, direction: Int)>, undo: Bool) {
        undoManager.beginUndoGrouping()
        if undo {
            let undo = moves.reversed().map { ($0.xyz, $0.target, 1 - $0.direction) }
            undo.forEach{ rotate($0.0, $0.1, $0.2) }
        } else {
            moves.forEach{ rotate($0.xyz, $0.target, $0.direction) }
        }
        undoManager.registerUndo(withTarget: self) { unownedSelf in
            unownedSelf.rotate_undoredo(moves, undo: !undo)
        }
        undoManager.endUndoGrouping()
    }
}
extension RCManager {
    var undoCount: Int { undoManager.undoCount }
    var redoCount: Int { undoManager.redoCount }
    var canUndo: Bool { undoManager.canUndo }
    var canRedo: Bool { undoManager.canRedo }
    func undo() { if canUndo { undoManager.undo() }}
    func redo() { if canRedo { undoManager.redo() }}
    
    func scramble() {
        reset()
        var prevMove = "x", move = "y"
        let MoveNotation: [String] = ["U", "U'", "U2", "F", "F'", "F2", "R", "R'", "R2", "D", "D'", "D2", "B", "B'", "B2", "L", "L'", "L2"]
        for _ in 0 ..< 30 {
            repeat {
                move = MoveNotation.randomElement()!
            } while move.first! == prevMove.first!
            rotate(move)
            prevMove = move
        }
    }
}

struct SubView: View {
    @EnvironmentObject var rcManager: RCManager
    var body: some View {
        ScrollView {
            RCUnfoldedDiagramView(size: CGFloat(40), rcData: $rcManager.rcData)
        }
        Spacer()
        HStack {
            Button("U", action: { rcManager.rotate("U") })
            Button("F", action: { rcManager.rotate("F") })
            Button("R", action: { rcManager.rotate("R") })
            Button("D", action: { rcManager.rotate("D") })
            Button("B", action: { rcManager.rotate("B") })
            Button("L", action: { rcManager.rotate("L") })
        }
        HStack {
            Button("U'", action: { rcManager.rotate("U'") })
            Button("F'", action: { rcManager.rotate("F'") })
            Button("R'", action: { rcManager.rotate("R'") })
            Button("D'", action: { rcManager.rotate("D'") })
            Button("B'", action: { rcManager.rotate("B'") })
            Button("L'", action: { rcManager.rotate("L'") })
        }
        HStack {
            Button("U2", action: { rcManager.rotate("U2") })
            Button("F2", action: { rcManager.rotate("F2") })
            Button("R2", action: { rcManager.rotate("R2") })
            Button("D2", action: { rcManager.rotate("D2") })
            Button("B2", action: { rcManager.rotate("B2") })
            Button("L2", action: { rcManager.rotate("L2") })
        }
        HStack {
            Button("Uw", action: { rcManager.rotate("Uw") })
            Button("Fw", action: { rcManager.rotate("Fw") })
            Button("Rw", action: { rcManager.rotate("Rw") })
            Button("Dw", action: { rcManager.rotate("Dw") })
            Button("Bw", action: { rcManager.rotate("Bw") })
            Button("Lw", action: { rcManager.rotate("Lw") })
        }
        HStack {
            Button("M" , action: { rcManager.rotate("M" ) })
            Button("M'", action: { rcManager.rotate("M'") })
            Button("E" , action: { rcManager.rotate("E" ) })
            Button("E'", action: { rcManager.rotate("E'") })
            Button("S" , action: { rcManager.rotate("S" ) })
            Button("S'", action: { rcManager.rotate("S'") })
        }
        HStack {
            Button("X" , action: { rcManager.rotate("X" ) })
            Button("X'", action: { rcManager.rotate("X'") })
            Button("Y" , action: { rcManager.rotate("Y" ) })
            Button("Y'", action: { rcManager.rotate("Y'") })
            Button("Z" , action: { rcManager.rotate("Z" ) })
            Button("Z'", action: { rcManager.rotate("Z'") })
        }
        HStack {
            Button("Undo (\(rcManager.undoCount))", action: { rcManager.undo() }).disabled(!rcManager.canUndo)
                .keyboardShortcut(.init("z"), modifiers: [.command])
                .padding(.trailing, 50)
            Button("Redo (\(rcManager.redoCount))", action: { rcManager.redo() }).disabled(!rcManager.canRedo)
                .keyboardShortcut(.init("z"), modifiers: [.shift, .command])
        }
        
        HStack {
            Button("Reset", action: { rcManager.reset() })
                .keyboardShortcut(.init("r"), modifiers: [.command])
                .padding(.trailing, 30)
            Button("Scramble", action: { rcManager.scramble() })
                .keyboardShortcut(.init("s"), modifiers: [.command])
        }
    }
}

struct ContentView: View {
    var body: some View {
        SubView()
            .environmentObject(RCManager.share)
    }
}

PlaygroundPage.current.setLiveView(ContentView())
```

</details>


![liveView5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/31589728-34d8-4b48-a800-156da9bea8c7.png)

![IMG_0149.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/c9b9c11d-7a93-4f99-b6d3-6f4410323db9.png)



# キューブの3D描画

↓ この記事を参考に、Swiftで実装しようと思います。

https://note.com/dimage/n/nac1f6c236f06


<br><br>今回はここまで。**以上**