C++蒙特卡洛树算法实现五子棋AI

蒙特卡洛方法:

随机的对当前局面进行后续状态模拟,根据模拟结果决定下一步行动

下面是蒙特卡洛树的实现原理:

 

                

下面是具体实现,详细讲解见注释。

一些优化:

1.中心邻域搜索:根据启发式信息,每一步棋子应当选择与对手上一步棋子周围的一步,于是可以只搜索对方上一步周围的后继状态

2.必胜状态:如果已经能一步走成必胜局面,直接走这一步,或者一个后继状态对应的对手所有后继状态都是失败的,则这步必胜。

3.判断棋盘状态:使用std::set(平衡树),也可以使用hash算法

Tips:调整select_num和sta_num可以让AI获得不同效果

#include<iostream>
#include<map>
#include<time.h>
#include<stdlib.h>
#include<cmath>
#include<vector>
#include<utility>
#include<windows.h>
#include<conio.h>
#define select_num 300        //选择次数
#define sta_num 15            //每个状态模拟次数
#define mp(x,y) make_pair(x,y)
const int row=11,col=11;//棋盘的行和列
const int search_range=2;//搜索范围 
 
const double inf=1e9+7;
 
using namespace std;
 
typedef struct chess{//定义棋盘 
	int g[12][12];
}chess;
 
//0代表无棋子,1代表白棋,2代表黑棋 
 
bool operator < (chess x,chess y){//用于搜索棋盘状态 
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(x.g[i][j]<y.g[i][j])return 1;
			else if(x.g[i][j]>y.g[i][j])return 0;
		}
	}
	return 0;
	
}
 
bool operator == (chess x,chess y){//用于判断棋盘状态是否相等 
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<col;j++)
		{
			if(x.g[i][j]!=y.g[i][j])return 0;
		}
	}
	return 1;
}
 
typedef struct property{//棋盘状态的一些性质 
	double a,b;
	vector<chess> vec;
}property; 
 
int rd=0;//回合数 
 
map<chess,property> mp;//用于记录棋盘状态的一些性质 
 
map<chess,chess> fa;//棋盘状态的父节点,用于反向传播 
 
pair<int,int> center;//搜索中心 
 
void init_chess(chess x);//初始化棋盘状态 
void init_window();//初始化窗口
void game_window();//游戏窗口 
void print_board(chess x);//打印棋盘 
void set_pos(int x,int y);//调整光标位置 
void white_win();//白棋胜利 
void black_win();//黑棋胜利 
 
chess UCT_search(chess x,pair<int,int> center,int player);//蒙特卡洛树搜索 
pair<chess,int> tree_policy(chess x,pair<int,int> center,int player);//选择子节点 
chess expand(chess x,pair<int,int> center,int player);//扩展当前节点 
double UCB(chess x,int player);//计算节点的UCB值 
pair<int,int> cal_centre(chess x);//计算当前局面的搜索中心 
double default_policy(chess x,int player);//模拟结果 
void back_up(chess x,chess y,int value);//反向传播 
pair< int,pair<int,int> > check_four(chess x);//预言胜利
pair< int,pair<int,int> > check_three(chess x);//预言胜利优化 
int check(chess x);//检查是否胜利,1为白胜,2为黑胜 
int check_win(chess x,int player);//分别检查黑白棋是否胜利 
int is_terminal(chess x);//检查是否为叶子节点 
int cnt_num(chess x,int x1,int x2,int y1,int y2);//查看节点的棋子数量 
 
//player:0为白,1为黑 
 
void init_chess(chess x){
	property p;
	p.a=p.b=0.0;
	mp[x]=p;
} 
 
void set_pos(int x,int y){
    HANDLE winHandle;
    COORD pos;
	pos.X=x,pos.Y=y; 
    winHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(winHandle,pos); 
}
 
void init_window(){
	system("cls");
	for(int i=1;i<=7;i++)cout<<"\n";
	for(int i=1;i<=6;i++)cout<<"\t";
	cout<<"五子棋"<<"\n\n\n\n";
	for(int i=1;i<=5;i++)cout<<"\t  ";
	cout<<"输入任意键开始游戏"; 
	char h;
	h=getch();
	game_window();
}
 
void game_window(){
	rd=0;
	mp.clear(),fa.clear();
	chess now;
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			now.g[i][j]=0;
		}
	}
	init_chess(now);
	
	//pair<int,int> center;
	
	center.first=row/2,center.second=col/2;
 
	srand(time(0));
	
	print_board(now);
 
	while(!check(now))
	{
		now=UCT_search(now,center,1);
		if(check(now)==2){
			print_board(now);
			black_win();
		}
		while(1)
		{
			print_board(now);
			set_pos(65,12);
			cout<<"轮到您执棋,请输入坐标:";
			set_pos(65,14);
			int x,y;
			cin>>x>>y;	
			x--,y--;
			if(x<0||x>row||y<0||y>col){
				set_pos(65,16);
				cout<<"您输入的数据超出棋盘范围"<<'\n';
				Sleep(1500);
			}else if(now.g[x][y]){
				set_pos(65,16);
				cout<<"该位置已有棋子";
				Sleep(1500);	
			}else{
				now.g[x][y]=1;
				center.first=cal_centre(now).first,center.second=cal_centre(now).second;
				rd++;
				break;
			}
		}
		print_board(now);
		if(check(now)==1)white_win();
	}
}
 
void print_board(chess x){
	system("cls");
	for(int i=1;i<=2;i++)cout<<"\t"; 
	for(int i=0;i<=col;i++)
	{
		if(i+1<10)cout<<" "<<i+1<<" ";
		else cout<<" "<<i+1;
	}
	cout<<"\n";
	for(int i=0;i<=row;i++)
	{
		for(int j=1;j<=2;j++)cout<<"\t";
		for(int j=0;j<=col;j++)cout<<" __";
		cout<<"\n";
		for(int j=1;j<=1;j++)cout<<"\t";
		cout<<i+1;
		cout<<"\t";
		for(int j=0;j<=col;j++)
		{
			char p;
			if(x.g[i][j]==0)p=' ';
			else if(x.g[i][j]==1)p='O';
			else if(x.g[i][j]==2)p='X';
			cout<<"|"<<p<<" ";
		}
		cout<<"|";
		cout<<"\n";
	}
	for(int j=1;j<=2;j++)cout<<"\t";
	for(int i=0;i<=col;i++)cout<<" __";
} 
 
void white_win(){
	Sleep(1500); 
	system("cls");
	for(int i=1;i<=8;i++)cout<<'\n';
	for(int i=1;i<=4;i++)cout<<'\t';
	cout<<"白棋胜利";
	cout<<"\n\n";
	Sleep(1500);
	init_window();
}
 
void black_win(){
	Sleep(1500);
	system("cls");
	for(int i=1;i<=8;i++)cout<<'\n';
	for(int i=1;i<=4;i++)cout<<'\t';
	cout<<"黑棋胜利";
	cout<<"\n\n";
	Sleep(1500);
	init_window();
}
 
chess UCT_search(chess x,pair<int,int> center,int player){
	chess y=x;
	chess ans=y;
	if(check_four(y).first)
	{
		pair<int,int> u=check_four(y).second; 
		ans.g[u.first][u.second]=player+1;
		return ans;
	}
	if(check_three(y).first)
	{
		pair<int,int> u=check_three(y).second;
		ans.g[u.first][u.second]=player+1;
		return ans;
	}
	if(mp.find(x)==mp.end())
	{
		init_chess(x);
	}
	int cnt=0;//选择次数 
	while(cnt<=select_num)
	{
		//int judge=rand()%100;
		//if(judge<1)center.first=max(0,center.first-1);
		//if(judge<1)center.second=min(col,center.second+1);
		//if(judge>98)center.first=min(row,center.first+1);
		//if(judge>98)center.second=max(0,center.second-1);
		cnt++;
		pair<chess,int> select_point=tree_policy(x,center,1);
		for(int j=1;j<=sta_num;j++)//每个状态多次模拟,增强效果 
		{
			double s=default_policy(select_point.first,select_point.second^1);
			back_up(select_point.first,y,s);
		}
	}
	vector<chess>::iterator it;
	double maxn=UCB(*(mp[y].vec.begin()),player);
	for(it=mp[y].vec.begin();it!=mp[y].vec.end();it++)
	{
		if(UCB(*it,player)>=maxn)
		{
			maxn=UCB(*it,player);
			ans=*it;
		}
		//print_board(*it);
		//cout<<mp[*it].a<<" "<<mp[*it].b<<'\n';
		//Sleep(1500);
	}
	vector<chess>().swap(mp[y].vec);//释放内存 
	return ans;
}
 
pair<chess,int> tree_policy(chess x,pair<int,int> center,int player){
	while(!check(x)&&!is_terminal(x))
	{
		int x1=max(0,center.first-search_range);
		int x2=min(row,center.first+search_range);
		int y1=max(0,center.second-search_range);
		int y2=min(col,center.second+search_range);
		if(cnt_num(x,x1,x2,y1,y2)+mp[x].vec.size()<(x2-x1+1)*(y2-y1+1))
		{
			return mp(expand(x,center,player),player);
		}else{
			chess y=x;
			vector<chess>::iterator it;
			if(mp[y].vec.size()==0)break;
			double maxn=UCB(*(mp[y].vec.begin()),player);
			for(it=mp[y].vec.begin();it!=mp[y].vec.end();it++)
			{
				if(UCB(*it,player)>=maxn)
				{
					maxn=UCB(*it,player);
					x=*it;
				}
			}
			fa[x]=y;
		}
		player^=1;
	}
	return mp(x,player);
}
 
chess expand(chess x,pair<int,int> center,int player){
	chess y=x;
	int x1=max(0,center.first-search_range);
	int x2=min(row,center.first+search_range);
	int y1=max(0,center.second-search_range);
	int y2=min(col,center.second+search_range);
	int cnt=0;
	while(cnt<=1000)
	{
		int i=x1+rand()%(x2-x1+1);
		int j=y1+rand()%(y2-y1+1);
		int o=x.g[i][j];
		y.g[i][j]=player+1;
		if(!x.g[i][j]&&mp.find(y)==mp.end())
		{
			init_chess(y);
			mp[x].vec.push_back(y);
			fa[y]=x;
			return y;
		}	
		y.g[i][j]=o;
		cnt++;
	}
	while(1)
	{
		int i=x1+rand()%(x2-x1+1);
		int j=y1+rand()%(y2-y1+1);
		int o=x.g[i][j];
		y.g[i][j]=player+1;
		if(!x.g[i][j]){
			if(mp.find(y)==mp.end()){
				init_chess(y);
			}
			return y;	
		}
		y.g[i][j]=o;
	}
}
 
double UCB(chess x,int player){
	if(mp[x].b==0)return 0;
	double a1=mp[x].a,b1=mp[x].b;
	if(a1+b1==0)return -inf;
	if(player==1)return a1/b1+sqrt(log(a1+b1)/b1);
	else if(player==0)return -a1/b1+sqrt(log(a1+b1)/b1);
}
 
pair<int,int> cal_centre(chess x){//以每个棋子横纵坐标的均值为搜索中心 
	int cnt=0,p1=0,p2=0;
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(x.g[i][j]){
				cnt++;
				p1+=i; 
				p2+=j;
			}
		}
	} 
	p1=max(0,p1/cnt);
	p2=max(0,p2/cnt);
	return mp(p1,p2);
}
 
double default_policy(chess x,int player){
	while(1)
	{
		if(check(x)||is_terminal(x))break;
		pair<int,int> h=cal_centre(x);
		int o=rand()%100;
		int i,j;
		if(o<75){
			i=min(max(0,h.first-search_range+rand()%(search_range*2+1)),row);
			j=min(max(0,h.second-search_range+rand()%(search_range*2+1)),col);
		}else{
			i=rand()%(row+1);
			j=rand()%(col+1);
		}
		if(!x.g[i][j])
		{
			x.g[i][j]=player+1;	
			player^=1;
		}
	}
	
	if(check(x)==1)return -1;
	else if(check(x)==2)return 1;
	else return 0;
}
 
void back_up(chess x,chess y,int value){
	mp[x].a+=value;
	mp[x].b++;
	while(!(x==y))
	{
		if(fa.find(x)==fa.end())break;
		x=fa[x];
		mp[x].a+=value;
		mp[x].b++;
	}
}
 
pair< int,pair<int,int> > check_four(chess x){
	chess y=x;
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(!x.g[i][j])
			{
				x.g[i][j]=2;
				if(check(x)==2)return mp(2,mp(i,j));
				x.g[i][j]=0;
			}
		}
	}
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(!y.g[i][j])
			{
				y.g[i][j]=1;
				if(check(y)==1)return mp(1,mp(i,j));
				y.g[i][j]=0;
			}
		}
	}
	return mp(0,mp(0,0));
}
 
pair< int,pair<int,int> > check_three(chess x){
	chess y1=x,y2=x;
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(!x.g[i][j])
			{
				x.g[i][j]=2;
				int flag=1;
				for(int k1=0;k1<=row;k1++)
				{
					for(int k2=0;k2<=col;k2++)
					{
						if(!x.g[k1][k2]){
							x.g[k1][k2]=1;
							if(check_four(x).first!=2){
								flag=0;
								x.g[k1][k2]=0;
								break;
							}else x.g[k1][k2]=0;
						}
					}
					if(!flag)break;
				}	
				if(flag)return mp(2,mp(i,j));
				x.g[i][j]=0;
			}	
		} 
	}
	
	vector< pair<int,int> > vec;
	
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(!y1.g[i][j])
			{
				y1.g[i][j]=1;
				int flag=1;
				for(int k1=0;k1<=row;k1++)
				{
					for(int k2=0;k2<=col;k2++)
					{
						if(!y1.g[k1][k2]){
							y1.g[k1][k2]=2;
							if(check_four(y1).first!=1){
								flag=0;
								y1.g[k1][k2]=0;
								break;
							}else y1.g[k1][k2]=0;
						}
					}
					if(!flag)break;
				}	
				if(flag)return mp(1,mp(i,j));
				//if(flag)s.push_back(mp(i,j));
				y1.g[i][j]=0;
			}	
		} 
	}
	
	vector< pair<int,int> >::iterator it;
	
	pair<int,int> ret;
	int minn=1e9+7;
	
	for(it=vec.begin();it!=vec.end();it++)
	{
		pair<int,int> k=*it;
		if((k.first-cal_centre(y2).first)+(k.second-cal_centre(y2).second)<minn)
		{
			minn=(k.first-cal_centre(y2).first)+(k.second-cal_centre(y2).second);
			ret=k;
		}
	}
	
	if(vec.size())return mp(1,ret);
	
	return mp(0,mp(0,0));
}
 
int check(chess x){
	if(check_win(x,1))return 1;
	else if(check_win(x,2))return 2;
	else return 0;
}
 
int check_win(chess x,int gamer){
	int cnt=0;
	for(int i=0;i<=row;i++)
	{
		cnt=0;
		for(int j=0;j<=col;j++)
		{
			if(x.g[i][j]==gamer)cnt++;
			else cnt=0;
			if(cnt>=5)return 1;
		}	
	}	
	cnt=0;
	for(int i=0;i<=col;i++)
	{
		cnt=0;
		for(int j=0;j<=row;j++)
		{
			if(x.g[j][i]==gamer)cnt++;
			else cnt=0;
			if(cnt>=5)return 1;
		}	
	}
	cnt=0;
	for(int i=0;i<=row;i++)
	{
		cnt=0;
		int l=i,r=0;
		while(l<=col&&r<=col)
		{
			if(x.g[r][l]==gamer)cnt++;
			else cnt=0;
			if(cnt>=5)return 1;
			l++,r++;
		}
	}
	cnt=0;
	for(int i=0;i<=row;i++)
	{
		cnt=0;
		int l=i,r=0;
		while(l<=col&&r<=col)
		{
			if(x.g[l][r]==gamer)cnt++;
			else cnt=0;
			if(cnt>=5)return 1;
			l++,r++;
		}
	}
	cnt=0;
	for(int i=row;i>=0;i--)
	{
		cnt=0;
		int l=i,r=0;
		while(l>=0&&r<=col)
		{
			if(x.g[r][l]==gamer)cnt++;
			else cnt=0;
			if(cnt>=5)return 1;
			l--,r++;
		}
	}
	cnt=0;
	for(int i=0;i<=row;i++)
	{
		cnt=0;
		int l=i,r=col;
		while(l<=col&&r>=0)
		{
			if(x.g[l][r]==gamer)cnt++;
			else cnt=0;
			if(cnt>=5)return 1;
			l++,r--;
		}
	}
	return 0;
}
 
int is_terminal(chess x){
	
	for(int i=0;i<=row;i++)
	{
		for(int j=0;j<=col;j++)
		{
			if(!x.g[i][j])return 0;
		}
	}
	
	return 1;
}
 
int cnt_num(chess x,int x1,int x2,int y1,int y2){
	int sum=0;
	for(int i=x1;i<=x2;i++)
	{
		for(int j=y1;j<=y2;j++)
		{
			if(x.g[i][j])sum++;
		}
	}
	return sum;
}
 
int main()
{	
	init_window();
	
	return 0;	
} 
 
 
 
 
 
 
 
 
 
 
 
 

可能的改进策略:

1.快速生成棋盘序列,以快速进行大量多次模拟

2.小范围可以使用博弈思想打表或者暴力查找必胜状态

3.使用并行计算

欢迎大佬萌帮忙改进!~

  • 16
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
五子棋AI算法C是一个用于实现五子棋AI的程序代码。它包含了一系列函数和算法来评估棋局、选择最佳下棋位置以及实现自动下棋。 在这个算法中,棋盘一般是15x15的大小,但可以根据需要进行修改。为了简化描述,作者将连五、活四、冲四、活三、眠三、活二、眠二等特殊棋型统称为特殊棋型。 对于棋盘上的一串棋子,有横向、竖向、左上到右下和左下到右上四种方向,分别命名为横(LAY)、竖(STAND)、主对角(MAIN)和副对角(VICE)。 在程序中,有当前棋子的上一个和下一个的概念,例如在横向上,上一个是左边一个,下一个是右边一个。 该算法还包括函数getChessChain(),用于返回某个空位棋子ce周围长度为n的横向、竖向、主对角和副对角的棋链。 总的来说,五子棋AI算法C通过评估棋局、分析棋型和选择最佳下棋位置来实现自动下棋。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [c++ 五子棋AI 算法实现 详细解析](https://blog.csdn.net/weixin_44062380/article/details/105881036)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值