6

RusselNorvigの人工知能に関する本に記載されている三目並べのミニマックスアルゴリズムをコーディングしようとしました。bestMoveをユーザーに返す方法を除いてすべてがありました。bestMoveを返そうと努力していますが、いつbestMoveを選択するかを決めることができません。助けて、誰か?

moveT MiniMax(stateT state)
{
    moveT bestMove;

    max_move(state,bestMove);

    return bestMove;

}

int max_move(stateT state,int & bestMove)
{
    int v = -10000;
    if(GameIsOver(state))
    {
        return EvaluateStaticPosition(state);

    }

    vector<moveT> moveList;
    GenerateMoveList(state, moveList);
    int nMoves = moveList.size();

    for(int i = 0 ; i < nMoves ; i++)
    {
        moveT move = moveList[i];
        MakeMove(state, move);

        int curValue = min_move(state,bestMove);

            if(curValue > v)
            {
              v = curValue;
              bestMove = move;
            }
        RetractMove(state, move);

    }

    return v;

}

int min_move(stateT state, int &bestMove)
{
    int v = 10000;
    if(GameIsOver(state))
    {
      return EvaluateStaticPosition(state);

    }
    vector<moveT> moveList;
    GenerateMoveList(state, moveList);

    int nMoves = moveList.size();

    for(int i = 0 ; i < nMoves; i++)
    {
        moveT move = moveList[i];
        MakeMove(state, move);

        int curValue = max_move(state,depth+1,bestMove);

            if(curValue < v)
            {
              curValue = v;
            }
        RetractMove(state, move);

    }
    return v;
}

PS:minmax値を見つけるための他の擬似コードがあります。ただし、三目並べのみに焦点を当てているため、他のゲームにも拡張しようとしています。ありがとう。

更新:コード全体はここにあります:http://ideone.com/XPswCl

4

3 に答える 3

7

ミニマックスの最も単純なバージョンでは、最初のプレーヤーは自分のスコアを最大化したいと考え、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に何も代入することはないため、ブロック内で代入する必要があります。MaxMoveMinMovebestMoveopponentsBestMovebestMoveMinMoveif(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;
}
于 2012-12-28T14:34:20.553 に答える
4

MiniMaxまあ、それはあなたのためにそれを正しく選択しているように見えます.初期状態と深さでそれを呼び出すだけです. (状態による最初のプレーヤーが 2 番目のプレーヤーでない限り、MiniMax で min_move を呼び出す必要があります。)

編集: はい、私は何かを見落としていました。現在、bestMove はあまり意味がありません。max_move 内のプログラムでは、次のようにループを変更します。

for(int i = 0 ; i < nMoves ; i++)
{
    moveT move = moveList[i];
    MakeMove(state, move);

    int new_value = min_move(state, depth+1);
    if(new_value > v)
    {
      v=new_value;
    }
    RetractMove(state, move);

}

その後、あなたのことを考えることができます bestMove は何を意味しますか? 私の考えでは、あなたは三目並べの「可能な限り最高の」一連の動きの 1 つを見つけることに興味があるということです。そのためには vector 、さらにはstackが必要です。しかし、それはまたstd::stack<int>* best_moves、最後のパラメーターとして持つことを意味します。

スタックの実装では、min_move で次の動きを返し、それらの値が最高の場合move、スタックの一番上にプッシュしbest_movesます。もちろん、ゲームの最後に空のスタックを返すだけです。それを適切にやってのけるには OOP アプローチが必要です。時間があるときに実行します。

必要なのは次の最良の動きだけである場合は、min_move と max_moe の戻り値の型を次のような構造体に変更することをお勧めします。

struct Value_move{
  int value;
  moveT best_move;
};

次に、max_move の新しい実装は次のようになります。

const int MOVE_INVALID = -12345;
const int MOVE_NOTHING = -12346;

Value_move max_move(stateT state, int depth)
{
    Value_move best;
    best.value = -10000; best.best_move = MOVE_INVALID;

    if(GameIsOver(state))
    {
        best.value = EvaluateStaticPosition(state);
        best.best_move = MOVE_NOTHING;
        return best;
    }

    vector<moveT> moveList;
    GenerateMoveList(state, moveList);
    int nMoves = moveList.size();

    for(int i = 0 ; i < nMoves ; i++)
    {
        moveT move = moveList[i];
        MakeMove(state, move);
        Value_move curr = min_move(state, depth+1);
        if(curr.value > best.value)
        {
            best.value = curr.value;
            best.best_move = move;
        }
        RetractMove(state, move);

    }

    return v;

}

MiniMax 関数で返された構造体の best_move フィールドを取得するだけで済みます。

注意:
これは多くの点で c++ プログラムに似ていませんが、ac プログラムに似ていることは認めざるを得ません。それ以外の場合、CapitalCamelCase のすべての関数はクラス メソッドである必要があり、値ではなく (const) ref で状態を渡す必要があります。ただし、このコード全体が意味をなすのは、ステータスが実際に typedef の背後にあるポインターである場合のみです。

于 2012-11-23T05:46:09.960 に答える
0

コードは正しい値を見つけますが、同じ参照を渡すことで上書きします。

int curValue = min_move(state,bestMove);

なるべき

moveT nextMove; // No need to actually do anything with this value
int curValue = min_move(state,nextMove);

min_move 関数にも同じ種類の変更を加える必要があります。

注意:関数に対して定義したよりも多くの引数を使用しmin_moveてコード呼び出しを行います。max_move

于 2012-12-27T06:42:46.553 に答える