1. 程式人生 > >超萌新級的Java專案實踐——五子棋(三)

超萌新級的Java專案實踐——五子棋(三)

這部分內容需要一定的資料結構的基礎,這一部分講解一下五子棋的AI演算法的思路和解決方案。

首先講一下演算法的概念:解題方案的準確而完整的描述簡單點說,就是解決問題用的方法的描述。

例如:比較經典的揹包問題,即將一堆物品裝進揹包,求裝入揹包的物品最高價值的值解決這種問題,有很多很多方法,比如把所有的方案都試試(窮舉法) ,比如列轉移方程使用動態規劃等等,這些就是演算法。

一般五子棋使用的是權值演算法(有的也叫評估函式;當然,想試試強化學習和深度神經網路的就無視吧),及對於一個點給予一定的權值分析,選擇一個最佳的點落子。

比方說下列情況:

這個情況下,白子在1位置的總權值和2位置的總權值是完全不一樣的,而且很明顯,1位置總權值是遠遠高於2位置的,原因就不詳細解釋了。

因此,我們只要計算出每一個可以下的位置的權值,對其進行計算,選出一個最高的就可以初步滿足AI的要求了,即至少會應付對手下棋。

然後問題來了,權值怎麼計算。

首先,電腦是不會下五子棋的,它不可能知道什麼地方是什麼情況,更不要說判斷最優的點了。所以,需要人為輸入棋子形態的權值。最好用字串存棋子形態,這裡我用“1”表示黑子用“0”表示白子,例如:“1”(一個黑子)設定為:20,“11”(兩個黑子)設定為:100,“111”(三個黑子)設定為:2000,“1111”(四個黑子)設定為:50000之類的。根據不同的下棋方式,可以使用不同的權值,可以說,每個人的權值是獨一無二的,完全看對於五子棋的理解。

然後,對於每一個可以下棋的點,通過掃描周圍的棋子形態判斷權值。

例如下圖:

1位置連線了下列棋子形態:“10”,“0”,“01”,“101”,“0”。

2位置連線了下列棋子形態:“1110”,“10”。

將上述棋子形態的權值相加即為各個點的總權值。

順便提一下,可以在判斷的時候手動加更大的權值。例如著名的兩三子的情況可以適當的額外權值來提高“AI智商”。

這裡提及一個數據結構:HashMap(基於雜湊表的地圖介面的實現),由於棋子情況是比較多的,對於所有點的周圍情況檢索起來是比較麻煩的,會花很多時間,於是這裡使用HashMap來儲存。(也可以直接用陣列等存)

首先預先將所有情況打好,建立一個介面。

public interface Gobang_window {
	public static final Integer onePiece = 20;
	public static final Integer twoPieces = 100;
	public static final Integer threePieces = 2000;
	public static final Integer fourPieces = 50000;
	public static final Integer Piece2 = 10;
	public static final Integer Piece12 = 10;
	public static final Integer Piece22 = 50;
	public static final Integer Piece2221 = 700;
	public static final Integer Piece222 = 1000;
	public static final Integer piecedouble3_1= 2000;
	public static final Integer piecedouble3_2= 2000;
	public static final Integer Piece22221= 10000;
	public static final Integer piececenter5or4= 20000;
	
}

然後,預先將數值存入雜湊表。

	private HashMap<String, Integer> hm = new HashMap<>();

	private void setPower() {
		hm.put("1", onePiece);
		hm.put("11", twoPieces);
		hm.put("111", threePieces);
		hm.put("1111", fourPieces);
		hm.put("2", Piece2);
		hm.put("12", Piece12);
		hm.put("22", Piece22);
		hm.put("222", Piece222);
		hm.put("22221", Piece22221);
	}

然後在玩家下一個棋子的時候,對棋盤的可用點進行權值估算,並確定下棋的位置,然後根據位置繪製棋子。

		int s = 0, d = 0;
		String code;//用於儲存棋子的形態

		for (int i = 0; i < RC; i++)//初始化權值表
			for (int j = 0; j < RC; j++)
				valueTable[i][j] = 0;
		
		for (int i = 0; i < RC; i++)
			for (int j = 0; j < RC; j++)
				//對於每個位置進行各個方向的判斷
				if (piece[i][j] == 0) {

					code = "";//棋子的形態重置
					c = 0;//相同的棋子判斷標誌變數
					for (int k = i + 1; k < RC; k++)
						if (piece[k][j] == 0)
							break;
						else if (c == 0) {
							c = piece[k][j];
							code += piece[k][j];
						} else if (piece[k][j] == c)
							code += piece[k][j];
						else {
							code += piece[k][j];
							break;
						}
					if (hm.get(code) != null) {//特殊情況要額外計數
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}
					code = "";
					c = 0;

					for (int k = j + 1; k < RC; k++)
						if (piece[i][k] == 0)
							break;
						else if (c == 0) {
							c = piece[i][k];
							code += piece[i][k];
						} else if (piece[i][k] == c)
							code += piece[i][k];
						else {
							code += piece[i][k];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);// ��
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}
					code = "";
					c = 0;

					for (int k = i - 1; k > 0; k--)
						if (piece[k][j] == 0)
							break;
						else if (c == 0) {
							c = piece[k][j];
							code += piece[k][j];
						} else if (piece[k][j] == c)
							code += piece[k][j];
						else {
							code += piece[k][j];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}

					code = "";
					c = 0;

					for (int k = j - 1; k > 0; k--)
						if (piece[i][k] == 0)
							break;
						else if (c == 0) {
							c = piece[i][k];
							code += piece[i][k];
						} else if (piece[i][k] == c)
							code += piece[i][k];
						else {
							code += piece[i][k];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}

					code = "";
					c = 0;

					for (int k = 1; (i + k < RC) && (j + k < RC); k++)
						if (piece[i + k][j + k] == 0)
							break;
						else if (c == 0) {
							c = piece[i + k][j + k];
							code += piece[i + k][j + k];
						} else if (piece[i + k][j + k] == c)
							code += piece[i + k][j + k];
						else {
							code += piece[i + k][j + k];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}

					code = "";
					c = 0;

					for (int k = 1; (i - k > 0) && (j - k > 0); k++)
						if (piece[i - k][j - k] == 0)
							break;
						else if (c == 0) {
							c = piece[i - k][j - k];
							code += piece[i - k][j - k];
						} else if (piece[i - k][j - k] == c)
							code += piece[i - k][j - k];
						else {
							code += piece[i - k][j - k];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}

					code = "";
					c = 0;

					for (int k = 1; (i + k < RC) && (j - k > 0); k++)
						if (piece[i + k][j - k] == 0)
							break;
						else if (c == 0) {
							c = piece[i + k][j - k];
							code += piece[i + k][j - k];
						} else if (piece[i + k][j - k] == c)
							code += piece[i + k][j - k];
						else {
							code += piece[i + k][j - k];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}

					code = "";
					c = 0;

					for (int k = 1; (i - k > 0) && (j + k < RC); k++)
						if (piece[i - k][j + k] == 0)
							break;
						else if (c == 0) {
							c = piece[i - k][j + k];
							code += piece[i - k][j + k];
						} else if (piece[i - k][j + k] == c)
							code += piece[i - k][j + k];
						else {
							code += piece[i - k][j + k];
							break;
						}
					if (hm.get(code) != null) {
						valueTable[i][j] += hm.get(code);
						if (code.equals("11") || (code.equals("111")) || code.equals("1111"))
							s++;
						if (code.equals("22") || (code.equals("222")) || code.equals("2222"))
							d++;
					}
					//對特殊棋子形態進行判斷
					if (s >= 2)
						valueTable[i][j] += piecedouble3_1;
					if (d >= 2)
						valueTable[i][j] += piecedouble3_2;
					s = 0;
					d = 0;
					piece[i][j] = 1;
					if ((Checkborad(5))||(Checkborad(4)))
						valueTable[i][j] += piececenter5or4;
					piece[i][j] = 0;
					
					
				}

		code = "";
		c = 0;

		for (int i = 0; i < RC; i++)
			for (int j = 0; j < RC; j++)
				if (max < valueTable[i][j]) {
					max = valueTable[i][j];
					x = i;
					y = j;
				}
		max = -1;

這樣就初步完成五子棋的AI。但是這樣的AI是比較“傻”的,判斷能力有限。如果需要更加“聰明”的AI的話,可以使用博弈樹等更好的(當然原理也不簡單),這裡就不會再說明了。

致此,五子棋的專案實現的差不多了,雖然大部分的內容是在講理論和實現方法,程式碼稍微少了點,但是,跟著這些思路自己手動一步一步寫的話會大大提升程式設計能力。

附上博弈樹的原始碼和五子棋介面