これは、筆者が子供の頃ゲームのやり方をテレビか何かで知って、ずっと遊んでいたものです。 必勝法もどきも、何となく経験的にわかってきました。 コンピュータをやるようになってから、いろいろ調べてみましたが、このゲームについての 記載はまだ見つかっていません。
ルールは、石山が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; }各山の石の数を表示する関数です。
今回のプログラムのポイントは、配列とポインタの関係、配列を関数に渡すときの 方法などです。よく理解できなかった場合は、それぞれの章を復習してみてください。
Update Mar/17/2002 By Y.Kumei