【问题标题】:3x3 Tic-Tac-Toe in C using MinimaxC 中使用 Minimax 的 3x3 井字游戏
【发布时间】:2020-12-09 09:46:52
【问题描述】:

注意 #1 - 有一个类似的问题,但那是在 Python 中,我无法在 C 中解决这个问题。

注意 #2 - 这是人类与 AI 的游戏,我将 AI 称为“cpu”。 cpu 符号为“O”,人类符号始终为“X”。

注意 #3 - 我想设计游戏,使 cpu 永远不会输(赢或平)。

我希望用户先选择 9 个方格中的任何一个。所以基本上我希望 cpu 蛮力计算游戏可能进行的每一种方式的结果,并基于此将“分数”分配给它可能的移动(剩下的 8 个选择)。这样它就会回溯并知道该走哪条路,以免迷路。

我也想使用 minimax 算法和 C 语言来做到这一点。

我的问题 - 我认为我缺少一个实际上利用返回的“分数”的函数。我不想要代码,但如果我能了解如何实现该功能,我将不胜感激。 nextMove() 函数也让我感到困惑,我希望它检查是否没有未填充的框,然后用“O”填充它,然后轮到玩家了。但由于它会在递归中再次被调用,它可能无法正常工作。有什么建议吗?

编辑 - 赏金用于指导我如何使用 minimax 算法实现此游戏的答案,如下所述:https://en.wikipedia.org/wiki/Minimax#Pseudocode

#include <stdio.h>
#define FALSE 0
#define TRUE 1
#define NEGINF -10000
#define POSINF +10000


struct node { // each node is like a snapshot of the game at that point in time
    char board[9]; // the 3x3 square is implemented as an array of 9 chars 
    int value; // the value of that snapshot (+1 if cpu wins, -1 if cpu loses, 0 for draw)
    int position; // 0 to 8 inclusive with 0 being [0][0] and 8 being [2][2] (row major fashion) and indicates position where the next 'X' or 'O' would be assigned
};



int max(int value, int returnedValue) {
    return value > returnedValue ?  value :  returnedValue;
}

int min(int value, int returnedValue) {
    return value < returnedValue ?  value :  returnedValue;
}

int someoneHasWon(struct node someNode) {
    if(someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'X')
        return TRUE;        
    
    if(someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'O')
        return TRUE;    
        //someNode.value = -1;
    if(someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'X')
        return TRUE;    
        //someNode.value = 1;//return 1;
    if(someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'O')
        return TRUE;    
        //someNode.value = -1;//return -1;
    if(someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'X')
        return TRUE;
            //someNode.value = 1;//return 1;
    if(someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'O')
        return TRUE;    
        //someNode.value = -1;//return -1;
    if(someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'X')
        return TRUE;    
        ////someNode.value = 1;//return 1;
    if(someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'O')
        return TRUE;    
        //someNode.value = -1;//return -1;
    if(someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'X')
        return TRUE;    
        //someNode.value = 1;//return 1;
    if(someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'O')
        return TRUE;    
        //someNode.value = -1;//return -1;
    if(someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'X')
        return TRUE;    
        //someNode.value = 1;//return 1;
    if(someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'O')
        return TRUE;    
        //someNode.value = -1;//return -1;
    if(someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'X')
        return TRUE;    
        //someNode.value = 1;//return 1;
    if(someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'O')
        return TRUE;    
        //someNode.value = -1;//return -1;
    if(someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'X')
        return TRUE;    
        //someNode.value = 1;//return 1;
    if(someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'O')
        return TRUE;

    return FALSE;
}

int isTerminal(struct node someNode) {
    for(int i = 0; i < 9; i++)
        if(someNode.board[i] == ' ') // if square left to fill then game is yet incomplete
            return FALSE;
    if(someoneHasWon(someNode) == TRUE) // if any player has won earlier with squares left
        return TRUE;
    return TRUE; // if it's a draw with no squares left to fill

}






struct node nextMove(struct node someNode) {
    for(int i = 0; i < 9; i++)
        if(someNode.board[i] == ' ') {
            someNode.board[i] = 'O';
            break;
        }
        return someNode;
    }

    int minimax(struct node someNode, int depth, int maximizingPlayer) {

        if(depth == 0 || isTerminal(someNode) == TRUE) {
            if(someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'X')
                someNode.value = 1;
            if(someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'O')
                someNode.value = -1;
            if(someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'O')
                someNode.value = -1;//return -1;
            if(someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'O')
                someNode.value = -1;//return -1;
            if(someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'O')
                someNode.value = -1;//return -1;
            if(someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'O')
                someNode.value = -1;//return -1;
            if(someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'O')
                someNode.value = -1;//return -1;
            if(someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'O')
                someNode.value = -1;//return -1;
            if(someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'X')
                someNode.value = 1;//return 1;
            if(someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'O')
                someNode.value = -1;//return -1;
            someNode.value = 0;//return 0;
        }
        if(maximizingPlayer == TRUE) { //maximizing player is cpu i.e. 'O'
        
            someNode.value = NEGINF;
            while(isTerminal(someNode) != TRUE)
                someNode.value = max(someNode.value, minimax(nextMove(someNode), depth - 1, FALSE));
            return someNode.value;
    }
    else { //minimizing player is me i.e. 'X'
        int boxNumber = 0;
        scanf("%d", &boxNumber);
        someNode.position = boxNumber;
        someNode.board[someNode.position] = 'X';
        someNode.value = POSINF;
        while(isTerminal(someNode) != TRUE)
            someNode.value = min(someNode.value, minimax(nextMove(someNode), depth - 1, TRUE));
        return someNode.value;
    }
}

int main() {
    int boxNumber = 0;
    printf("Assume you're X and cpu is O \nInput any box number you like \n(0 to 8 both inclusive) \n...you'll be defeated anyways lol :\n");
    scanf("%d", &boxNumber);
    struct node origin;
    for(int i = 0; i < 9; i++)
        origin.board[i] = ' ';
    origin.position = boxNumber;
    origin.board[origin.position] = 'X';
    origin.value = 0;
    minimax(origin, 8, TRUE);
}

代码版本 #2 -

#include <stdio.h>
#define FALSE 0
#define TRUE 1
#define NEGINF -10000
#define POSINF +10000

struct node
{
    char board[9];
    int value;
    int position;
};

//char someNode.board[9] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '};

int max(int value, int returnedValue)
{
    return value > returnedValue ? value : returnedValue;
}

int min(int value, int returnedValue)
{
    return value < returnedValue ? value : returnedValue;
}

int someoneHasWon(struct node someNode)
{
    if (someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'x')
        return TRUE;

    if (someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'o')
        return TRUE;
    //someNode.value = -1;
    if (someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'x')
        return TRUE;
    //someNode.value = 1;//return 1;
    if (someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'o')
        return TRUE;
    //someNode.value = -1;//return -1;
    if (someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'x')
        return TRUE;
    //someNode.value = 1;//return 1;
    if (someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'o')
        return TRUE;
    //someNode.value = -1;//return -1;
    if (someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'x')
        return TRUE;
    ////someNode.value = 1;//return 1;
    if (someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'o')
        return TRUE;
    //someNode.value = -1;//return -1;
    if (someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'x')
        return TRUE;
    //someNode.value = 1;//return 1;
    if (someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'o')
        return TRUE;
    //someNode.value = -1;//return -1;
    if (someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'x')
        return TRUE;
    //someNode.value = 1;//return 1;
    if (someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'o')
        return TRUE;
    //someNode.value = -1;//return -1;
    if (someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'x')
        return TRUE;
    //someNode.value = 1;//return 1;
    if (someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'o')
        return TRUE;
    //someNode.value = -1;//return -1;
    if (someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'x')
        return TRUE;
    //someNode.value = 1;//return 1;
    if (someNode.board[6] == someNode.board[4] && someNode.board[4] 

== someNode.board[2] && someNode.board[2] == 'o')
            return TRUE;
    
        return FALSE;
    }
    
    int isTerminal(struct node someNode)
    {
        for (int i = 0; i < 9; i++)
            if (someNode.board[i] == ' ') // if square left to fill then game is yet incomplete
                return FALSE;
        if (someoneHasWon(someNode) == TRUE) // if any player has won earlier with squares left
            return TRUE;
        return TRUE; // if it's a draw with no squares left to fill
    }
    
    struct node nextMove(struct node someNode)
    {
        for (int i = 0; i < 9; i++)
            if (someNode.board[i] == ' ')
            {
                someNode.board[i] = 'o';
                break;
            }
        return someNode;
    }
    
    int minimax(struct node someNode, int depth, int maximizingPlayer)
    {
    
        if (depth == 8 || isTerminal(someNode) == TRUE)
        {
            if (someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'x')
                someNode.value = 1;
            if (someNode.board[0] == someNode.board[3] && someNode.board[3] == someNode.board[6] && someNode.board[6] == 'o')
                someNode.value = -1;
            if (someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'x')
                someNode.value = 1; //return 1;
            if (someNode.board[1] == someNode.board[4] && someNode.board[4] == someNode.board[7] && someNode.board[7] == 'o')
                someNode.value = -1; //return -1;
            if (someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'x')
                someNode.value = 1; //return 1;
            if (someNode.board[2] == someNode.board[5] && someNode.board[5] == someNode.board[8] && someNode.board[8] == 'o')
                someNode.value = -1; //return -1;
            if (someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'x')
                someNode.value = 1; //return 1;
            if (someNode.board[0] == someNode.board[1] && someNode.board[1] == someNode.board[2] && someNode.board[2] == 'o')
                someNode.value = -1; //return -1;
            if (someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'x')
                someNode.value = 1; //return 1;
            if (someNode.board[3] == someNode.board[4] && someNode.board[4] == someNode.board[5] && someNode.board[5] == 'o')
                someNode.value = -1; //return -1;
            if (someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'x')
                someNode.value = 1; //return 1;
            if (someNode.board[6] == someNode.board[7] && someNode.board[7] == someNode.board[8] && someNode.board[8] == 'o')
                someNode.value = -1; //return -1;
            if (someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'x')
                someNode.value = 1; //return 1;
            if (someNode.board[0] == someNode.board[4] && someNode.board[4] == someNode.board[8] && someNode.board[8] == 'o')
       

         someNode.value = -1; //return -1;
        if (someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'x')
            someNode.value = 1; //return 1;
        if (someNode.board[6] == someNode.board[4] && someNode.board[4] == someNode.board[2] && someNode.board[2] == 'o')
            someNode.value = -1; //return -1;
        someNode.value = 0;      //return 0;
    }
    if (maximizingPlayer == TRUE)
    { //maximizing player is cpu i.e. 'o'

        someNode.value = NEGINF;
        while (isTerminal(someNode) != TRUE)
            someNode.value = max(someNode.value, minimax(nextMove(someNode), depth + 1, FALSE));
        if (someNode.value == -1) {
            printf("o %d \n", someNode.value);
            return someNode.value;
        }
    }
    else
    { //minimizing player is me i.e. 'x'
        int boxNumber = 0;
        printf("Enter your move :\n");
        scanf("%d", &boxNumber);
        someNode.position = boxNumber;
        someNode.board[someNode.position] = 'x';
        someNode.value = POSINF;
        while (isTerminal(someNode) != TRUE)
            someNode.value = min(someNode.value, minimax(nextMove(someNode), depth + 1, TRUE));
        if (someNode.value == 1)
            return someNode.value;
    }
}

int main()
{
    int boxNumber = 0;
    printf("Assume you're x and cpu is O \nInput any box number you like \n(0 to 8 both inclusive) \n...you'll be defeated anyways lol :\n");
    scanf("%d", &boxNumber);
    struct node origin;
    origin.position = boxNumber;
    origin.board[origin.position] = 'x';
    origin.value = 0;
    printf("%c %d \n", origin.board[origin.position], origin.position);
    int val = minimax(origin, 1, TRUE);
    if(val > 0)
        printf("You lose");
    else if(val < 0)
        printf("You win");
    else
        printf("It's a draw");
    return 0;
}

【问题讨论】:

  • 你想以特定的方式实现(你确实描述了很多关于你的想法和你想要的东西)还是你想要程序让人工智能永远不会失败?我问是因为我怀疑你没有得到答案(尽管有赏金),因为可能存在冲突。 IE。是什么让您认为按照您描述的方式制作永不丢失的 AI 实际上是可能的?考虑写一些类似“我将奖励实现永不丢失的人工智能的答案;最接近我打破关系的方法的答案。”或“根据我的方法获得最佳(失败)人工智能的答案。”
  • 是的,我希望 AI 永远不会输(赢或平)。另外,我这样做是为了自学 Minimax,如下所述:en.wikipedia.org/wiki/Minimax#Pseudocode

标签: c tic-tac-toe minimax


【解决方案1】:

我认为我缺少一个实际上利用返回的“分数”的函数。

嗯,不,minimax() 函数本身是唯一需要它(递归)计算分数的函数。问题是您在 Wikipedia 上查看的伪代码是 very “伪”。它很好地说明了算法的思想,但它并没有为实际实现它提供一个很好的模型。特别是,它省略了任何实际实现所需的关键组件:除了计算分数之外,minimax() 需要提供至少一个导致该分数的移动。这是顶级调用者选择的移动。

nextMove() 函数也让我感到困惑,我想让它检查是否没有未填充的框,然后用'O'填充它,然后轮到玩家。但由于它会在递归中再次被调用,它可能无法正常工作。有什么建议吗?

至少有两种可能的通用方法:

  1. 根据需要制作电路板的临时副本,以便您可以在不更改原件的情况下修改它们,或者
  2. 跟踪测试的每个位置,并在测试后清除。

无论哪种方式,将移动分析过程与移动的实际选择分开是有帮助的。

请注意,您已经通过按值传递 struct node 对象来制作副本。就其本身而言,这不一定是选项 (1) 的完整或正确实现,但绝对是需要注意的事情,尤其是关于我上面的主要观点。

另外,我并不特别喜欢伪代码使用minimax() 的布尔输入来指示是最大化还是最小化的方法。我更喜欢传递一个参数来指示它是哪个玩家的移动。在像井字游戏这样的对称游戏中,minimax() 函数利用了这样一个事实:一个玩家的结果越好,另一个玩家的结果就越差。除其他外,这往往会简化代码,因为代码不需要围绕该参数的值进行单独的案例。

【讨论】:

  • 我意识到我没有将minimax 函数返回的值用于main 方法。我更新了那个。另外,我注意到维基百科上的伪代码使用树的深度,就好像它是它的高度(基本上与我对高度的定义相反)。所以我在递归调用中将depth - 1改为depth + 1
  • 代码仍然无法正常工作,我的代码中的任何 cmets/hints 将不胜感激。
【解决方案2】:

井字游戏是一款非常简单的游戏,因此没有太多的游戏状态。因此,您无需将 AI 限制在某个深度。

function bestMove(board, maximisingPlayer) is
  if tie then
    return {value: 0}
  else if maximising player won then
    return {value: ∞}
  else if minimising player won then
    return {value: -∞}

  if maximisingPlayer then
    value := -∞
    move := 0

    for each child of node do
      tempValue := minimax(child, FALSE).value

      if tempValue > value then
        value := tempValue
        move := child
    
    return {value: value, move: move}
  else 
    value := ∞
    move := 0

    for each child of node do
      tempValue := minimax(child, TRUE).value

      if tempValue < value then
        value := tempValue
        move := child
    
    return {value: value, move: move}

/* Returns best child node after move is made. */
currentBestMove := bestMove(currentBoard, currentPlayer).move
    

另外作为对代码的注释,不要将 win 逻辑与 minimax 放在同一过程中,尽量将代码拆分为更小的函数。此外,您可以将获胜组合存储在一个数组中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多