“弈棋悟道”是某次柯洁在接受采访时致年轻人的寄语,受此启发我便决定将这次的五子棋AI实验命名为弈悟计划。
由于本人水平有限,遂把重点放在了AI水平的优化上,因此界面和一些细微的功能就稍显简陋。
在本实验中,棋盘大小为16×16、没有禁手规则、默认玩家执黑先手、无时间限制,同时只支持重新开始但不支持悔棋功能(落子无悔嘛)。
但为了供玩家复盘对局有记录对局落子轨迹的功能,在对局结束后会生成trail.txt文件供查看。
最后的成果个人也比较满意,大概有略强于普通人的棋力水平了。
使用EasyX图形库来绘制棋子,棋盘和菜单的图片就直接用网图了。
同时支持通过检测鼠标点击位置来进行落子,力求一个方便。
这部分的难点在于控制参数来让棋子精准地落在格点上,要多次修改具体的像素值来力求比较好的效果。
关于这部分的代码实现详见代码中的Menu::ChessBoard
、Menu::Display
、Play::PlayGame
等函数模块。
这部分的算法在参考了网上的教程之后,确认了两个大致方向启发式搜素或蒙特卡洛树。
最后考虑到实现难度和个人代码水平,遂选择了比较简单的启发式搜素的算法。
算法的核心就是通过估价函数给不同的棋局状态进行打分。
AI会在所有可能的合法后继状态中选择一种对自己最有利的局面(打分最高的状态)。
然后再考虑假定人类一方也会用同样的想法在该局面的所有可能的合法后继状态选择一个对人类最有力的局面。
这种人类和AI相继行动的搜索过程可以用Min-Max搜索来实现。
而通过搜索层数的加深,AI的算力就会不断增强,但与此同时每一步的时间开销也会显著增加。
我们可以通过调节搜索深度(比如AI下一步后再考虑人类走一步就算两层搜素深度),来力求棋力和时间的平衡。
而这部分的具体模块划分如下:
状态扩展函数
其核心目标就是寻找某个局面的所有后继可能的合法后继状态。
虽然理论上我们在选择落子的时候可以在棋盘的任意一个空位,但实战中我们一般不会凭空乱下。
因此我们可以建立一个大前提,AI的落子以及它预设的玩家的落子都落在现有棋局的附近,而不会跑的太远。
具体地,我们设立一个扩展范围常量MAX_DIS
,规定每次落子的位置在距离它切比雪夫距离小于等于MAX_DIS
的位置上必须要有棋子(不论敌我)。
关于这部分的代码实现详见代码中的generate
函数模块。
评估函数
其核心目标就是对棋局的状态进行打分。
考虑我们如何公式化地评价一个棋局对某一方的优劣,直观地,我们可以统计并根据连子的数量来打分。
并且对于上下左右和主副对角线,我们都要分别这六个方向上的连子情况。
同时有过实战经验的人一定知道,对手的活三和活二带来的防守压力是完全不同的,因此我们还要对不同的棋型设计基础分值。
具体地,作如下的基础分值评估:
- 成五,100000000
- 活四,10000
- 活三,1000
- 活二,100
- 活一,10
而对于有一边被其它棋子堵住(或者被边界堵住)的棋子,则对应的分数降一档:
根据上面的想法我们只要把棋盘根据不同方向划分为一个个线性单元,然后分别统计即可。
关于这部分的代码实现详见代码中的evaluate
和calc
函数模块。
α-β剪枝
由于一般情况下Min-Max搜索树上的状态数很多,因此通过舍弃一些显然不优秀的搜素方向来优化运行效率。
我们可以将搜索树上的节点分为三类:
- 已遍历完自身及其子树,得到了自身的估值的点。不妨称为A类点。
- 未完全遍历完自身子树,但已有至少一个儿子已完全遍历并确定了估值,即儿子中至少有一个A类点。我们称这类点为B类点
- 未遍历该点或该点的儿子中未有已确定估值的,即无A类点儿子。我们称这类点为C类点(C类点可能有B类点儿子)。
而α-β剪枝就是针对B类点的剪枝操作,虽然B类点的自身的估值尚未确定,但由于其部分儿子的估值已经确定了,所以可以根据这些信息为B类点的估值范围做出限制,并依此排除不可能的分支。
而在实际运用中,通过引入α-β剪枝,可以将需要计算的状态数缩减到原来的110左右,极大地优化了算法的性能。
关于这部分的代码实现详见代码中的min_search
和max_search
函数模块。
由于α-β剪枝的复杂度很依赖于遍历的顺序,因此调整后继状态的搜索顺序使得我们可以多减去一些状态就成了优化性能的关键所在。
比如在Min搜索中,如果我们先搜索出了一个得分比较小的局面,那么显然后面的很多状态都可以被减去,而Max搜索中的情况同理。
因此我们可以通过给所有后继状态也进行评估,根据这个分数将后继状态排序后再进行搜索来大大优化性能。
关于这部分的代码实现详见代码中的min_search
和max_search
函数模块。
由于前面实现的评估函数调用一次的复杂度大概就是O(163)级别的,再乘上在评估后继状态时庞大的状态数的话就会导致程序的运行时间大大增长。
但我们仔细观察发现在实现上面的“给所有后继状态进行评估”的过程中,其实不同的状态之间只有一颗棋子的落点不同。
因此我们可以只考虑这颗棋子所带来的局面影响,而不用重复计算其它无关棋子的得分了(因为所有的待排序的后继状态的这些分数都是一样的)。
因此这部分的复杂度一下就变成了原来的116了,性能有了巨大的提升。
关于这部分的代码实现详见代码中的evaluate_point
函数模块。
由于我们在搜索过程中会有很多重复的搜索过程,比如:
和下面这种的走法只是顺序不同 ,最终走出来的局面是一样的:
前面的算法在遇到这两种情况时,会分别给两种状态都打一次分,而其实我们完全可以把这两种状态归到同一类中,这样后面遇到这种本质相同的状态时就可以直接调用了。
Zobrist 是一个快速Hash算法,非常适合用在各种棋类游戏中,其具体实现如下:
- 初始化两个
Zobrist[16][16]
的二维数组,分别用于黑棋和白棋的落子,同时设置一个键值来表示当前状态。
- 上述数组的每一个都填上一个
unsigned long long
范围的随机数。
- 每下一步棋,就用当前键值异或Zobrist数组里对应位置的随机数,得到的结果即为新的键值。如果是删除棋子(悔棋),则再异或一次即可。
因此我们只要在每次走棋的时候进行一次异或操作即可,根据文献证明这种做法可以保证极高的正确率将本质相同的操作归为一类。
关于这部分的代码实现详见代码中的Zobrist
函数模块以及所有落子的代码部分。
由于棋局战至中后盘后每次搜索的状态数就会激增,AI的应对速度就会越来越慢。
而很多时候当人类一方下出必须防守的棋型时(比如死四,活三),一般情况下电脑是必须要去防守的,不存在别的选项。
因此在每次AI进行搜索前可以预先判断下是否存在必须要防守的棋型,如果有的话就直接选择防守就可以省去搜索部分的时间了。
关于这部分的代码实现详见代码中的defense
函数模块。

对局前期大概可以做到0.5s~1s的落子速度。
中后盘随着局面复杂程度上升,思考时间大致在5s左右。
可执行程序的大小为6120KB。
大致为略强于普通人的水平(一般普通人思考四步后的情况,AI有搜索六步以后的能力)。
在模拟对局测试中刚开始个人被AI杀得体无完肤,后面找到了擅长五子棋的同学可以和AI战成平手。
总体来看棋力水平还是不错的。
#define _CRT_SECURE_NO_WARNINGS 1
#include "YiWu.h"
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <map>
using namespace std;
typedef unsigned long long ULL;
FILE *printer = fopen("trail.txt", "w");
int a[16][16] = {0};
ULL cur;
const int INF = 1e9;
ULL seed1[16][16], seed2[16][16];
void Zobrist(void) {
srand(time(0));
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 16; ++j) {
seed1[i][j] = 1LL * rand() * rand() * rand() * rand();
seed2[i][j] = 1LL * rand() * rand() * rand() * rand();
}
}
void place1(int col, int hang, int lie) {
if (col == 1)
setfillcolor(BLACK);
else
setfillcolor(WHITE);
solidcircle(lie, hang, 12);
a[hang / 30][lie / 30] = col;
cur ^= seed1[hang / 30][lie / 30];
}
void place2(int col, int x, int y) {
setfillcolor(WHITE);
a[x][y] = col;
cur ^= seed2[x][y];
solidcircle(20 + y * 30, 20 + x * 30, 12);
}
const int live[5] = {0, 10, 100, 1000, 10000};
const int dead[5] = {0, 1, 10, 100, 1000};
const int rush[6] = {0, 0, 5, 50, 500, 10000};
int calc(vector <int> v, int col) {
v.push_back(0);
for (int i = v.size() - 1; i > 0; --i)
v[i] = v[i - 1];
v[0] = 3 - col;
v.push_back(3 - col);
int lst = 0, ret = 0;
for (int i = 1; i < v.size(); ++i) {
if (v[i] != col) {
int num = i - 1 - lst;
if (num == 5)
ret += 100000000;
if (!v[lst] && !v[i])
ret += live[num];
else if (!v[lst] || !v[i])
ret += dead[num];
lst = i;
} else if (v[i] == 0 && v[i - 1] == col && v[i + 1] == col) {
int L;
for (L = i - 1; L > 0 && v[L] == col; --L);
int R;
for (R = i + 1; R < v.size() && v[R] == col; ++R);
ret += rush[min(i - 1 - L + R - i - 1, 5)];
}
}
return ret;
}
int evaluate(void) {
int ret = 0;
vector <int> v;
for (int i = 0; i < 16; ++i) {
v.clear();
for (int j = 0; j < 16; ++j)
v.push_back(a[i][j]);
ret += calc(v, 2) - calc(v, 1);
}
for (int j = 0; j < 16; ++j) {
v.clear();
for (int i = 0; i < 16; ++i)
v.push_back(a[i][j]);
ret += calc(v, 2) - calc(v, 1);
}
for (int i = 0; i < 16; ++i) {
v.clear();
for (int x = i, y = 0; x < 16 && y < 16; ++x, ++y)
v.push_back(a[x][y]);
ret += calc(v, 2) - calc(v, 1);
}
for (int j = 1; j < 16; ++j) {
v.clear();
for (int x = 0, y = j; x < 16 && y < 16; ++x, ++y)
v.push_back(a[x][y]);
ret += calc(v, 2) - calc(v, 1);
}
for (int i = 0; i < 16; ++i) {
v.clear();
for (int x = i, y = 15; x < 16 && y >= 0; ++x, --y)
v.push_back(a[x][y]);
ret += calc(v, 2) - calc(v, 1);
}
for (int j = 0; j < 15; ++j) {
v.clear();
for (int x = 0, y = j; x < 16 && y >= 0; ++x, --y)
v.push_back(a[x][y]);
ret += calc(v, 2) - calc(v, 1);
}
return ret;
}
int evaluate_point(int x, int y) {
int ret = 0;
vector <int> v;
v.clear();
for (int i = 0; i < 16; ++i)
v.push_back(a[x][i]);
ret += calc(v, 2) - calc(v, 1);
v.clear();
for (int i = 0; i < 16; ++i)
v.push_back(a[i][y]);
ret += calc(v, 2) - calc(v, 1);
int stx = x, sty = y;
while (stx > 0 && sty > 0)
--stx, --sty;
v.clear();
for (; stx < 16 && sty < 16; ++stx, ++sty)
v.push_back(a[stx][sty]);
ret += calc(v, 2) - calc(v, 1);
stx = x, sty = y;
while (stx > 0 && sty < 15)
--stx, ++sty;
v.clear();
for (; stx < 16 && sty >= 0; ++stx, --sty)
v.push_back(a[stx][sty]);
ret += calc(v, 2) - calc(v, 1);
return ret;
}
struct Data {
int x, y, val;
};
vector <Data> generate(int dep) {
vector <Data> v;
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 16; ++j) {
if (a[i][j])
continue;
bool flag = 0;
for (int p = max(0, i - 1); p <= min(15, i + 1) && !flag; ++p)
for (int q = max(0, j - 1); q <= min(15, j + 1) && !flag; ++q)
if (a[p][q])
v.push_back({i, j, 0}), flag = 1;
if (dep < 2 || flag)
continue;
for (int p = max(0, i - 2); p <= min(15, i + 2) && !flag; ++p)
for (int q = max(0, j - 2); q <= min(15, j + 2) && !flag; ++q)
if (a[p][q])
v.push_back({i, j, 0}), flag = 1;
}
return v;
}
bool cmp_min(const Data &A, const Data &B) {
return A.val < B.val;
}
bool cmp_max(const Data &A, const Data &B) {
return A.val > B.val;
}
const int MAX_DEEP = 6, CK_DEEP = 12, MAX_DIS = 1;
int nxt_x, nxt_y, cnt;
bool count(int x, int y, int dx, int dy, int num, int col) {
for (int i = 1; i <= num; ++i) {
int nx = x + i * dx, ny = y + i * dy;
if (nx < 0 || nx > 15 || ny < 0 || ny > 15)
return 0;
if (a[nx][ny] != col)
return 0;
}
return 1;
}
void defense(void) {
bool flag = 0;
for (int i = 0; i < 16 && !flag; ++i)
for (int j = 0; j < 16 && !flag; ++j) {
if (a[i][j])
continue;
for (int k = 4; k >= 0 && !flag; --k) {
if (count(i, j, -1, 0, 4, 1) && count(i, j, 1, 0, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, 1, 0, 4, 1) && count(i, j, -1, 0, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, 0, -1, 4, 1) && count(i, j, 0, 1, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, 0, 1, 4, 1) && count(i, j, 0, -1, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, -1, -1, 4, 1) && count(i, j, 1, 1, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, -1, 1, 4, 1) && count(i, j, 1, -1, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, 1, -1, 4, 1) && count(i, j, -1, 1, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
if (count(i, j, 1, 1, 4, 1) && count(i, j, -1, -1, 4 - k, 1))
nxt_x = i, nxt_y = j, flag = 1;
}
}
}
int min_search(int dep, int alpha, int beta, ULL sts);
int max_search(int dep, int alpha, int beta, ULL sts);
map <ULL, int> status;
int min_search(int dep, int alpha, int beta, ULL sts) {
++cnt;
if (dep <= 0) {
if (status.count(sts))
return status[sts];
return status[sts] = evaluate();
}
vector <Data> nxt = generate(MAX_DIS);
for (int i = 0; i < nxt.size(); ++i) {
int pre = evaluate_point(nxt[i].x, nxt[i].y);
a[nxt[i].x][nxt[i].y] = 1;
nxt[i].val = evaluate_point(nxt[i].x, nxt[i].y) - pre;
a[nxt[i].x][nxt[i].y] = 0;
}
sort(nxt.begin(), nxt.end(), cmp_min);
int best = INF;
for (auto it : nxt) {
int x = it.x, y = it.y, val = it.val;
a[x][y] = 1;
sts ^= seed1[x][y];
int cur = max_search(dep - 1, min(best, alpha), beta, sts);
a[x][y] = 0;
sts ^= seed1[x][y];
if (cur < best)
best = cur;
if (best < beta)
break;
}
return best;
}
int max_search(int dep, int alpha, int beta, ULL sts) {
++cnt;
if (dep <= 0) {
if (status.count(sts))
return status[sts];
return status[sts] = evaluate();
}
vector <Data> nxt = generate(MAX_DIS);
for (int i = 0; i < nxt.size(); ++i) {
int pre = evaluate_point(nxt[i].x, nxt[i].y);
a[nxt[i].x][nxt[i].y] = 2;
nxt[i].val = evaluate_point(nxt[i].x, nxt[i].y) - pre;
a[nxt[i].x][nxt[i].y] = 0;
}
sort(nxt.begin(), nxt.end(), cmp_max);
int best = -INF, tx, ty;
for (auto it : nxt) {
int x = it.x, y = it.y, val = it.val;
a[x][y] = 2;
sts ^= seed2[x][y];
int cur = min_search(dep - 1, alpha, max(beta, best), sts);
a[x][y] = 0;
sts ^= seed2[x][y];
if (cur > best)
best = cur, tx = x, ty = y;
if (best > alpha)
break;
}
if (dep == MAX_DEEP)
nxt_x = tx, nxt_y = ty;
return best;
}
void trail(int x, int y, int col) {
if (col == 1)
fprintf(printer, "Play Set: Row %d Col %d\n", x, y);
else
fprintf(printer, "AI Set: Row %d Col %d\n", x, y);
}
void Play::PlayGame(MOUSEMSG ms) {
int success = 1;
while (success) {
ms = GetMouseMsg();
for (int lie = 20; lie <= 490; lie += 30) {
if (ms.x <= lie + 15 && ms.x >= lie - 15) {
for (int hang = 20; hang <= 490; hang += 30) {
if (ms.y <= hang + 15 && ms.y >= hang - 15) {
if (a[hang / 30][lie / 30] == 0) {
place1(1, hang, lie);
success = 0;
trail(hang / 30, lie / 30, 1);
break;
}
}
}
}
}
}
int win = Play().Win();
if (win == 1) {
return;
}
nxt_x = nxt_y = -1;
defense();
if (!~nxt_x) {
if (!~nxt_x) {
cnt = 0;
max_search(MAX_DEEP, INF, -INF, cur);
}
}
place2(2, nxt_x, nxt_y);
trail(nxt_x, nxt_y, 2);
win = Play().Win();
if (win == 2) {
return;
}
}
void Menu::Display() {
initgraph(416, 624, SHOWCONSOLE);
IMAGE img;
setaspectratio(1.1, 1);
loadimage(&img, "init.png", 377, 624, 1);
putimage(0, 0, &img);
MOUSEMSG m;
while (true) {
m = GetMouseMsg();
if (m.uMsg == WM_LBUTTONDOWN && (m.x >= 72 && m.x <= 307 && m.y >= 340 && m.y <= 400
|| m.x >= 72 && m.x <= 307 && m.y >= 420 && m.y <= 480)) {
if (m.x >= 72 && m.x <= 307 && m.y >= 340 && m.y <= 400) {
setlinecolor(YELLOW);
setlinestyle(PS_SOLID | PS_JOIN_ROUND, 2);
rectangle(72, 340, 300, 400);
} else if (m.x >= 72 && m.x <= 307 && m.y >= 420 && m.y <= 480) {
setlinecolor(YELLOW);
rectangle(72, 420, 300, 480);
}
Sleep(500);
cleardevice();
Sleep(300);
closegraph();
Menu().ChessBoard(m);
break;
}
}
}
void Menu::ChessBoard(MOUSEMSG m) {
initgraph(665, 490, SHOWCONSOLE);
IMAGE img;
loadimage(&img, "chessborad.png", 665, 490);
putimage(0, 0, &img);
while (true) {
for (int i = 20; i <= 470; i += 30) {
setlinecolor(WHITE);
line(20, i, 470, i);
line(i, 20, i, 470);
}
if (m.uMsg == WM_LBUTTONDOWN && m.x >= 72 && m.x <= 307 && m.y >= 420 && m.y <= 480) {
Play().TwoPlayerGame(m);
} else {
Play().ComputerUserGame(m);
}
}
}
void Play::buttonRingth(MOUSEMSG m, MOUSEMSG ms, int win) {
if (ms.x >= 500 && ms.x <= 655 && ms.y >= 30 && ms.y <= 80) {
memset(a, 0, sizeof(a));
setlinecolor(RED);
rectangle(500, 30, 655, 80);
Sleep(300);
Menu().ChessBoard(m);
} else if (ms.x >= 500 && ms.x <= 655 && ms.y >= 115 && ms.y <= 165) {
memset(a, 0, sizeof(a));
setlinecolor(RED);
rectangle(500, 115, 655, 165);
Sleep(300);
Menu().Display();
} else if (win == 0 && ms.x >= 500 && ms.x <= 655 && ms.y >= 200 && ms.y <= 250) {
setlinecolor(RED);
rectangle(500, 200, 655, 250);
}
}
void Play::displayWin(int n1, int n2) {
memset(a, 0, sizeof(a));
IMAGE img;
if (n1 == 0 && n2 == 0)
loadimage(&img, "victory.jpg", 700, 600);
if (n1 == 0 && n2 == 1)
loadimage(&img, "defeat.jpg", 700, 600);
if (n1 == 1 && n2 == 0)
loadimage(&img, "victory.jpg", 700, 600);
if (n1 == 1 && n2 == 1)
loadimage(&img, "defeat.jpg", 700, 600);
putimage(0, 0, &img);
MOUSEMSG m;
while (1) {
m = GetMouseMsg();
if (m.uMsg == WM_LBUTTONDOWN && m.x >= 215 && m.x <= 270 && m.y >= 285 && m.y <= 320) {
setlinecolor(YELLOW);
rectangle(215, 285, 270, 320);
Sleep(300);
Menu().Display();
break;
} else if (m.uMsg == WM_LBUTTONDOWN)
exit(0);
}
}
void Play::TwoPlayerGame(MOUSEMSG m) {
int win = 0;
int play1 = 1, play2 = 0;
MOUSEMSG ms;
while (win == 0) {
ms = GetMouseMsg();
if (ms.uMsg == WM_LBUTTONDOWN) {
buttonRingth(m, ms, win);
for (int lie = 20; lie <= 490; lie += 30) {
if (ms.x <= lie + 15 && ms.x >= lie - 15) {
for (int hang = 20; hang <= 490; hang += 30) {
if (ms.y <= hang + 15 && ms.y >= hang - 15) {
if (play1 == 1 && a[hang / 30 - 1][lie / 30 - 1] == 0) {
place1(1, hang, lie);
play1 = 0;
break;
}
if (play1 == 0 && a[hang / 30 - 1][lie / 30 - 1] == 0) {
place1(2, hang, lie);
play1 = 1;
break;
}
}
}
}
}
win = Play().Win();
if (win == 1) {
displayWin(0, 0);
break;
} else if (win == 2) {
displayWin(0, 1);
break;
}
}
}
}
void Play::ComputerUserGame(MOUSEMSG m) {
int win = 0;
Zobrist();
MOUSEMSG ms;
while (win == 0) {
ms = GetMouseMsg();
if (ms.uMsg == WM_LBUTTONDOWN) {
buttonRingth(m, ms, win);
Play::PlayGame(ms);
win = Play().Win();
if (win == 1) {
displayWin(1, 0);
break;
} else if (win == 2) {
displayWin(1, 1);
break;
}
}
}
}
int Play::Win() {
int win = 0;
for (int j = 0; j < 16 && (win == 0); j++) {
for (int i = 0; i < 16; i++) {
if ((a[j][i] == 1 && a[j][i + 1] == 1 && a[j][i + 2] == 1 && a[j][i + 3] == 1 && a[j][i + 4] == 1)
|| (a[i][j] == 1 && a[i + 1][j] == 1 && a[i + 2][j] == 1 && a[i + 3][j] == 1 &&
a[i + 4][j] == 1)) {
win = 1;
Sleep(100);
break;
}
if ((a[j][i] == 2 && a[j][i + 1] == 2 && a[j][i + 2] == 2 && a[j][i + 3] == 2 && a[j][i + 4] == 2)
|| (a[i][j] == 2 && a[i + 1][j] == 2 && a[i + 2][j] == 2 && a[i + 3][j] == 2 &&
a[i + 4][j] == 2)) {
win = 2;
Sleep(100);
break;
}
}
}
for (int j = 0; j < 12 && (win == 0); j++) {
for (int i = 0; i < 12; i++) {
if (a[j][i] == 1 && a[j + 1][i + 1] == 1 && a[j + 2][i + 2] == 1 && a[j + 3][i + 3] == 1 &&
a[j + 4][i + 4] == 1) {
win = 1;
Sleep(100);
break;
}
if (a[j][i] == 2 && a[j + 1][i + 1] == 2 && a[j + 2][i + 2] == 2 && a[j + 3][i + 3] == 2 &&
a[j + 4][i + 4] == 2) {
win = 2;
Sleep(100);
break;
}
}
for (int i = 4; i < 16 && (win == 0); i++) {
if (a[j][i] == 1 && a[j + 1][i - 1] == 1 && a[j + 2][i - 2] == 1 && a[j + 3][i - 3] == 1 &&
a[j + 4][i - 4] == 1) {
win = 1;
Sleep(100);
break;
}
if (a[j][i] == 2 && a[j + 1][i - 1] == 2 && a[j + 2][i - 2] == 2 && a[j + 3][i - 3] == 2 &&
a[j + 4][i - 4] == 2) {
win = 2;
Sleep(100);
break;
}
}
}
return win;
}
- 将代码模块化重构以得到更好的阅读体验。
- 增加更多功能,比如支持选择黑白棋,增加禁手规则,增加悔棋功能。
- 增加算杀模块,提高AI在处理后期棋局时的速度。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律