2012-11-23 13 views
6

Ich habe versucht, die Minimax-Algorithmus für Tic-Tac-Toe in Russel Norvig Buch über künstliche Intelligenz zu kodieren. Es hatte alles außer der Möglichkeit den bestMove an den Benutzer zurückzugeben. Ich bemühe mich sehr, den bestMove zurückzugeben, kann mich aber nicht entscheiden, wann ich den bestMove wählen soll. Hilfe, irgendjemand?Return bestMove in Minimax-Algorithmus für Tictactoe

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; 
} 

S.S .: Es gibt andere Pseudocode zum Finden des Minmax-Wertes. Allerdings konzentrieren sie sich nur auf Tic-Tac-Toe, ich versuche, es auf andere Spiele auszudehnen. Vielen Dank.

Update: Der gesamte Code ist hier zu finden: http://ideone.com/XPswCl

+0

Ist der Code, den Sie oben gepostet haben, aktuell? Weil es nicht so aussieht, als müsste es kompilieren. In 'min_move' rufen Sie' max_move' mit drei Argumenten auf, aber max_move kann nur zwei Argumente annehmen. – Kevin

+0

@Kevin: Ups, es ist jetzt aktualisiert. Ich habe versucht, die Tiefe irgendwann zu begrenzen. – motiur

+0

Danke für die Aktualisierung, aber die falsche Zeile ist immer noch da: 'int curValue = max_move (state, depth + 1, bestMove);' Das macht mir Sorgen; Das lässt mich vermuten, dass der Code, den Sie veröffentlichen, nicht der Code ist, den Sie kompilieren. Das macht es für potenzielle Beantworter doppelt schwierig, das Problem zu finden. Wir werden Bugs im geposteten Code identifizieren, die nicht im echten Code enthalten sind, und wir werden keine Fehler im echten Code finden, wenn sie nicht im geposteten Code enthalten sind. – Kevin

Antwort

7

In der einfachsten Version von Minimax möchte der erste Spieler seine Punktzahl maximieren, und der zweite Spieler möchte die Punktzahl des ersten Spielers minimieren. Da sowohl der erste als auch der zweite Spieler sich nur um die Punktzahl des ersten Spielers kümmern, sollte EvaluateStaticPosition einen Wert zurückgeben, der angibt, wie gut der Spielstand für den ersten Spieler ist. Wessen Drehung es ist, ist nicht relevant.

int EvaluateStaticPosition(stateT state) 
{ 
     if(CheckForWin(state, FIRST_PLAYER)) 
     { 
       return WINNING_POSITION; 
     } 
     if(CheckForWin(state, Opponent(FIRST_PLAYER))) 
     { 
       return LOSING_POSITION; 
     } 
     return NEUTRAL_POSITION; 
} 

Jetzt, wenn Sie den Zug wünschen, der für den ersten Spieler am besten ist, rufen Sie MaxMove. Wenn du den richtigen Zug für den zweiten Spieler willst, rufe MinMove an.

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; 
} 

Schließlich haben Sie einige Probleme innerhalb von MinMove und MaxMove. Wenn Sie curRating in beiden zuweisen, sollten Sie bestMove nicht als zweites Argument an MaxMove oder MinMove übergeben. Es wird dann die Gegner besten Umzug in bestMove setzen, die keinen Sinn macht. Deklarieren Sie stattdessen ein opponentsBestMove-Objekt und übergeben Sie das als zweites Argument. (Sie werden das Objekt tatsächlich nicht benutzen oder nachsehen, aber das ist ok). Mit dieser Änderung weisen Sie bestMove innerhalb MinMove niemals etwas zu, also sollten Sie dies innerhalb des if(curRating < v) Blocks tun.

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; 
} 

An diesem Punkt sollten Sie eine unschlagbare KI haben!

The final position looks like this: 

O | O | X 
---+---+--- 
X | X | O 
---+---+--- 
O | X | X 

Cat's game. 

Ein alternatives Verfahren sich die Tatsache zunutze, dass nimmt Tic-Tac-Toe-Spiel ein Nullsummen ist. Mit anderen Worten, am Ende des Spiels ist die Summe der Punktzahlen der Spieler gleich Null. Für ein Zwei-Spieler-Spiel bedeutet dies, dass die Punktzahl eines Spielers immer die des anderen Spielers ist. Dies ist für uns praktisch, da das Minimieren der Punktzahl des anderen Spielers dann identisch ist mit der Maximierung des eigenen Spielstandes. Anstatt dass ein Spieler seine Punktzahl maximiert und ein Spieler den Punktestand des anderen Spielers minimiert, können wir einfach beide Spieler versuchen, ihren eigenen Punktestand zu maximieren.

Ändern Sie EvaluateStaticPosition zurück in seine ursprüngliche Form, so dass es eine Punktzahl basierend darauf gibt, wie gut der Boardstatus für den aktuellen Spieler ist.

Löschen MinMove, da wir nur auf Maximierung achten. Schreiben Sie MaxMove so um, dass es den Zug auswählt, der dem Gegner die schlechteste Punktzahl gibt. Die Punktzahl für den besten Zug ist das Negativ des schlechtesten Ergebnisses des anderen Spielers.

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; 

} 

Da MaxMove für beide Spieler verwendet wird, müssen wir nicht mehr unter den Spielern in der Funktion MiniMax unterscheiden.

moveT MiniMax(stateT state) 
{ 
    moveT bestMove; 
    int i = 0; 
    i = MaxMove(state, bestMove); 
    cout<<"i is "<<i<<endl; 
    return bestMove; 
} 
+0

Wenn ich mich nicht irre, haben Sie bestMove = in den MinMove absichtlich verschoben, wenn Sie motiur

+0

Ja, ich habe absichtlich "bestMove = move" in die 'MinMove'-Funktion innerhalb des' curRating Kevin

+0

Mann, der funktioniert, ich wünschte, ich könnte dich umarmen; wo auch immer du bist, danke. Lass mich weiter testen, warte. – motiur

4

Nun, es sieht aus wie MiniMax richtig es für Sie wählt, ist es einfach anrufen mit einem Anfangszustand und einer Tiefe. (Es sei denn der erste Spieler nach dem Staat ist der zweite Spieler, dann sollten Sie min_move in MiniMax anrufen.)

EDIT: ja, ich habe etwas übersehen, bestMove macht derzeit nicht viel Sinn. Im Programm innerhalb max_move Sie die Schleife wie folgt ändern:

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); 

} 

Danach kann man über Sie denken können, was bestMove bedeutet? Meine Idee ist, dass Sie daran interessiert sind, eine der "bestmöglichen" Bewegungsserien für Tic-Tac-Toe zu finden. Dafür brauchst du einen Vektor oder besser noch einen stack. Das heißt aber auch, dass std::stack<int>* best_moves der letzte Parameter ist.

Für die Stapelimplementierung, in min_move, geben Sie die nächsten Züge zurück, und wenn ihr Wert der beste ist, werden Sie Ihre move oben auf den best_moves Stapel schieben. Natürlich gibst du am Ende des Spiels einfach den leeren Stapel zurück. Es braucht einen OOP-Ansatz, um es richtig abzuwickeln, ich werde es tun, wenn ich etwas Zeit habe.

Wenn alles, was Sie brauchen, ist nur der beste nächste Schritt dann empfehle ich Ihnen die Rückgabetypen min_move und max_moe wie dies bis zu einem gewissen Struktur ändern:

struct Value_move{ 
    int value; 
    moveT best_move; 
}; 

Dann wird die neue Implementierung von max_move wie das aussieht folgende:

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; 

} 

Sie nur das best_move Feld in der zurückgegebenen Struktur in der MiniMax-Funktion abholen müssen.

BEMERKUNG:
Sie müssen zugeben, obwohl dies in vielen Aspekten nicht ein C++ - Programm ähnelt, sondern ein c-Programm. Ansonsten sollten alle Funktionen in CapitalCamelCase Klassenmethoden sein, Sie sollten die Zustände durch (const) ref anstelle von value übergeben - aber dieser ganze Code macht nur Sinn, wenn der Status wirklich ein Zeiger hinter einem typedef ist.

+2

... aber cout und Vektoren machen keinen Sinn als C-Code, nur C++. – Mike

+0

wahr, korrigiert, sorry, +1. Ich hoffe jedoch, dass wir uns hier auf schlechte Praktiken für ein C++ Programm einigen. Dieser Code liegt irgendwo dazwischen. Es begann auch an einigen Stellen Referenzen zu verwenden. :) –

+0

@BarnabasSzabolcs Ich bin besorgt über curValue> v, ist es zu Recht in der for-Schleife platziert. curValue ist nicht initialisiert, es ist nicht möglich, dass es größer als v ist, der maximale Wert, der bisher erhalten wurde, sagen wir +10. Wie kann ich den Code so ändern, dass der 'curValue' v = max (v, min_move (state, depth + 1, bestMove)) darstellt und auch den bisher besten Wert mit curValue speichern und vergleichen kann. Ich bin hier ein wenig verschwommen. – motiur

0

Ihr Code findet den korrekten Wert, überschreibt ihn jedoch, indem er den gleichen Verweis nach unten gibt.

int curValue = min_move(state,bestMove); 

soll

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

Sie müssen auch die gleiche Art von Veränderung in Ihrer min_move Funktion zu machen.

Hinweis: In min_move ruft Ihr Code max_move mit mehr Argumenten auf, als Sie für die Funktion definiert haben.