Chapter 11

数当てゲームを作ろう / Goの繰り返し

eihigh
eihigh
2025.01.31に更新

前回までで条件分岐や変数について学んで、プログラムっぽさが出てきました。今回は同じ処理を何度も繰り返す方法について学びます。最後はこれまでの集大成として、数当てゲームを作ってみましょう!

繰り返し

for を使えば、繰り返す(ループ)処理を書くことができます。Goのfor文には複数の形式がありますが、まずは「指定回数繰り返す」for文を書いてみます。for range 回数 と書けば { } の中の処理を指定した回数繰り返します。

package main

import "fmt"

func main() {
	for range 3 {
		fmt.Println("ぶりんばんばん")
	}
	fmt.Println("ぼん")
}
$ go run .
ぶりんばんばん
ぶりんばんばん
ぶりんばんばん
ぼん

3回繰り返していますね。

rangeの前に代入文(:= または =)を書くことで、ループ回数を変数に入れられます。

func main() {
	for i := range 3 {
		fmt.Println(i)
	}
}
$ go run .
0
1
2

例によってゼロ始まりの値であることに注意してください。ループ回数を入れる変数の名前は慣習上 i, j, k... を使うことが多いので覚えておくと良いでしょう。

break

breakを使えば、ループを途中でやめることが可能です。

func main() {
    for i := range 5 {
        if i == 2 {
            break
        }
        fmt.Println(i)
    }
    fmt.Println("ループ後はここに来る")
}
$ go run .
0
1
ループ後はここに来る

本来 range 5 で5回繰り返すはずのループが、if i == 2 の条件を満たしたとき、breakが実行され、途中で中断されています。

continue

continueを使えば、今回のループの残りの部分をスキップし、次に進むことができます。

func main() {
    for i := range 5 {
        if i == 2 {
            fmt.Println("スキップします")
            continue
        }
        fmt.Println(i) // 上でcontinueすると、この部分がスキップされる
    }
}
$ go run .
0
1
スキップします
3
4

continueすると通常は実行されるはずの fmt.Println(i)が、そのループに限りスキップされているのがお分かりいただけるでしょう。

break, continueあたりを学んでいた頃は、覚えるキーワードが増えてきて筆者も大変だった記憶があります。そのうち徐々に慣れるものなので、例によって都度調べながら書いていけば大丈夫です。焦らず進めていきましょう。

数当てゲームを作ろう

これで準備が整いました。数当てゲームを作ってみましょう。ランダムな秘密の数字を生成し、プレイヤーがその数字を当てるゲームです。不正解の場合は、ヒントとして「もっと大きいです」または「もっと小さいです」と表示して、繰り返します。

実はこのゲーム、正解が0から99までの100個なら、ちゃんとやれば必ず7回以内に答えに辿り着けるようになっています。暇なら理由を考えてみると面白いかもしれません(ヒント:100を2で割っていくと...?)。

まずはランダムな秘密の数字を生成します。

package main

import (
	"fmt"
	"math/rand/v2"
)

func main() {
	fmt.Println("数当てゲームスタート!")
	fmt.Println("秘密の数字(0から99のどれか)を当てたらクリア!")
	answer := rand.N(100) // 0から99までのランダムな値を生成

	fmt.Println("答えは", answer, "でした")
}
$ go run .
数当てゲームスタート!
秘密の数字(0から99のどれか)を当てたらクリア!
答えは 69 でした

そしたらプレイヤーから予想を fmt.Scan で受け取って、正解なら「正解!」と表示します。

 func main() {
 	fmt.Println("数当てゲームスタート!")
 	fmt.Println("秘密の数字(0から99のどれか)を当てたらクリア!")
 	answer := rand.N(100) // 0から99までのランダムな値を生成
 
+	// 入力を受け取る
+	fmt.Println("予想を入力してください")
+	input := 0
+	fmt.Scan(&input)
+
+	// 正解と比較して分岐する
+	if input == answer {
+		fmt.Println("正解!")
+	}
 
 	fmt.Println("答えは", answer, "でした")
 }

そして、不正解だったら正解と比べて大きいか小さいかを教えてあげましょう。正解「ではない」時に行う処理なので、 else ifelse を使うのが良いでしょう。< の向きに気をつけながら書いてみます。

 	// 正解と比較して分岐する
 	if input == answer {
 		fmt.Println("正解!")
+	} else if input < answer {
+		fmt.Println("もっと大きいです")
+	} else {
+		fmt.Println("もっと小さいです")
 	}
$ go run .
数当てゲームスタート!
秘密の数字(0から99のどれか)を当てたらクリア!
予想を入力してください
5
もっと大きいです
答えは 80 でした

予想の入力、正解との比較までできたので、最後はこれを7回繰り返しましょう!完成品が以下です。

 package main
 
 import (
 	"fmt"
 	"math/rand/v2"
 )
 
 func main() {
 	fmt.Println("数当てゲームスタート!")
 	fmt.Println("秘密の数字(0から99のどれか)を当てたらクリア!")
 	answer := rand.N(100) // 0から99までのランダムな値を生成
 
+	for range 7 {
 		// 入力を受け取る
 		fmt.Println("予想を入力してください")
 		input := 0
 		fmt.Scan(&input)
 
 		// 正解と比較して分岐する
 		if input == answer {
 			fmt.Println("正解!")
+			break
 		} else if input < answer {
 			fmt.Println("もっと大きいです")
 		} else {
 			fmt.Println("もっと小さいです")
 		}
 	}
 
 	fmt.Println("答えは", answer, "でした")
 }

7回繰り返すので for range 7 と書きつつ、正解したらゲーム終了なので break を追加しました。

$ go run .
数当てゲームスタート!
秘密の数字(0から99のどれか)を当てたらクリア!
予想を入力してください
50
もっと大きいです
予想を入力してください
75
もっと小さいです
予想を入力してください
63
もっと小さいです
予想を入力してください
56
もっと大きいです
予想を入力してください
59
もっと大きいです
予想を入力してください
61
もっと大きいです
予想を入力してください
62
正解!
答えは 62 でした

というわけで、今までの知識の組み合わせで数当てゲームを作ることができました!if, for を組み合わせてこれだけのものが作れたらあなたはもう立派なプログラマーです。

今回作ったのは非常にシンプルな数当てゲームですが、たかが数当てゲームと侮るなかれ。のちに説明する「配列・スライス」と組み合わせれば、本格的な推理ゲームである「Hit & Blow」へと進化させることも可能です。ゲーム作りの第一歩はもうすでに踏み出しているのです。

まとめ

  • for を使えば繰り返し処理を書くことができる。
  • for range 回数 { ... } で指定した回数だけ繰り返す。
  • break, continue でループの制御ができる。

詳しい解説

Goのfor文には以下3つの形式があり、他言語でいう whileforeach の機能も for に集約されています。

for range 値 { ... }
スライスやマップ、イテレーター関数などから順番に取り出して繰り返す。他言語の foreach 的なもの

for 条件 { ... }
条件が満たされている間繰り返す。他言語の while 的なもの

for 初期化; 条件; 後処理 { ... }
初期化、条件、後処理を指定して繰り返す。

3番目がやや複雑ですが、そのくせ1番目と2番目より使う機会が少ないので、焦って覚えることはありません。

rangeパターン

すでに紹介した通り、range 回数 のように書くことで回数分繰り返せますが、回数のところには他にも、スライスやマップなど様々な値を置けます。詳しいことは都度紹介するので、ここでは深くは触れません。

条件のみのパターン

次に紹介するのは、条件を満たしている間繰り返すタイプのfor文です。他言語でいう while に相当します。

package main

import (
	"fmt"
	"math/rand/v2"
)

func main() {
	fmt.Println("ウラが出るまでコイントス")
	for rand.N(2) == 1 {
		fmt.Println("オモテ!")
	}
	fmt.Println("ウラ")
}
$ go run .
ウラが出るまでコイントス
オモテ!
オモテ!
ウラ
$ go run .
ウラが出るまでコイントス
ウラ

このプログラムでは、ループごとに rand.N(2) つまり0または1のランダムな値が、1と等しいかを確かめます。運良く1が出続ければ永遠に オモテ! と表示されますが、運が悪いとすぐにループが終了し ウラ と表示されます。このように、条件を満たしている間だけ { } の中身を繰り返すのが条件を書くタイプの for 文です。

条件は省略することもできます。その場合、条件に true が書いてあるのと同じ扱いになり(switch文と同じですね)、したがって永遠にループを繰り返す、いわゆる「無限ループ」となります。以下のプログラムは大量のテキストを出力するので、「Ctrl+C」ですぐに強制終了できるように構えておいてください。

package main

import "fmt"

func main() {
    for {
        fmt.Println("無限ループ")
    }
}

初期化・条件・後処理、のパターン

最後に紹介するのが、最も古典的なforループの使い方です。Cなどでお馴染みの方も多いでしょう。rangeでやったのと同じように5回繰り返すプログラムを書いてみます。

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
}
$ go run .
0
1
2
3
4

初期化; 条件; 後処理の形で、セミコロン ; で区切って3つの式を記述します。この場合は

  • 変数 i を0で初期化し、
  • i が5未満の間は繰り返し、
  • ループのたびに i を1増やす(++ は1を足す記号)

という意味になります。

見ての通り、rangeを使う方が短く書けるので、あまり使う機会はないかもしれません。しかし実は rangeで指定回数ループする機能はごく最近入ったものであり、それまではこの古典的な書き方をしていました。既存のプログラムを読むためには知っておいた方がよいでしょう。

ラベル

breakはforでもswitchでも使うので、組み合わせるとちょっと困ったことになる場合があります。

package main

import (
	"fmt"
	"math/rand/v2"
)

func main() {
	fmt.Println("ウラが出るまでコイントス")
loop:
	for {
		switch rand.N(2) {
		case 0:
			fmt.Println("ウラ")
			// break <- ラベルがないとswitch文の中断になるのでforループが終了しない
			break loop
		case 1:
			fmt.Println("オモテ!")
		}
	}
}

上のプログラムでは、コメントにある通りbreakがより近い方(この場合はswitch)を中断する挙動になるので、ただのbreakではforループが終了しません。こんな時はforの前の行に ラベル名: と書いてfor文にラベルを付与し、breakするときにそのラベルを指定します。ラベル名は変数名と同じように自由です。ラベル名: だけ変に字下げされているので違和感がありますが受け入れてください。

また、forとswitchの組み合わせだけでなく、forを入れ子にした場合でも同様に外側のループをbreakまたはcontinueするために使う機会があるでしょう。