はじめに
※読み飛ばしていただいて大丈夫です。
新しい言語を学ぶ際、理解度を測る一つの手段としてヌメロンを作ることにしている。基本文法の理解を深め、新しい知識の獲得も期待できるからだ。C言語はまだ基本文法しか勉強していないため、所要時間は10時間程度かかった。この10時間には不明点の深掘りや、知らない関数を調べる時間も含まれている。
ヌメロンのルール
まず、それぞれのプレイヤーが、0~9までの数字が書かれたカードのうち3つを使って、3桁の番号を作成する。0から始めても良い。ただし、「550」「377」といった同じ数字を2つ以上使用した番号は作れない。
これだけが、ゲーム前の準備。以下はゲームのルールである。
先攻のプレイヤーは相手が作成したと思われる番号をコールする。
相手はコールされた番号と自分の番号を見比べ、コールされた番号がどの程度合っているかを発表する。
数字と桁が合っていた場合は「EAT」、数字は合っているが桁は合っていない場合は「BITE」となる。
例として、相手の番号が「765」で、コールされた番号が「746」であった場合は、3桁のうち「7」は桁の位置が合致しているためEAT、「6」は数字自体は合っているが桁の位置が違うためBITE。EATが1つ・BITEが1つなので、「1EAT-1BITE」となる。
これを先攻・後攻が繰り返して行い、先に相手の番号を完全に当てきった(3桁なら3EATを相手に発表させた)プレイヤーの勝利となる。
このサイトでは人 vs 人で紹介されているが、今回作成したのはPlayer vs Computerである。また、すべてターミナル上で完結させる。
コード
300行になったため記事の最後に掲載。
github:https://github.com/YuyMat/C-numeron
宣言
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
void inputNum(char *player_num);
bool ruleNum(char *num);
int generateRandomNum(int mode, int min, int Max, char *computer_num);
void guessNum(int mode, char *who_guess);
void judgeEatBite(int mode, char *from_num, char *to_num, int *EAT);
bool isWin(int eat);
void saveNumArray(char *num, int eat, int bite);
void computerGuessNum(char *computer_guess_num);
bool check_history(int target);
struct number{
char num[4];
char guess_num[4];
};
// 0-9までの値を保持する配列 for computer
int num_array[10] = {0};
// guess_numの履歴
int guess_history[100];
int round_count = 1;
制御 (ゲーム全体の動き)
フローチャート:
- プレイヤー数字を入力
- コンピュータ数字作成
- プレイヤーがコンピュータの数字を予想
- eatが3だったらプレイヤーの勝利
- コンピュータがプレイヤーの数字を予想
- eatが3だったらコンピュータの勝利
- 3に戻る
ゲーム全体の制御コード:
int main(void) {
struct number player;
struct number computer;
int eat; //勝利確認用
// round 1,2,3は固定のため初期化
guess_history[1] = 123;
guess_history[2] = 456;
guess_history[3] = 789;
// ランダムシード初期化
srand((unsigned int)time(NULL));
// playerの数字決定
puts("----------------------------------------------------");
inputNum(player.num);
puts("----------------------------------------------------");
// computerの数字決定
generateRandomNum(1, 123, 987, computer.num);
printf("コンピュータの数字が決まりました\n");
puts("----------------------------------------------------");
while (true) {
// player 予想
guessNum(0, player.guess_num);
judgeEatBite(0, player.guess_num, computer.num, &eat);
if (isWin(eat)) {
printf("Player Win!!\n");
exit(0);
}
puts("----------------------------------------------------");
// computer 予想
guessNum(1, computer.guess_num);
judgeEatBite(1, computer.guess_num, player.num, &eat);
if (isWin(eat)) {
printf("Player Lose;;\n");
exit(0);
}
puts("----------------------------------------------------");
round_count ++;
}
return 0;
}
数字の確認
ヌメロンのルールでは3桁の数字の中に同じ数字を入れてはいけないルールがあるため、それを確認する必要がある。
そしてscanf時に数字以外を入れると無限ループを繰り返してしまう現象も確認できたため、その3桁が数字であるかの判定も行う。
bool ruleNum(char *num) {
int counter = 0;
// 数字か判定
if (isdigit(num[0])) {
// intに変換
int int_num = atoi(num);
sprintf(num, "%d", int_num);
int num_len = strlen(num);
// 3桁でかつ同じ文字が入っていないかの比較
if (num_len == 3) {
for (int i = 0; i < num_len; i++) {
for (int j = 0; j < num_len; j++) {
if (num[i] == num[j]) {
counter++;
}
}
}
// 問題なかったらtrue
if (counter == 3) {
return true;
} else {
return false;
}
} else {
return false;
}
// 数字でなかったらfalse
} else {
return false;
}
}
この関数で、プレイヤーとコンピュータが数字を決定するとき、相手の数字を予想するときにルール通りの数字かの確認をしている。
コンピュータの数字予想時の挙動
フローチャート:
- ラウンド1 = 123, ラウンド2 = 456, ラウンド3 = 789を評価する
- それぞれeat, biteが1つでも出たら可能性のある数字リストに入れる
⇒ラウンド3終了時に可能性のある数字が9個ない場合、0をリストに入れる - ラウンド4からは可能性のある数字リストから適当な3桁の数字を作成
- 評価をし、eat, biteが両方0の場合、可能性リストから除外
- eat + biteが3の時、その数字以外のすべての数字を可能性リストから除外
- 3に戻る
最初の3ラウンドは相手の数字の情報を掴むのが定番の流れだ(少なくとも私の育った環境ではそうだった)。コンピュータにもこの流れを持たせている。
void guessNum(int mode, char *who_guess) {
// mode 0 = player guess
// mode 1 = computer guess
char tmp[10];
bool loop = true;
if (mode == 0) {
while (loop) {
printf("あなたの番です。\nコンピュータの数字を予想してください:");
scanf("%s", tmp);
if (ruleNum(tmp)) {
strcpy(who_guess, tmp);
loop = false;
} else {
printf("正しい数字を入力してください\n");
}
}
} else if (mode == 1) {
printf("コンピュータの予想の番です\n");
if (round_count == 1) {
sprintf(who_guess, "123");
} else if (round_count == 2) {
sprintf(who_guess, "456");
} else if (round_count == 3) {
sprintf(who_guess, "789");
} else {
computerGuessNum(who_guess);
}
}
}
ラウンド4からは前述の3ラウンドで得た、可能性のある数字からランダムな3桁の数字を作成し、それをコンピュータの予想ナンバーにしている。上記の数字のルールに加え、過去の予想数字と被っていないかを確認している。その機能が以下のコードである。
void computerGuessNum(char *computer_guess_num) {
int int_tmp_num = 0;
int random_num1;
int random_num2;
int random_num3;
char str_tmp_num[11];
int array_count = 0;
char return_tmp_num[4];
// 可能性のある数字達を一つの文字列に
for (int i = 9; i >= 0; i--) {
if (num_array[i]) {
int_tmp_num *= 10;
int_tmp_num += i;
array_count++;
}
}
sprintf(str_tmp_num, "%d", int_tmp_num);
// 3桁の数字作成
bool flag = true;
while (flag) {
// generateRandomNum(mode, min, Max, *computer_num)
// mode == 1:for computer, else mode:just for generate num(likes 0)
random_num1 = generateRandomNum(0, 0, array_count - 1, NULL);
random_num2 = generateRandomNum(0, 0, array_count - 1, NULL);
random_num3 = generateRandomNum(0, 0, array_count - 1, NULL);
sprintf(return_tmp_num, "%c%c%c", str_tmp_num[random_num1], str_tmp_num[random_num2], str_tmp_num[random_num3]);
// 前に試したことがない数字 && ルール通りの数字
if (check_history(atoi(return_tmp_num)) && ruleNum(return_tmp_num)) {flag = false;}
}
strcpy(computer_guess_num, return_tmp_num);
guess_history[round_count] = atoi(computer_guess_num);
}
また可能性のある数字の情報はint num_array[10] = {0}
をグローバル変数で宣言しており、可能性のある数字の配列番号の0, 1を切り替えることにより保持している。
void saveNumArray(char *num, int eat, int bite) {
int tmp_sum = 0;
// 可能性がある数字をtrueにする
if ((eat + bite > 0) && (eat + bite < 3)) {
for (int i = 0; i < 3; i++) {
int int_a_num = num[i] - '0';
num_array[int_a_num] = 1;
}
} else if (eat + bite == 3) { //数字が確定したら他の数を0にする
memset(num_array, 0, sizeof(num_array)); //num_array リセット
for (int i = 0; i < 3; i++) {
int int_a_num = num[i] - '0';
num_array[int_a_num] = 1;
}
} else { //可能性がない時
for (int i = 0; i < 3; i++) {
int int_a_num = num[i] - '0';
num_array[int_a_num] = 0;
}
}
// ラウンド3の時に可能性のある数字の個数が9個なかったら0をtrueにする
if (round_count == 3) {
// 可能性のある数字の個数を調べる
for (int i = 1; i < 10; i++) {
tmp_sum += num_array[i];
}
if (tmp_sum != 9) {num_array[0] = 1;}
}
}
この関数をeat数、bite数を評価する関数に入れている。
void judgeEatBite(int mode, char *from_num, char *to_num, int *EAT) {
int eat = 0, bite = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// eat
if (from_num[i] == to_num[j] && i == j) {
eat++;
// bite
} else if (from_num[i] == to_num[j] && i != j) {
bite++;
}
}
}
// computerの場合、num_arrayの処理
if (mode == 1) {saveNumArray(from_num, eat, bite);}
*EAT = eat;
printf("%s : %deat %dbite\n", from_num, eat, bite);
}
コード全体
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
void inputNum(char *player_num);
bool ruleNum(char *num);
int generateRandomNum(int mode, int min, int Max, char *computer_num);
void guessNum(int mode, char *who_guess);
void judgeEatBite(int mode, char *from_num, char *to_num, int *EAT);
bool isWin(int eat);
void saveNumArray(char *num, int eat, int bite);
void computerGuessNum(char *computer_guess_num);
bool check_history(int target);
struct number{
char num[4];
char guess_num[4];
};
// 0-9までの値を保持する配列 for computer
int num_array[10] = {0};
// guess_numの履歴
int guess_history[100];
int round_count = 1;
int main(void) {
struct number player;
struct number computer;
int eat; //勝利確認用
// round 1,2,3は固定のため初期化
guess_history[1] = 123;
guess_history[2] = 456;
guess_history[3] = 789;
// ランダムシード初期化
srand((unsigned int)time(NULL));
// playerの数字決定
puts("----------------------------------------------------");
inputNum(player.num);
puts("----------------------------------------------------");
// computerの数字決定
generateRandomNum(1, 123, 987, computer.num);
printf("コンピュータの数字が決まりました\n");
puts("----------------------------------------------------");
while (true) {
// player 予想
guessNum(0, player.guess_num);
judgeEatBite(0, player.guess_num, computer.num, &eat);
if (isWin(eat)) {
printf("Player Win!!\n");
exit(0);
}
puts("----------------------------------------------------");
// computer 予想
guessNum(1, computer.guess_num);
judgeEatBite(1, computer.guess_num, player.num, &eat);
if (isWin(eat)) {
printf("Player Lose;;\n");
exit(0);
}
puts("----------------------------------------------------");
round_count ++;
}
return 0;
}
void inputNum(char *player_num) {
char tmp[10];
bool rule = false;
while (!rule) {
printf("あなたの数字を入力してください:");
scanf("%s", tmp);
if (ruleNum(tmp)) {
sprintf(player_num, "%s", tmp);
rule = true;
} else {
printf("正しい数字を入力してください\n");
}
}
}
// in rule == true, not in rule == false
bool ruleNum(char *num) {
int counter = 0;
// 数字か判定
if (isdigit(num[0])) {
// intに変換
int int_num = atoi(num);
sprintf(num, "%d", int_num);
int num_len = strlen(num);
// 3桁でかつ同じ文字が入っていないかの比較
if (num_len == 3) {
for (int i = 0; i < num_len; i++) {
for (int j = 0; j < num_len; j++) {
if (num[i] == num[j]) {
counter++;
}
}
}
// 問題なかったらtrue
if (counter == 3) {
return true;
} else {
return false;
}
} else {
return false;
}
// 数字でなかったらfalse
} else {
return false;
}
}
// mode == 1:for computer, else mode:just for generate num(likes 0);
int generateRandomNum(int mode, int min, int Max, char *computer_num) {
bool loop = true;
int int_num;
char str_num[4];
// 適正数字になるまで3桁のランダムな数字を作成
while(loop) {
// 3桁作成
int_num = min + rand() % (Max - min + 1);
if (mode == 1) {
sprintf(str_num, "%d", int_num);
if (ruleNum(str_num)) {loop = false;}
}
else {loop = false;}
}
if (mode == 1) {
strcpy(computer_num, str_num);
return 0;
} else {
return int_num;
}
}
void guessNum(int mode, char *who_guess) {
// mode 0 = player guess
// mode 1 = computer guess
char tmp[10];
bool loop = true;
if (mode == 0) {
while (loop) {
printf("あなたの番です。\nコンピュータの数字を予想してください:");
scanf("%s", tmp);
if (ruleNum(tmp)) {
strcpy(who_guess, tmp);
loop = false;
} else {
printf("正しい数字を入力してください\n");
}
}
} else if (mode == 1) {
printf("コンピュータの予想の番です\n");
if (round_count == 1) {
sprintf(who_guess, "123");
} else if (round_count == 2) {
sprintf(who_guess, "456");
} else if (round_count == 3) {
sprintf(who_guess, "789");
} else {
computerGuessNum(who_guess);
}
}
}
void judgeEatBite(int mode, char *from_num, char *to_num, int *EAT) {
int eat = 0, bite = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// eat
if (from_num[i] == to_num[j] && i == j) {
eat++;
// bite
} else if (from_num[i] == to_num[j] && i != j) {
bite++;
}
}
}
// computerの場合、num_arrayの処理
if (mode == 1) {saveNumArray(from_num, eat, bite);}
*EAT = eat;
printf("%s : %deat %dbite\n", from_num, eat, bite);
}
void saveNumArray(char *num, int eat, int bite) {
int tmp_sum = 0;
// 可能性がある数字をtrueにする
if ((eat + bite > 0) && (eat + bite < 3)) {
for (int i = 0; i < 3; i++) {
int int_a_num = num[i] - '0';
num_array[int_a_num] = 1;
}
} else if (eat + bite == 3) { //数字が確定したら他の数を0にする
memset(num_array, 0, sizeof(num_array)); //num_array リセット
for (int i = 0; i < 3; i++) {
int int_a_num = num[i] - '0';
num_array[int_a_num] = 1;
}
} else { //可能性がない時
for (int i = 0; i < 3; i++) {
int int_a_num = num[i] - '0';
num_array[int_a_num] = 0;
}
}
// ラウンド3の時に可能性のある数字の個数が9個なかったら0をtrueにする
if (round_count == 3) {
// 可能性のある数字の個数を調べる
for (int i = 1; i < 10; i++) {
tmp_sum += num_array[i];
}
if (tmp_sum != 9) {num_array[0] = 1;}
}
}
bool isWin(int eat) {
if (eat == 3) {
return true;
}
return false;
}
// is existed == false, is not existed == true
bool check_history(int target) {
for (int i = 1; i <= round_count; i++) {
if (target == guess_history[i]) {
return false;
}
}
return true;
}
// round >= 4の時のコンピュータ予想番号
void computerGuessNum(char *computer_guess_num) {
int int_tmp_num = 0;
int random_num1;
int random_num2;
int random_num3;
char str_tmp_num[11];
int array_count = 0;
char return_tmp_num[4];
// 可能性のある数字達を一つの文字列に
for (int i = 9; i >= 0; i--) {
if (num_array[i]) {
int_tmp_num *= 10;
int_tmp_num += i;
array_count++;
}
}
sprintf(str_tmp_num, "%d", int_tmp_num);
// 3桁の数字作成
bool flag = true;
while (flag) {
random_num1 = generateRandomNum(0, 0, array_count - 1, NULL);
random_num2 = generateRandomNum(0, 0, array_count - 1, NULL);
random_num3 = generateRandomNum(0, 0, array_count - 1, NULL);
sprintf(return_tmp_num, "%c%c%c", str_tmp_num[random_num1], str_tmp_num[random_num2], str_tmp_num[random_num3]);
// 前に試したことがない数字 && ルール通りの数字
if (check_history(atoi(return_tmp_num)) && ruleNum(return_tmp_num)) {flag = false;}
}
strcpy(computer_guess_num, return_tmp_num);
guess_history[round_count] = atoi(computer_guess_num);
}