A way to approach this problem is to study possible futures. Usually (for chess or AI drafts) you consider futures a certain number of moves forward, but because games with tic tac toe are so short, you can research until the end of the game.
Conceptual implementation
So you create a branch structure:
- AI imagines taking every legal step
- The AIs imagine the user making every legal move that they can make after each of his legal moves.
- AI then presents each of its next legal steps.
- and etc.
Then, moving from the most branched end (farthest forward), the player whose turn (AI or user) chooses which future is best for him (to win, lose or draw) at each branch point. Then he passes the player above the tree (closer to the present); each time choosing a better future for the player whose imaginary turn is until, finally, you find yourself at the first branch point, where the AI ββcan see futures that lose to it, losing, drawing and winning. He chooses a future in which he wins (or if no draws are available).
Actual implementation
Note that conceptually this is what happens, but you donβt need to create the whole tree, and then judge like that. You can work just as easily, although the tree gets to the most distant points of time and then chooses.
Here, this approach works well with a recursive function. Each level of a function checks all its branches; passing them a possible future and returns -1.0, + 1; choosing the best score for the current player at each point. The highest level chooses a move, without actually knowing how each future rolls out, how well they come out.
Pseudo code
I assume in this pseudocode that +1 is AI gain, 0 is drawing, -1 is user loss
determineNextMove(currentStateOfBoard) currentBestMove= null currentBestScore= - veryLargeNumber for each legalMove score=getFutureScoreOfMove(stateOfBoardAfterLegalMove , AI'sMove) if score>currentBestScore currentBestMove=legalMove currentBestScore=score end end make currentBestMove end getFutureScoreOfMove(stateOfBoard, playersTurn) if no LegalMoves return 1 if AI wins, 0 if draw, -1 if user wins end if playersTurn=AI'sTurn currentBestScore= - veryLargeNumber //this is the worst case for AI else currentBestScore= + veryLargeNumber //this is the worst case for Player end for each legalMove score=getFutureScoreOfMove(stateOfBoardAfterLegalMove , INVERT playersTurn) if playersTurn ==AI'sTurn AND score>currentBestScore //AI wants positive score currentBestScore=score end if playersTurn ==Users'sTurn AND score<currentBestScore //user wants negative score currentBestScore=score end end return currentBestScore end
This pseudo-code does not care what the initial board is (you call this function each time the AI ββmoves with the current board) and does not return which path it will take in the future (we cannot know if the user will play optimally, therefore this information is useless), but she will always choose the step that goes towards an optimal future for AI.
Considerations for big tasks
In this case, when you research until the end of the game, it is obvious that the best future is possible (win, lose or attract), but if you are only going to (for example) five movements in the future, you will have to find a way to determine this; in a checkerboard or rough piece is the easiest way to do this when a piecewise position is a useful addition.