TCAでじゃんけんアプリを作ってみた

# 動作
![Simulator Screen Recording - iPhone 15 - 2024-02-27 at 16.30.03.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2361573/7e5493d1-b305-1c8f-8562-fcffeff8f105.gif)


# 実装

```App.swift
import SwiftUI
import ComposableArchitecture

@main
struct testTCAApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(
                store: Store(initialState: RockScissorPaper.State()) {
                    RockScissorPaper()
                }
              )
        }
    }
}
```

```ContentView.swift
import SwiftUI
import ComposableArchitecture

struct ContentView: View {
    let store: StoreOf<RockScissorPaper>

    var body: some View {
        VStack {
            Spacer()

            if let result = store.state.result {
                Text(result.description)
                    .font(.system(size: 64))
                    .multilineTextAlignment(.center)
            }

            Spacer()

            HStack {
                VStack {
                    Text(store.handOfPlayerA?.rawValue ?? "?")
                        .fixedSize()
                        .font(.system(size: 64))
                    Button("Random Hand for A") {
                        store.send(
                            .setHand(.random(), to: .playerA)
                        )
                    }
                }
                VStack {
                    Text(store.handOfPlayerB?.rawValue ?? "?")
                        .fixedSize()
                        .font(.system(size: 64))
                    Button("Random Hand for B") {
                        store.send(
                            .setHand(.random(), to: .playerB)
                        )
                    }
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

#Preview {
    ContentView(
        store: Store(initialState: RockScissorPaper.State()) {
            RockScissorPaper()
        }
    )
}

```

```RockScissorPaper.swift
import ComposableArchitecture

@Reducer
struct RockScissorPaper {
    @ObservableState
    struct State: Equatable {
        var handOfPlayerA: Hand?
        var handOfPlayerB: Hand?
        var result: GameResult?
    }

    enum Action {
        case setHand(Hand, to: Player)
        case checkHands
    }

    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case let .setHand(hand, to: player):
                switch player {
                case .playerA: state.handOfPlayerA = hand
                case .playerB: state.handOfPlayerB = hand
                }
                return .send(.checkHands)
            case .checkHands:
                guard let handOfPlayerA = state.handOfPlayerA,
                      let handOfPlayerB = state.handOfPlayerB else {
                    return .none
                }

                let playerAOutcome = handOfPlayerA.compete(with: handOfPlayerB)
                switch playerAOutcome {
                case .win: state.result = GameResult(winner: .playerA)
                case .lose: state.result = GameResult(winner: .playerB)
                case .draw: state.result = GameResult(winner: nil)
                }
                return .none
            }
        }
    }

}

struct GameResult: Equatable{
    let winner: Player?
    var description: String {
        switch winner {
        case .playerA: "winner \nA!"
        case .playerB: "winner \nB!"
        case .none: "Drew!"
        }
    }

}

enum Player {
    case playerA
    case playerB
}

enum Hand: String {
    case rock = "✊"
    case scissor = "✌️"
    case paper = "🖐️"

    func compete(with hand: Hand) -> Outcome {
        guard self != hand else { return .draw }
        return switch self {
        case .rock: hand == .scissor ? .win : .lose
        case .scissor: hand == .paper ? .win : .lose
        case .paper: hand == .rock ? .win : .lose
        }
    }
}

extension Hand: CaseIterable {
    static func random() -> Hand {
        Hand.allCases.randomElement()!
    }
}

enum Outcome {
    case win
    case lose
    case draw
}
```

# 感想
自分の場合、MVVMではViewから使いたいAPIの形から考えて実装していたが、構造が少し複雑になると途端にどのように部品同士を結合するかで混乱していた。TCAを用いると、Stateに状態を、Actionに動作を定義し、ReducerにActionの実装を書けばよい、とかっちりと型が決まっているため、その点においては楽かもしれない。多少冗長な気もするが一貫性を保ちやすそうなので、拡張性の高さが求められる大規模なプロジェクトに向いている気がする。
まだ副作用に関してはきちんと理解していないので、そちらも調べていきたい。

# 参考資料
https://github.com/pointfreeco/swift-composable-architecture?tab=readme-ov-file

https://qiita.com/zeero/items/b77cb689d9a707d94ac7