1. 程式人生 > >五子棋(人機)-極大極小值搜尋演算法

五子棋(人機)-極大極小值搜尋演算法

從人落子開始到出現勝負或者和局,之間所落的子,構成了一個解。而解空間就是一個樹,解就是這解空間中的一條路徑。只不過這個解空間是電腦的選擇和人的選擇共同構成的(奇數層是電腦(因為輪到電腦落子麼),偶數層是人)。

極大極小值搜尋演算法,來搜尋回溯這個解空間:它假設人和電腦都是極其聰明的,他們都會選擇出最優的一步。

但是搜尋整棵樹是不現實的,16*16!指數級,所以只回溯n步,即這個AI考慮的是N步之內的最優解,它是考慮了n步之後的情況的

-------------------------------------------------------------------我是分割線-------------------------------------------------------------------------

假設玩家執黑子,電腦執白子

評估函式:評估函式將對棋盤上的所有黑子做出評分(連成線的等級越高,數量越多,估分就越高)作scorehumber;也將對棋盤上的所有白子做出評分(連成線的等級越高,數量越多,估分就越高)作scorecomputer。然後評估值為【scorecoputer-scorehumber】。它將認為,這個評估值越高,整個局面對電腦越有利;這個評估值越低,整個局面對玩家越有利。

max-min搜尋最優解,即向後回溯depth步,輪到電腦時,電腦做出最有利於自己的選擇(選擇最高的評估值),輪到玩家時,玩家做出最有利於自己的選擇(選擇最低的評估值)。(他們的選擇將被推遲,葉子節點先做出選擇,然後層層往上推出那一層的最優解

)。偽碼:

int MinMax(int depth) { // 函式的評估都是以白方的角度來評估的
 if (SideToMove() == WHITE) { // 白方是“最大”者 
  return Max(depth); 
 } else {           // 黑方是“最小”者 
  return Min(depth); 
 } 
}   
int Max(int depth) { 
 int best = -INFINITY; 
 if (depth <= 0) { 
  return Evaluate(); 
 } 
 GenerateLegalMoves(); 
 while (MovesLeft()) { 
  MakeNextMove(); 
  val = Min(depth - 1); 
  UnmakeMove(); 
  if (val > best) { 
   best = val; 
  } 
 } 
 return best; 
}   
int Min(int depth) { 
 int best = INFINITY; // 注意這裡不同於“最大”演算法 
 if (depth <= 0) { 
  return Evaluate(); 
 } 
 GenerateLegalMoves(); 
 while (MovesLeft()) { 
  MakeNextMove(); 
  val = Max(depth - 1); 
  UnmakeMove(); 
  if (val < best) {  // 注意這裡不同於“最大”演算法 
   best = val; 
  } 
 } 
 return best; 
} 
-------------------------------------------------------------------我是分割線-------------------------------------------------------------------------

計分板

成五 +100000
活四 +10000
死四 +1000
活三 +1000
死三 +100
活二 +100
死二 +10
活一 +10
-------------------------------------------------------------------我是分割線-------------------------------------------------------------------------
int max_noalphabeta(int depth,int i1,int i2);//輪到電腦走步時,電腦作的選擇 
int min_noalphabeta(int depth,int i1,int i2);//輪到人走步時,人作的選擇 
void generatepoint(vector< pair<int,int> > &v);//產生空子序列 
int scoretable(int number,int empty1);//積分表 
int countscore(vector<int> n,int turn);	//算單個數組分 
bool hasne(int x,int y);//周圍是否有子存在,無子的就加考慮 

bool hasne(int x,int y)//空子只算旁邊有子的
{
	int i,j;
	for(i=(x-3>0?x-3:0);i<=x+3&&i<16;++i)
		for(j=(y-3>0?y-3:0);j<=y+3&&j<16;++j)
			if(i!=0||j!=0)
				if(pos[i][j]!=0)
					return true;
	return false;
} 
void generatepoint(vector< pair<int,int> > &v)//產生空子序列		
{
	for(int i=0;i<16;++i)
		for(int j=0;j<16;++j)
			if(pos[i][j]==0&&hasne(i,j))
			{
				pair<int,int> p;
				p.first=i;
				p.second=j;
				v.push_back(p);
			} 
}
//按照成五100000、活四10000、活三1000、活二100、活一10、死四1000、死三100、死二10的規則 
//給棋盤上的所有棋子打分,之和為電腦的單方面得分scorecomputer,然後對玩家的棋子同樣打分,之和為scorehumber
//scoreComputer-scorehumber即為當前局勢的總分數 
int scoretable(int number,int empty1)//計分板
{
	if(number>=5)	return 100000;
	else if(number==4)
	{
		if(empty1==2)	return 10000;
		else if(empty1==1)	return 1000;
	}
	else if(number==3)	
	{
		if(empty1==2)	return 1000;
		else if(empty1==1)	return 100;
	}
	else if(number==2)
	{
		if(empty1==2)	return 100;
		else if(empty1==1)	return 10;
	}
	else if(number==1&&empty1==2)	return 10;
	return 0;
}
int countscore(vector<int> n,int turn)//正斜線、反斜線、橫、豎,均轉成一維陣列來計算 
{
	int scoretmp=0;
	int len=n.size();
	int empty1=0;
	int number=0;
	if(n[0]==0)	++empty1;
	else if(n[0]==turn)	++number;
	int i=1;
	while(i<len)
	{
		if(n[i]==turn)	++number;
		else if(n[i]==0)
		{
			if(number==0)	empty1=1;
			else
			{
				scoretmp+=scoretable(number,empty1+1);
				empty1=1;
				number=0;
			}
		}
		else
		{
			scoretmp+=scoretable(number,empty1);
			empty1=0;
			number=0;
		}
		++i;
	}
	scoretmp+=scoretable(number,empty1);
	return scoretmp;
}
int evaluate_minmax_noalphabeta()//評估函式,評估局勢
{
	int scorecomputer=0;
	int scorehumber=0;
	//橫排們 
	for(int i=0;i<16;++i)
	{
		vector<int> n;
		for(int j=0;j<16;++j)
			n.push_back(pos[i][j]);
		scorecomputer+=countscore(n,2);
		scorehumber+=countscore(n,1);
		n.clear();
	}
	//豎排們
	for(int j=0;j<16;++j)
	{
		vector<int> n;
		for(int i=0;i<16;++i)
			n.push_back(pos[i][j]);
		scorecomputer+=countscore(n,2);
		scorehumber+=countscore(n,1);
		n.clear();
	} 
	//上半正斜線們 
	for(int i=0;i<16;++i)
	{
		int x,y;
		vector<int> n;
		for(x=i,y=0;x<16&&y<16;++x,++y)
			n.push_back(pos[y][x]);
		scorecomputer+=countscore(n,2);
		scorehumber+=countscore(n,1);
		n.clear();
	} 
	//下半正斜線們
	for(int j=1;j<16;++j)
	{
		int x,y;
		vector<int> n;
		for(x=0,y=j;y<16&&x<16;++x,++y)
			n.push_back(pos[y][x]);
	 	scorecomputer+=countscore(n,2);
		scorehumber+=countscore(n,1);
		n.clear();
	} 
	//上半反斜線們
	for(int i=0;i<16;++i)
	{
		vector<int> n;
		int x,y;
		for(y=i,x=0;y>=0&&x<16;--y,++x)
			n.push_back(pos[y][x]);
		scorecomputer+=countscore(n,2);
		scorehumber+=countscore(n,1);
		n.clear();
	} 
	//下半反斜線們
	for(int j=1;j<16;++j)
	{
		vector<int> n;
		int x,y;
		for(y=j,x=15;y<16&&x>=0;++y,--x)
			n.push_back(pos[x][y]);
		scorecomputer+=countscore(n,2);
		scorehumber+=countscore(n,1);
		n.clear();
	} 
	return scorecomputer-scorehumber;
} 
int min_noalphabeta(int depth,int i1,int i2)//玩家落子時													//當min(人)走步時,人的最好情況 
{
	int res=evaluate_minmax_noalphabeta();
	Chess cc;
	cc.chess_isover(i1,i2,2);
	if(isover!=0||depth<=0)
	{
		isover=0;
		return res;
	}
	vector< pair<int,int> > v;
	generatepoint(v);
	int len=v.size();
	int best=INT_MAX;
	for(int i=0;i<len;++i)
	{
		pos[v[i].first][v[i].second]=1;
		int tmp=max_noalphabeta(depth-1,v[i].first,v[i].second);
		if(tmp<best)	best=tmp;//玩家落子時選擇最有利自己的局面,將推遲,葉子節點做出選擇後,層層往上推	
		pos[v[i].first][v[i].second]=0;
	} 
	return best;
}
int max_noalphabeta(int depth,int i1,int i2)													//當max(電腦)走步時,max(電腦)應該考慮最好的情況 
{
	int res=evaluate_minmax_noalphabeta();
	Chess cc;
	cc.chess_isover(i1,i2,1);
	if(isover!=0||depth<=0)
	{
		isover=0;
		return res;
	}
	vector< pair<int,int> > v;
	generatepoint(v);
	int len=v.size();
	int best=INT_MIN;
	for(int i=0;i<len;++i)
	{
		pos[v[i].first][v[i].second]=2;
		int tmp=min_noalphabeta(depth-1,v[i].first,v[i].second);
		if(tmp>best)	best=tmp;//電腦落子時,選擇最有利於自己的局面,將推遲	
		pos[v[i].first][v[i].second]=0;
	} 
	return best;
}
void Chess::chess_ai_minmax_noalphabeta(int &x,int &y,int depth)//極大極小值演算法搜尋n步後的最優解 
{
	vector< pair<int,int> > v;
	generatepoint(v);
	int best=INT_MIN;
	int len=v.size();
	vector< pair<int,int> > v2; 
	for(int i=0;i<len;++i)
	{
		pos[v[i].first][v[i].second]=2;	//選該子,將該子置白,防止後面遞迴時,再遞迴到 
		int tmp=min_noalphabeta(depth-1,v[i].first,v[i].second);
		if(tmp==best)
			v2.push_back(v[i]);
		if(tmp>best)
		{
			best=tmp;
			v2.clear();
			v2.push_back(v[i]);
		}
		pos[v[i].first][v[i].second]=0;	//假設完之後,該子需要重新置空,恢復原來的樣子 
	}
	len=v2.size();
	int i=(int)(rand()%len);
	x=v2[i].first;
	y=v2[i].second; 
}

參考:

http://blog.csdn.net/lihongxun945/article/details/50625267

http://blog.csdn.net/kingkong1024/article/details/7639401

相關推薦

五子棋人機-極大搜尋演算法

從人落子開始到出現勝負或者和局,之間所落的子,構成了一個解。而解空間就是一個樹,解就是這解空間中的一條路徑。只不過這個解空間是電腦的選擇和人的選擇共同構成的(奇數層是電腦(因為輪到電腦落子麼),偶數層是人)。 極大極小值搜尋演算法,來搜尋(回溯)這個解空間:它假設人和電腦都

QT五子棋專案詳解之四:AI人機對戰max-min極大博弈演算法

不考慮博弈的演算法怎麼能算是AI呢?max-min極大極小值演算法就是考慮了博弈的演算法。來看一個簡單的例子在這個棋局中,電腦為白旗,白旗走哪一步更好呢,也許使用策略表會告訴你,應該衝4,但是衝4後,玩家就會連成4。這就是考慮了博弈之後,這一步棋就是敗局。這就是為什麼有max

極大搜尋 + 剪枝

/* 題意: Alice和Bob玩遊戲,在一個4x4 的方格上 每個人每次選擇2x2的區域將裡面的四個值求和加到最後的分數當中(兩個人共用一個分數), 然後逆時針翻轉它們, Alice想要分數儘量大Bob想要分數儘量小 兩個人每次的選擇都是最優的,求最後的分數

201803-4棋局評估_極大演算法_對抗搜尋轉載

問題描述 試題編號:201803-4 試題名稱:棋局評估 時間限制:1.0s 記憶體限制:256.0MB 問題描述:問題描述  Alice和Bob正在玩井字棋遊戲。   井字棋遊戲的規則很簡單

中國象棋人機對弈搜尋演算法學習-極大,負極大值,alpha-beta演算法

極大極小值法 深度搜索(dfs)虛擬碼 /** 1。 p 為棋盤 2。 d 為規定的搜素最大深度,比如d層紅方,d-1層為黑方,d-2層為紅方...依此類推,可採用mod2來判斷當前是哪一方 4。評估棋盤的函式evaluation,當然需要看

POJ 1085 Triangle War博弈,極大搜尋+alpha_beta剪枝

題目:給出10個點,總共有18條邊,每次兩個人輪流加入一條邊,如果形成一個三角形,則三角形歸他所有,而且可以額外再走一步。最後三角形多的人勝 博弈問題 所謂的極大極小搜尋,其實就是搞個估價函式。然後主角肯定選個估價函式最大的,即對自己最有利的局面走。 而輪到對方的時候,

牛頓法求極大

牛頓法至少有兩個應用方向,1、求方程的根,2、最優化。牛頓法涉及到方程求導,下面的討論均是在連續可微的前提下討論。 1、求解方程。 並不是所有的方程都有求根公式,或者求根公式很複雜,導致求解困難。利用牛頓法,可以迭代求解。 原理是利用泰勒公式,在x0處展開,且展開

極大搜索思想+α/β減枝 【轉自-----https://blog.csdn.net/hzk_cpp/article/details/79275772】

ima 基本 個數 博弈論 數字 這就是 pre -- 繼續 極大極小搜索,即minimax搜索算法,專門用來做博弈論的問題的暴力. 多被稱為對抗搜索算法. 這個搜索算法的基本思想就是分兩層,一層是先手,記為a,還有一層是後手,記為b. 這個搜索是認為這a與b的利益關

Mathematica 繪製二元函式隨引數變化圖二維

問題來自群友,直接上程式碼 ListLinePlot[Labeled[{#[[2,1,2]],#[[2,2,2]]},NumberForm[#[[1]],3]]&/@Table[FindMinimum[{Cos[x]-Exp[x y],z+x^2+y^2<=8

梯度爆炸與梯度消失的原因以及解決方法,區域性問題以及學習率問題對SGD的改進

梯度爆炸與梯度消失的原因:簡單地說,根據鏈式法則,如果每一層神經元對上一層的輸出的偏導乘上權重結果都小於1的話( ),那麼即使這個結果是0.99,在經過足夠多層傳播之後,誤差對輸入層的偏導會趨於0( )。下面是數學推導推導。假設網路輸出層中的第 個神經元輸出為,而要學習的目標

【BZOJ2669】區域性容斥原理+狀壓dp

題意:有一個nn行mm列的整數矩陣,其中11到nmnm之間的每個整數恰好出現一次。如果一個格子比所有相鄰格子(相鄰是指有公共邊或公共頂點)都小,我們說這個格子是區域性極小值。給出所有區域性極小值的位置,你的任務是判斷有多少個可能的矩陣。(1<=n<=

MATLAB—一字棋極大搜尋

init.m %初始化棋盤狀態 function cur=init() cur=rand(3,3); %儲存當前棋盤的狀態 %計算機為先手時的初值,即均為0 for i=1:3 for j=1:3 cur(i,j)=0;

POJ 1085 Triangle War極大搜尋+alpha-beta剪枝

// // main.cpp // Richard // // Created by 邵金傑 on 16/8/29. // Copyright © 2016年 邵金傑. All rights reserved. // #include<iostream&g

動態規劃求解添+號求最和問題

案例提出:在一個n位整數a(只考慮正整數的情況)中插入r個加號,將它分成r+1個整數,找出一種加號的插入方法,使得這r+1個整數的和最小。 動態規劃設計要點:對於一般插入r個+號問題,採用列舉不適合。注意到插入r個+號是一個多階層決策問題,所以採用動態規劃 來求解是最適宜的

微信程式遊戲----五子棋總結

思路分析 繪製棋盤: 計算橫線和豎線的起始、終結點座標,繪製棋盤網格; 棋盤交叉點座標: 計算每格寬高,迴圈儲存棋盤所有點座標,並初始化狀態為0,表示此位置沒有棋子,形成“棋盤座標

巧婦能為少米之炊1——Android下內存下的生存之道

直接內存 -a 響應時間 分區 popu 身邊 執行 人的 算法 常常聽到身邊用安卓的朋友抱怨手機卡頓,內存動不動就快沒了。而Google聲稱在512M的內存下也能流暢執行Android 4.4。究竟它做了什麽? 總結一下它主要做了四件事: 1.優化內核,使用Activ

HTML5基礎小結——標簽

加速 支持 ide oat enter controls 畫圓 side tint 隨篇博客的思維導圖。繼續: 二。看下標簽的使用,這裏看幾個小樣例(效果圖不再給出): 1。結構標簽的使用,這裏來看一個頁面的布局:<!doc

計蒜客 2017 NOIP 提高組模擬賽Day1 T1 X的質數 線性篩素數

範圍 線性篩 mat 需要 接下來 包含 能夠 數字 bottom 小 X 是一位熱愛數學的男孩子,在茫茫的數字中,他對質數更有一種獨特的情感。小 X 認為,質數是一切自然數起源的地方。 在小 X 的認知裏,質數是除了本身和 1 以外,沒有其他因數的數字。 但由於小 X

微信程序7--微信程序連續旋轉動畫

creat position ref step nsh bsp debug pan anim 微信小程序連續旋轉動畫 https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-animation.html <view ani

2669[cqoi2012]局部 容斥+狀壓dp

方案 容斥 ng- scrip pad iostream set mes scan 2669: [cqoi2012]局部極小值Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 774 Solved: 411[Submit][St