基于bfs的五子棋AI v1.2

基于bfs的五子棋AI v1.2(含完整代码)

网上大部分五子棋引擎,都是基于dfs进行ab剪枝,但我要给大家介绍另一种方法,基于bfs扩展节点。

1.思路

思路很简单:一开始的局面作为根节点,然后一层一层地不断向下扩展。每个节点评分后,向上minmax,直到根节点为止。

2.优化

[ 重要 ] \color{red}\small[重要] [重要] 节点储存优化:在每个节点储存整个棋盘会浪费大量内存。其实,只储存了一个落子位置就够了。因为思考层数不深,bfs时,只需要从叶子节点向上遍历到根节点,就可以还原当时的棋盘状态了。

[ 重要 ] \color{red}\small[重要] [重要] 搜索顺序优化:一层一层地暴力搜索,会浪费大量时间在无用的节点上。因此,我们可以优先搜索评分最高的(min节点先搜最低的),合理分配时间。实现:权值减去访问量,得到优先级。(为了给后面的节点一些机会)

评分常数优化:扩展节点时,不需要评估整个棋盘。只需要评估落子位置一圈的棋子即可。还可以通过搜索预处理一些权重信息,进一步提高速度。

上代码

v1.2

  • 把结构体内的 bestvaldep 变量删除了,因为用处不大。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 无法发挥全部实力,变得比较弱智。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值