単純に4桁の数字を作るだけならば、次のようなプログラムで可能である。
/* point1.c */ #include <stdio.h> #include <stdlib.h> #include <time.h> int create_prob(char *); int main() { char string[5]; /* 重なりのない4桁の数字を格納する配列。 * 長さ4なので配列の大きさは '\0' を * 格納するための 1 を加えて 5 必要 */ srand(time(NULL)); /* 乱数の種の初期化 */ create_prob(string); printf("作成した文字列は \"%s\" です。\n", string); return 0; } int create_prob(char *prob) { int i; char next; for (i = 0; i < 4; ++i) { next = (rand() % 10) + '0'; *(prob + i) = next; } *(prob + 4) = '\0'; /* 文字列 とするために * 最後に '\0' を追加 */ return 0; }
しかし、これでは当然同じ数字が入ってくる場合がある。 そのために、少し工夫を行う。
自分で、重なりがないかどうかをどのようにチェックするかを考えてみよう。
前には何もないのでチェックは不要である。
1文字目の数字と、比較を行い同じだったらやり直す。
1文字目の数字と、比較を行い同じだったらやり直す。
2文字目の数字と、比較を行い同じだったらやり直す。
1文字目の数字と、比較を行い同じだったらやり直す。
2文字目の数字と、比較を行い同じだったらやり直す。
3文字目の数字と、比較を行い同じだったらやり直す。
上のチェックの方法をにらむと、繰り返し処理が見えないだろうか? たとえば、4文字目のチェックは
/* 4文字目のチェック */ for (j = 0; j < 4; ++j) { if (*(prob + j) == *(prob + 4)) { やり直す; } }
と書けるはずである。同様に3文字目、2文字目も考えると、for loop を 二重にするとチェックができる事が理解できると思う。
/* 文字の重なりがないかどうかのチェック */ /* i: チェックの対象 * j: チェックの比較対象 (i より前の文字) */ for (i = 0; i < 4; ++i) { for (j = 0; j < i; ++j) { if (*(prob + j) == *(prob + i)) { やり直す; } } }
人の解答の読み込みをどうするのかも、ポイントである。 特に、scanf を用いた場合、数値以外が入力された場合の エラー処理を行わなければならないことや先頭が 0 の場合の処理等に めんどうなことが予想される。
そこで、課題13で取り上げた fgets + sscanf を用いることを考える。 また、入力をチェックし、場合に応じた返り値(エラーコード)を返すことで 呼び出し元で適切なメッセージを表示するようにしてみた。
/* point2.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char answer[5]; int err; err = read_answer(answer); switch (err) { case 0: /* 正常 */ printf("OK: 入力は \"%s\" でした。\n", answer); break; case 1: /* 長さが違う */ printf("NG: 長さが4ではありません\n"); break; case 2: /* 数字以外が入っていた */ printf("NG: 数字以外の文字が含まれていました\n"); break; case 3: /* 数字が重なっていた */ printf("NG: 数字が重複しています\n"); break; default: /* ??? */ printf("FATAL: 予期せぬエラーが発生しました\n"); exit(1); } return 0; } int read_answer(char *answer) { char buf[80]; char string[80]; int i, j; fgets(buf, sizeof(buf), stdin); /* 「1行」読み込む */ sscanf(buf, "%s", string); /* その中から先頭部分を * 文字列として読み出す */ /* string の長さチェックを行う */ if (string の長さが 4ではない) { return 1; /* 長さが違うときは 1 */ } /* string に数字以外が含まれていないかのチェック */ for (i = 0; i < 4; ++i) { if (i 番目の文字が数字ではない) return 2; /* 数字以外が含まれたら 2 */ } /* 同じ数字が含まれていないかのチェック */ for (i = 0; i < 4; ++i) for (j = 0; j < i; ++j) /* 問題作成のところと同じ考え方 */ if (*(string+i) == *(string+j)) /* 同じ数字がふくまれていたら3 */ return 3; /* 問題がなければ answer にコピー */ for(i = 0; i < 5; ++i) *(answer+i) = string[i]; return 0; /* 正常ならば 0 */ }
なお、通常、上のように返り値として数値を直接書くと、プログラムを読む場合に 分かりづらいので、
#define LENGTH_ERR (1) #define WRONG_CHAR (2) #define DUPLICATE (3)
のように数値に名前をつけてプログラムに埋め込まれる場合が多い
正解と人の入力した文字列の二つに加えて、返り値として hit と blow の数を 返さなければならないので、関数の引数は4つとなる。
この段階では、正解も入力文字列も重なりのない4桁の数字であることが、 保証されているので単純に比較を行う。
/* point3.c */ #include <stdio.h> int main() { check(problem, answer, &hit, &blow); } int check(char *problem, char *answer, int *hit, int *blow) { int i, j; /* はじめに hit と blow を初期化しておく */ *hit = 0; *blow = 0; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { if (*(problem + i) == *(answer + j)) { /* 正解の i 番目と、 * 入力の j 番目が同じ数字*/ if (i == j) { /* 場所も同じ */ *hit ++; } else { /* 場所は違う */ *blow ++; } } } } return 0; }
課題14 | 日程表 | 課題16 |