文章目录
前言
看似简单的井字棋(Tic Tac Toe),其实蕴藏着深奥的策略。当两位玩家都熟悉游戏的基本技巧时,常常会以平局告终。不过,通过一些巧妙的策略和战术,你可以在每一局游戏中增加胜算。本文将为你揭示这些秘诀,帮助你成为井字棋的常胜将军。
人类玩家策略
这里说的人类玩家是说从一个真人的角度去玩这个游戏,本文下面还有一个电脑玩家策略主要是讲从计算机的角度如何实现
先手玩家的制胜策略
角落策略
从任意角落下手
将第一个X放在任意一个角落。这一开局能使对手更容易犯错。如果对手落子位置不在中心,可以做到百分之百的胜率。
对手不在中心落子
对手在中心落子
如果对手首先将O放在中心,只有在他犯错的情况下才能赢得比赛。如果比赛中双方都没有失误,游戏结果将是平局。
中心策略
中心开局虽然没有角落策略胜率高,但也有其优势。
对手的回应
- O在角落:通常会导致平局。
- O在边缘:先手玩家可通过制造双重威胁获胜。
X的第二步应走向任何一个角落,制造一个必须阻挡的威胁。在O阻挡后,X的第三步(最终的第五步)走向另一个角落,形成不可避免的双重威胁,确保胜利。
后手玩家的应对策略
作为后手玩家,目标通常是实现平局,获胜的机会。
开局的关键
对手从中央开始
占据一个角落追求打平,具体原因看上面先手制胜策略。
对手从角落开始
此时我们只能占据中心,具体原因看上面先手制胜策略。
对手从边缘开始
同样占据中心,基本都是平局。
电脑玩家策略
接下来从计算机的角度讲讲这个游戏的策略
Minimax算法
Minimax是一种递归算法,用于在回合制游戏中帮助决策。它通过假设对手总是作出最优选择,来评估每一个可能的走法,并选择一个最优的走法。具体步骤如下:
- 终止条件:如果当前棋局为胜、负或平局,返回相应的分数。
- 最大化玩家:尝试在所有可能走法中找到最高得分。
- 最小化玩家:尝试在所有可能走法中找到最低得分。
核心函数解释
isWinner
:检查是否有玩家获胜。isBoardFull
:检查棋盘是否已满。minimax
:递归地评估每一个可能的走法,返回最优分数。getBestMove
:遍历所有可能的走法,利用Minimax算法找到最佳走法。
深入解析Minimax算法中的递归
递归是Minimax算法的核心机制,它通过模拟每一个可能的游戏状态,帮助计算机选择最佳策略。让我们详细剖析Minimax算法的递归过程。
1. 基础概念
在井字棋中,计算机和玩家交替进行,每一步都可能改变游戏的状态。Minimax算法通过递归模拟每一个可能的走法,并评估其结果,从而选择最佳策略。
2. 递归过程的具体步骤
Minimax算法通过以下步骤进行递归评估:
- 检查终止条件:在每一个递归调用开始时,首先检查当前游戏状态是否为终止状态(胜、负或平局)。如果是,返回相应的分数。
- 最大化和最小化:根据当前玩家的角色(最大化玩家或最小化玩家),尝试通过所有可能的走法,计算每一个走法的得分。
- 递归调用:对每一个可能的走法,递归地调用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;
}