受到哈佛大学CS50.3 人工智能导论课的启发,写了这个demo,用程序证明了在玩家双方都十分聪明的情况下,井字棋必平局。不过由于井字棋的机制实在太简单了,以人类算力就可以轻松得出出必平局的结论。不过这种对抗博弈的思路可以迁移到几乎任何类似的过程中,后续可能会以此为框架探究一些其他棋牌类游戏的必胜法。
原课程B站链接:哈佛大学CS50.3 人工智能导论课
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//棋盘状态
class State
{
public:
int data[3][3];
State()
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
this->data[i][j] = 0;
}
}
}
};
//打印当前棋盘状态,调试用
void print(State state)
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
cout << state.data[i][j] << " ";
}
cout << endl;
}
cout << "------------------------------------" << endl;;
}
//三元组,表示一个下棋动作,三个量分别表示“落子横坐标”“落子纵坐标”“棋子值”
typedef struct Triple
{
int data[3];
Triple(int a, int b, int c)
{
data[0] = a;
data[1] = b;
data[2] = c;
}
};
//判断下一步该谁走,返回玩家名字字符串
string Player(State state)
{
int n_min = 0, n_max = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (state.data[i][j] == 1)n_max++;
if (state.data[i][j] == -1)n_min++;
}
}
if (n_max <= n_min)return "Max";
else return "Min";
}
//输入一个state和轮到谁下棋,返回所有可能操作的三元组向量
vector<Triple> Actions(State state, string player)
{
int t = 0;
vector<Triple> res;
if (player == "Max")t = 1;
else t = -1;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (state.data[i][j] == 0)
{
res.push_back(Triple(i, j, t));
}
}
}
return res;
}
//输入一个state和一个动作三元组,返回更新后的state
State Result(State state, Triple triple)
{
int x = triple.data[0];
int y = triple.data[1];
int value = triple.data[2];
state.data[x][y] = value;
//print(state);
return state;
}
//根据输入的state判断游戏是否结束,若结束返回true,否则返回false
bool Terminal(State state)
{
int count = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (state.data[i][j] != 0)count++;
}
}
if (count == 9)return true;
int a[8][3][2] =
{
{{0, 0}, {0, 1}, {0, 2}},
{{1, 0}, {1, 1}, {1, 2}},
{{2, 0}, {2, 1}, {2, 2}},
{{0, 0}, {1, 0}, {2, 0}},
{{0, 1}, {1, 1}, {2, 1}},
{{0, 2}, {1, 2}, {2, 2}},
{{0, 0}, {1, 1}, {2, 2}},
{{0, 2}, {1, 1}, {2, 0}}
};
int ju1 = 0, ju2 = 0, ju3 = 0;
for (int i = 0; i < 8; i++)
{
ju1 = state.data[a[i][0][0]][a[i][0][1]];
ju2 = state.data[a[i][1][0]][a[i][1][1]];
ju3 = state.data[a[i][2][0]][a[i][2][1]];
if (ju1 == ju2 && ju2 == ju3)
{
if (ju1 != 0)return true;
}
}
return false;
}
//计算当前棋盘的得分值
int Utility(State state)
{
int a[8][3][2] =
{
{{0, 0}, {0, 1}, {0, 2}},
{{1, 0}, {1, 1}, {1, 2}},
{{2, 0}, {2, 1}, {2, 2}},
{{0, 0}, {1, 0}, {2, 0}},
{{0, 1}, {1, 1}, {2, 1}},
{{0, 2}, {1, 2}, {2, 2}},
{{0, 0}, {1, 1}, {2, 2}},
{{0, 2}, {1, 1}, {2, 0}}
};
int ju1 = 0, ju2 = 0, ju3 = 0, v = 0;
for (int i = 0; i < 8; i++)
{
ju1 = state.data[a[i][0][0]][a[i][0][1]];
ju2 = state.data[a[i][1][0]][a[i][1][1]];
ju3 = state.data[a[i][2][0]][a[i][2][1]];
if (ju1 == ju2 && ju2 == ju3)
{
if (ju1 == 1)v = 1;
else if (ju1 == -1)v = -1;
else v = 0;
}
}
return v;
}
//工具函数,作用是比较两数
int Maxx(int a, int b)
{
if (a > b)return a;
else return b;
}
int Minn(int a, int b)
{
if (a < b)return a;
else return b;
}
//极大值玩家函数
int Max_Value(State state);
//极小值玩家函数
int Min_Value(State state);
int Max_Value(State state)
{
//递归出口,即游戏结束
if (Terminal(state) == true)
{
//结算分数
return Utility(state);
}
//取尽量小值作为v的初始值
int v=-100;
//求出当前棋盘下所有可能的操作
vector<Triple> all_actions = Actions(state, Player(state));
//遍历所有可能的操作,考虑Min玩家下一步应该怎么会做来更新v值
for (int i = 0; i < all_actions.size(); i++)
{
v = Maxx(v, Min_Value(Result(state, all_actions[i])));
}
return v;
}
int Min_Value(State state)
{
//和Max玩家同理
if (Terminal(state) == true)
{
return Utility(state);
}
int v = 100;
vector<Triple> all_actions = Actions(state, Player(state));
for (int i = 0; i < all_actions.size(); i++)
{
v = Minn(v, Max_Value(Result(state, all_actions[i])));
}
return v;
}
int main()
{
//初始棋盘
State s;
//Min玩家先手
int res=Min_Value(s);
//输出res值,意义为res=1:Max玩家必胜,res=0:必平局,res=-1:Min玩家必胜
cout << res;
return 0;
}