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?

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

Posted at

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

英語で

ルービックキューブ の「回転記号」は、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)』と呼ぶそうです。

回転記号

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

この記号を、前回の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を実装することを考えると、クラス化しておくと使い勝手が良さそうです。

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 で確認します。

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

Undo Redoを実装

シンプルにUndoManagerを使って実装します。

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

RCManager変更箇所

UndoManagerのインスタンス追加

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

rotate変更箇所

undo動作、redo動作を登録

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関数、他を定義

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ボタンを追加

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

Playground 確認

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

コードを表示する(480行)
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())

liveView5.png

IMG_0149.PNG

キューブの3D描画

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



今回はここまで。以上

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?