1. 程式人生 > >java五子棋實現---權值、博弈樹

java五子棋實現---權值、博弈樹

花了很多天學習的一個關於五子棋的博弈樹,記錄一下。
先講一下五子棋的基本實現過程:
一、介面實現
Gobang.java
show(){}
main(){}
paint(){}
視窗使用Border佈局,寫倆JPanel,一個畫棋盤,一個做動作按鈕。這裡要注意的有兩點:一是重繪機制,直接把棋盤畫在paint方法裡;二是我們先不對棋盤做滑鼠監聽,等到我們點選了動作按鈕後再去監聽獲取畫筆。

二、介面實現了,要考慮在監聽類中做事件處理。我把後續的程式碼都放在了這個類裡。
在這之前,為了後續用起來方便,寫一個介面,把要用的常量定義好,另外兩個類只要實現這個介面就可以了。
1.先實現黑白棋交替下;(黑白棋用不同的標誌位)
2.棋子必須要下載交叉點上;(計算座標)
3.同一個位置只能下一顆棋子;(判斷該座標點的標誌是否為0)
4.棋子不能消失;(重繪機制)
5.考慮判斷輸贏。(四個方向判斷是否連成5子,我用的方法很垃圾很冗餘)
至此的話,人人對戰是可以的了。

三、人機對戰
這裡就要用到五子棋AI演算法了,有三個方法:權值法、博弈樹、機器學習。
權值法:
1.準備工作
1.考慮誰先下棋?
AI先下,棋子隨機下在中間一定的範圍內
2.定義陣列儲存權值
3.考慮棋子相連的情況和對應的權值
活連 
1連 10
2連 100
3連 1000       
4連 10000       
眠連
1連 1
2連 5
3連 50
4連 10000
4.使用Java的中一個Map集合來儲存棋子相連的情況和對應的權值
HashMap<String,Integer> map = new HashMap<String,Integer>();
map.put("010",10);//儲存活1連
map.put("020",10);//儲存活1連
map.put("021",1);//儲存眠1連
2.演算法的實現
1.人下棋後,判斷輸贏,如果贏了遊戲結束了,如果沒有贏就進入AI下棋;
2.根據棋子陣列統計當前棋盤上的最優位置,使用權值表示出來。
3.找出wieghtArray陣列中最大的權值,獲取最大權值所在的行數和列數。
4.根據行數和列數,讓AI在這個位置下棋
5.清空wieghtArray陣列中的資料。
6.AI的棋子判斷輸贏,如果贏了遊戲結束了,如果沒有贏就進入人下棋(回到1步);

博弈樹:
  關於博弈基礎--極大極小搜尋我就不多說了,估計也說不清楚。請看:https://blog.csdn.net/u013351484/article/details/50789521

      當輪到電腦下棋的時候,電腦會試著下子然後進行遞迴,直到得到葉子節點,判斷棋盤格局,若電腦返回分值大的,若人返回分值小的。這就構成了一棵博弈樹。這顆博弈樹有點龐大,並不能依靠暴力搜尋來尋找最佳解法。因此需要用到一些剪枝手段。常用的比較初級的有 alpha-beta 剪枝。這裡用的也是這個,具體過程還是去看連結裡的解釋吧,看完再看五子棋博弈的程式碼會比較清晰。要注意的是:將第0層電腦層放出來寫,下一個棋子我記錄一下位置(下完遞迴後要回溯),如果後面返回的情況比它好,我就更新它。最後我肯定會得到電腦選擇的分值高的那個座標落子便是。貼點程式碼如下:

	//輪到電腦下棋,試著下子然後進行遞迴,直到得到葉子節點,判斷棋盤格局,若電腦返回分值大的,若人返回分值小的。這就構成了一棵博弈樹。
	//由於效能有限,我們考慮到第四層。第0層電腦層我們把它放出來寫,因為是要記錄一下最好情況,方便以後落最好位置。
	public Point turnComputer() {
		Point point = new Point();
		int max = -INF;
	
		for (int i = 1; i < raw - 1; i++) {
			for (int j = 1; j < column - 1; j++) {
				if (array[i][j] == 0) {//判斷落子點周圍有沒有棋子,沒有則跳過
					if (array[i-1][j+1]==0&&array[i][j + 1] == 0 &&array[i+1][j+1]==0&& array[i-1][j-1]==0&&array[i][j - 1] == 0 &&array[i+1][j-1]==0&& array[i + 1][j] == 0 && array[i - 1][j] == 0) {
						continue;
					} else {//有棋子則電腦下子進行判斷
						array[i][j] = -1;
						int k = alphabeta(array, 1, -INF, INF, 1);//往下遞迴
						array[i][j] = 0; //下完子要回溯
						if (k > max) {//電腦下子當然要選擇大
							max = k;
							point.x = i;
							point.y = j;
						}
					}
				}
			}
		}
		return point;
	}


	public int alphabeta(int array[][], int player, int alpha, int beta, int depth) {
		if (depth == 4)//效能有限,只往下四層
			return getScore();	//葉子層返回棋盤格局的分值
		//不是葉子層則繼續試著下子
		for (int i = 1; i < raw - 1; i++) {
			for (int j = 1; j < column - 1; j++) {
				if (array[i][j] != 0)
					continue;

				if (array[i-1][j+1]==0&&array[i][j + 1] == 0 &&array[i+1][j+1]==0&& array[i-1][j-1]==0&&array[i][j - 1] == 0 &&array[i+1][j-1]==0&& array[i + 1][j] == 0 && array[i - 1][j] == 0) {
					continue;
				}

				array[i][j] = player;
				int v = alphabeta(array, player * -1, alpha, beta, depth + 1);//遞迴
				array[i][j] = 0;//回溯
				if (player == -1)
					alpha = max(alpha, v);
				else
					beta = min(beta, v);
				if (beta <= alpha) {//剪枝    這裡要畫樹進行分析,我也琢磨了好久
					if (player == -1)
						return alpha;
					return beta;
				}
			}
		}
		if (player == -1)
			return alpha;
		return beta;
	}

電腦下棋啦~

	// 電腦下棋的方法   通過AI演算法得出的最有利於電腦的落子點下棋
	public void computerChess() {
		Point point = turnComputer();
		int i = point.x;
		int j = point.y;
		gr.fillOval(X + size * j - size / 2, Y + size * i - size / 2, size, size);

		array[i][j] = -1;
		player = 1;

		icCopy = i;
		jcCopy = j;
	}

       那麼葉子節點的棋盤格局分值怎麼去估計呢? 從最終棋盤格局的每行、每列、每正上斜、每正下斜、每反上斜、每反下斜分析電腦方和我方棋子的分佈問題,分別計算各方的棋子成型個數乘以相應的權重,最後整個棋盤格局得分是電腦方得分減去我方。

四、重新開始事件和悔棋事件
重新開始需要將棋子陣列所有棋子全部置0然後重繪,悔棋是將剛剛下的白子黑子置0然後重繪。