今回から、「石取りゲーム」を作ります。これは、昔からあるゲームで
必勝法のアルゴリズムも決まっています。「必勝法」と言っても必ず勝つわけではありません。
(これじゃ、必勝法といえませんが・・・)
ルールは至って簡単です。石が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を返して終了します。
このゲームでは、コンピュータは取る石の数を運任せにしています。しかし、実際に やってみると、人間が負けることも多々あります。遊んでみてください。
Update Jan/19/2002 By Y.Kumei