Cの練習のため数当てゲーム（ヌメロン）を作成

# はじめに
※読み飛ばしていただいて大丈夫です。
新しい言語を学ぶ際、理解度を測る一つの手段としてヌメロンを作ることにしている。基本文法の理解を深め、新しい知識の獲得も期待できるからだ。C言語はまだ基本文法しか勉強していないため、所要時間は10時間程度かかった。この10時間には不明点の深掘りや、知らない関数を調べる時間も含まれている。

# ヌメロンのルール
https://ameblo.jp/a-la-carte-1012/entry-11401369990.html

> まず、それぞれのプレイヤーが、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

## 宣言
```c
#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;
```

## 制御 (ゲーム全体の動き)
フローチャート：
```mermaid
graph TD
    A[開始] --> B[プレイヤーが数字を入力]
    B --> C[コンピュータが数字を作成]
    C --> D[プレイヤーがコンピュータの数字を予想]
    D --> E{プレイヤーのeatが3?}
    E -->|はい| F[プレイヤーの勝利]
    E -->|いいえ| G[コンピュータがプレイヤーの数字を予想]
    G --> H{コンピュータのeatが3?}
    H -->|はい| I[コンピュータの勝利]
    H -->|いいえ| D
    F --> J[終了]
    I --> J
```

***
1. プレイヤー数字を入力
2. コンピュータ数字作成
3. プレイヤーがコンピュータの数字を予想
4. eatが3だったらプレイヤーの勝利
5. コンピュータがプレイヤーの数字を予想
6. eatが3だったらコンピュータの勝利
7. 3に戻る
***

ゲーム全体の制御コード：
```c
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桁が数字であるかの判定も行う。
```c
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;
    }
}
```
この関数で、プレイヤーとコンピュータが数字を決定するとき、相手の数字を予想するときにルール通りの数字かの確認をしている。

## コンピュータの数字予想時の挙動
フローチャート：
```mermaid
graph TD
    A[開始] --> B[ラウンド1-3: 123, 456, 789を評価]
    B --> C{eat or biteが1以上?}
    C -->|はい| D[可能性のある数字リストに追加]
    C -->|いいえ| E[次のラウンドへ]
    D --> E
    E --> F{ラウンド3終了?}
    F -->|はい| G{可能性のある数字が9個未満?}
    G -->|はい| H[0をリストに追加]
    G -->|いいえ| I[次のステップへ]
    H --> I
    F -->|いいえ| B
    I --> J[ラウンド4以降: リストから3桁の数字を作成]
    J --> K[数字を評価]
    K --> L{eat=0 かつ bite=0?}
    L -->|はい| M[その数字をリストから除外]
    L -->|いいえ| N{eat+bite=3?}
    N -->|はい| O[その数字以外をリストから除外]
    N -->|いいえ| P[次のラウンドへ]
    M --> P
    O --> P
    P --> Q{正解?}
    Q -->|はい| R[終了]
    Q -->|いいえ| J
```

***
1. ラウンド1 = 123, ラウンド2 = 456, ラウンド3 = 789を評価する
2. それぞれeat, biteが1つでも出たら可能性のある数字リストに入れる
    ⇒ラウンド3終了時に可能性のある数字が9個ない場合、0をリストに入れる
3. ラウンド4からは可能性のある数字リストから適当な3桁の数字を作成
4. 評価をし、eat, biteが両方0の場合、可能性リストから除外
5. eat + biteが3の時、その数字以外のすべての数字を可能性リストから除外
6. 3に戻る
***

最初の3ラウンドは相手の数字の情報を掴むのが定番の流れだ（少なくとも私の育った環境ではそうだった）。コンピュータにもこの流れを持たせている。
```c
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桁の数字を作成し、それをコンピュータの予想ナンバーにしている。上記の[数字のルール](#数字の確認)に加え、過去の予想数字と被っていないかを確認している。その機能が以下のコードである。

```c
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を切り替えることにより保持している。
```c
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数を評価する関数に入れている。
```c
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);
}
```

# コード全体
```c
#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);
}

```