套娃井字棋规则介绍
套娃井字棋是由传统井字棋改良得到的。双方各有三个大棋子,三个中棋子和三个小棋子,棋盘依然是3*3。在每一步中,双方既可以在棋盘上的一个空格上落子,也可以在一个已有我方或对方棋子的格子上进行替换操作,即可以把尺寸大的棋子,套到尺寸小的棋子上,依然是率先实现三点连线的一方获胜(注:此图片来源于1688)
复杂度分析
引用了大佬“csuzhucong”的分析(https://blog.csdn.net/nameofcsdn/article/details/125057136)
棋盘上每个格子有7种状态(空白、双方的大中小棋子)
则9个格子的状态有7^9=40353607种,双方手里剩下的棋子数量有(4*4*4)^ 2 = 4096种情况,一共有40353607*4096=1653亿种情况。
考虑到双方手里剩下的大棋子的数量其实也可以推算出来,那么就只有40353607*16*16=103亿种情况。
代码编写思路(未完待续)
1.表示棋盘状态
在这里,我们用二维数组表示棋盘状态,用A、B、C表示先手方的大、中、小棋子,用a、b、c表示后手方的大、中、小棋子
2.游戏树(多路树)节点的定义
值得注意的是,我们需要记录每个节点上双方剩余的棋子个数,毕竟双方的棋子都是有限的
// 定义多路树节点结构体
struct TreeNode {
int player;
vector<vector<char>> board; // 使用二维数组表示棋盘状态
int flag;//flag值是用来标记获胜的玩家的
vector<TreeNode*> children;
int a, b, c, A, B, C; // 双方的三种棋子(a b c A B C)的剩余个数
//用A、B、C表示先手方的大、中、小棋子,用a、b、c表示后手方的大、中、小棋子
};
3.检查当前是否有玩家获胜
在某个状态下,如果棋盘上有三个同一方的棋子三子相连,则当前玩家获胜,但是由于双方各有三种棋子,直接检查每一条线上的三个棋子是否属于同一方较为困难,所以我们可以考虑先将当前棋盘状态复制到二维数组tempboard,并将先手方的棋子ABC都替换成A,将后手方的棋子abc都替换成a,然后再来检查每一行,每一列和每一条对角线上有无三个相同棋子,如果有,那么当前玩家获胜(由于在构建游戏树过程中,每当一个节点有玩家获胜,该节点处就不会继续往下遍历,也就是说不会出现有玩家获胜后依然继续走棋的情况,所以checkwin函数只需要判断tempboard上有无三个相同棋子相连即可,如果有,那么就是当前玩家获胜,而不需要检测这三个相同的棋子是A还是a)。
bool checkWin(TreeNode* node) {
char tempboard[3][3];
// 复制node->board到tempboard
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
tempboard[i][j] = node->board[i][j];
}
}
// 将tempboard上所有格子上的B棋子和C棋子修改成A棋子,将tempboard所有格子上的b棋子和c棋子修改成a棋子
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (tempboard[i][j] == 'B') tempboard[i][j] = 'A';
else if (tempboard[i][j] == 'C') tempboard[i][j] = 'A';
else if (tempboard[i][j] == 'b') tempboard[i][j] = 'a';
else if (tempboard[i][j] == 'c') tempboard[i][j] = 'a';
}
}
// 检查行、列和对角线,以确定当前玩家是否获胜
for (int i = 0; i < 3; ++i) {
if (tempboard[i][0] == tempboard[i][1] && tempboard[i][1] == tempboard[i][2] && tempboard[i][0] != ' ') return true;
if (tempboard[0][i] == tempboard[1][i] && tempboard[1][i] == tempboard[2][i] && tempboard[0][i] != ' ') return true;
}
if (tempboard[0][0] == tempboard[1][1] && tempboard[1][1] == tempboard[2][2] && tempboard[0][0] != ' ') return true;
if (tempboard[0][2] == tempboard[1][1] && tempboard[1][1] == tempboard[2][0] && tempboard[0][2] != ' ') return true;
return false;
}
4.游戏树的构建思路
这是整个代码中最重要的部分。对于每个节点,我们都要通过遍历生成该节点的所有后续状态。具体来说,我们要依次考虑在棋盘的九个格子上走棋的可能性。对于每一个格子
1.如果该格子上没有棋子,那么考虑在该格子上落子,包括下大棋子、中棋子、小棋子三种可能(当然,是在对应的棋子有剩余的情况下才能落子),并且完成落子后,新节点上刚刚使用的棋子数量要减一。
2.如果该格子上有棋子,那么考虑在该格上能否进行替换(即套娃)操作
2-1.如果该格子上的棋子是A或a,那么不能进行替换操作
2-2.如果该格子上的棋子是B或b,并且当前玩家的大棋子有剩余,则考虑用大棋子来替换当前格子上的棋子,同样新节点上刚刚用过的大棋子数量要减一(注意被替换的棋子数量不变)
2-3.如果该格子上的棋子是C或c,那么(在当前玩家相应棋子仍有剩余的情况下)除了可以用大棋子去替换外,也可以用中棋子去替换,同样在新节点上要更新棋子数量
在这里,为了便于观察程序的运行情况,我添加了一个功能:遍历到有玩家获胜的节点时,输出当前棋盘状态和双方各种棋子剩余个数。
另外,在此程序中,flag值是用来记录胜负状态的,先手方(ABC方)获胜即为1,后手方(abc方)获胜即为2,count变量是用来记录末端节点总数的
class MultiwayTree {
public:
TreeNode* root;
MultiwayTree() {
root = new TreeNode();
root->a = root->b = root->c = 3;
root->A = root->B = root->C = 3;
root->board = vector<vector<char>>(3, vector<char>(3, ' ')); // 初始化棋盘状态为空
root->flag = -1; // 初始化flag值为-1
}
// 添加子节点的方法
void addChild(TreeNode* parent, TreeNode* child) {
parent->children.push_back(child);
}
// 检查是否有一方获胜
bool checkWin(TreeNode* node) {
char tempboard[3][3];
// 复制node->board到tempboard
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
tempboard[i][j] = node->board[i][j];
}
}
// 将tempboard上所有格子上的B棋子和C棋子修改成A棋子,将tempboard所有格子上的b棋子和c棋子修改成a棋子
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (tempboard[i][j] == 'B') tempboard[i][j] = 'A';
else if (tempboard[i][j] == 'C') tempboard[i][j] = 'A';
else if (tempboard[i][j] == 'b') tempboard[i][j] = 'a';
else if (tempboard[i][j] == 'c') tempboard[i][j] = 'a';
}
}
// 检查行、列和对角线,以确定当前玩家是否获胜
for (int i = 0; i < 3; ++i) {
if (tempboard[i][0] == tempboard[i][1] && tempboard[i][1] == tempboard[i][2] && tempboard[i][0] != ' ') return true;
if (tempboard[0][i] == tempboard[1][i] && tempboard[1][i] == tempboard[2][i] && tempboard[0][i] != ' ') return true;
}
if (tempboard[0][0] == tempboard[1][1] && tempboard[1][1] == tempboard[2][2] && tempboard[0][0] != ' ') return true;
if (tempboard[0][2] == tempboard[1][1] && tempboard[1][1] == tempboard[2][0] && tempboard[0][2] != ' ') return true;
return false;
}
// 输出棋盘状态
void printBoard(TreeNode* node) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
cout << node->board[i][j] << " ";
}
cout << endl;
}
}
void generateAllMoves(TreeNode* node, int depth, int maxDepth) {
if (depth >= maxDepth || checkWin(node)) { // 检查是否有一方获胜或达到最大深度
count++;
if (checkWin(node)) { // 如果当前节点是获胜节点
if (node->player == 0) { // ABC棋子的玩家获胜
node->flag = 1;
system("pause");
} else { // abc棋子的玩家获胜
node->flag = 2;
}
}
if (depth >= maxDepth) { // 如果达到最大深度,将flag设置为0
node->flag = 0;
}
if (depth >= maxDepth || checkWin(node)) { // 如果达到最大深度或当前节点是获胜节点
if(count%1000000==0) cout << "已研究的末端子节点总数为" << count << endl;
cout << "Flag值为:" << node->flag << endl; // 输出flag值
cout<<"已经使用的棋子总数"<<18-node->a-node->b-node->c-node->A-node->B-node->C<<endl;
cout<<"剩余棋子A为"<<node->A <<endl<<" 剩余棋子B为"<<node->B<<endl;
cout<<"剩余棋子C为"<<node->C <<endl<<" 剩余棋子a为"<<node->a<<endl;
cout<<"剩余棋子b为"<<node->b <<endl<<" 剩余棋子c为"<<node->c<<endl;
cout << "当前棋盘状态:" << endl;
printBoard(node);
// 输出棋盘状态
cout<<endl;
//if(node->flag!=0) system("pause");
}
return;
}
if (1==1) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (node->board[i][j] == ' ') {
if (node->player == 0) { // 甲玩家走棋
for (char piece : {'A', 'B', 'C'}) {
if (piece == 'A' && node->A > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->A--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'B' && node->B > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->B--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'C' && node->C > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->C--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
}
}
if (node->player == 1)
{
for (char piece : {'a', 'b', 'c'}) {
if (piece == 'a' && node->a > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->a--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'b' && node->b > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->b--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'c' && node->c > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->c--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
}
}
}
else { // 处理替换(即套娃)操作
char currentPiece = node->board[i][j];
if (node->player == 0) { // 甲玩家走棋
if ((currentPiece == 'B'||currentPiece == 'b') && node->A > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'A';
child->A--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->A > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'A';
child->A--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->B > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'B';
child->B--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
} else { // 乙玩家走棋
if ((currentPiece == 'B'||currentPiece == 'b') && node->a > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'a';
child->a--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->b > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'b';
child->b--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->a > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'a';
child->a--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
}
}
}
}
}
}
};
完整代码
注:部分代码由星火AI生成,但是由AI生成的部分本人都进行了详尽的检查和修改,保证其不出现逻辑错误
根据测试,如果不输出末端节点的棋盘状态,那么此代码每10分钟可以遍历出一千万个末端节点,则运行以下代码生成整棵游戏树的时间约为1天(如果要输出末端节点的棋盘状态,那么速度自然会变慢)(如果用多线程,也许会更快一些)
#include <iostream>
#include <vector>
#include<windows.h>
using namespace std;
long long count = 0; // count为末端节点数目
// 定义多路树节点结构体
struct TreeNode {
int player;
vector<vector<char>> board; // 使用二维数组表示棋盘状态
int flag;//flag值是用来标记获胜的玩家的
vector<TreeNode*> children;
int a, b, c, A, B, C; // 双方的三种棋子(a b c A B C)的剩余个数
//用A、B、C表示先手方的大、中、小棋子,用a、b、c表示后手方的大、中、小棋子
};
// 创建多路树类
class MultiwayTree {
public:
TreeNode* root;
MultiwayTree() {
root = new TreeNode();
root->a = root->b = root->c = 3;
root->A = root->B = root->C = 3;
root->board = vector<vector<char>>(3, vector<char>(3, ' ')); // 初始化棋盘状态为空
root->flag = -1; // 初始化flag值为-1
}
// 添加子节点的方法
void addChild(TreeNode* parent, TreeNode* child) {
parent->children.push_back(child);
}
// 检查是否有一方获胜
bool checkWin(TreeNode* node) {
char tempboard[3][3];
// 复制node->board到tempboard
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
tempboard[i][j] = node->board[i][j];
}
}
// 将tempboard上所有格子上的B棋子和C棋子修改成A棋子,将tempboard所有格子上的b棋子和c棋子修改成a棋子
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (tempboard[i][j] == 'B') tempboard[i][j] = 'A';
else if (tempboard[i][j] == 'C') tempboard[i][j] = 'A';
else if (tempboard[i][j] == 'b') tempboard[i][j] = 'a';
else if (tempboard[i][j] == 'c') tempboard[i][j] = 'a';
}
}
// 检查行、列和对角线,以确定当前玩家是否获胜
for (int i = 0; i < 3; ++i) {
if (tempboard[i][0] == tempboard[i][1] && tempboard[i][1] == tempboard[i][2] && tempboard[i][0] != ' ') return true;
if (tempboard[0][i] == tempboard[1][i] && tempboard[1][i] == tempboard[2][i] && tempboard[0][i] != ' ') return true;
}
if (tempboard[0][0] == tempboard[1][1] && tempboard[1][1] == tempboard[2][2] && tempboard[0][0] != ' ') return true;
if (tempboard[0][2] == tempboard[1][1] && tempboard[1][1] == tempboard[2][0] && tempboard[0][2] != ' ') return true;
return false;
}
// 输出棋盘状态
void printBoard(TreeNode* node) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
cout << node->board[i][j] << " ";
}
cout << endl;
}
}
void generateAllMoves(TreeNode* node, int depth, int maxDepth) {
if (depth >= maxDepth || checkWin(node)) { // 检查是否有一方获胜或达到最大深度
count++;
if (checkWin(node)) { // 如果当前节点是获胜节点
if (node->player == 0) { // ABC棋子的玩家获胜
node->flag = 1;
} else { // abc棋子的玩家获胜
node->flag = 2;
}
}
if (depth >= maxDepth) { // 如果达到最大深度,将flag设置为0
node->flag = 0;
}
if (depth >= maxDepth || checkWin(node)) { // 如果达到最大深度或当前节点是获胜节点
if(count%1000000==0) cout << "已研究的末端子节点总数为" << count << endl;
cout << "Flag值为:" << node->flag << endl; // 输出flag值
cout<<"已经使用的棋子总数"<<18-node->a-node->b-node->c-node->A-node->B-node->C<<endl;
cout<<"剩余棋子A为"<<node->A <<endl<<" 剩余棋子B为"<<node->B<<endl;
cout<<"剩余棋子C为"<<node->C <<endl<<" 剩余棋子a为"<<node->a<<endl;
cout<<"剩余棋子b为"<<node->b <<endl<<" 剩余棋子c为"<<node->c<<endl;
cout << "当前棋盘状态:" << endl;
printBoard(node);
// 输出棋盘状态
cout<<endl;
//if(node->flag!=0) system("pause");
}
return;
}
if (1==1) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (node->board[i][j] == ' ') {
if (node->player == 0) { // 甲玩家走棋
for (char piece : {'A', 'B', 'C'}) {
if (piece == 'A' && node->A > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->A--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'B' && node->B > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->B--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'C' && node->C > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->C--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
}
}
if (node->player == 1)
{
for (char piece : {'a', 'b', 'c'}) {
if (piece == 'a' && node->a > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->a--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'b' && node->b > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->b--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
} else if (piece == 'c' && node->c > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = piece;
child->c--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
}
}
}
else { // 处理替换(即套娃)操作
char currentPiece = node->board[i][j];
if (node->player == 0) { // 甲玩家走棋
if ((currentPiece == 'B'||currentPiece == 'b') && node->A > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'A';
child->A--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->A > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'A';
child->A--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->B > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'B';
child->B--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
} else { // 乙玩家走棋
if ((currentPiece == 'B'||currentPiece == 'b') && node->a > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'a';
child->a--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->b > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'b';
child->b--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
if ((currentPiece == 'C'||currentPiece == 'c') && node->a > 0) {
TreeNode* child = new TreeNode(*node);
child->player = 1 - node->player;
child->board[i][j] = 'a';
child->a--;
addChild(node, child);
generateAllMoves(child, depth + 1, maxDepth);
}
}
}
}
}
}
}
};
/*vector<int> checkcurrentstatus(TreeNode* node) {
vector<int> result;
char player_piece = (node->player == 0) ? 'A' : 'a'; // 根据当前玩家选择相应的棋子字符
int count_row[3] = {0}; // 记录每行的棋子数量
int count_col[3] = {0}; // 记录每列的棋子数量
int count_diag[2] = {0}; // 记录两个对角线的棋子数量
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (node->board[i][j] == player_piece) {
count_row[i]++;
count_col[j]++;
if (i == j) count_diag[0]++; // 主对角线
if (i + j == 2) count_diag[1]++; // 副对角线
}
}
}
// 检查行和列是否有至少一条有2个棋子的线段,并将满足条件的行或列编号加入结果列表中
for (int i = 0; i < 3; ++i) {
if (count_row[i] >= 2) result.push_back(i);
if (count_col[i] >= 2) result.push_back(i + 3); // 列编号加上3以区分行和列
}
// 检查对角线是否有至少一条有2个棋子的线段,并将满足条件的对角线编号加入结果列表中
if (count_diag[0] >= 2) result.push_back(6); // 主对角线编号为6
if (count_diag[1] >= 2) result.push_back(7); // 副对角线编号为7
return result; // 返回满足条件的行、列或对角线的编号列表,如果没有满足条件的则返回空列表
}*/
int main() {
MultiwayTree tree;
int maxDepth = 18; // 设置遍历深度为18,因为双方共有18个棋子
tree.generateAllMoves(tree.root, 0, maxDepth);
cout << "末端子节点总数为" << count<<endl;
return 0;
}