0

i want to code a recursive function to determine the best move in a given tic - tac - toe game

int nextMove(struct Game g, enum Symbol player) {
if (game_over(g) != 0) {
return -1;
}
int i, j;
int score, end;
for(i = 0; i < 3; i += 1) {
    for (j = 0; j < 3; j += 1) {
        if(g.board.fields[i][j] == 0) {
            g.board.fields[i][j] = player;
            score = nextMove(g, -player);
        }
    }
}
end = game_over(g)
}

My game_over function :

enum Symbol game_over(struct Game g)
{
int x, y = x = 0;
int full = 0;

for (y = 0; y < SIZE_Y_AXIS; y += 1)
{
    for (x = 0; x < SIZE_X_AXIS; x += 1)
    {
        if (g.board.fields[x][y] == NONE)
            full++;
        else if (x < SIZE_X_AXIS - 2 &&
                 g.board.fields[x][y] == g.board.fields[x+1][y] &&
                 g.board.fields[x][y] == g.board.fields[x+2][y])
            return g.board.fields[x][y];
        else if (y < SIZE_Y_AXIS - 2 &&
                 g.board.fields[x][y] == g.board.fields[x][y+1] &&
                 g.board.fields[x][y] == g.board.fields[x][y+2])
            return g.board.fields[x][y];
        else if (x < SIZE_X_AXIS - 2 && y < SIZE_Y_AXIS - 2 &&
                 g.board.fields[x][y] == g.board.fields[x+1][y+1] &&
                 g.board.fields[x][y] == g.board.fields[x+2][y+2])
            return g.board.fields[x][y];
        else if (x >= 2 && y < SIZE_Y_AXIS - 2 &&
                 g.board.fields[x][y] == g.board.fields[x-1][y+1] &&
                 g.board.fields[x][y] == g.board.fields[x-2][y+2])
            return g.board.fields[x][y];
    }
}
if (full == 0)
    return FULL;
return NONE;
}

I think the leaf production works fine, but i don't know how to determine which leaf (or which move) is the best one? Any suggestions by now?

Thanks

MrShow
  • 13
  • 5
  • 1
    you have to chose the minim or maxim of the children. You can do with 2 functions wich call one another. – bolov Jan 20 '15 at 22:47
  • Can you give some advise how to do that? – MrShow Jan 20 '15 at 23:05
  • You are not the first to ask: http://stackoverflow.com/questions/125557/what-algorithm-for-a-tic-tac-toe-game-can-i-use-to-determine-the-best-move-for http://stackoverflow.com/questions/6240995/tic-tac-toe-c-algorithm-debugging-help http://stackoverflow.com/questions/21100216/minimax-algorithm-for-tic-tac-toe-failure – Misunderstood Jan 20 '15 at 23:06
  • c is not equal to c++ – MrShow Jan 20 '15 at 23:14
  • @bolov is right about minmax. Look for the min when the player is of one sign and look for the max when the player is of the other sign and return that score. – chux - Reinstate Monica Jan 21 '15 at 00:21

1 Answers1

5

In a game like tic-tac-toe, the search tree is so small that all leaf nodes can be evaluated (unlike in a game like chess or Go where the search is cut short). Since all nodes are examined, the leaf nodes can be evaluated by checking if they are a win, lose, or draw. You can use 1,-1, and 0 respectively to represent these values. Once you do that, return the value of the node to its parent. For non-leaf nodes, it must choose either the highest value of its children, or the lowest, depending on if it is a max node (the computer) or min node (its opponent).This will back up the tree all the way to the root, giving it a value for all its possible moves. The move at the root of the tree with the highest value is the best move for that position This is the minimax algorithm. Additionally, in the code sample you provided, you failed to check if the game was over before all fields are full. Instead, check if a player has already gotten three in a row, as then the game is already over. Note:Your nextMove function claims to return an int but doesn't in most cases. That has to be fixed. Here is what I would add to your code (the added parts in psuedo-code). I am not sure exactly what the game_over function does, so I can't be sure of what the exact code should be. Therefore, I am going to take it out and add psuedocode to take its place.

int nextMove(struct Game g, enum Symbol player) {
    if (computerWon) {
    return 1;
    }
    if (OpponnentWon) {
    return -1;
    }
    if (allSquaresAreFull) {
    return 0;
    }
    int i, j;
    int bestScore;//use this to calculate which move to return
    int score, end;
    //now check whose turn it is
    if(player == 1){//I'm assuming this is the computer player
        bestScore = -100;//start with an arbitrary low value 
        for(i = 0; i < 3; i += 1) {
            for (j = 0; j < 3; j += 1) {
                if(g.board.fields[i][j] == 0) {
                    g.board.fields[i][j] = player;
                    score = nextMove(g, -player);
                    g.board.fields[i][j] = 0;//make sure to undo every move made in the search
                    if(score > bestScore){//a better move for this player
                         bestScore = score;//update bestScore
                    }
               }
           }
        }
        return bestScore;//return best move to parent;

    }
    if(player == -1){//I'm assuming this is the opponent
        bestScore = 100;//start with an arbitrary high value 
        for(i = 0; i < 3; i += 1) {
            for (j = 0; j < 3; j += 1) {
                if(g.board.fields[i][j] == 0) {
                    g.board.fields[i][j] = player;
                    score = nextMove(g, -player);
                    g.board.fields[i][j] = 0;//make sure to undo every move made in the search
                    if(score < bestScore){//a better move for the opponent is a lower move
                         bestScore = score;//update bestScore
                    }
               }
           }
        }
        return bestScore;//return best move to parent;

    }
end = game_over(g)//this variable is never used. Not sure what it does. I would remove it, it seems useless
}

This should return the value of the position(if it is won, lost, or drawn). To determine which move to take, this would have to be slightly modified. Add a test to see if we are in the first time nextMove is called (ie the root) if we are, instead of keeping track of ony bestMove, which is the value of the move, also keep track of what the actual best move is (perhaps in a move struct). Return that instead if best move. Now nextMove will return which move to take.

chessprogrammer
  • 768
  • 4
  • 15
  • My code fills the the board with Circles(1) and Crosses(-1). I also determine which player won by the "end = game_over(g)". But I really don't know how to do the second part of your test. Returning value of the child to the parent?! – MrShow Jan 21 '15 at 07:21
  • Why do you want to return: if (computerWon) { return 1; } if (OpponnentWon) { return -1; } if (allSquaresAreFull) { return 0; } – MrShow Jan 22 '15 at 14:40
  • I want to return the move, but you return "The Winner"? – MrShow Jan 22 '15 at 14:40
  • 1 means that the computer won. -1 means it lost, and 0 means tie – chessprogrammer Jan 23 '15 at 02:59
  • for efficiency, you only return the value of the move, not the move itself, unless it is the root node. By the root, you keep track of the bestvalue and bestmove, and return the bestmove. There is no reason to keep track of the move when we don't need to. – chessprogrammer Jan 23 '15 at 03:03
  • @chessprogrammer it is 3 years later and frankly, your code tho in C is one that made much sense. If you are still active, what do 100, -100 mean? My current status returns 100 (computer is playing) but I don't know what to do with that 100 now. I know I have to block on square or else the opponent will win. I know the last move the opp made. (1,0). I need to fill 0,0 or he will win now cos 0,2 is filled by him as well. – Nie Selam Nov 21 '18 at 02:59
  • @NieSelam It is the basic technique for trying to find the highest value in a list of values. Take the maximizing player. We want to find the move with the highest value. So we initialize bestvalue as a number lower than any possible real value, and every time we encounter a value higher than bestvalue we update bestvalue to reflect that. Your function should not actually be returning 100 or -100. If it is, that likely means you are not properly exploring all moves, as otherwise that number should be replaced by an actual move value. – chessprogrammer Nov 22 '18 at 04:38