これまでに勉強したことを使って、次のような簡単な数当てゲームを作ります。
コンピューターがある数を一つ決め、プレイヤーがその数を当てます。
コンピューターは、プレイヤーが答えた数が正解よりも大きいか小さいかだけを教えます。
*実行結果をイメージする [#c224395d]
プログラムを作るときは、まず、プログラムの実行結果をイメージします。
このプログラムを実行すると、まずはじめに、コンピューターが正解の数を決めます。
とりあえず、正解の数を33として話を進めます。
次に、コンピューターが、プレイヤーに数の入力を要求します。
入力を要求するメッセージは次のような感じにしましょう。
整数を入力してください:
ここで、プレイヤーが99と入力したとすると、99は正解の33より大きいので、「大きすぎる」というメッセージを出力し、もう一度、プレイヤーに入力を要求するメッセージを表示します。
整数を入力してください:
99
大きすぎます
整数を入力してください:
今度は、プレイヤーが11と入力したとすると、11は正解の33より小さいので、「小さすぎる」というメッセージを出力し、もう一度、プレイヤーに入力を要求するメッセージを表示します。
整数を入力してください:
99
大きすぎます
整数を入力してください:
11
小さすぎます
整数を入力してください:
プレイヤーが正解を入力すると、「正解」というメッセージを出力し、プログラムを終了します。
整数を入力してください:
99
大きすぎます
整数を入力してください:
11
小さすぎます
整数を入力してください:
33
正解!
このように、実行結果をイメージすることで、プログラムの中で、どんな処理が行われるのか、どんな処理が繰り返されるのかが、ある程度わかります。
*フローチャートを作る [#e134d110]
次に、イメージした実行結果になるように、プログラムのフローチャート(またはアルゴリズム)を考えます。
このプログラムは、二つの部分に分けることができます。
+コンピューターがある数(正解)を一つ決める
+プレイヤーが答える
最初の、コンピューターがある数(正解)を一つ決めるところは、[[計算ゲーム>授業/C言語基礎/計算ゲーム]]と同じように、乱数を使えばできます。
乱数の使い方がわからない場合は、自分で正解の値を決めてもかまいません。
その次の、プレイヤーが答えるところは、これまでに勉強した制御構造(条件分岐と繰り返し)を組み合わせて作ります。
この部分の中心は、ユーザーが答えて、それが正解よりも大きいか小さいかを教えるというものですから、キーボードからの入力と条件分岐を組み合わせると、次のようになります。
+scanf関数でキーボードから入力する
+if文で入力された数と正解を比べる
--入力された数が正解よりも大きいならば、「大きすぎる」と表示する
--入力された数が正解よりも小さいならば、「小さすぎる」と表示する
これを、正解が入力されるまで繰り返します。
この流れを、そのままフローチャートにすると、次のようになります。
#ref(./number1.png,50%)
ところが、これだと、繰り返しの部分がC言語の繰り返し処理のフローチャートと対応していないので、プログラムとして書きにくいです。
そこで、繰り返しの文をC言語の繰り返し処理のフローチャートに対応するように、フローチャートを書き直します。
#ref(./number2.png,50%);
*プログラムを作る [#k81d93b9]
フローチャートができたら、プログラムを作ります。
乱数を使って正解の数を決める部分だけを作ると、次のようになります。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int r;
srand((unsigned int) time(NULL)); // 乱数のシード
r = rand() % 100 + 1;
return 0;
}
}}
ここでは、正解の数は、1から100までの整数としています。
rand関数、srand関数、time関数の使い方については、[[計算ゲーム>授業/C言語基礎/計算ゲーム]]を復習しましょう。
(ここで、一度コンパイルして、エラーがないか確認します。)
次に、その後の繰り返し処理を形だけ作ります。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int r;
srand((unsigned int) time(NULL)); // 乱数のシード
r = rand() % 100 + 1;
while (1) {
}
printf("正解!\n");
return 0;
}
}}
while文の繰り返し条件が1になっているのは、コンパイル時にエラーにならないようにするためです。
条件は後で作り直します。
忘れないように注意しましょう。
(ここで、もう一度コンパイルして、エラーがないか確認します。)
while文の形ができたら、先に、繰り返し処理の中を考えましょう。
繰り返し処理の中だけを考えると、次の3つの処理が必要になります。
+プレイヤーに入力を要求するprintf関数
+プレイヤーの答をキーボードから入力するscanf関数
+大きいか小さいかを判定するif文
したがって、繰り返し処理の中は、次のようになります。
#geshi(c){{
printf("1から100までの整数を入力してください:\n");
scanf("%d", &a);
if (a > r) {
printf("大きすぎます\n");
} else if (a < r) {
printf("小さすぎます\n");
}
}}
ここで、a はプレイヤーが入力した数を記憶しておくための変数です。
そこで、これをwhile文の波括弧 { } の中にいれて、新しく登場した変数 a の宣言を追加します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int r, a;
srand((unsigned int) time(NULL)); // 乱数のシード
r = rand() % 100 + 1;
while (1) {
printf("1から100までの整数を入力してください:\n");
scanf("%d", &a);
if (a > r) {
printf("大きすぎます\n");
} else if (a < r) {
printf("小さすぎます\n");
}
}
printf("正解!\n");
return 0;
}
}}
(ここで、もう一度コンパイルして、エラーがないか確認します。)
正解でない間、繰り返すので、繰り返し条件は「a と r が等しくない」という式になります。
また、最初は、繰り返し条件を必ず満たすようにしなければならないので、a の値を1から100まででない値に初期化します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int r, a;
srand((unsigned int) time(NULL)); // 乱数のシード
r = rand() % 100 + 1;
a = -1;
while (a != r) {
printf("1から100までの整数を入力してください:\n");
scanf("%d", &a);
if (a > r) {
printf("大きすぎます\n");
} else if (a < r) {
printf("小さすぎます\n");
}
}
printf("正解!\n");
return 0;
}
}}
これで完成です(プログラム1)。
プログラムができたら、コンパイルして、エラーを直します。
途中で何度かコンパイルしてそのたびにエラーを直しておくと、一度にたくさんのエラーがでないのでエラーを直しやすいです。
**演習1 [#sd1ca6c5]
プログラム1を作成し、実行結果を確認せよ。
*プログラムをテストする(おまけ) [#s201611e]
プログラムが完成したら、何度か実行して設計図(フローチャート)と同じ動きをするかどうかを確認します。
これを''テスト''といいます。
テストでは、まず、プログラムをモジュールに分割し、モジュールごとにテストを行います。
これを''単体テスト''といいます。
単体テストのやり方はいくつかありますが、その中に、''ブラック・ボックス・テスト''と''ホワイト・ボックス・テスト''があります。
これはこの後で説明します。
すべてのモジュールのテストが終わったら、モジュールを組み合わせてテストを行います。
これを''結合テスト''といいます。
このプログラムは、次の二つのモジュールに分割できます。
+コンピューターが乱数で問題を生成する
+プレイヤーが答えて判定する
そこで、まず、コンピューターがきちんと問題を生成できているかどうかを確認します。
乱数を生成した後にprintf関数を置いて生成された数を表示し、これを何回も繰り返し実行して生成された数が1から100までの数であることを確認します(プログラム2)。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int r, a;
int i; // テスト用
srand((unsigned int) time(NULL)); // 乱数のシード
// テスト用
for (i = 1; i <= 1000; i++) {
r = rand() % 100 + 1;
printf("# r = %d\n", r);
if (r < 1 || r > 100) { exit(1); }
}
/*
r = rand() % 100 + 1;
*/
a = -1;
while (a != r) {
printf("1から100までの整数を入力してください:\n");
scanf("%d", &a);
if (a > r) {
printf("大きすぎます\n");
} else if (a < r) {
printf("小さすぎます\n");
}
}
printf("正解!\n");
return 0;
}
}}
if文の中の''exit関数''は、プログラムの実行を終了する関数です。
テスト用のコードが間違えていると、正しくテストができませんので気をつけましょう。
また、テストが終わったらすぐに元に戻せるように、元のプログラムはコピーしてコメント・アウトしておきます。
**演習2 [#s573810c]
プログラム1をプログラム2に変更し、テストを実行せよ。
**ブラック・ボックス・テスト [#lbb3ab44]
コンピューターが問題を正しく作成できることを確認したら、続いて、プレイヤーの答を入力して正しく判定できるかどうかを確認します。
コンピュータが問題を正しく生成できることは確認済みなので、テストを行うために、正解を真ん中くらいの適当な数に固定します。
ここでは、実行結果をイメージするときに使った33とします(プログラム3)。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int r, a;
srand((unsigned int) time(NULL)); // 乱数のシード
r = rand() % 100 + 1;
r = 33; // テスト用
a = -1;
while (a != r) {
printf("1から100までの整数を入力してください:\n");
scanf("%d", &a);
if (a > r) {
printf("大きすぎます\n");
} else if (a < r) {
printf("小さすぎます\n");
}
}
printf("正解!\n");
return 0;
}
}}
''ブラック・ボックス・テスト''は、プログラムの中身をわからないものと考えて、仕様に決められたとおりの動きをするかどうかを確認します。
ブラック・ボックス・テスト用のテスト・ケースを作成する方法として、''同値分割法''と''境界値分析''があります。
''同値分割法''では、入力データを仕様に基づいてグループに分割し、各グループの代表値を用いてテストを行います。
ここでは、正解よりも大きい数、正解よりも小さい数、正解と同じ数という3つグループが考えられます。
そこで、正解よりも大きい数の代表として99、正解よりも小さい数の代表として11、正解と同じ数(の代表)として33を選び、これらをテスト・ケースとします。
テスト・ケースを選んだら、テスト・ケースを順番に入力し、実行結果を確認します。
#geshi(sh){{
luna% a.out
整数を入力してください:
99
大きすぎます
整数を入力してください:
11
小さすぎます
整数を入力してください:
33
正解!
}}
''境界値分析''では、境界値を用いてテストを行います。
ここでは、正解よりも小さい数と正解の境界値である32と33、正解と正解よりも大きい数の33と34をテスト・ケースとします(33は重複しているので、テスト・ケースは3つになります)。
テスト・ケースを選んだら、テスト・ケースを順番に入力し、実行結果を確認します。
#geshi(sh){{
luna% a.out
整数を入力してください:
32
小さすぎます
整数を入力してください:
33
正解!
luna% a.out
整数を入力してください:
34
大きすぎます
整数を入力してください:
}}
**演習3 [#zd2969f9]
ブラック・ボックス・テストの同値分割法と境界値分析を用いて、プログラム3のテストを行え。
**ホワイト・ボックス・テスト [#ob534ab6]
''ホワイト・ボックス・テスト''は、プログラムの制御構造に基づいて、決められたとおりの動きをするかどうかを確認します。
ホワイト・ボックス・テスト用のテスト・ケースを作成する方法として、''ステートメント・カバレッジ''(''命令網羅'')と''ブランチ・カバレッジ''(''分岐網羅'')があります。
''ステートメント・カバレッジ''では、すべての命令文(ステートメント)が一度は実行されるように(命令文をすべてカバーするように)テスト・ケースを作成します。
プレイヤーが答えて判定する部分には、1つの代入、4つのprintf関数、1つのscanf関数と合計6つのステートメントが含まれています。
そこで、この6つのステートメントをできる限り多くカバーするようにテスト・ケースを作成します。
最初の代入、printf関数、scanf関数は、入力データに関係なく実行されるので、考える必要はありません。
2番目の「大きすぎる」と出力するprintf関数は、入力データが正解よりも大きいときに実行されるはずなので、正解よりも大きい数をテスト・ケースとして用意します。
ここでは、99とします。
3番目の「小さすぎる」と出力するprintf関数は、入力データが正解よりも小さいときに実行されるはずなので、正解よりも小さい数をテスト・ケースとして用意します。
ここでは、11とします。
最後の「正解」と出力するprintf関数は、入力データが正解に等しいときに実行されるはずなので、正解と同じ数をテストケースとして用意します。
ここでは、33です。
テスト・ケースを選んだら、テスト・ケースを順番に入力し、命令文が正しく実行されることを確認します。
#geshi(sh){{
luna% a.out
整数を入力してください:
99
大きすぎます
整数を入力してください:
11
小さすぎます
整数を入力してください:
33
正解!
}}
すべての命令文を実行できたので、このテストのステートメント・カバレッジは100%です。
''ブランチ・カバレッジ''(''分岐網羅'')では、フローチャートのすべての分岐を通るようにテスト・ケースを生成します。
フローチャート2に基づいて、次のようなケースを用意すると、すべての分岐を通ります。
+入力された数が正解よりも大きい場合
+入力された数が正解よりも大きくない、かつ、正解よりも小さい場合
+入力された数が正解よりも大きくない、かつ、正解よりも小さくない場合(つまり、正解に等しい場合)
すると、テスト・ケースは 99, 11, 33 とすればいいことになり、ステートメント・カバレッジと同じになりました。
つまり、このテストは、ステートメント・カバレッジが100%で、ブランチ・カバレッジも100%です。
**演習4 [#bbd75a56]
ホワイト・ボックス・テストのステートメント・カバレッジとブランチ・カバレッジを用いて、プログラム3のテストを行え。
----
*まとめ [#wd06b188]
プログラム作成は、設計、コーディング、テストの順に行います。
まず、出力結果のイメージを作成し、プログラムの流れをフローチャートで表します(設計)。
フローチャートができたら、フローチャートに基づいてプログラムを作成します(コーディング)。
プログラムができたら、正しく動くことを確認します(テスト)。