第76章 357ゲーム その1


今回は、石取りゲームを少し複雑にしたものを考えます。

これは、筆者が子供の頃ゲームのやり方をテレビか何かで知って、ずっと遊んでいたものです。 必勝法もどきも、何となく経験的にわかってきました。 コンピュータをやるようになってから、いろいろ調べてみましたが、このゲームについての 記載はまだ見つかっていません。

ルールは、石山が3つあり、それぞれに3,5,7の石があります。 2人で順番に石を取っていきます。一度に取る石の数は自由です。 ただし1個以上取らなくてはいけません。 また、一度に一つの山からしか取れません。 最後の1個の石を取った人が負けです。

ルールはわかったでしょうか。 何回か遊ぶと、何となく必勝法が見えてきます。

今回は、コンピュータは必勝法は使わずに、乱数で取る石を決定します。



では、プログラムを見てみましょう。

// stone35701.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

int comptake(int *);
int judge(int *);
int takestone(int *);
int showno(int *);

// sente:人が先手 1,後手 0,    order:現在先手の番 1, 後手の番 0
int sente, order;
乱数に種をまく関係上、time.hが必要となります。

各関数の引数はint型へのポインタとなっています。

int main()
{
    // mt[x] 各石山の数A,B,Cの山の数はmt[0],mt[1],mt[3]に格納
    int mt[3], ret;
    char ans[8];

    mt[0] = 3;
    mt[1] = 5;
    mt[2] = 7;

    showno(mt);

    printf("あなたが先手になりますか(Y/N)---");
    gets(ans);
    if (strcmp(ans, "y") == 0 || strcmp(ans, "Y") == 0) {
        sente = 1;
    } else {
        sente = 0;
    }

    order = 1;

    while (1) {
        ret = takestone(mt);
        if (ret == 1)
            break;
    }
    
    return 0;
}
mt配列には各山の石を保存することにします。

最初にshowno関数を呼んで初期の石山の数を表示しています。 このとき引数にmtを渡していることに注意してください。 showno関数については、後で解説します。

次に、人間が先手かどうかを決めます。 人間が先手の場合はsenteに1が、そうでない場合は0が格納されます。 orderは現在の取り手を示します。1なら先手の番、0なら後手の番です。

whileの無限ループに入ります。 このループから抜けるのはtakestone関数が1を返した(勝敗がついた)時のみです。

無限ループを抜けたらmain関数を抜けて、このプログラムが終了します。

int comptake(int *mtno)
{
    int mtorder, stone;

    srand( (unsigned)time( NULL ) );
    
    while (1) {
        mtorder = rand() % 3;
        if (mtno[mtorder] == 0)
            continue;
        else
            break;
    }
    while (1) {
        stone = rand() % (mtno[mtorder]) + 1;
        mtno[mtorder] -= stone;
        if (mtno[0] + mtno[1] + mtno[2] == 0) {
            mtno[mtorder] += stone;
            continue;
        } else {
            break;
        }
    }

    printf("コンピュータは%cから%d個取りました\n", mtorder + 'A', stone);
    showno(mtno);
    return 0;
}
コンピュータが石を取る関数です。main関数からは直接は呼び出されていません。 srand関数で乱数の種をまきます。

最初の無限ループに入り、どの山から石を取るかを決めます。 rand関数の値を3で割ったあまりは、0,1,2のいずれかで、これがどの山かを 示します。もしすでに石のない山(mtno[mtorder]が0)が選ばれてしまったときは continueでループの最初に戻り、石山を決めます。 0でない山が選ばれたら、ループを抜けます。

第2の無限ループに入ります。 ここでは、取る石の数を決めます。 mtorderという山には、mtno[mtorder]個の石があるので rand関数の値をこれで割ったあまりの数に1を足したものを取ります。

ここで、もし全部の山が0個になってしまうとまずいのでそのようなときは、 continueでループの最初に戻り、取る石の数を再検討します。

取る石の数が決まったら、その旨表示します。

int takestone(int *mt)
{
    char ans[8];
    int stone, mtorder;

    if (sente != order) {
        comptake(mt);
        if (judge(mt) == 0) {
            printf("コンピュータの勝ちです\n");
            return 1;
        }
        if (order == 1)
            order = 0;
        else
            order = 1;
        return 0;
    }
    while (1) {
        printf("どの山からとりますか(A,B,C)---");
        gets(ans);
        if (strcmp(ans, "A") != 0 && strcmp(ans, "B") != 0 && strcmp(ans, "C") != 0 &&
            strcmp(ans, "a") != 0 && strcmp(ans, "b") != 0 && strcmp(ans, "c") != 0) {
            printf("山の指定が正しくありません\n");
            continue;
        } else {
            ans[0] = toupper(ans[0]);
            mtorder = ans[0] - 'A';
            if (mt[mtorder] == 0) {
                printf("その山にはもう石はありません\n");
                continue;
            }
            break;
        }
    }
    
    while (1) {
        printf("いくつとりますか---");
        gets(ans);
        stone = atoi(ans);
        mt[mtorder] -= stone;
        if (mt[0] + mt[1] + mt[2] == 0) {
            printf("その取り方では山の石が全部0になります\n");
            mt[mtorder] += stone;
            continue;
        }
        mt[mtorder] += stone;
        if (mt[mtorder] - stone < 0 || stone <= 0) {
            printf("取る石の数が不正です\n");
            continue;
        } else {
            break;
        }
    }
    mt[mtorder] -= stone;
    showno(mt);
    if (judge(mt) == 0) {
        printf("あなたの勝ちです\n");
        return 1;
    }
        
    if (order == 1)
        order = 0;
    else
        order = 1;

    return 0;
}
main関数の無限ループ内から呼ばれる関数です。

senteとorderの値が異なる場合(現在の取り手が後手、人間が先手もしくは 現在の取り手が先手、人間が後手の場合)は、コンピュータが取ります。

つまりcomptake関数が呼ばれます。

この関数には石山の配列の先頭アドレスが渡されます。

コンピュータに石をとってもらったらjudge関数で勝負がついたかどうか判定します。

勝負がついたのならコンピュータの勝ちなのでその旨表示してreturnします。

このとき戻り値は1なので、main関数側ではループを抜けてプログラムが終了します。

勝負がついていないときはorderを反転して(1なら0に、0なら1にする)0を返します。

senteとorderが一致するときは人間の取る番なので、どの山から取るかをたずねます。 人間は「A,B,C」または「a,b,c」でどの山かを指定します。

小文字で答えた場合は大文字に変換します。(toupper関数)

さて、

mtorder = ans[0] - 'A';

とすると、mtorderは、ans[0]が'A'なら0,'B'なら1,'C'なら2となります。

すでに0になっている山が指定されたときはcontinueでループの最初に戻り 取る山を再検討します。 取る山が決定したらループを抜けて、次の無限ループに入ります。

取る石の数をたずねます。全部の山が0になる場合は、注意を促して 再度石の数をたずねます。 もし、取る石の数が0または負の数であったり、石山の数より多い数を指定した場合は 注意を促して、再度石の数をたずねます。

取る石の数が決まったら、showno関数で各山の個数を表示します。 そして、judge関数を呼んで勝敗がついたかどうか調べます。 勝敗がついたのなら、人間の勝ちなのでその旨を表示してreturnします。

勝敗がつかない場合はorderを反転して戻ります。

int judge(int *mt)
{
    if (mt[0] + mt[1] + mt[2] == 1)
        return 0;
    else
        return 1;
}
勝敗を判定する関数です。

各山の石の数を合計して1になれば、その時の順番のプレーヤーの勝ちです。

int showno(int *mt)
{
    printf("[A] = %d, [B] = %d, [C] = %d\n", mt[0], mt[1], mt[2]);

    return 0;
}
各山の石の数を表示する関数です。

今回のプログラムのポイントは、配列とポインタの関係、配列を関数に渡すときの 方法などです。よく理解できなかった場合は、それぞれの章を復習してみてください。


[Index][総合Index] [Previous Chapter] [Next Chapter]

Update Mar/17/2002 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。