首先讲一下算法的概念:解题方案的准确而完整的描述简单点说,就是解决问题用的方法的描述。
例如:比较经典的背包问题,即将一堆物品装进背包,求装入背包的物品最高价值的值解决这种问题,有很多很多方法,比如把所有的方案都试试(穷举法) ,比如列转移方程使用动态规划等等,这些就是算法。
一般五子棋使用的是权值算法(有的也叫评估函数;当然,想试试强化学习和深度神经网络的就无视吧),及对于一个点给予一定的权值分析,选择一个最佳的点落子。
比方说下列情况:
这个情况下,白子在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 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的话,可以使用博弈树等更好的(当然原理也不简单),这里就不会再说明了。
致此,五子棋的项目实现的差不多了,虽然大部分的内容是在讲理论和实现方法,代码稍微少了点,但是,跟着这些思路自己手动一步一步写的话会大大提升编程能力。
附上博弈树的源码和五子棋接口
源代码:https://github.com/IamA1536/GobangTeamWork.git
简易接口:https://github.com/IamA1536/GobangWork.git