【C言語】数当てゲーム(Hit & Blow)を開発

数当てゲームの開発の解説ページアイキャッチ

今回は、C言語で「数当てゲーム」を開発していきたいと思います!

おそらく、単に数当てゲームと聞くよりも、下記のようなゲームをイメージしていただくのが分かりやすいかと思います。

  • Hit & Blow
  • マスターマインド
  • Numer0n(ヌメロン)

今回開発していく数当てゲームは、上記の Hit & Blow に近いと思います。

紹介したソースコードをコンパイルして実行すれば、下のアニメのように数当てゲームをプレイすることができます。

数当てゲームプログラムの実行イメージ

C言語を習い始めてすぐに面白いゲームを開発するのは難しいですが、この数当てゲームはコンソール出力だけで実現できますし、処理も単純なものが多いため、C言語を一通り学んだことのある方が挑戦するのには良い題材のゲームだと思います。

このページでも、C言語にあまり慣れていない方でも理解できるよう、分かりやすく・詳しく解説していきたいと思います。

また、出来上がりのソースコードは 数当てゲームのサンプルプログラム で紹介していますので、先にソースコードを読みたい方は 数当てゲームのサンプルプログラム までスキップしていただければと思います。

開発する数当てゲーム

まずは、今回開発していく数当てゲームがどんなものであるのかについて解説していきます。

今回開発する数当てゲームは、ランダムに選ばれた正解となる N 桁の数を予想し、その N 桁の数を当てるゲームになります(今後は簡単のため、桁数は N = 4 であることを前提に解説していきます)。

数当てゲームの説明図

プレイヤーは予想した 4 桁の数を入力することで、正解となる数を当てていきます。

もし入力された数が正解と一致した場合、ゲームクリアとなりゲーム終了となります。

また、もし入力された数が正解と一致しない場合は、下記のヒントを得ることができます。

  1. Hit 数:正解と一致している桁数
  2. Blow 数:位置は違うが正解に含まれている桁数

例えば正解が 3689 であり、入力された数が 3689 であれば、4 Hit・0 Blow ということになります。つまり、4 桁とも正解と一致していることになりますので、この時点でゲームクリアということになります。

hitとblowの例1

また、例えば正解が 3689 であり入力された数が 3964 であれば、1 Hit、2 Blow ということになります。

hitとblowの例2

さらに、例えば正解が 3689 であり、入力された数が 1250 であれば、0 Hit・0 Blow ということになります。つまり、この時点で正解の数には 3 4 6 7 8 9 のいずれかしか含まれないことが分かります。

hitとblowの例3

こういった Hit と Blow のヒントを得ながら正解となる数を予想していくのが、つまり 4 Hit を目指していくのが、今回作成するゲームになります。

今回は Hit & Blow に倣って Hit と Blow で表現していきますが、例えば Numer0n の場合は EAT と BITE で表現されたりするので、表現の仕方は具体的なゲームや製品等によってバラバラになると思います。

また、正解となる数と入力可能な数は双方とも、各桁の値が重複しないものとしていきたいと思います(おそらく Hit & Blow もそういうルールのはず)。

正解の数や入力する数に重複があってはならないことを示す図

ですので、数当てゲームを開発していくためには、正解となる数の各桁が重複しないようにランダムに設定する必要がありますし、プレイヤーによって入力された数の各桁に重複があるような場合は、再度プレイヤーから入力を受け付けるような制御が必要になります。

また、ユーザーが 4 桁の数を入力しないことを想定するのであれば、数字以外の文字が入力されたり、桁数が異なるような場合も再度プレイヤーから入力を受け付けるようにする必要があります。

不適切な入力例

もちろん、数当てゲームの肝となる Hit の数や Blow の数をカウントするような処理も必要です。

ただ、数当てゲーム開発においてポイントになるのは上記くらいであり、あとの処理は割と簡単に実現できるかなぁと思います。

数当てゲームの開発

では、数当てゲームを開発していきましょう!

数当てゲームは下記の処理の流れを実現することで開発することができます。

  1. 正解を用意する
  2. プレイヤーの入力を受け付ける
    • 不正な入力である間繰り返す
  3. Hit 数と Blow 数を表示する
  4. ゲームクリアでなければ 2. に戻る
  5. ゲームクリアを表示する

ここからは、まず正解となる数や入力された数の管理の仕方について解説し、その後、上記の各処理について解説していきます。ここまでの解説を読んで、「自分で開発できそう」と感じた方もおられると思いますので、ぜひそういった方は、まずは自分の力で挑戦してみてください!

スポンサーリンク

数の管理の仕方

今回、数当てゲームを開発するにあたって、各桁の数字は配列で管理するようにしていきたいと思います。また、各桁は数値ではなく、文字(数字)で管理するようにしていきたいと思います。

単純に int 型の変数で1つの数値として扱っても良いのですが、これだと各桁の比較を行う際に毎回各桁を分解する処理が必要になって面倒です(Hit 数や Blow 数を数える際に各桁の比較が必要になります)。

例えば 1234 の100の位の桁を得ようと思うと、まず 1234 に対して 1000 で剰余算を行なって 234 を取得し、さらに取得した結果 234100 で割って 2 を得る必要があります(整数同士の割り算を行なって小数点以下は切り捨てる)。

単なる整数から特定の桁を取り出すのは、上記のように剰余算や除算が必要になって結構面倒です。この特定の桁を取り出す処理については下記ページで詳しく解説していますので、興味があれば読んでみてください。

値から特定の桁のみを取得する方法の解説ページアイキャッチ【C言語】値の特定の桁のみを取得する方法

それに対し、配列で各桁の値を扱う場合、一度配列に各桁の数字を格納してやれば、あとは添字を指定するだけで特定の桁を取得することができます。

例えば、1234 であれば、配列名を arr とすれば、arr[0] = 1arr[1] = 2arr[2] = 3arr[3] = 4 と各桁を個別にを配列に格納しておけば、100の位は arr[1] を指定するだけで取得することができて楽です。

配列を利用することで各桁の値が取得しやすいくなる例

また、文字で扱う理由は、プレイヤーから入力された数の最上位の桁が 0 であるかどうかを判断しやすくするようにするためです。

例えば 4 桁の整数の入力を受け付けようと考えて scanf("%d", &x) を実行した場合、プレイヤーが 01234 桁の数を入力したとしても、3 桁の 123 を入力したとしても、x には同じ 123 が格納されてしまいます。

つまり、x の値からは、プレイヤーが 01234 桁の数を入力したのか、間違って 3 桁の 123 を入力したのかが判断できません。

整数型で入力を受け付ける場合の欠点

それに対し、scanf("%s", str) などで文字列の入力を受け付けるようにした場合、入力された文字列がそのまま str に得られるので(例えば 0123 と入力された場合には "0123" が得られ、123 と入力された場合は "123" が得られる)、あとは strlen(str) などで文字列の長さを確認すれば、ユーザーが入力した数の桁数を確実に判定することができます。

入力された文字列をそのまま取得できる例

もちろん、桁数が正しいと判断できた後に文字から数値に変換しても良いのですが、今回は文字(数字)のまま扱うようにしていきたいと思います。

ここまで解説してきた数の管理の仕方は、あくまでも今後解説しやすくするために私が決めたものですので、この辺りは自身の好きなように変更してしまっても問題ないです!

1. 正解を用意する

数の管理の仕方が決まったので、次は数当てゲームを実現するために必要な処理について説明していきます。

まず、数当てゲームを実現するためには「正解となる数」を用意する必要があります。

この正解の数が毎回同じだとゲームとしてつまらないので、ランダムに決定するようにしていきたいと思います。ここでポイントになるのが、数当てゲームの場合、各桁の数字が重複しないように正解となる数を用意する必要があるという点です。

ランダムな値自体は rand 関数を利用することで取得することができますが、単に rand 関数を実行するだけだと重複が発生する可能性があります。

こういった重複なしでランダムな値を取得したい場合、重複無しの値を予め配列に格納しておき、その配列の各要素をランダムに入れ替える方法が有効です。イメージとしてはトランプのカードをシャッフルするイメージですね!

重複無しでランダムな値を得る手順1

これにより元々重複無しの配列の各要素の順番がランダムに入れ替わりますので、あとは配列の先頭から順番に必要な桁数分だけ取得して他の配列に格納すれば、重複無しのランダムな数を生成することができます。

重複無しでランダムな値を得る手順2

この辺りの解説は下記ページで行っていますので、詳しく知りたい方は下記ページをご参照いただければと思います。トランプゲームなどを作成する場合は重複無しのランダムな値が欲しくなることが多いので、知っておくと役に立つ場面もあると思います。

C言語で重複なしの乱数を生成する方法解説ページアイキャッチ【C言語】重複なしで乱数を生成する方法

配列の要素の並びをシャッフルする関数を shuffle とすれば、下記のように処理を行えば、NUM_DIGIT 桁の正解の数 answer を得ることができます(answer は配列で、数の管理の仕方 で解説したように、数の桁ごとに分離されて格納されます)。

正解を用意する

char nums[] = {
    '0', '1', '2', '3', '4',
    '5', '6', '7', '8', '9'
};
int num_num = sizeof(nums)/ sizeof(nums[0]);

shuffle(nums, num_num);
for (int i = 0; i < NUM_DIGIT; i++) {
    answer[i] = nums[i];
}

さらに、上記ページで解説している Fisher–Yates shuffle を利用した場合、shuffle 関数は下記のような関数になります(要素数 num_num 個の配列 nums の各要素の並びをシャッフルする関数)。

shuffle関数

/***************************
 * 数字の並びをシャッフルする 
 * nums[in,out]:数字が格納される配列
 * num_num[in]:数字の枚数
 * *************************/
void shuffle(char nums[], int num_num) {
    int i, j;
    char tmp;

    /* シャッフル対象の末尾を設定 */
    i = num_num - 1;

    while (i > 0) {
        /* シャッフル対象(0〜i)から位置をランダム決定 */
        j = rand() % (i + 1);

        /* ランダムに決めた位置と
           シャッフル対象の末尾の位置のデータを交換 */
        tmp = nums[j];
        nums[j] = nums[i];
        nums[i] = tmp;

        /* シャッフル対象の範囲を狭める */
        i--;
    } 
}

上記の関数を呼び出す前に srand 関数を実行しておく必要がある点に注意してください。その理由や rand 関数については下記ページで解説していますので、詳しくはこちらを参考にしていただければと思います。

C言語で乱数を扱う方法の解説ページアイキャッチC言語で乱数を扱う方法(rand関数とsrand関数) 

2. プレイヤーの入力を受け付ける

正解の用意ができたら、次はいよいよ数当てゲームを開始していきます。

まずはプレイヤーからの数の入力を受け付ける必要があります。

プレイヤーから入力を受け付けること自体は scanf で実現できますが、数当てゲームを実現する上では、プレイヤーの入力した数が下記を満たしているかどうかを確認する必要がある点がポイントになるかと思います。

  • 桁数が合っている?
  • 数字以外の文字が入力されていない?
  • 各桁が重複していない?

上記の全てが YES(true)であれば、プレイヤーの入力した数が今回開発する数当てゲームの入力値として妥当であると言えるため、続いて Hit 数と Blow 数を求めていくことになります。

もし、1つでも No(false)のものがあれば、再度プレイヤーに数を入力してもらうよう制御する必要があります。

つまり、プレイヤーの入力を受け付ける関数を inputNum とすれば、この関数の中で下記のように無限ループを作成し、上記の全てが YES となった場合のみ無限ループを抜け出すように制御する必要があります(入力された数は最終的に input に格納しています)。

入力を受け付けるループ

/***************************
 * 数の入力を受け付ける
 * input[out]:入力された数が格納される配列
 * num_digit[in]:数の桁数
 * *************************/
void inputNum(char input[], int num_digit) {
    char input_str[256];

    while (1) {
        printf("%d桁の数字を入力してください:", num_digit);
        scanf("%s", input_str);

        if (桁数が合っている?) {
            /* NO の場合 */
            continue;
        }

        if (数字以外の文字が入力されていない?) {
            /* NO の場合 */
            continue;
        }

        if (各桁が重複していない?) {
            /* NO の場合 */
            continue;
        }

        for (int i = 0; i < num_digit; i++) {
            input[i] = input_str[i];
        }

        break;
    }
}

ここで使用している break はループを終了させる命令で、continue はループの次の週までスキップする命令になります。

詳細は下記ページで解説していますので、breakcontinue をご存知ない方はこちらを参照していただければと思います。

breakとcontinueの解説ページアイキャッチ【C言語】breakとcontinueについて解説(ループを抜け出す・ループをスキップする)

また上記のソースコードでは、scanf 関数により入力された数(文字列)が格納される配列 input_str の要素数を 256 としていますが、これはプレイヤーが実際に入力する文字列の長さが事前に分からないためです。

もちろんプログラムとして期待する桁数はあるのですが、その期待通りの桁数でプレイヤーが数を入力してくれるとは限りません。そのため、プレイヤーがある程度大きな桁数の数を入力したとしても配列 input_str に格納しきれるよう、配列 input_str の要素数はちょっと大きめに設定しています。

そして、プレイヤーが正しく数を入力してくれたことが確認できた後に、改めて引数で指定された input に配列 input_str の中身をコピーするようにしています。

プレイヤーからの入力を受け付ける際の処理の流れは上記のソースコードのループで実現できるため、以降では、先程挙げた下記3つの条件が成立しているかどうかの確認方法を解説していきます。

  • 桁数が合っている?
  • 数字以外の文字が入力されていない?
  • 各桁が重複していない?

桁数が合っている?

scanf("%s", str) のようにフォーマット指定子に %s を指定して scanf を実行すれば、プレイヤーからの入力結果を文字列として受け取ることができます。

さらに、文字列の長さは strlen 関数等により取得することができるので、その取得結果と期待する桁数が一致するかどうかを判定すれば、プレイヤーが入力した数の桁数が合っているかを判断することができます。

数字以外の文字が入力されていない?

また、変数や配列の要素に格納されている文字が数字であるかどうかは isdigit 関数により調べることができます。

isdigit 関数に文字を格納した変数や配列の要素を指定すれば、その文字が数字であるかどうかを判断してくれます。数字である場合は 0 以外を返却し、数字でない場合、例えばアルファベットなどの場合は 0 を返却します。

ですので、入力された文字列の各文字に対して isdigit 関数を実行し、全ての結果が 0 以外であれば、数字以外の文字は入力されていないと判断することができます(つまり数字のみが入力されている)。

この考え方に基づいて作成した、入力された文字列に数字以外の文字が含まれていないかどうかを判断する関数 is_allDigit は下記のようになります。is_allDigit0 を返却した場合は、入力された文字のいずれかが数字以外であると判断でき、それ以外の場合は入力された文字の全てが数字であると判断できます。

is_allDigit

/***************************
 * 入力された文字が全て数字であるかを判断する 
 * input[in]:入力された文字が格納された配列
 * len[in]:文字数
 * 返却値:1(全ての文字が数字である場合)
 *      :0(それ以外)
 * *************************/
int is_allDigit(char input_str[], int len) {
    for (int i = 0; i < len; i++) {
        if (!isdigit(input_str[i])) {
            return 0;
        }
    }
    return 1;
}

より正確に言うと、isdigit 関数は引数で指定された文字コードに対応する文字が数字であるかどうかを判断する関数です。

コンピュータ内では、実は文字は数値で管理されており、文字ごとに異なる数値が割り当てられています。その文字に割り当てられている数値を文字コードと言います。

この辺りの話は数字と数値の変換方法に絡めて下記ページで解説していますので、興味があれば読んでみてください。

数字と数値の変換方法の解説ページアイキャッチ【C言語】数字⇔数値の変換方法

また、上記では返却値を 10 で表現していますが、bool 型を利用して truefalse で表現することも可能です。そして、この方がソースコードの可読性が上がります。

C言語での bool 型の利用方法は下記ページで解説していますので、bool 型や truefalse を利用したい場合はこちらを参考にしてください。

C言語でbool型を扱う方法の解説ページアイキャッチC言語でのbool型の使い方

各桁が重複していない?

また、今回開発しようとしている数当てゲームを実現するためには、プレイヤーが入力した数の各桁に重複がないかどうかを調べる必要があります。

これは、単純に、各桁同士の数字を比較して一致するかどうかを確認していくことで実現することができます。そして、一致するものが1つでもあれば、重複している桁が存在すると判断することができます。

この考え方に基づいて作成した関数 is_allDifferent は下記のようになります。is_allDifferent が 0 を返却した場合は、入力された文字の中に重複があると判断でき、それ以外の場合は入力された文字の全てが重複していないと判断できます。

is_allDifferent

/***************************
 * 入力された文字が重複していないかを判断する 
 * input_str[in]:入力された文字が格納された配列
 * len[in]:文字数
 * 返却値:1(全ての文字が重複していない場合)
 *      :0(それ以外)
 * *************************/
int is_allDifferent(char input_str[], int len) {
    for (int i = 0; i < len; i++) {
        for (int j = i + 1; j < len; j++) {
            if (input_str[i] == input_str[j]) {
                return 0;
            }
        }
    }
    return 1;
}

ポイントは「同じ桁同士の比較は行わない」点くらいだと思います。同じ桁同士の比較を行ってしまうと、必ず重複している桁が存在することになってしまいます。

スポンサーリンク

3. Hit 数と Blow 数を表示する

プレイヤーから数が入力された後には、Hit 数と Blow 数を表示してあげましょう!

その表示された Hit 数と Blow 数が、プレイヤーが正解の数を推測する際のヒントになります。

ただ、表示するためには事前に Hit 数と Blow 数を求めておく必要がありますね!この Hit 数と Blow 数は、正解の数と入力された数の各桁を比較していくことで求めることができます。

Hit 数を求めるのは簡単で、単に2つの数の「同じ桁同士」のみ比較し、一致した個数を数え上げたものが Hit 数となります。

Hit数を数える様子

配列 input を入力された数の各桁が格納された配列、配列 answer を正解の数の各桁が格納された配列とすれば、Hit 数を数える関数 countHit は下記のように記述することができます。返却値が Hit 数となります。

countHit

/***************************
 * Hit数を数える
 * input[in]:入力された数が格納された配列
 * answer[in]:正解の数が格納された配列
 * num_digit[in]:数の桁数
 * 返却値:Hit数
 * *************************/
int countHit(char input[], char answer[], int num_digit) {
    int num_hit = 0;

    for (int i = 0; i < num_digit; i++) {
        for (int j = 0; j < num_digit; j++) {
            if (answer[i] == input[j]) {
                if (i == j) {
                    num_hit++;
                }
            }
        }
    }

    return num_hit;
}

それに対し、Blow 数を求める場合は、2つの数の「異なる桁同士」を比較し、一致した個数を数え上げたものが Blow 数となります。

Blow数を数える様子

配列 input を入力された数の各桁が格納された配列、配列 answer を正解の数の各桁が格納された配列とすれば、Blow 数を数える関数 countBlow は下記のように記述することができます。返却値が Blow 数となります。

countBlow

/***************************
 * Blow数を数える
 * input[in]:入力された数が格納された配列
 * answer[in]:正解の数が格納された配列
 * num_digit[in]:数の桁数
 * 返却値:Blow数
 * *************************/
int countBlow(char input[], char answer[], int num_digit) {

    int num_blow = 0;

    for (int i = 0; i < num_digit; i++) {
        for (int j = 0; j < num_digit; j++) {
            if (answer[i] == input[j]) {
                if (i != j) {
                    num_blow++;
                }
            }
        }
    }

    return num_blow;
}

2つの数において同じ桁というのは必ず1つですが、異なる桁というのは複数存在します(数が N 桁とすれば N - 1 個)。

そのため、Hit 数を求めるのに対し、Blow 数を求める際にはループの数が多くなって処理が若干複雑になります。

4. ゲームクリアでない場合は 2. に戻る

この数当てゲームにおいて、ゲームクリアとなるのはプレイヤーが正解の数を当てた場合、つまり、Hit 数が正解の数の桁数と一致した場合となります。

逆に Hit 数が正解の数の桁数よりも小さい場合は、まだゲームクリアでないと判断することができます。

そして、ゲームクリアでない場合は、2. プレイヤーの入力を受け付ける に戻って再度プレイヤーから数を入力してもらう必要があります。

つまり、2. プレイヤーの入力を受け付ける3. Hit 数と Blow 数を表示する を1つのループの中で処理しているとすれば、ゲームクリアの場合はそのループを終了し、ゲームクリアでない場合はそのループを継続してやれば良いということですね!

こういった制御は、正解の数の桁数を定数 NUM_DIGIT、Hit 数を変数 num_hit とすれば、下記のようにループを組むことで実現することができます。

ゲームクリアかどうか判断する

do {

    /* 2.プレイヤーの入力を受け付ける */

    /* 3.Hit数とBlow数を表示する */
    num_hit を求める;

} while (num_hit < NUM_DIGIT);

ループを for や while ではなく do while で実現しているのは、ループ回数が何回か分からない&必ずループの中の処理を一度は実行するためです。

for や while の使い分け、while や do while の使い分けに関しては下記ページでそれぞれ解説していますので、こちらも興味があれば読んでみていただければと思います(といっても、これらの使い分けに明確な一般的なルールはないので、参考程度にしてもらうので良いと思います)。

forとwhileの使い分けの解説ページアイキャッチfor と while の使い分け

whileとdo whileの違いの解説ページアイキャッチ【C言語】while と do while の違い

5. ゲームクリアを表示する

ゲームクリアになった際には、その旨を表示し、プレイヤーにゲームクリアであることを伝えてあげましょう!

4. ゲームクリアでない場合は 2. に戻る でゲームクリアでない場合はループを継続するようにしていますので、逆にループが終了した場合はゲームクリアであると判断することができます。

そのため、4. ゲームクリアでない場合は 2. に戻る で作ったループが終了した後に printfなどで表示してやれば、適切なタイミングでゲームクリアであることをプレイヤーに示すことができます。

ゲームクリアを表示する

do {

    /* 略 */

} while (num_hit < NUM_DIGIT);

/* 5.ゲームクリアを表示する */
printf("**** GAME_CLEAR ***\n");
printf("* answer = [");
for (int i = 0; i < NUM_DIGIT; i++) {
    printf("%c", answer[i]);
}
printf("] *\n");

以上、数当てゲームを開発する上で必要になる主な処理の解説となります。

スポンサーリンク

数当てゲームのサンプルプログラム

最後に、ここまで解説してきた数当てゲームのサンプルプログラムのソースコードを紹介します。

といっても、数当てゲームの開発 で紹介した各処理を下記の流れで実行するようにしただけです。

  1. 正解を用意する
  2. プレイヤーの入力を受け付ける
    • 不正な入力である間繰り返す
  3. Hit 数と Blow 数を表示する
  4. ゲームクリアでなければ 2. に戻る
  5. ゲームクリアを表示する

ソースコードのコメントで、どの行がどの処理に対応しているか大体分かると思いますので、処理内容がよく分からない場合は 数当てゲームの開発 の解説ページを参考にしていただければと思います。

数当てゲームのサンプルプログラムのソースコードは下記のようになります。

数当てゲーム

#include <stdio.h> /* printf,scanf */
#include <string.h> /* strlen */
#include <stdlib.h> /* srand, rand */
#include <ctype.h> /* isDigit */
#include <time.h> /* time */

/* 数の桁数 */
#define NUM_DIGIT 4

/***************************
 * 数字の並びをシャッフルする 
 * nums[in,out]:数字が格納される配列
 * num_num[in]:数字の枚数
 * *************************/
void shuffle(char nums[], int num_num) {
    int i, j;
    char tmp;

    /* シャッフル対象の末尾を設定 */
    i = num_num - 1;

    while (i > 0) {
        /* シャッフル対象(0〜i)から位置をランダム決定 */
        j = rand() % (i + 1);

        /* ランダムに決めた位置と
           シャッフル対象の末尾の位置のデータを交換 */
        tmp = nums[j];
        nums[j] = nums[i];
        nums[i] = tmp;

        /* シャッフル対象の範囲を狭める */
        i--;
    } 
}

/***************************
 * 入力された文字が全て数字であるかを判断する 
 * input[in]:入力された文字が格納された配列
 * len[in]:文字数
 * 返却値:1(全ての文字が数字である場合)
 *      :0(それ以外)
 * *************************/
int is_allDigit(char input_str[], int len) {
    for (int i = 0; i < len; i++) {
        if (!isdigit(input_str[i])) {
            return 0;
        }
    }
    return 1;
}

/***************************
 * 入力された文字が重複していないかを判断する 
 * input_str[in]:入力された文字が格納された配列
 * len[in]:文字数
 * 返却値:1(全ての文字が重複していない場合)
 *      :0(それ以外)
 * *************************/
int is_allDifferent(char input_str[], int len) {
    for (int i = 0; i < len; i++) {
        for (int j = i + 1; j < len; j++) {
            if (input_str[i] == input_str[j]) {
                return 0;
            }
        }
    }
    return 1;
}

/***************************
 * 数の入力を受け付ける
 * input[out]:入力された数が格納される配列
 * num_digit[in]:数の桁数
 * *************************/
void inputNum(char input[], int num_digit) {
    char input_str[256];

    while (1) {
        printf("%d桁の数字を入力してください:", num_digit);
        scanf("%s", input_str);

        if (strlen(input_str) != num_digit) {
            printf("%d桁じゃないです...\n", num_digit);
            continue;
        }

        if (!is_allDigit(input_str, num_digit)) {
            printf("数字以外が入力されてます...\n");
            continue;
        }

        if (!is_allDifferent(input_str, num_digit)) {
            printf("同じ数字が入力されてます...\n");
            continue;
        }

        for (int i = 0; i < num_digit; i++) {
            input[i] = input_str[i];
        }

        break;
    }
}

/***************************
 * Hit数を数える
 * input[in]:入力された数が格納された配列
 * answer[in]:正解の数が格納された配列
 * num_digit[in]:数の桁数
 * 返却値:Hit数
 * *************************/
int countHit(char input[], char answer[], int num_digit) {
    int num_hit = 0;

    for (int i = 0; i < num_digit; i++) {
        for (int j = 0; j < num_digit; j++) {
            if (answer[i] == input[j]) {
                if (i == j) {
                    num_hit++;
                }
            }
        }
    }

    return num_hit;
}

/***************************
 * Blow数を数える
 * input[in]:入力された数が格納された配列
 * answer[in]:正解の数が格納された配列
 * num_digit[in]:数の桁数
 * 返却値:Blow数
 * *************************/
int countBlow(char input[], char answer[], int num_digit) {

    int num_blow = 0;

    for (int i = 0; i < num_digit; i++) {
        for (int j = 0; j < num_digit; j++) {
            if (answer[i] == input[j]) {
                if (i != j) {
                    num_blow++;
                }
            }
        }
    }

    return num_blow;
}


int main(void) {
    
    char answer[NUM_DIGIT];
    int num_hit, num_blow;

    /* 乱数のSEEDを設定 */
    srand((unsigned int)time(NULL));

    /* 1.正解を用意する */
    char nums[] = {
        '0', '1', '2', '3', '4',
        '5', '6', '7', '8', '9'
    };
    int num_num = sizeof(nums)/ sizeof(nums[0]);

    shuffle(nums, num_num);
    for (int i = 0; i < NUM_DIGIT; i++) {
        answer[i] = nums[i];
    }

    do {
        /* 2.プレイヤーの入力を受け付ける */
        char input[4];
        inputNum(input, NUM_DIGIT);

        /* 3.Hit数とBlow数を表示する */
        num_hit = countHit(answer, input, NUM_DIGIT);
        num_blow = countBlow(answer, input, NUM_DIGIT);
        printf("%d HIT!! / %d BLOW!!\n", num_hit, num_blow);

    } while (num_hit < NUM_DIGIT); /* 4.ゲームクリアかどうか判断する */

    /* 5.ゲームクリアを表示する */
    printf("**** GAME_CLEAR ***\n");
    printf("* answer = [");
    for (int i = 0; i < NUM_DIGIT; i++) {
        printf("%c", answer[i]);
    }
    printf("] *\n");

    return 0;
}

実行すれば、下記のような画面が表示され、4 桁の数字を入力すると Hit 数や Blow 数が表示されます。さらに、Hit 数が 4 になった際には **** GAME_CLEAR *** と表示されるようになっています。

4桁の数字を入力してください:4730
2 HIT!! / 2 BLOW!!
4桁の数字を入力してください:4037
4 HIT!! / 0 BLOW!!
**** GAME_CLEAR ***
* answer = [4037] *

正解の数の桁数を変更したい場合は、ソースコード先頭部分で定義している NUM_DIGIT の定義値を変更してください。例えば下記のように変更すれば、3 桁の数でゲームをプレイすることができるようになります。

桁数の変更

/* 数の桁数 */
#define NUM_DIGIT 3

まとめ

このページでは、C言語での「数当てゲーム」の開発について解説しました!

ゲームとしては単純ではありますが、いざ開発してみようと割とC言語で重要な考え方などを改めて学ぶことができ、いい復習になったり、新たな気づきもあっていい学習になるのではないかと思います。

今回は一人用の数当てゲームを開発しましたが、コンピュータと対戦できるようにしてみるのも面白いと思います。コンピュータにどうやって数を予測させるのかのあたりを考えると、かなりアルゴリズムを考える力が付くのではないかと思います!

今回紹介した数当てゲームのように、簡単なゲームであっても実際に開発してみると学べることも多いですし、また楽しく学べるというメリットもありますので、是非いろんなゲームの開発に挑戦してみてください!このサイトでもまた良い題材のゲームが思いついたら紹介していきたいと思います!

同じカテゴリのページ一覧を表示