1
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?

5. ルービックキューブ アプリ

Posted at

・ 展開図

・ 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してアップしました。


次回はマシンを作成します。




以上

1
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
1
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?