超萌新級的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的話,可以使用博弈樹等更好的(當然原理也不簡單),這裡就不會再說明了。
致此,五子棋的專案實現的差不多了,雖然大部分的內容是在講理論和實現方法,程式碼稍微少了點,但是,跟著這些思路自己手動一步一步寫的話會大大提升程式設計能力。
附上博弈樹的原始碼和五子棋介面