SwiftUI初心者がじゃんけんアプリを作ってみた

## はじめに
SwiftUIの勉強のためにじゃんけんアプリを作成しました。
SwiftUIについては、1年以上前に少し触った程度で書き方もあまり詳しくないため、勉強がてらじゃんけんアプリを作成してみました。
もし、手探りで制作したため、誤用などがあればご指摘いただけると幸いです。

## ゴール
以下のようなじゃんけんアプリの作成をゴールとします。
プレイヤーがグー、チョキ、パーいずれかの手のボタンをタップすると、CPU😎の手もグー、チョキ、パーのいずれかでランダム生成され、プレイヤーとCPUの手の組み合わせに応じて結果が表示されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017091/0fd3efa8-7a0f-06fe-a8c5-bde7e699ba6c.gif" width="300">

## じゃんけんアプリの作成
### 列挙型Handの定義
まず、じゃんけんで使用するグー、チョキ、パーの手を定義した列挙型Handを定義します。
Handにはextensionを用いてtextプロパティとemojiプロパティも定義します。
例えば、`Hand.rock.text`は`グー`として扱われます。
```swift
import Foundation

enum Hand {
    case rock
    case scissors
    case paper
}

extension Hand {
    var text: String {
        switch self {
        case .rock:
            return "グー"
        case .scissors:
            return "チョキ"
        case .paper:
            return "パー"
        }
    }
    
    var emoji: String {
        switch self {
        case .rock:
            return "✊"
        case .scissors:
            return "✌️"
        case .paper:
            return "✋"
        }
    }
}

```

### 列挙型Resultの定義
次に、引き分け、勝ち、負けの結果を表す列挙型Resultを定義します。
Resultにはextensionを用いてtextプロパティも定義します。
例えば、`Result.draw.text`は`引き分け`として扱われます。
```swift
import Foundation

enum Result {
    case draw
    case win
    case lose
}

extension Result {
    var text: String {
        switch self {
        case .draw:
            return "引き分け"
        case .win:
            return "勝ち"
        case .lose:
            return "負け"
        }
    }
}

```

### Utilクラスの定義
ロジック（状態管理を行わない部分）を定義したUtilクラスを定義します。
CPUの手をランダムで生成するgenerateCPUHand関数とじゃんけんの勝敗を判定するjudgeResult関数を定義します。
```swift
import Foundation

class Util {
    static func generateCPUHand() -> Hand {
        let hands = [Hand.rock, Hand.scissors, Hand.paper]
        let hand = hands.randomElement()
        return hand!
    }

    static func judgeResult(playerHand: Hand, cpuHand: Hand) -> Result {
        let isDraw = playerHand == cpuHand
        let isWin = (playerHand == .rock && playerHand == .scissors) || (playerHand == .scissors && cpuHand == .paper) || (playerHand == .paper && cpuHand == .rock)
        if isDraw {
            return .draw
        } else if isWin {
            return .win
        } else {
            return .lose
        }
    }
}

```

### Stateクラスの定義
続いて、状態管理を行うために、ObservableObjectを用いてStateクラスを定義します。
`@Published`で状態管理をする変数を定義します。
ボタンをタップした際に状態管理を行う関数onTapHandも定義します。

```swift

import Foundation

final class State: ObservableObject {
    @Published var message = "じゃんけん…"
    @Published var result = Result.draw
    @Published var cpuHand = Hand.rock
    
    func onTapHand(hand: Hand) -> Void {
        let playerHand = hand
        cpuHand = Util.generateCPUHand()
        result = Util.judgeResult(playerHand: playerHand, cpuHand: cpuHand)
        message = result.text
    }
}

```

### 構造体HandButtonの定義
グー、チョキ、パーのボタンのUIコンポーネントを定義したクラスを定義します。
引数として`() -> Void`型のonTapと`Hand`型のhandを定義します。
```swift

import SwiftUI

struct HandButton: View {
    let onTap: () -> Void
    let hand: Hand

    var body: some View {
        Button(action: {
            onTap()
        }, label: {
            VStack {
                Text(hand.emoji).font(.title)
                Text(hand.text).font(.title)
            }
        })
    }
}

struct HandButton_Previews: PreviewProvider {
    static var previews: some View {
        HandButton(onTap: {
            print("Rock button tapped")
        } ,hand: .rock)
    }
}

```

### ContentViewの編集
最後に、ContentViewを編集し、じゃんけんアプリのUIを表示します。
`@ObservedObject`で状態を監視し、UIに反映させます。
ここまでできたらじゃんけんアプリの完成です！
```swift
import SwiftUI

struct ContentView: View {
    @ObservedObject private var state = State()

    var body: some View {
        VStack {
            Spacer()
            Text(state.message).font(.title)
            Text("😎" + state.cpuHand.emoji)
            Spacer()
            HStack {
                Spacer()
                HandButton(onTap: {
                    state.onTapHand(hand: .rock)
                }, hand: .rock)
                Spacer()
                HandButton(onTap: {
                    state.onTapHand(hand: .scissors)
                }, hand: .scissors)
                Spacer()
                HandButton(onTap: {
                    state.onTapHand(hand: .paper)
                }, hand: .paper)
                Spacer()
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

```

## 最後に
enumで列挙型を定義し、ObservableObjectで状態管理を行い、SwiftUIでUIを構築することにより、じゃんけんアプリを作ってみました。
じゃんけんアプリの制作により、SwiftUIの書き方が少しだけわかるようになりました。

## 参考文献
- https://capibara1969.com/2033/
- https://qiita.com/maiki055/items/b24378a3707bd35a31a8
- https://qiita.com/0102_0102johnny/items/fba13b2e7cfa61d437e9