在Tic-Tac-Toe游戏中获得MiniMax算法的最佳移动

时间:2015-05-06 12:38:05

标签: c tic-tac-toe minimax

我正在尝试在用C编写的Tic-Tac-Toe游戏中实现基于Wikipedia pseudocode的MiniMax算法。但是,我无法获得最佳移动。这是我的代码:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

// compile and run: gcc minimax.c -std=c99 && ./a.out

int max(int x, int y) {
  return x > y ? x : y;
}

int min(int x, int y) {
  return x < y ? x : y;
}

int whoWon(char ch) {
  switch (ch) {
    case 'O':
      return -1;
      break;
    case 'X':
      return 1;
      break;
  }
}

void printArray(char array[]) {
  printf("# START\n"
         "%c | %c | %c\n"
         "--|---|--\n"
         "%c | %c | %c\n"
         "--|---|--\n"
         "%c | %c | %c\n"
         "# END\n\n", array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8]);
}

int anyWinners(char board[])
{
  int i;

  /* check every row */
  for(i = 0; i < 7; i += 3)
    if(board[i] != ' ' && board[i] == board[i+1] && board[i] == board[i+2])
      return whoWon(board[i]);

  /* check every column */
  for(i = 0; i < 3; i++)
    if(board[i] != ' ' && board[i] == board[i+3] && board[i] == board[i+6])
      return whoWon(board[i]);

  /* check diagonals */
  if(board[4] != ' ' && ((board[0] == board[4] && board[0] == board[8]) || (board[2] == board[4] && board[2] == board[6])))
    return whoWon(board[4]);

  return 0;
}

int fullBoard(char board[]) {
  for (int i = 0; i < 9; ++i) {
    if (board[i] == ' ')
      return 0;
  }
  return 1;
}

int minimax(char node[], int depth, bool maximizingPlayer, int * move) {
  int terminalNode = anyWinners(node);
  if (depth == 0 || terminalNode || fullBoard(node)) {
    printf("################## END OF SUBTREE ##################\n");
    return terminalNode;
  }

  int bestValue, val;
  if (maximizingPlayer) {
    bestValue = -2;
    for (int i = 0; i < 9; ++i) {
      if (node[i] == ' ') {
        char child[9];
        strcpy(child, node);
        child[i] = 'X';

        // debug
        printArray(child);

        val = minimax(child, depth - 1, false, move);

        // debug
        printf("X: ^^ i = %d ^^ depth = %d ^^ val = %d\n", i, depth, val);

        //bestValue = max(bestValue, val);
        if (val > bestValue) {
          bestValue = val;
          if (depth == 9) *move = i;
        }
      }
    }
    return bestValue;
  } else {
    bestValue = 2;
    for (int i = 0; i < 9; ++i) {
      if (node[i] == ' ') {
        char child[9];
        strcpy(child, node);
        child[i] = 'O';

        // debug
        printArray(child);

        val = minimax(child, depth - 1, true, move);

        // debug
        printf("O: ^^ i = %d ^^ depth = %d ^^ val = %d\n", i, depth, val);

        bestValue = min(bestValue, val);
      }
    }
    return bestValue;
  }
}

int main() {
  int move = -999; // initialize only for debug

  // X will always win no matter what, first best move for X is 8
  // char board[] = {'O', ' ', ' ',
  //                 ' ', ' ', ' ',
  //                 'X', 'X', ' '};

  // best move for X is 3
  char board[] = {'O', 'O', ' ',
                  ' ', 'X', 'X',
                  ' ', ' ', ' '};

  // Initial call for maximizing player
  int result = minimax(board, 9, true, &move);
  printf("minimax returned: %d\n", result);
  printf("chosen move: %d\n", move);

  return 0;
}

代码使用所有变量的状态为每次移动打印板。主要还有两个失败的测试。现在算法返回坏动作,我找不到错误。

1 个答案:

答案 0 :(得分:2)

我看到两个问题:

  1. 启发式错误
  2. strcpy存在问题。
  3. 启发式错误

    维基百科的伪代码说:

    if depth = 0 or node is a terminal node
        return the heuristic value of node
    

    您的实施是这样做的:

    if depth = 0 or node is a terminal node
        return 1 if X wins, -1 if O wins, 0 if it is a draw
    

    但这不是一个很好的启发式方法。有了这种启发式方法,X可以赢得的所有可能方式都具有相同的权重。因此,如果X在3次移动中找到了获胜的方法,那么加权就像X在2次移动中找到获胜的方式一样,并且加权就像X在1次移动中找到获胜的方式一样。

    因此,以下是您的测试用例中发生的情况:

    1. X尝试位置2.
    2. O尝试位置3.
    3. X尝试位置6.
    4. 这是一个终端节点。 X获胜。所以返回正面1。
    5. 此决策路径的启发式= 1

      它遇到的另一种可能性是:

      1. X尝试位置3.
      2. 这是一个终端节点。 X获胜。所以返回正面1。
      3. 此决策路径的启发式= 1

        由于这两种解决方案都具有相同的启发式算法,因此它们具有相同的价值。你可能认为这个解决方案不是最理想的,因为它需要太多的动作才能获胜。我建议根据到达这里所采取的动作数量进行启发式算法,乘以胜者是谁。因此,如果X在1次移动中获胜,则启发式为5000.如果X在2次移动中获胜,则启发式为2500.如果O在2次移动中获胜,则启发式为-2500。这样的事情。

        strcpy

        存在问题

        这一行:

        strcpy(child, node);
        

        应该是:

        memcpy(child, node, 9*sizeof(char));
        

        因为“node”不是以空字符结尾的字符串。当我在VS2013 / Windows 8.1上运行它时,我的输出是垃圾,因为。你的平台可能会很幸运。