ミニマックスの最も単純なバージョンでは、最初のプレーヤーは自分のスコアを最大化したいと考え、2 番目のプレーヤーは最初のプレーヤーのスコアを最小化したいと考えています。最初のプレーヤーと 2 番目のプレーヤーの両方が最初のプレーヤーのスコアのみを気にするEvaluateStaticPosition
ため、最初のプレーヤーのボードの状態がどれだけ良好かを示す値を返す必要があります。誰の番かは関係ありません。
int EvaluateStaticPosition(stateT state)
{
if(CheckForWin(state, FIRST_PLAYER))
{
return WINNING_POSITION;
}
if(CheckForWin(state, Opponent(FIRST_PLAYER)))
{
return LOSING_POSITION;
}
return NEUTRAL_POSITION;
}
ここで、最初のプレーヤーに最適な動きが必要な場合は、MaxMove を呼び出します。2 番目のプレーヤーに最適な動きが必要な場合は、MinMove を呼び出します。
moveT MiniMax(stateT state)
{
moveT bestMove;
int i = 0;
if (state.whoseTurn == FIRST_PLAYER){
i = MaxMove(state, bestMove);
}
else{
i = MinMove(state,bestMove);
}
cout<<"i is "<<i<<endl;
return bestMove;
}
最後に、 と の内部にいくつかの問題がMinMove
ありMaxMove
ます。curRating
いずれかを代入する場合、 orbestMove
の 2 番目の引数として渡すべきではありません。その後、相手のベスト ムーブを に入れますが、これは意味がありません。代わりに、オブジェクトを宣言し、それを 2 番目の引数として渡します。(実際にオブジェクトを使用したり、後でその値を確認したりすることはありませんが、問題ありません)。その変更により、 withinに何も代入することはないため、ブロック内で代入する必要があります。MaxMove
MinMove
bestMove
opponentsBestMove
bestMove
MinMove
if(curRating < v)
int MaxMove(stateT state, moveT &bestMove)
{
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
int v = -1000;
for(int i = 0 ;i<nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
moveT opponentsBestMove;
int curRating = MinMove(state, opponentsBestMove);
if (curRating > v)
{
v = curRating;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
int MinMove(stateT state, moveT &bestMove)
{
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT>moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
int v = 1000;
for(int i = 0 ; i<nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state , move);
moveT opponentsBestMove;
int curRating = MaxMove(state,opponentsBestMove);
if(curRating < v)
{
v = curRating;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
この時点で、無敵の AI を手に入れることができます。
The final position looks like this:
O | O | X
---+---+---
X | X | O
---+---+---
O | X | X
Cat's game.
別の方法では、三目並べがゼロサム ゲームであるという事実を利用します。つまり、ゲームの終了時に、プレイヤーのスコアの合計はゼロになります。2 人用ゲームの場合、これは一方のプレイヤーのスコアが常に他方のプレイヤーのマイナスになることを意味します。他のプレイヤーのスコアを最小化することは、自分のスコアを最大化することと同じであるため、これは便利です。したがって、1 人のプレーヤーが自分のスコアを最大化し、1 人のプレーヤーが他のプレーヤーのスコアを最小化する代わりに、両方のプレーヤーが自分のスコアを最大化しようとするだけです。
元のフォームEvaluateStaticPosition
に戻して、現在のプレイヤーにとってボードの状態がどれだけ良好であるかに基づいてスコアを与えるようにします。
int EvaluateStaticPosition(stateT state)
{
if(CheckForWin(state, state.whoseTurn))
{
return WINNING_POSITION;
}
if(CheckForWin(state, Opponent(state.whoseTurn)))
{
return LOSING_POSITION;
}
return NEUTRAL_POSITION;
}
MinMove
最大化だけに関心があるため、 を削除します。MaxMove
対戦相手に最悪のスコアを与える動きを選択するように書き換えます。最良の手のスコアは、他のプレイヤーの最悪のスコアのマイナスです。
int MaxMove(stateT state, moveT &bestMove)
{
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
int v = -1000;
for(int i = 0 ;i<nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
moveT opponentsBestMove;
int curRating = -MaxMove(state, opponentsBestMove);
if (curRating > v)
{
v = curRating;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
は両方のプレイヤーに使用されるため、関数MaxMove
内でプレイヤーを区別する必要がなくなりました。MiniMax
moveT MiniMax(stateT state)
{
moveT bestMove;
int i = 0;
i = MaxMove(state, bestMove);
cout<<"i is "<<i<<endl;
return bestMove;
}