【博弈论】经典游戏三子棋如何做到100%不败


前言

看似简单的井字棋(Tic Tac Toe),其实蕴藏着深奥的策略。当两位玩家都熟悉游戏的基本技巧时,常常会以平局告终。不过,通过一些巧妙的策略和战术,你可以在每一局游戏中增加胜算。本文将为你揭示这些秘诀,帮助你成为井字棋的常胜将军。

人类玩家策略

这里说的人类玩家是说从一个真人的角度去玩这个游戏,本文下面还有一个电脑玩家策略主要是讲从计算机的角度如何实现

先手玩家的制胜策略

角落策略

从任意角落下手

将第一个X放在任意一个角落。这一开局能使对手更容易犯错。如果对手落子位置不在中心,可以做到百分之百的胜率。

对手不在中心落子

8a8e762b1b754bc8edf39228e57ff0e.jpg

对手在中心落子

如果对手首先将O放在中心,只有在他犯错的情况下才能赢得比赛。如果比赛中双方都没有失误,游戏结果将是平局。
22e99fdab21ec4fd87bfbc580a7651f.jpg

中心策略

中心开局虽然没有角落策略胜率高,但也有其优势。
a422a6734114921acb44be2c41ce09e.jpg

对手的回应
  • O在角落:通常会导致平局。
  • O在边缘:先手玩家可通过制造双重威胁获胜。

X的第二步应走向任何一个角落,制造一个必须阻挡的威胁。在O阻挡后,X的第三步(最终的第五步)走向另一个角落,形成不可避免的双重威胁,确保胜利。
5dcc8443fd7c0e60011e0e05b699a83.jpg

后手玩家的应对策略

作为后手玩家,目标通常是实现平局,获胜的机会。

开局的关键

对手从中央开始

占据一个角落追求打平,具体原因看上面先手制胜策略。

对手从角落开始

此时我们只能占据中心,具体原因看上面先手制胜策略。

对手从边缘开始

同样占据中心,基本都是平局。
1879e0d341890111fd5bacc8dc8b98e.jpg

电脑玩家策略

接下来从计算机的角度讲讲这个游戏的策略

Minimax算法

Minimax是一种递归算法,用于在回合制游戏中帮助决策。它通过假设对手总是作出最优选择,来评估每一个可能的走法,并选择一个最优的走法。具体步骤如下:

  1. 终止条件:如果当前棋局为胜、负或平局,返回相应的分数。
  2. 最大化玩家:尝试在所有可能走法中找到最高得分。
  3. 最小化玩家:尝试在所有可能走法中找到最低得分。

核心函数解释

  • isWinner:检查是否有玩家获胜。
  • isBoardFull:检查棋盘是否已满。
  • minimax:递归地评估每一个可能的走法,返回最优分数。
  • getBestMove:遍历所有可能的走法,利用Minimax算法找到最佳走法。

深入解析Minimax算法中的递归

递归是Minimax算法的核心机制,它通过模拟每一个可能的游戏状态,帮助计算机选择最佳策略。让我们详细剖析Minimax算法的递归过程。

1. 基础概念

在井字棋中,计算机和玩家交替进行,每一步都可能改变游戏的状态。Minimax算法通过递归模拟每一个可能的走法,并评估其结果,从而选择最佳策略。

2. 递归过程的具体步骤

Minimax算法通过以下步骤进行递归评估:

  1. 检查终止条件:在每一个递归调用开始时,首先检查当前游戏状态是否为终止状态(胜、负或平局)。如果是,返回相应的分数。
  2. 最大化和最小化:根据当前玩家的角色(最大化玩家或最小化玩家),尝试通过所有可能的走法,计算每一个走法的得分。
  3. 递归调用:对每一个可能的走法,递归地调用Minimax算法,模拟对手的最佳反应,直到到达终止状态。
3. C++代码中的递归实现

让我们逐步分析minimax函数,解释递归的具体过程。

int minimax(vector<char>& board, bool isMaximizing) {
    // 检查终止条件
    if (isWinner(board, COMPUTER)) return 1;    // 如果计算机获胜,返回1分
    if (isWinner(board, PLAYER)) return -1;     // 如果玩家获胜,返回-1分
    if (isBoardFull(board)) return 0;           // 如果棋盘已满,返回0分(平局)

    // 如果是最大化玩家(计算机)的回合
    if (isMaximizing) {
        int bestScore = -2;  // 初始化最优分数为一个很小的值
        for (int i = 0; i < 9; ++i) {
            if (board[i] == EMPTY) {
                board[i] = COMPUTER;  // 尝试在当前位置下棋
                int score = minimax(board, false);  // 递归调用Minimax算法,模拟玩家的回合
                board[i] = EMPTY;  // 撤销走法
                bestScore = max(score, bestScore);  // 更新最优分数
            }
        }
        return bestScore;  // 返回最优分数
    } else {  // 如果是最小化玩家(玩家)的回合
        int bestScore = 2;  // 初始化最优分数为一个很大的值
        for (int i = 0; i < 9; ++i) {
            if (board[i] == EMPTY) {
                board[i] = PLAYER;  // 尝试在当前位置下棋
                int score = minimax(board, true);  // 递归调用Minimax算法,模拟计算机的回合
                board[i] = EMPTY;  // 撤销走法
                bestScore = min(score, bestScore);  // 更新最优分数
            }
        }
        return bestScore;  // 返回最优分数
    }
}

4. 递归调用详细解释
  • 终止条件:在每一次递归调用开始时,首先检查当前游戏状态是否为终止状态。如果当前玩家已经获胜或棋盘已满,返回相应的分数(1、-1或0)。
  • 最大化玩家的回合
    • 初始化bestScore为一个很小的值(-2),表示计算机尝试在所有可能的走法中找到最高得分。
    • 遍历所有棋盘位置,尝试每一个可能的走法。
    • 递归调用minimax函数,模拟玩家的回合(isMaximizing设为false)。
    • 撤销当前走法,并更新bestScore为最大值。
  • 最小化玩家的回合
    • 初始化bestScore为一个很大的值(2),表示玩家尝试在所有可能的走法中找到最低得分。
    • 遍历所有棋盘位置,尝试每一个可能的走法。
    • 递归调用minimax函数,模拟计算机的回合(isMaximizing设为true)。
    • 撤销当前走法,并更新bestScore为最小值。
5. 递归过程的终止

递归过程将不断深入,直到到达终止状态(胜、负或平局),然后逐层返回每一个可能走法的得分,并在每一层选择最优分数,最终返回到初始调用层,确定最佳走法。

6. 示例说明

假设棋盘状态如下(X为玩家,O为计算机,-为空):

X | O | X
O | X | -
- | - | O
  • 计算机尝试在所有空位(第5、6、7、8位)下棋。
  • 递归调用minimax,评估每一个走法的得分。
  • 根据每一个走法的得分,选择最佳走法,确保计算机取得最优结果。

通过递归调用,Minimax算法能够全面评估每一个可能的游戏状态,确保计算机在井字棋中做出最优决策。

人机大战代码

井字棋人机大战(人先手版本)

#include <iostream>
#include <vector>

using namespace std;

char PLAYER = 'X';
char COMPUTER = 'O';
char EMPTY = ' ';

void printBoard(vector<char>& board) {
    for (int i = 0; i < 9; i += 3) {
        //cout << board[i] << "|" << board[i + 1] << "|" << board[i + 2] << endl;
        printf("\n   %c\t|   %c\t|   %c",board[i],board[i + 1],board[i + 2]);
        if (i < 6) cout << "\n-------------------------";
    }
    cout << endl;
}

bool isWinner(vector<char>& board, char player) {
    int winPatterns[8][3] = {
    {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, // Rows
    {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, // Columns
    {0, 4, 8}, {2, 4, 6}             // Diagonals
};
    for (auto& pattern : winPatterns) {
        if (board[pattern[0]] == player && board[pattern[1]] == player && board[pattern[2]] == player)
            return true;
    }
    return false;
}

bool isBoardFull(vector<char>& board) {
    for (char cell : board) {
        if (cell == EMPTY)
            return false;
    }
    return true;
}

int minimax(vector<char>& board, bool isMaximizing) {
    if (isWinner(board, COMPUTER)) return 1;
    if (isWinner(board, PLAYER)) return -1;
    if (isBoardFull(board)) return 0;

    if (isMaximizing) {
        int bestScore = -2;
        for (int i = 0; i < 9; ++i) {
            if (board[i] == EMPTY) {
                board[i] = COMPUTER;
                int score = minimax(board, false);
                board[i] = EMPTY;
                bestScore = max(score, bestScore);
            }
        }
        return bestScore;
    } else {
        int bestScore = 2;
        for (int i = 0; i < 9; ++i) {
            if (board[i] == EMPTY) {
                board[i] = PLAYER;
                int score = minimax(board, true);
                board[i] = EMPTY;
                bestScore = min(score, bestScore);
            }
        }
        return bestScore;
    }
}

int getBestMove(vector<char>& board) {
    int bestScore = -2;
    int bestMove = -1;
    for (int i = 0; i < 9; ++i) {
        if (board[i] == EMPTY) {
            board[i] = COMPUTER;
            int moveScore = minimax(board, false);
            board[i] = EMPTY;
            if (moveScore > bestScore) {
                bestScore = moveScore;
                bestMove = i;
            }
        }
    }
    return bestMove;
}

void playerMove(vector<char>& board) {
    int move;
    while (true) {
        cout << "按照棋盘位置输入(1-9): ";
        cin >> move;
        move--; // Adjust for 0-based indexing
        if (move >= 0 && move < 9 && board[move] == EMPTY) {
            board[move] = PLAYER;
            break;
        } else {
            cout << "输入数字无效,请重新输入!" << endl;
        }
    }
}

void computerMove(vector<char>& board) {
    int bestMove = getBestMove(board);
    board[bestMove] = COMPUTER;
}

int main() {
    vector<char> board(9, EMPTY);
    cout << "游戏开始!" << endl;
    printBoard(board);

    while (true) {
        playerMove(board);
        if (isWinner(board, PLAYER)) {
            printBoard(board);
            cout << "恭喜!您胜利了!" << endl;
            break;
        }
        if (isBoardFull(board)) {
            printBoard(board);
            cout << "平局!没有地方可以下了!" << endl;
            break;
        }

        computerMove(board);
        printBoard(board);
        if (isWinner(board, COMPUTER)) {
            cout << "电脑玩家获胜!" << endl;
            break;
        }
        if (isBoardFull(board)) {
            cout << "平局!没有地方可以下了!" << endl;
            break;
        }
    }

    return 0;
}

井字棋人机大战或人人大战(先手可选择版本)

#include <iostream>
#include <vector>

using namespace std;

char PLAYER = 'X';
char COMPUTER = 'O';
char EMPTY = ' ';

void printBoard(vector<char>& board) {
    for (int i = 0; i < 9; i += 3) {
        printf("\n   %c\t|   %c\t|   %c", board[i], board[i + 1], board[i + 2]);
        if (i < 6) cout << "\n-------------------------";
    }
    cout << endl;
}

bool isWinner(vector<char>& board, char player) {
    int winPatterns[8][3] = {
            {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, // Rows
            {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, // Columns
            {0, 4, 8}, {2, 4, 6}             // Diagonals
    };
    for (auto& pattern : winPatterns) {
        if (board[pattern[0]] == player && board[pattern[1]] == player && board[pattern[2]] == player)
            return true;
    }
    return false;
}

bool isBoardFull(vector<char>& board) {
    for (char cell : board) {
        if (cell == EMPTY)
            return false;
    }
    return true;
}

int minimax(vector<char>& board, bool isMaximizing) {
    if (isWinner(board, COMPUTER)) return 1;
    if (isWinner(board, PLAYER)) return -1;
    if (isBoardFull(board)) return 0;

    if (isMaximizing) {
        int bestScore = -2;
        for (int i = 0; i < 9; ++i) {
            if (board[i] == EMPTY) {
                board[i] = COMPUTER;
                int score = minimax(board, false);
                board[i] = EMPTY;
                bestScore = max(score, bestScore);
            }
        }
        return bestScore;
    } else {
        int bestScore = 2;
        for (int i = 0; i < 9; ++i) {
            if (board[i] == EMPTY) {
                board[i] = PLAYER;
                int score = minimax(board, true);
                board[i] = EMPTY;
                bestScore = min(score, bestScore);
            }
        }
        return bestScore;
    }
}

int getBestMove(vector<char>& board) {
    int bestScore = -2;
    int bestMove = -1;
    for (int i = 0; i < 9; ++i) {
        if (board[i] == EMPTY) {
            board[i] = COMPUTER;
            int moveScore = minimax(board, false);
            board[i] = EMPTY;
            if (moveScore > bestScore) {
                bestScore = moveScore;
                bestMove = i;
            }
        }
    }
    return bestMove;
}

void playerMove(vector<char>& board, char player) {
    int move;
    while (true) {
        cout << "按照棋盘位置输入(1-9): ";
        cin >> move;
        move--; // Adjust for 0-based indexing
        if (move >= 0 && move < 9 && board[move] == EMPTY) {
            board[move] = player;
            break;
        } else {
            cout << "输入数字无效,请重新输入!" << endl;
        }
    }
}

void computerMove(vector<char>& board) {
    int bestMove = getBestMove(board);
    board[bestMove] = COMPUTER;
}

void gameLoop(vector<char>& board, bool playerFirst, bool isVsComputer) {
    printBoard(board);

    while (true) {
        if (playerFirst) {
            playerMove(board, PLAYER);
            if (isWinner(board, PLAYER)) {
                printBoard(board);
                cout << "恭喜!玩家X胜利了!" << endl;
                break;
            }
        } else if (isVsComputer) {
            computerMove(board);
            if (isWinner(board, COMPUTER)) {
                printBoard(board);
                cout << "电脑玩家获胜!" << endl;
                break;
            }
        } else {
            playerMove(board, COMPUTER);
            if (isWinner(board, COMPUTER)) {
                printBoard(board);
                cout << "恭喜!玩家O胜利了!" << endl;
                break;
            }
        }

        if (isBoardFull(board)) {
            printBoard(board);
            cout << "平局!没有地方可以下了!" << endl;
            break;
        }

        playerFirst = !playerFirst; // 轮流下棋
        printBoard(board);
    }
}

int main() {
    vector<char> board(9, EMPTY);

    char choice;
    cout << "选择游戏模式:\n1. 人机对战\n2. 人人对战\n请输入(1或2): ";
    cin >> choice;

    bool isVsComputer = (choice == '1');
    bool playerFirst;

    if (isVsComputer) {
        cout << "你想先手吗?(y/n): ";
        char first;
        cin >> first;
        playerFirst = (first == 'y');
    } else {
        playerFirst = true; // 人人对战默认X先手
    }

    cout << "游戏开始!" << endl;
    gameLoop(board, playerFirst, isVsComputer);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想要AC的dly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值