1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

※読み飛ばしていただいて大丈夫です。
新しい言語を学ぶ際、理解度を測る一つの手段としてヌメロンを作ることにしている。基本文法の理解を深め、新しい知識の獲得も期待できるからだ。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;

制御 (ゲーム全体の動き)

フローチャート:


  1. プレイヤー数字を入力
  2. コンピュータ数字作成
  3. プレイヤーがコンピュータの数字を予想
  4. eatが3だったらプレイヤーの勝利
  5. コンピュータがプレイヤーの数字を予想
  6. eatが3だったらコンピュータの勝利
  7. 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. ラウンド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ラウンドは相手の数字の情報を掴むのが定番の流れだ(少なくとも私の育った環境ではそうだった)。コンピュータにもこの流れを持たせている。

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);
}

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?