第74章 石取りゲーム その1


今回から、「石取りゲーム」を作ります。これは、昔からあるゲームで 必勝法のアルゴリズムも決まっています。「必勝法」と言っても必ず勝つわけではありません。 (これじゃ、必勝法といえませんが・・・)



ルールは至って簡単です。石がn個ある山から、交互にm個以内で石を取っていきます。 最後の1個の石を取った人が負けです。自分の順番の時は最低でも1個の石を取らなくては いけません。

今回は「必勝法もどき」を使わず、コンピュータは乱数に従って石を取ります。 ただ、残りの石がm+1個の時は、迷わずm個の石を取ってコンピュータが勝ちます。

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

// game2_01.c

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

int takestone(int *, int, int, int);

int main()
{
    // nStone:石の数 nGet:取れる石の数 nSente:1なら人が先手
    // nOrder:1なら先手、-1なら後手の番

    int nStone, nGet, nSente, nOrder, nResult;
    char answer[8];

    printf("石を交互に取り、最後の1個を取った人が負けです\n");
    while (1) {
        nOrder = 1;
        printf("石の数は(5以上100以下)==");
        gets(answer);
        nStone = atoi(answer);
        if (nStone < 5 || nStone > 100) {
            printf("石の数が不正です\n");
            continue;
        }
        while (1) {
            printf("一度に取れる石の数は(2以上)==");
            gets(answer);
            nGet = atoi(answer);
            if (nGet >= nStone) {
                printf("一度に取れる石の数が多すぎます\n");
                continue;
            }
            if (nGet < 2) {
                printf("一度に取れる石の数が少なすぎます\n");
                continue;
            }
            break;
        }
        printf("あなたが先手になりますか(Y/N)--");
        gets(answer);
        if (strcmp(answer, "y") == 0 || strcmp(answer, "Y") == 0) {
            nSente = 1;
        } else {
            nSente = 0;
        }
        while (1) {
            nResult = takestone(&nStone, nGet, nSente, nOrder);
            if (nResult == -1)
                break;
            nOrder *= -1;
        }
        printf("続けますか(Y/N)--");
        gets(answer);
        if (strcmp(answer, "n") == 0 || strcmp(answer, "N") == 0)
            break;
        printf("======================\n\n");
    }
    return 0;
}
やたらと、while文が多いですね。しかも、全部無限ループになっています。

最初に、簡単なルールの説明を表示します。

次に1番目の無限ループに入ります。

順番は最初は「先手」に決まっているので、nOrderを1にしておきます。

次に「山の石の数」を決めます。5個以上100個以内としました。 もし、条件に合わない入力があると、「石の数が不正です」と表示して、 continueで最初の質問に戻ります。

石の数を正しく入力すると、2番目の無限ループに入ります。 ここでは、一度に取れる石の数を聞いています。条件に合わない入力をすると continueでこの無限ループ内を回り続けることになります。正しい入力をすると、 breakで2番目の無限ループを抜けます。

次に、人間が先手になるかどうかを決めます。

その後、こんどは第3の無限ループに入ります。 ここでは、takestone関数を呼びだしています。この関数の戻り値が-1になると ループを抜けます。また、takestone関数が呼ばれるたびに、nOrderに-1をかけます。 すると、nOrderの値は、1,-1,1,-1,...というようになります。これが、1の時は 先手、-1の時は後手の順番ということです。なお、takestone関数は勝負がついたら -1を返します。また、この関数の最初の引数にnStoneのアドレスを渡している点に注意してください。

勝敗がついたら、このゲームを続けるかどうかを尋ねます。続ける場合はcontinueで 第1の無限ループの最初に戻り、再度ゲームを始めることになります。続けないと 答えると、ループを抜けて、returnでメイン関数を抜けてプログラムが終了します。

int takestone(int *pstone, int nGet, int nSente, int nOrder)
{
    char answer[8];
    int n;

    srand((unsigned)time(NULL));

    if ((nOrder == 1 && nSente == 1) || (nOrder == -1 && nSente == 0)) { //人が先手で現在先手の番
        while (1) {
            printf("取る石の数は--");
            gets(answer);
            n = atoi(answer);
            if (n > nGet) {
                printf("一度に取れる石の数は%d個までです\n", nGet);
                continue;
            }
            if (n <= 0) {
                printf("1個以上を指定してください\n");
                continue;
            }
            if (n >= *pstone) {
                printf("そのような取り方は出来ません\n");
                continue;
            }
            *pstone -= n;
            if (*pstone == 1) {
                printf("あなたの勝ちです\n");
                return -1;
            }
            break;
        }
    } else {
        if (*pstone <= nGet + 1) {
            n = *pstone - 1;
        } else {
            n = rand() % nGet + 1;
        }
        printf("コンピュータは%d個取りました\n", n);
        *pstone -= n;
        if (*pstone == 1) {
            printf("コンピュータの勝ちです\n");
            return -1;
        }
    }
    printf("残りの石は%d個です\n", *pstone);
    return 0;
}
各順番(先手、後手)で、石を取り、残りの石を表示する関数です。

最初の引数がポインタである点に注意してください。この関数内で 処理した値はmain関数内のnStoneに反映されます。

まず、人間の順番の時(nOrderが1でnSenteが1の時、または、nOrderが-1でnSenteが0の時) は、取る石の数を尋ねます。条件に合わない入力をすると無限ループ内を回り続けます。

石を取った結果山の石が1個になった場合は人間の勝ちと判定し、-1を返します。

順番がコンピュータである場合は、まず、山の石の数を調べます。これが1回に取れる 石の個数+1以下の時は、山の石の数-1だけ石を取ります。これでコンピュータが勝ちます。

山の石の数がこの条件に当てはまらない時は、1から1回に取れる石の数の範囲で乱数を 発生させます。

rand()をnGetで割ったあまりは0からnGet-1です。これに1を足すと条件に合う乱数を 発生させることができます。

コンピュータが石を取ったあと山の石の数が1となればコンピュータの勝ちです。

勝敗がつかなかった時は、この関数は0を返して終了します。

このゲームでは、コンピュータは取る石の数を運任せにしています。しかし、実際に やってみると、人間が負けることも多々あります。遊んでみてください。


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

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