



ここまでの経緯は、上記記事を参照ください。
Sponsor Link
目次
アプリ設計:振り返り
これまでのところ、前回作った設計に沿って作ってこれています。
今回は、10フレームの特別処理への対応を行います。
10フレームの特別処理とは、以下のことです。
- スペアの場合は、さらに1投投げられる(3投目を投げられる)
- 1投目がストライクの場合は、さらに2投を投げられる
- 1、2投目がストライクの場合は、さらに1投投げられる
スコア計算も少し特別です。
- 10フレームでの計算は、ストライク・スペアにかかわらず、2投もしくは3投の合計とする
# 詳細は、ボーリングのスコア計算を調べてみてください
10フレームでの3投を記録できるように
これまでの機能追加と同じように、<テスト追加>→<テスト失敗確認>→<機能実装追加>→<テスト成功確認>→<リファクタリング> というサイクルを実行していきます。
TDD では、テスト結果の色の変化をイメージして red-green-refactoring サイクルと呼ばれたりします。
10フレーム目に3投するテストの追加
ルールでは、10フレームは、最大3投投げられるのですが、今回作っているモデルでは、そのための処理を追加していないので、投げられません。
まずは、10フレームで3投投げられることをテストします。
3投できるケースは、以下のケースです。
- ストライクx3
- ストライクx2 +1投
- ストライク+2投
- スペア+1投
パーフェクトと呼ばれますが、全ての投球がストライクだった時には、合計で12回(各1回@1〜9フレーム+3回@10フレーム)のストライクが記録されます。
上記の4ケースのテストを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
func test_recordScore_12Strikes_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<12 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } } func test_recordScore_11StrikesPlus1_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<11 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertTrue(bowlingGame.addBowlResult(3), "failed to record 3rd throw in 10th frame") } func test_recordScore_10StrikesPlus2_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<10 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertTrue(bowlingGame.addBowlResult(3), "failed to record 2nd throw in 10th frame") XCTAssertTrue(bowlingGame.addBowlResult(3), "failed to record 3rd throw in 10th frame") } func test_recordScore_9StrikesPlusSparePlus1_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<9 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertTrue(bowlingGame.addBowlResult(4), "failed to record 1st throw in 10th frame") XCTAssertTrue(bowlingGame.addBowlResult(6), "failed to record 2nd throw in 10th frame") XCTAssertTrue(bowlingGame.addBowlResult(5), "failed to record 3rd throw in 10th frame") } |
実行してみると最初のテストでは、”XCTAssertTrue failed – failed to record 11-th strike” というエラーになります。11番目のストライクは 10フレーム目の2投目のストライクに該当します。
現在のスコア記録は、以下の関数で行なっています。
1 2 3 4 5 6 7 8 9 10 11 12 |
mutating func addBowlResult(_ num: Int) -> Bool { if let addIndex = self.findRecordableFrameBowl() { self.frames[addIndex.frameIndex].bowls[addIndex.bowlIndex] = .Done(num) // case Strike if (num == 10) && (addIndex.bowlIndex == 0) { // Strike ! self.frames[addIndex.frameIndex].bowls[1] = .NoNeed } return true } return false |
ストライクだった時に、2投目を .NoNeed に設定するだけで、10フレーム目の特別ルールを実装していないため、1投目がストライクであれば、2投目が記録できなくなっています。
10フレーム目でのストライクの記録に対応する
ということで、10フレーム目のときの処理を追加します。
追加する処理は、以下です。
- 10フレーム目の1投目がストライクであっても2投目を .NotYet のままにし、3投目を .NoNeed から .NotYet に変更する
- 10フレーム目の2投目でスペアになったら、3投目を .NoNeed から .NotYet に変更する
10フレーム目の2投目を .NotYet にキープするだけでなく、3投目を記録可能にするように処理しておきます。
少し複雑ですが、以下のようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
mutating func addBowlResult(_ num: Int) -> Bool { if let addIndex = self.findRecordableFrameBowl() { self.frames[addIndex.frameIndex].bowls[addIndex.bowlIndex] = .Done(num) if (addIndex.frameIndex == 9) { // new <start> if addIndex.bowlIndex != 2 { if num == 10 { self.frames[9].bowls[2] = .NotYet } if addIndex.bowlIndex == 1 { if let bowl1 = bowlResult(frameIndex: 9, bowlIndex: 0) { if bowl1 + num == 10 { self.frames[9].bowls[2] = .NotYet } } } } return true } // new <end> // case Strike if (num == 10) && (addIndex.bowlIndex == 0) { // Strike ! self.frames[addIndex.frameIndex].bowls[1] = .NoNeed } return true } return false } |
10フレーム目に3投するテスト
上記のコードで、先に追加した4つのテストケースはパスすることがわかります。
10フレーム目で3投するケースでのスコア計算
さきの 10フレーム目で3投する4つのケースで、スコア計算も正しいかを確認することにします。
10フレーム目が3投になっても、3投を合計するだけで、スペアやストライクの計算は行いません。
ただし、9フレーム目がストライクであれば、10フレーム目の1投目2投目を考慮して計算します。1投目2投目がストライクであれば、ターキーとなるように計算するということです。
テストに、スコア計算も追加
プログラミングの知識ではなく、ボーリングの知識が必要となります・・・
それぞれの正しいスコアを計算して、チェックするようにしました。10フレームの影響が考えられる、9フレーム目、10フレーム目のスコアをテスト対象としています。
チェック対象は、スコアの値だけでなく、どのタイミングで計算可能になっているかもテストしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
func test_recordScore_12Strikes_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<9 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record 10-th strike ") XCTAssertNil(bowlingGame.frameScore(frameIndex: 8)) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record 11-th strike ") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 8), 270) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record 12-th strike ") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 9), 300) } func test_recordScore_11StrikesPlus1_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<11 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertEqual(bowlingGame.frameScore(frameIndex: 8), 270) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(3), "failed to record 3rd throw in 10th frame") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 9), 293) } func test_recordScore_10StrikesPlus2_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<10 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertNil(bowlingGame.frameScore(frameIndex: 8)) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(3), "failed to record 2nd throw in 10th frame") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 8), 263) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(3), "failed to record 3rd throw in 10th frame") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 9), 279) } func test_recordScore_9StrikesPlusSparePlus1_shouldBeRecorded() { var bowlingGame = BowlingGame() for index in 0..<9 { XCTAssertTrue(bowlingGame.addBowlResult(10), "failed to record \(index+1)-th strike ") } XCTAssertNil(bowlingGame.frameScore(frameIndex: 8)) XCTAssertTrue(bowlingGame.addBowlResult(4), "failed to record 1st throw in 10th frame") XCTAssertNil(bowlingGame.frameScore(frameIndex: 8)) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(6), "failed to record 2nd throw in 10th frame") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 8), 254) XCTAssertNil(bowlingGame.frameScore(frameIndex: 9)) XCTAssertTrue(bowlingGame.addBowlResult(5), "failed to record 3rd throw in 10th frame") XCTAssertEqual(bowlingGame.frameScore(frameIndex: 9), 269) } |
実行すると、テストが失敗するのではなく、Index out of range という Fatal Error が発生します。
ストライクやスペアのスコア計算をするために、次の投球や、次々の投球の値が必要になりますが、10フレーム目での次の投球や次々の投球の値を取得しようとして、11フレーム目を探してエラーとなっています。
10フレーム目で3投するスコア計算の実装
テストでエラーになることを確認したので、実装していきます。
特定の投球結果を取得する bowlResult と フレームでのスコアを計算する frameScore は、以下のような実装になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
func bowlResult(frameIndex: Int, bowlIndex: Int) -> Int? { let bowl = frames[frameIndex].bowls[bowlIndex] switch bowl { case .Done(let num): return num default: return nil } } func frameScore(frameIndex: Int) -> Int? { if let lastResult = frameIndex == 0 ? 0 : frameScore(frameIndex: frameIndex - 1) { switch frameState(frameIndex: frameIndex) { case .Spare: if let nextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 0) { return lastResult + 10 + nextBowl } return nil // need further info case .Strike: if let nextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 0) { switch frameState(frameIndex: frameIndex+1) { case .Strike: if let nextNextBowl = bowlResult(frameIndex: frameIndex+2, bowlIndex: 0) { return lastResult + 10 + 10 + nextNextBowl } case .Spare: return lastResult + 10 + 10 case .Others: if let nextNextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 1) { return lastResult + 10 + nextBowl + nextNextBowl } } } return nil // need further info case .Others: if let bowl0 = bowlResult(frameIndex: frameIndex, bowlIndex: 0) { if let bowl1 = bowlResult(frameIndex: frameIndex, bowlIndex: 1) { return lastResult + bowl0 + bowl1 } } } } return nil } } |
みてわかるように、単純に次の投球を探して辿っています。
現在の実装に追加していっても良いのですが、コードが複雑になりそうですので、ストライクのケース、スペアのケース、通常ケースに分けて、前者2つを別メソッドにしてしまいましょう。
以下が、分割した、frameScore です。(単純に該当部分を外部メソッドに分割しただけです)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
func frameScore(frameIndex: Int) -> Int? { if let prevFrameResult = frameIndex == 0 ? 0 : frameScore(frameIndex: frameIndex - 1) { switch frameState(frameIndex: frameIndex) { case .Spare: return calcSpareFrame(frameIndex: frameIndex, prevFrameResult: prevFrameResult) case .Strike: return calcStrikeFrame(frameIndex: frameIndex, prevFrameResult: prevFrameResult) case .Others: if let bowl0 = bowlResult(frameIndex: frameIndex, bowlIndex: 0) { if let bowl1 = bowlResult(frameIndex: frameIndex, bowlIndex: 1) { return prevFrameResult + bowl0 + bowl1 } } } } return nil } func calcSpareFrame(frameIndex:Int, prevFrameResult: Int) -> Int? { if let nextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 0) { return prevFrameResult + 10 + nextBowl } return nil // need further info } func calcStrikeFrame(frameIndex:Int, prevFrameResult: Int) -> Int? { if let nextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 0) { switch frameState(frameIndex: frameIndex+1) { case .Strike: if let nextNextBowl = bowlResult(frameIndex: frameIndex+2, bowlIndex: 0) { return prevFrameResult + 10 + 10 + nextNextBowl } case .Spare: return prevFrameResult + 10 + 10 case .Others: if let nextNextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 1) { return prevFrameResult + 10 + nextBowl + nextNextBowl } } } return nil // need further info } } |
10フレームの計算は、単純に、2投分もしくは3投分を計算すれば良いので、簡単です。以下のようなメソッドを作り、10フレーム目の計算を要求された時に使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func frameScoreFor10th(prevFrameResult:Int) -> Int? { if let f10b1 = bowlResult(frameIndex: 9, bowlIndex: 0) { if let f10b2 = bowlResult(frameIndex: 9, bowlIndex: 1) { if let f10b3 = bowlResult(frameIndex: 9, bowlIndex: 2) { return prevFrameResult + f10b1 + f10b2 + f10b3 } return prevFrameResult + f10b1 + f10b2 } } return nil } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
func frameScore(frameIndex: Int) -> Int? { if let prevFrameResult = frameIndex == 0 ? 0 : frameScore(frameIndex: frameIndex - 1) { if frameIndex == 9 { return frameScoreFor10th(prevFrameResult: prevFrameResult) // NEW !! } switch frameState(frameIndex: frameIndex) { case .Spare: return calcSpareFrame(frameIndex: frameIndex, prevFrameResult: prevFrameResult) case .Strike: return calcStrikeFrame(frameIndex: frameIndex, prevFrameResult: prevFrameResult) case .Others: if let bowl0 = bowlResult(frameIndex: frameIndex, bowlIndex: 0) { if let bowl1 = bowlResult(frameIndex: frameIndex, bowlIndex: 1) { return prevFrameResult + bowl0 + bowl1 } } } } return nil } |
10フレームの途中であれば、計算終了できないので、nil を返しています。
次に、10フレームが影響を与える9フレームのスコア計算のケースを考えると、以下のようになります。
- 9フレーム目がストライクだった時は、10フレーム目で次の投球と次々の投球を探す。このとき:10フレーム目の1投目と2投目が必要な情報となる(10フレーム目の1投目がストライクかどうかにかかわらず)
- 9フレーム目がスペアだったときは、10フレーム目で次の投球を探す。このとき、10フレーム目の1投目が必要な情報となる。が、これは、通常の処理と変わらない
上記より、9フレーム目がストライクだった時に特殊処理を入れれば良いはずなので、ストライク時の計算を行う calcStrikeFrame に特殊処理を追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
func calcStrikeFrame(frameIndex:Int, prevFrameResult: Int) -> Int? { if (frameIndex == 8) { if let f10b1 = bowlResult(frameIndex: 9, bowlIndex: 0) { if let f10b2 = bowlResult(frameIndex: 9, bowlIndex: 1) { return prevFrameResult + 10 + f10b1 + f10b2 } } return nil } if let nextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 0) { switch frameState(frameIndex: frameIndex+1) { case .Strike: if let nextNextBowl = bowlResult(frameIndex: frameIndex+2, bowlIndex: 0) { return prevFrameResult + 10 + 10 + nextNextBowl } case .Spare: return prevFrameResult + 10 + 10 case .Others: if let nextNextBowl = bowlResult(frameIndex: frameIndex+1, bowlIndex: 1) { return prevFrameResult + 10 + nextBowl + nextNextBowl } } } return nil // need further info } } |
改めて、テストを通してみると、全てパスすることが確認できます。
UI を使って、試そうとすると、10フレーム目の表示がおかしい & 3投分の表示枠がないことに気づきます。
ビューを10フレームの3投に対応
10フレーム3投目の表示枠
FrameView で、10フレームの時だけ、3つ表示するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
struct FrameView: View { @ObservedObject var viewModel: BowlingGameViewModel let index:Int var body: some View { VStack(spacing:0) { FrameIndexView(index: index) HStack(spacing: 0) { FrameBowlView(viewModel: viewModel, frameIndex: index, bowlIndex: 0) FrameBowlView(viewModel: viewModel, frameIndex: index, bowlIndex: 1) if (index == 9) { // NEW FrameBowlView(viewModel: viewModel, frameIndex: index, bowlIndex: 2) // NEW } // NEW } FrameScoreView(viewModel: viewModel, frameIndex: index) } } } |
これまでは、frame を固定値指定だったので、少し表示が崩れます。そこを整えるようにして以下のような表示になります。
10フレーム目の3投の表示をテスト
モデルで使ったテストをベースに、各投球の表示と各フレームのスコアをテストしています。 (長いので、全投球ストライクのテストケースのみ抜粋しました)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
func test_recordScore_12Strikes_displayedCorrectly() { for _ in 0..<9 { bowlButtons[10].tap() } bowlButtons[10].tap() XCTAssertEqual(frameScoreLabels[8].label, "-") XCTAssertEqual(frameScoreLabels[9].label, "-") XCTAssertEqual(frameBowlLabels[9][0].label, "X") XCTAssertEqual(frameBowlLabels[9][1].label, "-") XCTAssertEqual(frameBowlLabels[9][2].label, "-") bowlButtons[10].tap() XCTAssertEqual(frameScoreLabels[8].label, "270") XCTAssertEqual(frameScoreLabels[9].label, "-") XCTAssertEqual(frameBowlLabels[9][0].label, "X") XCTAssertEqual(frameBowlLabels[9][1].label, "X") XCTAssertEqual(frameBowlLabels[9][2].label, "-") bowlButtons[10].tap() XCTAssertEqual(frameScoreLabels[9].label, "300") XCTAssertEqual(frameBowlLabels[9][0].label, "X") XCTAssertEqual(frameBowlLabels[9][1].label, "X") XCTAssertEqual(frameBowlLabels[9][2].label, "X") } |
このテストを動かしてみると 第10フレームの2投目、3投目の表示で失敗していることがわかります。通常のフレームであれば、1投目がストライクであれば、2投目の表示は、””になります。
その点が、10フレーム目での期待する表示と異なるために失敗となっています。
ビューの変更
この振る舞いは、FrameBowlView ビューではなく、BowlingGameViewModel で制御されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func bowlAsText(frameIndex:Int, bowlIndex: Int) -> String { switch game.frameState(frameIndex: frameIndex) { case .Strike: return bowlIndex == 0 ? "X" : "" case .Spare: if bowlIndex == 1 { return "/" } fallthrough case .Others: if let num = game.bowlResult(frameIndex: frameIndex, bowlIndex: bowlIndex) { return String(num) } return "-" } } |
表示要素を決める時に、フレームの状態を最初にチェックしているため、1投目がストライクのフレームでは、2投目の枠が””になっています。
特殊な表示を必要とするのは、10フレーム目だけなので、分岐させて処理してしまいます。
10フレーム目の表示をまとめると、以下になります。
- 1投目が10であれば、ストライク表示、それ以外は、該当数字表示
- 2投目は、1投目がストライクであれば、2投目は、該当数字表示(ストライクであればストライク表示)
- 2投目は、1投目が通常投球であれば、スペア表示するか、該当数字表示
- 3投目は、ストライク表示 or (該当すれば)スペア表示 or 該当数字表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
func bowlAsText(frameIndex:Int, bowlIndex: Int) -> String { if (frameIndex == 9) { switch bowlIndex { case 0: if let bowl0 = game.bowlResult(frameIndex: 9, bowlIndex: 0) { if bowl0 == 10 { return "X" } return String(bowl0) } return "-" case 1: if let bowl0 = game.bowlResult(frameIndex: 9, bowlIndex: 0) { if let bowl1 = game.bowlResult(frameIndex: 9, bowlIndex: 1) { if bowl0 == 10 { if bowl1 == 10 { return "X" } return String(bowl1) } if (bowl0 + bowl1 == 10) { return "/" } return String(bowl1) } } return "-" case 2: if let bowl1 = game.bowlResult(frameIndex: 9, bowlIndex: 1) { if let bowl2 = game.bowlResult(frameIndex: 9, bowlIndex: 2) { if bowl2 == 10 { return "X" } if bowl1 + bowl2 == 10 { return "/" } return String(bowl2) } } return "-" default: return "-" } } switch game.frameState(frameIndex: frameIndex) { case .Strike: return bowlIndex == 0 ? "X" : "" case .Spare: if bowlIndex == 1 { return "/" } fallthrough case .Others: if let num = game.bowlResult(frameIndex: frameIndex, bowlIndex: bowlIndex) { return String(num) } return "-" } } |
上記コードで、UITest も通るようになりました。
リファクタリング
先ほどの、bowlAsText メソッドで、10フレーム対応のコードの方が、通常フレーム処理のコードよりも大きくなってしまっていますので、別メソッドに分けることにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
func bowlAsTextForFrame10(bowlIndex: Int) -> String { switch bowlIndex { case 0: if let bowl0 = game.bowlResult(frameIndex: 9, bowlIndex: 0) { if bowl0 == 10 { return "X" } return String(bowl0) } return "-" case 1: if let bowl0 = game.bowlResult(frameIndex: 9, bowlIndex: 0) { if let bowl1 = game.bowlResult(frameIndex: 9, bowlIndex: 1) { if bowl0 == 10 { if bowl1 == 10 { return "X" } return String(bowl1) } if (bowl0 + bowl1 == 10) { return "/" } return String(bowl1) } } return "-" case 2: if let bowl1 = game.bowlResult(frameIndex: 9, bowlIndex: 1) { if let bowl2 = game.bowlResult(frameIndex: 9, bowlIndex: 2) { if bowl2 == 10 { return "X" } if bowl1 + bowl2 == 10 { return "/" } return String(bowl2) } } return "-" default: return "-" } } func bowlAsText(frameIndex:Int, bowlIndex: Int) -> String { if (frameIndex == 9) { return bowlAsTextForFrame10(bowlIndex: bowlIndex) } switch game.frameState(frameIndex: frameIndex) { case .Strike: return bowlIndex == 0 ? "X" : "" case .Spare: if bowlIndex == 1 { return "/" } fallthrough case .Others: if let num = game.bowlResult(frameIndex: frameIndex, bowlIndex: bowlIndex) { return String(num) } return "-" } } |
メインとなるフローが見やすくなりました。
まとめ その5
ほとんど完成していますが、手動でアプリをテストしていると、1フレームに対して合計で10を超える入力ができてしまうことが目につきました。
次回、入力できないボタンを disable にするのと、ボタンの見た目を少し変更して終わりにしようかと思います。
説明は以上です。次回に続きます。Happy Coding!
Sponsor Link