知っている人は知っていると思いますが、ヒット&ブローというゲームを作ってみます。
ヒット&ブローのルール
4桁の数値を乱数で決定し、これを答えとします。 使える数字は0から9まで、1桁ごとに異なる数字である必要があります。 プレイヤーは適当な4桁の数値を入力し、答えを当てます。 外れた場合、ヒントがもらえます。 入力した数値と答えの数値の同じ桁に同じ数値があれば1ヒット。 異なる桁に同じ数値があれば1ブローというヒントです。 例) 答え 6294 入力 1273 ← 1ヒット ※2は同じ桁にある 答え 1432 入力 4285 ← 2ブロー ※4と2は桁は異なるが数値は合っている 答え 8024 入力 0284 ← 1ヒット、3ブロー ※同じ桁の数値が合っているのは4のみ、他の3つの数値は桁が異なる 4ヒットになれば正解です。 |
まずは、答えを準備するところから考えてみましょう。
準備段階を書いておきます。
<sample program 076-00>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 int main(void) { int answer[DIGIT]; srand((unsigned int)time(NULL)); return 0; } |
DIGITというのは数字という意味です。
int型の要素4つを持つanswerという配列を用意します。
乱数で数値を設定しますので、srandも書きました。
さて、ここからが問題です。
ルールに書きましたが、4桁の数値を乱数で決めます。
適当に決めるのであれば、楽に作ることが出来ます。
<sample program 076-01>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 int main(void) { int answer[DIGIT]; int i; int number; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { number = rand() % 10; answer[i] = number; } return 0; } |
上のプログラムでは、配列の要素1つ1つに0から9までの乱数を代入しています。
しかし、ルールにあるように1桁ごとに異なる数値でなければなりません。
上のプログラムを実行した結果を見てみましょう。
answerの中身を表示するプログラムを追加します。
<sample program 076-02>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define DIGIT 4
int main(void)
{
int answer[DIGIT];
int i;
int number;
/* 答えの決定 */
srand((unsigned int)time(NULL));
for (i = 0; i < DIGIT; i++) {
number = rand() % 10;
answer[i] = number;
}
/* 答えの表示(確認用) */
for (i = 0; i < DIGIT; i++) {
printf("%2d", answer[i]);
}
printf("\n");
return 0;
}
|
<実行結果>
9 9 7 1 続行するには何かキーを押してください・・・
何度か実行すると、上記のように重複した数値が出てくると思います。
これではゲームになりませんので、別の方法が必要です。
少し難しいかも知れませんが、色々考えて作ってみてください。
方法はいくつかありますが、これまでの知識を組み合わせることで作ることが出来ます。
難しいかもしれませんので、方法の1つを説明します。
考え方としては、1度使った数値をチェックしておき、乱数で同じ数値が出た場合には、もう一度乱数を出しなおすという方法です。
数値のチェック用に、配列を準備します。
個数は「0」から「9」までの数値をチェックするので10個です。
初期値としては全ての要素に0を入れておき、フラグとして使います。
添え字をチェックする数値とみなし、要素に0が入っていれば未使用、1であれば使用済みと考えます。
<例> 添え字 0123456789 配 列 0000100000 ↑数値の4は使用済みという意味
手順を書きます。
乱数を発生させ、「0」から「9」までの数値を変数numberに入れます。
数値チェック用配列のnumber番目を調べ、1であれば再度乱数を発生させます。
数値チェック用配列のnumber番目が0であれば、配列のnumber番目を1に変更し、answerに設定します。
これを4回繰り返せば、全て異なる数値が設定出来ると思います。
やってみてください。
数値チェック用配列の名前はuseNumberとし、#defineでNUMBERを10としておきましょう。
解答例です。
<sample program 076-03>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int number; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while ( useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); return 0; } |
<実行結果>
2 4 3 8 続行するには何かキーを押してください・・・
これで重複することが無くなりました。
では、答えを当てるために、プレイヤーが入力出来る仕組みを作りましょう。
プレイヤーも4桁の数値を入力する必要がありますが、int型変数にscanfで入力するとプログラムが複雑になります。
例えば3628と入力するとこれは整数の(さんぜんろっぴゃくにじゅうはち)ですよね。
ヒットかブローかのチェックは1桁ずつ行わなければなりませんので、この4桁の数値を1桁ずつばらすプログラムを組まなければなりません。
もちろん工夫すれば作れるのですが、今回は最初から分けて入力する方法を取ります。
入力用配列inputをDIGIT分用意し、for文で1桁ずつ入力します。
<sample program 076-04>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int input[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int number; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while (useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); /* 回答の入力 */ for (i = 0; i < DIGIT; i++) { scanf("%d", &input[i]); } return 0; } |
<実行結果>
2 4 3 8 0 ←入力値 2 ←入力値 4 ←入力値 5 ←入力値 続行するには何かキーを押してください・・・
次はヒットやブローのチェックを行います。
前にも書きましたが、人間であれば「パッと」見てチェック出来ますが、コンピュータはそうはいきません。
きちんとチェック手順を教えてあげなければ、ヒットやブローの数を数えることが出来ないのです。
まずはヒットから考えます。
ヒットは、同じ桁に同じ数値があるという意味です。
これはanswerとinputを比較すればすぐに調べることが出来ます。
カウンタとして変数hitを準備し、数を数えるプログラムを作ってください。
<実行結果>
2 4 3 8 2 0 3 5 2ヒット 続行するには何かキーを押してください・・・
答えが表示されているので、チェックも楽ですね。
解答例です。
<sample program 076-05>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int input[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int number; int hit; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while ( useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); /* 回答の入力 */ for (i = 0; i < DIGIT; i++) { scanf("%d", &input[i]); } /* ヒットのチェック */ hit = 0; for (i = 0; i < DIGIT; i++) { if (input[i] == answer[i]) { hit++; } } printf("%dヒット\n", hit); return 0; } |
<実行結果>
2 4 3 8 2 0 3 5 2ヒット 続行するには何かキーを押してください・・・
次はブローです。
ブローは異なる桁に同じ数値があるという意味です。
これは、inputの1桁分をanswerの全ての桁と比較しなければ分かりません。
< 例 > answer 3752 input 5981
まずは、inputの0番目「5」がanswerに含まれるかどうかチェックするため、ループを使って
input[0]とanswer[0]を比較 input[0]とanswer[1]を比較 input[0]とanswer[2]を比較 input[0]とanswer[3]を比較
というプログラムを組みます。
次に、inputの1番目「9」がanswerに含まれるかどうかチェックするため、
input[1]とanswer[0]を比較 input[1]とanswer[1]を比較 input[1]とanswer[2]を比較 input[1]とanswer[3]を比較
というプログラムを組みます。
これをinputの3番目まで繰り返しチェックします。
これは、以前使った二重ループを使えば作れそうです。
比較して同じ数値であれば、変数blowをカウントアップします。
とりあえず作ってみてください。
<実行結果>
7 3 9 2 3 6 7 0 0ヒット:2ブロー 続行するには何かキーを押してください・・・
解答例です。
<sample program 076-06>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int input[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int j; int number; int hit; int blow; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while ( useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); /* 回答の入力 */ for (i = 0; i < DIGIT; i++) { scanf("%d", &input[i]); } /* ヒットのチェック */ hit = 0; for (i = 0; i < DIGIT; i++) { if (input[i] == answer[i]) { hit++; } } /* ブローのチェック */ blow = 0; for (i = 0; i < DIGIT; i++) { for (j = 0; j < DIGIT; j++) { if (input[i] == answer[j]) { blow++; } } } printf("%dヒット:%dブロー\n", hit, blow); return 0; } |
<実行結果>
7 3 9 2 3 6 7 0 0ヒット:2ブロー 続行するには何かキーを押してください・・・
とりあえず動いているように見えます。
しかし、皆さんが作ったプログラムからすると何か足りない箇所があるかも知れません。
例えば、次のようなケース
<実行結果>
1 7 5 6 1 7 0 3 2ヒット:2ブロー 続行するには何かキーを押してください・・・
結果がおかしいですよね。
2ヒットは間違いないですが、2ブローは間違っています。
これは、ヒットとブローが重複してチェックされていることが原因です。
ブローのチェックの時に、同じ桁であればヒットになるべきケースも含めて数えてしまっています。
そこで、次のように変更してみます。
<sample program 076-07>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define DIGIT 4
#define NUMBER 10
int main(void)
{
int answer[DIGIT];
int input[DIGIT];
int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int i;
int j;
int number;
int hit;
int blow;
srand((unsigned int)time(NULL));
/* 答えの決定 */
for (i = 0; i < DIGIT; i++) {
do {
number = rand() % NUMBER;
} while (useNumber[number] == 1);
answer[i] = number;
useNumber[number] = 1;
}
/* 答えの表示(確認用) */
for (i = 0; i < DIGIT; i++) {
printf("%2d", answer[i]);
}
printf("\n");
/* 回答の入力 */
for (i = 0; i < DIGIT; i++) {
scanf("%d", &input[i]);
}
/* ヒット、ブローのチェック */
hit = 0;
blow = 0;
for (i = 0; i < DIGIT; i++) {
for (j = 0; j < DIGIT; j++) {
if (input[i] == answer[j]) {
if (i == j) {
hit++;
}
else{
blow++;
}
}
}
}
printf("%dヒット:%dブロー\n", hit, blow);
return 0;
}
|
<実行結果>
7 0 3 9 7 0 1 2 2ヒット:0ブロー 続行するには何かキーを押してください・・・
ヒットのプログラムを削除し、ブローのプログラムにヒットのカウントも入れました。
先ほどのバグは直りました。
しかし、全てのバグが無くなった訳ではありません。
次のケース、
<実行結果>
7 0 3 9 7 7 7 7 1ヒット:3ブロー 続行するには何かキーを押してください・・・
同じ数値を入力出来ますので、このような結果が出てくることもあります。
これを解消するためには、回答の入力時に同じ数値を入力させない仕組みが必要です。
答えを決定する時に使ったチェック用配列を使えば、同じようにチェック出来そうです。
入力する前にフラグを0に戻しておき、1桁入力するごとにチェックを行います。
答えの決定のプログラムを参考にして、作ってみてください。
解答例です。
<sample program 076-08>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int input[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int j; int number; int hit; int blow; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while (useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); /* チェック用フラグのクリア */ for (i = 0; i < NUMBER; i++) { useNumber[i] = 0; } /* 回答の入力 */ for (i = 0; i < DIGIT; i++) { do { scanf("%d", &number); } while (useNumber[number] == 1); input[i] = number; useNumber[number] = 1; } /* ヒット、ブローのチェック */ hit = 0; blow = 0; for (i = 0; i < DIGIT; i++) { for (j = 0; j < DIGIT; j++) { if (input[i] == answer[j]) { if (i == j) { hit++; } else{ blow++; } } } } printf("%dヒット:%dブロー\n", hit, blow); return 0; } |
<実行結果>
0 1 3 6 0 0 0 0 6 3 2 2ヒット:1ブロー 続行するには何かキーを押してください・・・
上の実行結果だと、最初に入力した「0」はきちんとinputに入っていますが、続いて入れた3つの「0」はチェックで弾かれています。
最終的には「0632」という数値が入力され、answer「0136」と比較された結果が表示されました。
それでは、当たる(4ヒット)まで繰り返す仕組みを入れましょう。
当たった場合は、「当たり!」と表示してプログラムを終了します。
解答例です。
<sample program 076-09>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int input[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int j; int number; int hit; int blow; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while (useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); for (;;) { /* チェック用フラグのクリア */ for (i = 0; i < NUMBER; i++) { useNumber[i] = 0; } /* 回答の入力 */ for (i = 0; i < DIGIT; i++) { do { scanf("%d", &number); } while (useNumber[number] == 1); input[i] = number; useNumber[number] = 1; } /* ヒット、ブローのチェック */ hit = 0; blow = 0; for (i = 0; i < DIGIT; i++) { for (j = 0; j < DIGIT; j++) { if (input[i] == answer[j]) { if (i == j) { hit++; } else{ blow++; } } } } printf("%dヒット:%dブロー\n", hit, blow); if (hit == DIGIT) { printf("当たり!\n"); break; } } return 0; } |
<実行結果>
0 1 3 6 0 1 3 6 4ヒット:0ブロー 当たり! 続行するには何かキーを押してください・・・
これで一通り完成しました!
が、答えが表示されたままです・・・
このように開発時やデバッグ時には動かしたいが、完成後は必要ない「部品」は消しておきましょう。
ただ、後日メンテナンスや改良のため、必要になる「部品」があるかも知れません。
そこで、プログラムを無効にする方法を書いておきます。
<sample program 076-10>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define DIGIT 4 #define NUMBER 10 int main(void) { int answer[DIGIT]; int input[DIGIT]; int useNumber[NUMBER] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int i; int j; int number; int hit; int blow; srand((unsigned int)time(NULL)); /* 答えの決定 */ for (i = 0; i < DIGIT; i++) { do { number = rand() % NUMBER; } while (useNumber[number] == 1); answer[i] = number; useNumber[number] = 1; } #if 0 /* 答えの表示(確認用) */ for (i = 0; i < DIGIT; i++) { printf("%2d", answer[i]); } printf("\n"); #endif for (;;) { /* チェック用フラグのクリア */ for (i = 0; i < NUMBER; i++) { useNumber[i] = 0; } /* 回答の入力 */ for (i = 0; i < DIGIT; i++) { do { scanf("%d", &number); } while (useNumber[number] == 1); input[i] = number; useNumber[number] = 1; } /* ヒット、ブローのチェック */ hit = 0; blow = 0; for (i = 0; i < DIGIT; i++) { for (j = 0; j < DIGIT; j++) { if (input[i] == answer[j]) { if (i == j) { hit++; } else{ blow++; } } } } printf("%dヒット:%dブロー\n", hit, blow); if (hit == DIGIT) { printf("当たり!\n"); break; } } return 0; } |
<実行結果>
0 1 3 6 4ヒット:0ブロー 当たり! 続行するには何かキーを押してください・・・
#if 0 から #endif で囲んだ箇所はプログラムとして無効になります。
後で、このプログラムを消すか、#if 1 と変更すれば無効は解除され、再度コンパイル出来るようになります。
これで今回は終了です。
「当たるまでの回数を数える」「1桁ずつでなく、4桁一度に入力出来るようにする」等の工夫は各自で挑戦してみてください。