基于bfs的五子棋AI v1.2(含完整代码)
网上大部分五子棋引擎,都是基于dfs进行ab剪枝,但我要给大家介绍另一种方法,基于bfs扩展节点。
1.思路
思路很简单:一开始的局面作为根节点,然后一层一层地不断向下扩展。每个节点评分后,向上minmax,直到根节点为止。
2.优化
[ 重要 ] \color{red}\small[重要] [重要] 节点储存优化:在每个节点储存整个棋盘会浪费大量内存。其实,只储存了一个落子位置就够了。因为思考层数不深,bfs时,只需要从叶子节点向上遍历到根节点,就可以还原当时的棋盘状态了。
[ 重要 ] \color{red}\small[重要] [重要] 搜索顺序优化:一层一层地暴力搜索,会浪费大量时间在无用的节点上。因此,我们可以优先搜索评分最高的(min节点先搜最低的),合理分配时间。实现:权值减去访问量,得到优先级。(为了给后面的节点一些机会)
评分常数优化:扩展节点时,不需要评估整个棋盘。只需要评估落子位置一圈的棋子即可。还可以通过搜索预处理一些权重信息,进一步提高速度。
上代码
v1.2
-
把结构体内的
bestval
和dep
变量删除了,因为用处不大。dep
在扩展的时候顺便记一下就行了。bestval
也可以递归得到 -
把
fa
(父节点)列表也删除了,因为只需要向下扩展,记了父节点也没什么用。
#include<bits/stdc++.h>
#include<windows.h>
#pragma GCC optimize(1,2,3,"Ofast","inline")
#pragma G++ optimize(1,2,3,"Ofast","inline")
using namespace std;
// 权重
const short W[15][3] = {{0,0,0},{8,4,0},{24,12,0},{80,40,0},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999}};
short weight1[65536], weight2[65536]; // 1黑 2白
namespace Winit {
int b[9];
int get(int x,int color) {
int con=0, stop=0;
for(int i=x+1 ;; i++) {
if(i<0 || i>8) {
stop++;
break;
}
if(b[i]==color) con++;
else {
if(b[i]==3-color || b[i]==3) stop++;
break;
}
}
for(int i=x-1 ;; i--) {
if(i<0 || i>8) {
stop++;
break;
}
if(b[i]==color) con++;
else {
if(b[i]==3-color || b[i]==3) stop++;
break;
}
}
return W[con][stop];
}
short sc() {
short rv=0;
for(int i=0; i<9; i++) {
if(b[i] == 1) rv -= get(i,1)<<1;
else if(b[i] ==2) rv += get(i,2);
}
return rv;
}
void dfs(int dep) {
if(dep == 8) {
int tmp=sc();
b[4]=1;
weight1[b[0]<<14 | b[1]<<12 | b[2]<<10 | b[3]<<8 | b[5]<<6 | b[6]<<4 | b[7]<<2 | b[8]] = sc()-tmp;
b[4]=2;
weight2[b[0]<<14 | b[1]<<12 | b[2]<<10 | b[3]<<8 | b[5]<<6 | b[6]<<4 | b[7]<<2 | b[8]] = sc()-tmp;
b[4]=0;
return;
}
for(int i=0; i<=3; i++) b[dep+(dep>=4)]=i, dfs(dep+1); // 0 空 1 黑 2 白 3 墙
}
}
int board[32][32], Time;
void UI() {//输出
cout << "-------------------------------" << endl;
for(int i=1; i<=15; i++) {
cout << "|";
for(int j=1; j<=15; j++) {
if(board[i][j] == 0) cout << " |";
else if(board[i][j] == 1) cout << "●|";
else cout << "○|";
}
cout << endl << "-------------------------------" << endl;
}
}
int get(int &x,int &y,int xx,int yy,int color) { //得到x,y往xx,yy方向的评分
int con = 0,stop = 0;
for(int i=x+xx,j=y+yy ;; i+=xx,j+=yy) {
if(i<1 || i>15 || j<1 || j>15) {
stop++;
break;
}
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
for(int i=x-xx,j=y-yy ;; i-=xx,j-=yy) {
if(i<1 || i>15 || j<1 || j>15) {
stop++;
break;
}
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
return W[min(4,con)][stop];
}
short calc() { //计算整个棋盘的权重
short a=0, b=0;
for(int i=1; i<=15; i++) {
for(int j=1; j<=15; j++) {
if(board[i][j] == 2) a+=get(i,j,-1,-1,2)+get(i,j,-1,0,2)+get(i,j,-1,1,2)+get(i,j,0,-1,2);
else if(board[i][j] == 1) b+=get(i,j,-1,-1,1)+get(i,j,-1,0,1)+get(i,j,-1,1,1)+get(i,j,0,-1,1);
}
}
return a - (b<<1);
}
const int F[8][2]= {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
inline short minicalc(int &x, int &y, int color) { //计算某点周围的权重
short rv=0;
#define g(p) ((x+F[i][0]*p<1||x+F[i][0]*p>15||y+F[i][1]*p<1||y+F[i][1]*p>15)?3:board[x+F[i][0]*p][y+F[i][1]*p])
if(color == 1)
for(int i=0; i<4; i++)
rv += weight1[g(-4)<<14 | g(-3)<<12 | g(-2)<<10 | g(-1)<<8 | g(1)<<6 | g(2)<<4 | g(3)<<2 | g(4)];
else
for(int i=0; i<4; i++)
rv += weight2[g(-4)<<14 | g(-3)<<12 | g(-2)<<10 | g(-1)<<8 | g(1)<<6 | g(2)<<4 | g(3)<<2 | g(4)];
#undef g
return rv;
}
inline int find(int &x,int &y) {
#define ck(xx,yy) (x+xx>0 && y+yy>0 && x+xx<=15 && y+yy<=15 && board[x+xx][y+yy]!=0)
return ck(-1,-1)||ck(-1,0)||ck(-1,1)||ck(0,-1)||ck(0,1)||ck(1,-1)||ck(1,0)||ck(1,1)||ck(-2,0)||ck(2,0)||ck(0,-2)||ck(0,2);
#undef ck
}
#pragma pack(1)
// unsigned char 不是字符,单纯是为了省内存
struct node {
unsigned char move; // 落子位置
short val; // 当前局面评分
unsigned char best; // 最佳儿子编号
int vis; // 访问次数
};
#pragma pack()
#define NODE_MAX 60000000
node *s; // 节点储存
/*
子节点都是连续的,因此可以节省一些内存
cd_l[i] 到 (cd_l[i]+cd_cnt[i]) 之间是 i 的子节点。
*/
int *cd_l;
unsigned char *cd_cnt;
int dep, path[1005];
int bv(int idx){ // get bestval
while(s[idx].best != 255) idx=cd_l[idx]+s[idx].best;
return s[idx].val;
}
int select() {
int i=0;
dep=0;
while(~cd_l[i]) { // 一直向下找到叶子节点
s[i].vis++, path[dep++]=i;
if(dep&1) {
int maxn=INT_MIN, maxi=-1;
for(int j=cd_l[i]; j<=cd_l[i]+cd_cnt[i]; j++) {
if(abs(s[j].val) < 1000) {
int val = (int)s[j].val-(s[j].vis<<3);
if(val>maxn) maxn=val, maxi=j;
}
}
if(maxi == -1) return -1; // 必输或必胜局面,没有任何节点可以扩展
i=maxi, board[s[i].move>>4][s[i].move&15]=2;
} else {
int maxn=INT_MAX, maxi=-1;
for(int j=cd_l[i]; j<=cd_l[i]+cd_cnt[i]; j++) {
if(abs(s[j].val) < 1000) {
int val = (int)s[j].val+(s[j].vis<<3);
if(val<maxn) maxn=val, maxi=j;
}
}
if(maxi == -1) return -1; // 必输或必胜局面,没有任何节点可以扩展
i=maxi, board[s[i].move>>4][s[i].move&15]=1;
}
}
path[dep]=i;
return i;
}
int AI() {
int cnt=0, maxdep=0;
int st=clock();
s[0] = {0,calc(),255,0};
cd_l[0] = -1;
while(clock()-st < Time*CLOCKS_PER_SEC && cnt<NODE_MAX-230) {
int idx=select();
if(idx == -1) break; // 必输或必胜局面,没有任何节点可以扩展
maxdep = max(maxdep, dep+1);
cd_l[idx]=cnt+1;
// 遍历棋盘
for(int i=1; i<=15; i++) {
for(int j=1; j<=15; j++) {
if(find(i,j) && board[i][j] == 0) {
// 创建节点
s[++cnt].move=(i<<4|j), s[cnt].best=255, s[cnt].vis=0;
cd_l[cnt] = -1;
// 评分
s[cnt].val=s[idx].val+minicalc(i,j,(dep&1?1:2));
// 迭代
path[dep+1] = cnt;
for(int p=dep+1; p>=1; p--) {
int &k=path[p], &f=path[p-1];
if(p&1) { // max 节点
if(bv(k) > bv(f)) {
s[f].best=k-cd_l[f];
} else if(s[f].best == k-cd_l[f]) {
for(int p=cd_l[f]; p<=cd_l[f]+cd_cnt[f]; p++) {
if(bv(p) > bv(f))
s[f].best=p-cd_l[f];
}
} else break;
} else { // min 节点
if(bv(k) < bv(f)) {
s[f].best=k-cd_l[f];
} else if(s[f].best == k-cd_l[f]) {
for(int p=cd_l[f]; p<=cd_l[f]+cd_cnt[f]; p++) {
if(bv(p) < bv(f))
s[f].best=p-cd_l[f];
}
} else break;
}
}
}
}
}
cd_cnt[idx]=cnt-cd_l[idx];
// 恢复棋盘
for(int i=dep; i>=1; i--) board[s[path[i]].move>>4][s[path[i]].move&15]=0;
}
cout << "node:" << cnt << " " << endl;
cout << "maxdep:" << maxdep << " " << endl;
cout << "bestval:" << bv(0) << " " << endl;
return s[cd_l[0]+s[0].best].move;
}
signed main() {
SetConsoleOutputCP(CP_UTF8); // 设置格式为 UTF-8,输出中文不会乱码
cout << "正在初始化...\n";
Winit::dfs(0);
// 分配内存
s = (node*)malloc((sizeof(node))*NODE_MAX);
cd_l = (int*)malloc((sizeof(int))*NODE_MAX);
cd_cnt = (unsigned char*)malloc((sizeof(unsigned char))*NODE_MAX);
// 游戏
int x,y;
cout<<"1 初级:思考1秒\n2 中级:思考5秒\n3 高级:思考10秒\n";
int d=-1;
cin >> d;
while(d<1 || d>3) cout<<"输入错误,请重新输入:", cin>>d;
if(d == 1) Time=1;
else if(d == 2) Time=5;
else Time=10;
while(1) {
UI();
cin>>x>>y;
board[x][y]=1;
if(calc() < -1000) {
UI(), cout<<"玩家胜!\n";
system("pause");
return 0;
}
int ai=AI();
board[ai>>4][ai&15]=2;
if(calc() > 1000) {
UI(), cout<<"AI胜!\n";
system("pause");
return 0;
}
}
return 0;
}
v1.1
-
预处理权重,使评分函数几乎为 O ( 1 ) O(1) O(1) 复杂度
-
评分使用
short
储存,落子位置用unsigned char
储存,节省内存。BFS 太费内存了
#include<bits/stdc++.h>
#include<windows.h>
#pragma GCC optimize(1,2,3,"Ofast","inline")
#pragma G++ optimize(1,2,3,"Ofast","inline")
using namespace std;
// 权重
const short W[15][3] = {{0,0,0},{8,4,0},{24,12,0},{80,40,0},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999},{999,999,999}};
short weight1[65536], weight2[65536]; // 1黑 2白
namespace Winit {
int b[9];
int get(int x,int color) {
int con=0, stop=0;
for(int i=x+1 ;; i++) {
if(i<0 || i>8) {
stop++;
break;
}
if(b[i]==color) con++;
else {
if(b[i]==3-color || b[i]==3) stop++;
break;
}
}
for(int i=x-1 ;; i--) {
if(i<0 || i>8) {
stop++;
break;
}
if(b[i]==color) con++;
else {
if(b[i]==3-color || b[i]==3) stop++;
break;
}
}
return W[con][stop];
}
short sc() {
short rv=0;
for(int i=0; i<9; i++) {
if(b[i] == 1) rv -= get(i,1)<<1;
else if(b[i] ==2) rv += get(i,2);
}
return rv;
}
void dfs(int dep) {
if(dep == 8) {
int tmp=sc();
b[4]=1;
weight1[b[0]<<14 | b[1]<<12 | b[2]<<10 | b[3]<<8 | b[5]<<6 | b[6]<<4 | b[7]<<2 | b[8]] = sc()-tmp;
b[4]=2;
weight2[b[0]<<14 | b[1]<<12 | b[2]<<10 | b[3]<<8 | b[5]<<6 | b[6]<<4 | b[7]<<2 | b[8]] = sc()-tmp;
b[4]=0;
return;
}
for(int i=0; i<=3; i++) b[dep+(dep>=4)]=i, dfs(dep+1); // 0 空 1 黑 2 白 3 墙
}
}
int board[32][32], Time;
void UI() {//输出
cout << "-------------------------------" << endl;
for(int i=1; i<=15; i++) {
cout << "|";
for(int j=1; j<=15; j++) {
if(board[i][j] == 0) cout << " |";
else if(board[i][j] == 1) cout << "●|";
else cout << "○|";
}
cout << endl << "-------------------------------" << endl;
}
}
int get(int &x,int &y,int xx,int yy,int color) { //得到x,y往xx,yy方向的评分
int con = 0,stop = 0;
for(int i=x+xx,j=y+yy ;; i+=xx,j+=yy) {
if(i<1 || i>15 || j<1 || j>15) {
stop++;
break;
}
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
for(int i=x-xx,j=y-yy ;; i-=xx,j-=yy) {
if(i<1 || i>15 || j<1 || j>15) {
stop++;
break;
}
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
return W[min(4,con)][stop];
}
short calc() { //计算整个棋盘的权重
short a=0, b=0;
for(int i=1; i<=15; i++) {
for(int j=1; j<=15; j++) {
if(board[i][j] == 2) a+=get(i,j,-1,-1,2)+get(i,j,-1,0,2)+get(i,j,-1,1,2)+get(i,j,0,-1,2);
else if(board[i][j] == 1) b+=get(i,j,-1,-1,1)+get(i,j,-1,0,1)+get(i,j,-1,1,1)+get(i,j,0,-1,1);
}
}
return a - (b<<1);
}
const int F[8][2]= {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
inline short minicalc(int &x, int &y, int color) { //计算某点周围的权重
short rv=0;
#define g(p) ((x+F[i][0]*p<1||x+F[i][0]*p>15||y+F[i][1]*p<1||y+F[i][1]*p>15)?3:board[x+F[i][0]*p][y+F[i][1]*p])
if(color == 1)
for(int i=0; i<4; i++)
rv += weight1[g(-4)<<14 | g(-3)<<12 | g(-2)<<10 | g(-1)<<8 | g(1)<<6 | g(2)<<4 | g(3)<<2 | g(4)];
else
for(int i=0; i<4; i++)
rv += weight2[g(-4)<<14 | g(-3)<<12 | g(-2)<<10 | g(-1)<<8 | g(1)<<6 | g(2)<<4 | g(3)<<2 | g(4)];
#undef g
return rv;
}
inline int find(int &x,int &y) {
#define ck(xx,yy) (x+xx>0 && y+yy>0 && x+xx<=15 && y+yy<=15 && board[x+xx][y+yy]!=0)
return ck(-1,-1)||ck(-1,0)||ck(-1,1)||ck(0,-1)||ck(0,1)||ck(1,-1)||ck(1,0)||ck(1,1)||ck(-2,0)||ck(2,0)||ck(0,-2)||ck(0,2);
#undef ck
}
#pragma pack(1)
struct node {
unsigned char move; // 落子位置
short val; // 当前局面评分
int best; // 最佳儿子
short bestval; // 最佳儿子的评分
unsigned char dep; // 深度
int vis; // 访问次数
};
#pragma pack()
#define NODE_MAX 60000000
node *s; // 节点储存
int *fa;
/*
子节点都是连续的,因此可以节省一些内存
cd_l[i] 到 (cd_l[i]+cd_cnt[i]) 之间是 i 的子节点。
*/
int *cd_l;
unsigned char *cd_cnt;
int select() {
int i=0;
while(~cd_l[i]) { // 一直向下找到叶子节点
s[i].vis++;
if(s[i].dep&1) {
int maxn=INT_MAX, maxi=-1;
for(int j=cd_l[i]; j<=cd_l[i]+cd_cnt[i]; j++) {
if(abs(s[j].val) < 1000) {
int val = (int)s[j].val+(s[j].vis<<3);
if(val<maxn) maxn=val, maxi=j;
}
}
if(maxi == -1) return -1; // 必输或必胜局面,没有任何节点可以扩展
i=maxi, board[s[i].move>>4][s[i].move&15]=1;
} else {
int maxn=INT_MIN, maxi=-1;
for(int j=cd_l[i]; j<=cd_l[i]+cd_cnt[i]; j++) {
if(abs(s[j].val) < 1000) {
int val = (int)s[j].val-(s[j].vis<<3);
if(val>maxn) maxn=val, maxi=j;
}
}
if(maxi == -1) return -1; // 必输或必胜局面,没有任何节点可以扩展
i=maxi, board[s[i].move>>4][s[i].move&15]=2;
}
}
return i;
}
int AI() {
int cnt=0;
unsigned char maxdep=0;
int st=clock();
s[0] = {0,calc(),0,-32767,0,0};
cd_l[0] = -1;
while(clock()-st < Time*CLOCKS_PER_SEC && cnt<NODE_MAX-230) {
int idx=select();
if(idx == -1) break; // 必输或必胜局面,没有任何节点可以扩展
cd_l[idx]=cnt+1;
// 设置棋盘
for(int i=1; i<=15; i++) {
for(int j=1; j<=15; j++) {
if(find(i,j) && board[i][j] == 0) {
maxdep = max(maxdep, s[cnt].dep);
// 创建节点
s[++cnt].move=(i<<4|j), s[cnt].best=-1, s[cnt].dep=s[idx].dep+1, s[cnt].vis=0;
fa[cnt]=idx;
cd_l[cnt] = -1;
// 评分
s[cnt].val=s[cnt].bestval=s[idx].val+minicalc(i,j,(s[cnt].dep&1 ? 2 : 1));
// 迭代
for(int k=cnt; k; k=fa[k]) {
int &f=fa[k];
if(s[k].dep & 1) { // max 节点
if(s[k].bestval > s[f].bestval) {
s[f].best=k, s[f].bestval=s[k].bestval;
} else if(s[f].best == k) {
s[f].bestval=-32767;
for(int p=cd_l[f]; p<=cd_l[f]+cd_cnt[f]; p++) {
int val = (~cd_l[p] ? s[p].bestval : s[p].val);
if(val > s[f].bestval)
s[f].best=p, s[f].bestval=val;
}
} else {
break;
}
} else { // min 节点
if(s[k].bestval < s[f].bestval) {
s[f].best=k, s[f].bestval=s[k].bestval;
} else if(s[f].best == k) {
s[f].bestval=32767;
for(int p=cd_l[f]; p<=cd_l[f]+cd_cnt[f]; p++) {
int val = (~cd_l[p] ? s[p].bestval : s[p].val);
if(val < s[f].bestval)
s[f].best=p, s[f].bestval=val;
}
} else {
break;
}
}
}
s[cnt].bestval=(s[idx].dep&1?INT_MIN:INT_MAX);
}
}
}
cd_cnt[idx]=cnt-cd_l[idx];
// 恢复棋盘
for(; idx; idx=fa[idx]) board[s[idx].move>>4][s[idx].move&15]=0;
}
cout << "node:" << cnt << " " << endl;
cout << "maxdep:" << (int)maxdep << " " << endl;
cout << "bestval:" << s[0].bestval << " " << endl;
return s[s[0].best].move;
}
signed main() {
SetConsoleOutputCP(CP_UTF8); // 设置格式为 UTF-8,输出中文不会乱码
cout << "正在初始化...";
Winit::dfs(0);
// 分配内存
s = (node*)malloc((sizeof(node))*NODE_MAX);
fa = (int*)malloc((sizeof(int))*NODE_MAX);
cd_l = (int*)malloc((sizeof(int))*NODE_MAX);
cd_cnt = (unsigned char*)malloc((sizeof(unsigned char))*NODE_MAX);
// 游戏
int x,y;
cout<<"1 初级:思考1秒\n2 中级:思考5秒\n3 高级:思考10秒\n";
int d=-1;
cin >> d;
while(d<1 || d>3) cout<<"输入错误,请重新输入:", cin>>d;
if(d == 1) Time=1;
else if(d == 2) Time=5;
else Time=10;
while(1) {
UI();
cin>>x>>y;
board[x][y]=1;
if(calc() < -1000) {
UI(), cout<<"玩家胜!\n";
system("pause");
return 0;
}
int ai=AI();
board[ai>>4][ai&15]=2;
if(calc() > 1000) {
UI(), cout<<"AI胜!\n";
system("pause");
return 0;
}
}
return 0;
}
v1.0
#include<bits/stdc++.h>
#include<windows.h>
#pragma GCC optimize(1,2,3,"Ofast","inline")
#pragma G++ optimize(1,2,3,"Ofast","inline")
using namespace std;
int board[32][32], Time;
const int W[5][3] = {{2,1,0},{8,4,0},{24,12,0},{80,40,0},{9999,9999,9999}}; // 权重
void UI() {//输出
cout << "-------------------------------" << endl;
for(int i=1; i<=15; i++) {
cout << "|";
for(int j=1; j<=15; j++) {
if(board[i][j] == 0) cout << " |";
else if(board[i][j] == 1) cout << "●|";
else cout << "○|";
}
cout << endl << "-------------------------------" << endl;
}
}
int get(int &x,int &y,int xx,int yy,int color) { //得到x,y往xx,yy方向的评分
int con = 0,stop = 0;
for(int i=x+xx,j=y+yy ;; i+=xx,j+=yy) {
if(i<1 || i>15 || j<1 || j>15) {
stop++;
break;
}
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
for(int i=x-xx,j=y-yy ;; i-=xx,j-=yy) {
if(i<1 || i>15 || j<1 || j>15) {
stop++;
break;
}
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
return W[min(4,con)][stop];
}
int get2(int &x,int &y,int xx,int yy,int color) { //得到x,y往xx,yy方向的评分(不检测边界)
int con = 0,stop = 0;
for(int i=x+xx,j=y+yy ;; i+=xx,j+=yy) {
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
for(int i=x-xx,j=y-yy ;; i-=xx,j-=yy) {
if(board[i][j] == color) con++;
else {
if(board[i][j] == 3-color) stop++;
break;
}
}
return W[min(4,con)][stop];
}
int calc() { //计算整个棋盘的权重
int a=0, b=0;
for(int i=1; i<=15; i++) {
for(int j=1; j<=15; j++) {
if(board[i][j] == 2) a+=get(i,j,-1,-1,2)+get(i,j,-1,0,2)+get(i,j,-1,1,2)+get(i,j,0,-1,2);
else if(board[i][j] == 1) b+=get(i,j,-1,-1,1)+get(i,j,-1,0,1)+get(i,j,-1,1,1)+get(i,j,0,-1,1);
}
}
return a - (b<<1);
}
const int F[8][2]= {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
inline int minicalc(int &x, int &y) { //计算某点周围的权重
int rv=0;
if(x>4 && x<12 && y>4 && y<12) {
// 神奇的优化:如果棋子在中心,则不考虑边界情况
for(int i=0; i<8; i++) {
for(int j=1; j<=4; j++) {
int nx=x+F[i][0]*j, ny=y+F[i][1]*j;
if(!board[nx][ny]) break;
else if(board[nx][ny] == 2)rv+=get2(nx,ny,-F[i][0],-F[i][1],2);
else rv-=get2(nx,ny,-F[i][0],-F[i][1],1)<<1;
}
}
if(board[x][y] == 2) rv+=get2(x,y,-1,-1,2)+get2(x,y,-1,0,2)+get2(x,y,-1,1,2)+get2(x,y,0,-1,2);
else if(board[x][y] == 1) rv-=(get2(x,y,-1,-1,1)+get2(x,y,-1,0,1)+get2(x,y,-1,1,1)+get2(x,y,0,-1,1))<<1;
} else {
for(int i=0; i<8; i++) {
for(int j=1; j<=4; j++) {
int nx=x+F[i][0]*j, ny=y+F[i][1]*j;
if(nx>0 && nx<=15 && ny>0 && ny<=15) {
if(!board[nx][ny]) break;
else if(board[nx][ny] == 2)rv+=get(nx,ny,-F[i][0],-F[i][1],2);
else rv-=get(nx,ny,-F[i][0],-F[i][1],1)<<1;
} else break;
}
}
if(board[x][y] == 2) rv+=get(x,y,-1,-1,2)+get(x,y,-1,0,2)+get(x,y,-1,1,2)+get(x,y,0,-1,2);
else if(board[x][y] == 1) rv-=(get(x,y,-1,-1,1)+get(x,y,-1,0,1)+get(x,y,-1,1,1)+get(x,y,0,-1,1))<<1;
}
return rv;
}
inline int find(int &x,int &y) {
#define ck(xx,yy) (x+xx>0 && y+yy>0 && x+xx<=15 && y+yy<=15 && board[x+xx][y+yy]!=0)
return ck(-1,-1)||ck(-1,0)||ck(-1,1)||ck(0,-1)||ck(0,1)||ck(1,-1)||ck(1,0)||ck(1,1)||ck(-2,0)||ck(2,0)||ck(0,-2)||ck(0,2);
#undef ck
}
struct node {
int move; // 落子位置
int val; // 当前局面评分
int best, bestval; // 最佳儿子及评分
int dep; // 深度
int vis; // 访问次数
};
#define NODE_MAX 40000000
node *s; // 节点储存
int *fa;
/*
子节点都是连续的,因此只需要记录两个端点。
cd_l[i] 到 cd_r[i] 之间是 i 的子节点。
*/
int *cd_l, *cd_r;
int select() {
int i=0;
while(~cd_l[i]) { // 一直向下找到叶子节点
s[i].vis++;
if(s[i].dep&1) {
int maxn=INT_MAX, maxi=-1;
for(int j=cd_l[i]; j<=cd_r[i]; j++) {
if(abs(s[j].val) < 1000) {
int val = s[j].val+(s[j].vis<<3);
if(val<maxn) maxn=val, maxi=j;
}
}
if(maxi == -1) return -1; // 无解局面,没有任何节点可以扩展
i=maxi, board[s[i].move>>4][s[i].move&15]=1;
} else {
int maxn=INT_MIN, maxi=-1;
for(int j=cd_l[i]; j<=cd_r[i]; j++) {
if(abs(s[j].val) < 1000) {
int val = s[j].val-(s[j].vis<<3);
if(val>maxn) maxn=val, maxi=j;
}
}
if(maxi == -1) return -1; // 无解局面,没有任何节点可以扩展
i=maxi, board[s[i].move>>4][s[i].move&15]=2;
}
}
return i;
}
int AI() {
int cnt=0,maxdep=0;
int st=clock();
s[0] = {0,calc(),0,INT_MIN,0,0};
cd_l[0] = -1;
while(clock()-st < Time*CLOCKS_PER_SEC && cnt<NODE_MAX-230) {
int idx=select();
if(idx == -1) break; // 无解局面,没有任何节点可以扩展
cd_l[idx]=cnt+1;
// 设置棋盘
for(int i=1; i<=15; i++) {
for(int j=1; j<=15; j++) {
if(find(i,j) && board[i][j] == 0) {
maxdep = max(maxdep, s[cnt].dep);
// 创建节点
s[++cnt].move=(i<<4|j), s[cnt].best=-1, s[cnt].dep=s[idx].dep+1, s[cnt].vis=0;
fa[cnt]=idx;
cd_l[cnt] = -1;
// 评分
int tmp=s[idx].val-minicalc(i,j);
board[i][j] = (s[cnt].dep&1 ? 2 : 1);
s[cnt].val=s[cnt].bestval=tmp+minicalc(i,j);
board[i][j] = 0;
// 迭代
for(int k=cnt; k; k=fa[k]) {
int &f=fa[k];
if(s[k].dep & 1) { // max 节点
if(s[k].bestval > s[f].bestval) {
s[f].best=k, s[f].bestval=s[k].bestval;
} else if(s[f].best == k) {
s[f].bestval=INT_MIN;
for(int p=cd_l[f]; p<=cd_r[f]; p++) {
int val = (~cd_l[p] ? s[p].bestval : s[p].val);
if(val > s[f].bestval)
s[f].best=p, s[f].bestval=val;
}
} else {
break;
}
} else { // min 节点
if(s[k].bestval < s[f].bestval) {
s[f].best=k, s[f].bestval=s[k].bestval;
} else if(s[f].best == k) {
s[f].bestval=INT_MAX;
for(int p=cd_l[f]; p<=cd_r[f]; p++) {
int val = (~cd_l[p] ? s[p].bestval : s[p].val);
if(val < s[f].bestval)
s[f].best=p, s[f].bestval=val;
}
} else {
break;
}
}
}
s[cnt].bestval=(s[idx].dep&1?INT_MIN:INT_MAX);
}
}
}
cd_r[idx]=cnt;
// 恢复棋盘
for(; idx; idx=fa[idx]) board[s[idx].move>>4][s[idx].move&15]=0;
}
cout << "node:" << cnt << " " << endl;
cout << "maxdep:" << maxdep << " " << endl;
cout << "bestval:" << s[0].bestval << " " << endl;
return s[s[0].best].move;
}
signed main() {
SetConsoleOutputCP(CP_UTF8); // 设置格式为 UTF-8,输出中文不会乱码
cout << "Loading...\n";
s = (node*)malloc((sizeof(node))*NODE_MAX);
fa = (int*)malloc((sizeof(int))*NODE_MAX);
cd_l = (int*)malloc((sizeof(int))*NODE_MAX);
cd_r = (int*)malloc((sizeof(int))*NODE_MAX);
int x,y;
cout << "1 初级:思考1秒\n2 中级:思考5秒\n3 高级:思考10秒\n";
int d=-1;
cin >> d;
while(d < 1 || d > 3) {
cout << "输入错误,请重新输入:";
cin >> d;
}
if(d == 1) Time=1;
else if(d == 2) Time=5;
else Time=10;
while(1) {
UI();
cin>>x>>y;
board[x][y]=1;
int ai=AI();
board[ai>>4][ai&15]=2;
}
return 0;
}
注:bfs 可能有点费内存,你可以修改 NODE_MAX
的值。但如果 NODE_MAX
过小,会导致 AI 无法发挥全部实力,变得比较弱智。