サイトアイコン BergamotMagic

おそるおそるSwift 4-2 ハイローゲームを作る ~基盤部分を作成する~

Swift5+Xcode11.6

今回のゴール

 おそるおそる機能を追加していくことにして、今回は基盤部分である①乱数を発生させる、②それを配列に入れる、③勝敗を判定する、という部分を作ってみます。

https://bergamotmagic.tokyo/wp-content/uploads/2020/09/b7e9966286a80b2e84f6754944e09695.mp4
今回のゴールのプレビュー

Swiftの配列の取り回しに苦戦

 あまり意味はないのですが、処理部分をProcessorクラスにまとめてみることにしました。本来の使い方とはちょっと違う気がしますが、クラスの使い方に慣れるため、またContentView.swiftを表示ルーチンに特化するために今回はそうしてみました(途中で変更するかもしれません)。
 さて、以前使ったことがある機能ばかりなのに、すっかり文法を忘れてしまい、かすかに自己嫌悪に陥りながら、新しいswiftファイル”processorx.swift”を作成し、コードを組んでみました(予め言っておきますが、動きません)。

現在のファイル構造
import Foundation

class Processor : ObservableObject {
    @Published var history :[Int] = [5]
    var randNum: Int = 0
    
    func judge(choice:String) -> String {
        randNum = Int.random(in:1...9)
        history.append(randNum)
        let prevNum:Int = history[history.count - 1]
        let nowNum:Int = history[history.count]
        if nowNum == prevNum {
            return "Win"
        } else if nowNum >= prevNum, choice == "high" {
            return "Win"
        } else if nowNum <= prevNum, choice == "low" {
            return "Win"
        } else {
            return "Lose"
        }
    }
}

 また、動作確認のためContentView.swiftの”High””Low”ボタンのaction文にデバッグウィンドウに処理結果を出力させるためのコードを入れておきます。

import SwiftUI

struct ContentView: View {
    @ObservedObject var processor = Processor()
    
    var body: some View {
        VStack {
            Text("High&Low Game!")
                .font(.largeTitle)
                .fontWeight(.heavy)
                .foregroundColor(Color.white)
                .background(Color.orange)
            Spacer()
            HStack {
                Spacer()
                Text("3回前")
                    .frame(width: 50.0)
                Text("2回前")
                    .frame(width: 50.0)
                Text("前回")
                    .frame(width: 50.0)
                Text("今回")
                    .font(.title)
                    .frame(width: 80.0)
                Spacer()
            }
            HStack {
                Spacer()
                Text("3")
                    .frame(width: 50.0)
                Text("2")
                    .frame(width: 50.0)
                Text("1")
                    .frame(width: 50.0)
                Text("?")
                    .font(.largeTitle)
                    .frame(width: 80.0)
                Spacer()
            }
                Spacer()
            HStack {
                Spacer()
                Button(action: {
                    print(self.processor.judge(choice: "high"))
                }) {
                    Text("High")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .foregroundColor(Color.white)
                }
                .frame(width: 100.0)
                .background(Color.red)
                Spacer()
                Button(action: {
                    print(self.processor.judge(choice: "low"))
                }) {
                    Text("Low")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundColor(Color.white)
                }
                .frame(width: 100.0)
                .background(Color.blue)
                Spacer()
            }
            Spacer()
            Text("$1")
                .font(.largeTitle)
                .fontWeight(.black)
                .foregroundColor(Color.red)
            Spacer()
        }

    }
}

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

 早速動かしてみると、早速processor.swiftの11行目(NowNumの定義のところ)でエラー(Thread 1: Fatal error: Index out of range)が吐き出されました。簡単に言えば「配列の範囲外の値を取りに行ってるよ」といったところでしょう。また、1つ前の数字を取得させるつもりのprevNumも今の数字を取得しており、うまく動いていません。
 配列の値の個数をインデックスに入れたのが間違いだったのかと思い、histori[history.count]をhistory[history.endindex]に修正しましたが、うまくいきませんでした。
 日本語のサイトでは情報が得られなかったので、珍しくApple Developer Documentationを参照したところ、endIndexは1つ大きい値を取得していることがわかりました。

 一時は「配列のインデックス値([]内)で演算させることはできないのか?」など、悶々としてしまいましたが、判れば大したことはありませんでした。ついでに、動作確認のためのprint文をあちこちにちりばめてproccessorx.swiftを書き直したところ、無事動作しました。
 10行目のenumerated()は配列から複数の値(タプル)を取り出すことができる関数とのことで、python由来の関数のようです。

import Foundation

class Processor : ObservableObject {
    @Published var history :[Int] = [5] //history:抽選数字履歴保持用配列
    var randNum: Int = 0     //randNum:乱数
    
    func judge(choice:String) -> String {
        randNum = Int.random(in:1...9)
        history.append(self.randNum)
        for (index, his) in history.enumerated() { 
            print("history[\(index)]: \(his)")
        }
        print("endIndex:\(history.endIndex)")
        let prevNum:Int = history[history.endIndex - 2] //prevNum:1つ前の抽選数字
        print ("prevNum:\(prevNum)")
        let nowNum:Int = history[history.endIndex - 1]  //nowNum:今回の抽選数字
        print ("NowNum:\(nowNum)")
        if nowNum == prevNum {
            return "Win"
        } else if nowNum >= prevNum, choice == "high" {
            return "Win"
        } else if nowNum <= prevNum, choice == "low" {
            return "Win"
        } else {
            return "Lose"
        }
    }
}

 最後に判定部分のif文のネストが冗長な気がしたので、少しまとめてみました。

import Foundation

class Processor : ObservableObject {
    @Published var history :[Int] = [5] //history:抽選数字履歴保持用配列
    var randNum: Int = 0     //randNum:乱数
    
    func judge(choice:String) -> String {
        randNum = Int.random(in:1...9)
        history.append(self.randNum)
        for (index, his) in history.enumerated() { 
            print("history[\(index)]: \(his)")
        }
        print("endIndex:\(history.endIndex)")
        let prevNum:Int = history[history.endIndex - 2] //prevNum:1つ前の抽選数字
        print ("prevNum:\(prevNum)")
        let nowNum:Int = history[history.endIndex - 1]  //nowNum:今回の抽選数字
        print ("NowNum:\(nowNum)")
        if (nowNum == prevNum) || ((nowNum >= prevNum)&&(choice == "high")) || ((nowNum <= prevNum)&&( choice == "low")) {
            return "Win"
        } else {
            return "Lose"
        }
    }
}

次回は

 デバッグウィンドウにチロチロ表示させても仕方がないので、画面に表示させてみます。

モバイルバージョンを終了