↑ 前回の記事で予告した内容です。
英語で
ルービックキューブ の「回転記号」は、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
を実装することを考えると、クラス化しておくと使い勝手が良さそうです。
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())
Undo
Redo
を実装
シンプルにUndoManager
を使って実装します。
RCManager
クラスにUndoManager
のインスタンスを保持して、rotate
するときにregisterUndo
でundo
動作を登録します。同様に、undo
動作時はredo
動作を登録します。
・RCManager
変更箇所
UndoManager
のインスタンス追加
final class RCManager: ObservableObject {
@MainActor static let share = RCManager()
@Published var rcData: RCData = RCDataInitialPattern
+ let undoManager = UndoManager()
private init() { }
・rotate
変更箇所
undo
動作、redo
動作を登録
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
関数、他を定義
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
ボタンを追加
+ 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 })
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())
キューブの3D描画
↓ この記事を参考に、Swiftで実装しようと思います。
今回はここまで。以上