・ 展開図
・ 3D 表示
今回は、展開図 と 3D の ルービックキューブ の動きを同期させ、さらに、解法アルゴリズムを導入します。
展開図 と 3D の ルービックキューブ の同期
これまでの記事で紹介した、2つの Managerクラス を統括する上位のManagerクラス を新設することで 実現します。
機能としては、面の回転要求とリセット要求を、下位の 展開図 と 3D に指示することです。同様に、Undo / Redo の回転要求も下位に指示することで、展開図 と 3D の ルービックキューブ の状態や回転を完全に同期させます。
下位Managerクラスに実装していた、Undo / Redo 処理は削除します。
統括Managerクラス
import SwiftUI
class RubiksCubeManager: ObservableObject {
@MainActor static let share = RubiksCubeManager()
@Published var moves = [String]()
@Published var moved = ""
@Published var baseCount = 0
@Published var reqAnimation = true
let undoManager = UndoManager()
var rcManager = RCManager.share
let cubeManager = CubeManager.share
private init() {
undoManager.groupsByEvent = false
}
func reset() {
baseCount = 0
moves.removeAll()
undoManager.removeAllActions()
rcManager.reset()
cubeManager.reset()
cubeManager.setFrontCam()
}
func move(_ _notation: String, continuous: Bool = false, undo: Bool = false) {
var notation = _notation
if undo {
let reverse = ["U": "U'", "U'": "U", "U2": "U2", "F": "F'", "F'": "F", "F2": "F2", "R": "R'", "R'": "R", "R2": "R2", "D": "D'", "D'": "D", "D2": "D2", "B": "B'", "B'": "B", "B2": "B2", "L": "L'", "L'": "L", "L2": "L2"]
if let undoNotation = reverse[notation] {
notation = undoNotation
} else {
assertionFailure("notation undo failer [\(_notation)]")
}
}
if !continuous {
baseCount = 1
moved = notation
}
undoManager.beginUndoGrouping()
rcManager.rotate(notation)
reqAnimation ? cubeManager.animate_move(notation) : cubeManager.static_move(notation)
undoManager.registerUndo(withTarget: self) { unownedSelf in
unownedSelf.move(notation, continuous: false, undo: !undo)
}
undoManager.endUndoGrouping()
if reqAnimation { Task { try await Task.sleep(nanoseconds: 700_000_000) } }
}
func scramble() {
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"]
moved = ""
moves.removeAll()
for _ in 0 ..< 30 {
repeat {
move = MoveNotation.randomElement()!
} while move.first! == prevMove.first!
moves.append(move)
prevMove = move
}
print("Scramble 30 moves:", moves.joined(separator: " "))
animationMove(message: "Scramble completed!")
}
private func animationMove(message: String? = nil) {
baseCount = moves.count
var doubleMove = false
Timer.scheduledTimer(withTimeInterval: reqAnimation ? 0.7: 0.1, repeats: true) { timer in
if !doubleMove {
if self.moves.isEmpty {
timer.invalidate()
if let message { print(message) }
}
else {
self.moved = self.moves.first!
self.move(self.moved, continuous: true)
let move = self.moves.removeFirst()
if move.last! == "2" && self.reqAnimation {
doubleMove = true
}
}
} else {
doubleMove = false
}
}
}
}
extension RubiksCubeManager {
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() }}
}
解法アルゴリズムの導入
前回の記事でSwiftに移植した kociemba を、プロジェクトに追加するだけです。
一部の冗長なヘッダーファイル(xxx_h.swift)は削除して、ファイル構成を少し単純化しました。
・ 面データの生成
kociemba.solve()の引数に渡す 面データ の生成は、 展開図 情報RCDataに実装します。
extension RCData {
func getCubeStringNotation() -> String {
let RCPosition = ["U", "L", "F", "R", "B", "D"]
func getFace(_ face: RCFace) -> String {
var result = ""
for row in 0 ..< 3 {
for col in 0 ..< 3 {
result += RCPosition[face[row][col] / 9]
}
}
return result
}
var block = ""
block += getFace(self[0]) // U
block += getFace(self[3]) // R
block += getFace(self[2]) // F
block += getFace(self[5]) // D
block += getFace(self[1]) // L
block += getFace(self[4]) // B
return block
}
}
各キューブの色を数値(0〜53)で保持していたので、面データ の生成は 簡単です。
コード
数・量とも増えたので、XcodeプロジェクトをZipしてアップしました。
次回はマシンを作成します。
以上