石の山が3つあります。それぞれに3,5,7個の石があります。
プレーヤーは交互に石を取っていきます。最後の1個の石を取った人が負けです。
ルールは
1.一度に何個の石をとっても良いが、最低でも1個の石を取らなくてはいけない。
2.一度に1つの山からしか石はとれない。
となっています。
これは、C言語編第76章のC++版です。
では、プログラムを見てみましょう。
// game35701.cpp #include <iostream.h> #include <stdlib.h> #include <string.h> #include <time.h> class Mountain { int mt[3]; int sente, order; int judge(); int show_stone(); int init(); int take_stone(); int comp_take(); public: int play(); };まずは、Mountainクラスを作ってみました。
このクラスのメンバには、mtという配列があります。この配列に現在の山の石の数を 格納することにします。
山の名前を仮にA,B,Cとするとそれぞれの山にある石の数は
mt[0],mt[1],mt[2]
を調べれば良いことになります。
senteメンバは、人間が先手なら1、コンピュータが先手なら0を入れておきます。
orderは現在の取り手の番を示します。1なら先手の番、0なら後手の番というようにしておきます。
後のメンバ関数は、名前から想像がつくと思います。
judgeメンバ関数は、勝敗がついたかどうかを判定します。
show_stoneメンバ関数は、現在の山の石の数を表示します。
initメンバ関数は、ゲームを始めるときの初期化を行います。コンストラクタで 行わずに、メンバ関数を作ったのは、繰り返しゲームを行えるようにするためです。
comp_takeメンバ関数は、コンピュータがどの山から、いくつ石を取るかを決めます。
playメンバ関数は、このゲームを取り仕切ります(?)。
int Mountain::show_stone() { cout << "[A]---" << mt[0] << ", [B]---" << mt[1] << ", [C]---" << mt[2] << endl; return 0; }現在の、石山の数を表示します。これは、簡単ですね。
int Mountain::judge() { if (mt[0] + mt[1] + mt[2] == 1) return 0; else return 1; }勝敗がついたかどうかを判定します。
プレーヤーが、石を取った後、すべての山の石の合計が1であれば、そのプレーヤーの勝ちです。
勝負がついたら0を返し、その他の時は1を返します。
もし、山Aに3個、B,Cに石が0個の時、Aから3個の石を取ったらどうでしょうか。 プレーヤーは、Aから2個石を取れば勝つのに、あえて3個の石を取るという行為をしない、という 暗黙のルールを作らなくてはいけません。
そうしなければ勝負がつきません。コンピュータが石を取るときも、このことを念頭に置きます。 人間が取る場合、間違って3個石を取ろうとした場合は、注意する仕組みも作る必要がありそうです。
int Mountain::take_stone() { char ans[8]; int stone, mtorder; if (sente != order) { comp_take(); if (judge() == 0) { cout << "コンピュータの勝ちです" << endl; return 1; } if (order == 1) order = 0; else order = 1; return 0; } while (1) { cout << "どの山からとりますか(A,B,C)---"; cin >> 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) { cout << "山の指定が正しくありません" << endl; continue; } else { ans[0] = toupper(ans[0]); mtorder = ans[0] - 'A'; if (mt[mtorder] == 0) { cout << "その山にはもう石はありません" << endl; continue; } break; } } while (1) { cout << "いくつとりますか---"; cin >> ans; stone = atoi(ans); mt[mtorder] -= stone; if (mt[0] + mt[1] + mt[2] == 0) { cout << "その取り方では山の石が全部0になります" << endl; mt[mtorder] += stone; continue; } mt[mtorder] += stone; if (mt[mtorder] - stone < 0 || stone <= 0) { cout << "取る石の数が不正です" << endl; continue; } else { break; } } mt[mtorder] -= stone; show_stone(); if (judge() == 0) { cout << "あなたの勝ちです" << endl; return 1; } if (order == 1) order = 0; else order = 1; return 0; }石を取る関数です。
senteとorderの値が異なる場合は、コンピュータの取る番です。 そのような時は、comp_take関数を呼んで、コンピュータに石を取らせます。 コンピュータが石を取った後で、judgeメンバ関数を呼んで、勝敗がついたかどうかを調べます。 勝敗がついたのなら、コンピュータの勝ちなのでその旨表示してreturnします。
勝敗がつかない場合は、orderを反転して(1なら0に、0なら1にして)returnします。
senteとorderが一致する場合は、人間の番なので、まず、どの山から取るかをたずねます。
この時、プレーヤーは「A,B,C,a,b,c」のいずれかを指定しなくてはいけません。
これ以外の文字を指定すると、continueでループの最初に戻されます。
ただし、「aZ」などと2文字以上で答えることも出来ますが、この場合は先頭文字のみが 比較の対象となります。
さて、山を指定された場合、Aまたはaの山の石の個数はmt[0]です。Aを0に関連づけるには どうすればよいのでしょうか。これは、簡単です。プログラムを見てください。小文字を 指定されたときのためにtoupper関数で大文字にしておいてから
mtorder = ans[0] - 'A'
としています。ans[0]が'A'ならmtorderは0,'B'なら1、'C'なら2となります。
次に、その山からいくつ取るかをたずねます。もし、その通取って、すべての山が0になってしまっては 勝負がつかないので、そのチェックをします。
また、取る石の数が0または負であってはいけません。さらに、山の数より多い石はとれません。 これらのチェックもします。
その後、石山の数を最新のものに変更します。
次に、show_stoneメンバ関数関数を呼んで、各山の石の個数を表示します。
その後、judgeメンバ関数で勝敗の判定をします。
勝敗がつかなければ、orderを反転してreturnします。
int Mountain::init() { char ans[8]; mt[0] = 3; mt[1] = 5; mt[2] = 7; cout << "あなたが先手になりますか(Y/N)---"; cin >> ans; if (ans[0] == 'y' || ans[0] == 'Y') { sente = 1; } else { sente = 0; } order = 1; return 0; }ゲームを初期化するメンバ関数です。
山の石の数をそれぞれ、3,5,7にします。
先攻、後攻を決めます。
orderを1に設定します。
int Mountain::comp_take() { int mtorder, stone; char mtname; srand((unsigned)time(NULL)); while (1) { mtorder = rand() % 3; if (mt[mtorder] == 0) continue; else break; } while (1) { stone = rand() % (mt[mtorder]) + 1; mt[mtorder] -= stone; if (mt[0] + mt[1] + mt[2] == 0) { mt[mtorder] += stone; continue; } else { break; } } mtname = 'A' + mtorder; cout << "コンピュータは" << mtname << "から" << stone << "個取りました" << endl; show_stone(); return 0; }コンピュータが、石を取ります。
必勝法は使わずに、乱数で適当に石を取ります。
まず、srand関数で、乱数の種をまきます。 srand関数についてはC言語編第24章で 出てきています。
最初にどの山から取るかを乱数で決めます。 rand関数は、0からRAND_MAXまでのint型整数を返すので、これを3で割り余りを 調べます。あまりは、0,1,2のいずれかですので、山の指定になります。
もし、その山にすでに石がないときは、continueでループの最初に戻り、やり直します。
山が決定したら、取る石の個数を決めます。 やは、rand関数で乱数を発生させて、これをmt[x]でわります。余りは0からmt[x]-1のいずれかなので これに1を加えた数を取る石の個数とします。
この時、すべての山に石がなくなってしまっては勝負にならないので、これをチェックします。 もし、引っかかるようならcontinueでループの最初に戻りやり直します。
取る石の個数が決定したら、show_stoneメンバ関数で各山の石の数を表示します。
int Mountain::play() { int ret; char ans[8]; while (1) { init(); show_stone(); while (1) { ret = take_stone(); if (ret == 1) break; } cout << endl << "もう一度やりますか(Y/N)"; cin >> ans; if (ans[0] == 'y' || ans[0] == 'Y') continue; else break; } return 0; }ゲームを取り仕切る(?)関数です。
無限ループに入ります。
ゲームを初期化して、石の数を表示したら、次の無限ループに入ります。
take_stoneメンバ関数を呼び、石を取ります。 勝敗がつくまで、take_stoneメンバ関数を呼びます。
勝敗がついたら、再度ゲームをするかどうかたずね、再度ゲームをするなら 最初のinitメンバ関数の所に戻ります。やめるなら、breakでループを抜けて プログラムを終了します。
int main() { Mountain m; m.play(); return 0; }メイン関数です。これは、たったこれだけです。 もっと短くしたい場合は、playメンバ関数をコンストラクタで呼び出せばよいのですが、 これでは何がなんだかわからなくなるので、この程度にしておきました。
どうやったらこのゲームに勝てるのか、考えてみてください。
Update Mar/22/2002 By Y.Kumei