Java第十课——封装性和哈希表实现五子棋AI
一.Java封装性
Emm…为什么在这里讲封装性呢?主要是看到某一小伙伴的代码,是份五子棋的代码,感觉写的非常有条理,而我们码龄差不多,到现在我才意识到最开始学代码时的那句话——自己的类做自己的事。而码了一年才注意到这件事也是惭愧!
先讲讲什么是封装性,正如字面理解,就是把一个类要做的事情封起来,像一个小盒子,一般来说,类外调用时就只能调用方法或是利用get()来取值,内部的变量一般是不能直接获取的。
同时,封装性在内也被处理的很好。之前写代码时不喜欢在函数里加形参,不喜欢用构造方法来获取参数,就容易导致变量名不匹配而出错,其实也是内部封装没有做好的结果。另外内部部的参数也不习惯设置访问权限,也很容易引起数值被东改西改,回头找不到错误原因。
最后,封装性还是要回到那句话:自己的类做自己的事。拿之前写的五子棋来说,监听器类做了很多事情,但从封装性的角度来看,其实监听器只要做一件事情,判断事件源,然后选择性可以加上少量运算,再调用对应方法,这其实才是监听器应该做的事情。至于调用什么方法,就看事件源的需求了。
那么实现封装性的方法就多种多样了,通俗来讲就是依据访问性来加入修饰符。例如只在类内使用的变量一般加private,同包下用protected,常量用static final,只在方法里使用的变量放进形参,多用构造方法实现赋值可以避免破坏封装性…等等
拿五子棋举个例子,建立一个类,这个类实现了五子棋的所有方法(如:绘制,下棋,悔棋,AI等等)
里面的变量有
private static final int ROW = 18;// 行数
private static final int COL = 18;// 列数
private static final int Height = 50;// 格子和落子区间的高度
private static final int Width = 50;// 格子的和落子区间宽度
private static final int PHeight = 1000;// 棋盘的总高度
private static final int PWidth = 1000;// 棋盘的总宽度
private static final int Length = 75;// 棋盘外围到边界的长度
private static final int Diameter = 30;// 棋子的直径
private static final int Radius = Diameter / 2;// 棋子的半径
protected int type[][] = new int[ROW][COL];// 保存棋子的类型
protected int row[] = new int[Record];// 在悔棋中记录行位置
protected int col[] = new int[Record];// 在悔棋中记录列位置
最后我把五子棋的代码修正了,分为4类ChessPanel封装了和棋盘有关的所有方法;ToolPanel封装了工具栏(包括悔棋按钮,重新开始按钮和切换模式的拉下框);Frame窗体,包含前两个类的对象;Listener监听器。具体代码这里就不放了。
哈希表实现AI算法
1.什么是哈希表?
先讲讲哈希表,哈希表是一种数据结构,通过关键码值(Key)而访问对应权值(Value)。举个例子:哈希表像是一个仓库,仓库里堆满了上了锁的箱子,每个锁对应一把钥匙,当我们像得到箱子里的东西(Value)时就需要对应的钥匙(Key)来访问。从数学上来讲就是一种映射关系,比如定义映射f为:"A"表示100,"B"表示1000,那么自变量输入"A"就能得到100。
2.AI算法实现思路
大概了解机制之后,即使没完全弄清楚,那从利用哈希表实现AI来再分析分析。
想要实现电脑下棋,实质就是电脑扫描棋盘,找到最适合下棋的那个点落子就可以了,那么怎么选出这个点?那就要一个方法,使得电脑找到下棋位置最佳,或者说获得收益最大的点,于是我们需要定义一个权值(Value)来表示在某一点的收益大小,Value越大表示收益越高。接下来会有四个问题
1、权值怎么计算得到的?
2、怎么通过场上的局势来获取到对应的权值?比如玩家已经连成三子了,那如何让电脑知道并拿到这个三个子所对应的权值?
3、要判断多少种情况?每种情况都要判断吗?
4、权值的大小如何决定?比如连成三个子和连成两个子的权值肯定不一样,或是可能会出现两个点权值大小相同,如何权衡?
一个一个解决:
1、不难理解,在某一个点的权值应该要综合考量,从这个点出发,八个方向都要判断权值大小,最后在权值的和为这一点的权值大小。
2、这里便要用到哈希表,先定义一个String Key 来获取场上的情况,比如如果1表示黑子,2表示白子的话,"111"表示三个黑子连在一起,"121"表示黑白黑…而这个String便是哈希表里的Key,通过这个Key去访问权值Value而得到在当前方向的权值,哈希表的具体使用方法和创建会在后面介绍。
3、某个方向上的情况比较多,当然各种情况都判断理论上能更确切的贴近场上情况,但实际操作时可以忽略掉一部分意义不太大的。比如电脑作为白子: “1”,“11”,“111”,"1111"这四种情况是肯定不能忽略的,而如果黑子旁已经连了一个白子像: “12”,“112”,“1112”,“11112"也是需要判断的,但像"212”,“2212”这类的就可以忽略
4、权值大小的话,没有什么特别的数字和比例,可以自己摸索或是在网上摘录都可以,可以自己先思考一些决定性的大小:比如已经电脑自己连成四个了,那就给一个较大的权值
3.哈希表的使用方法
找到思路和实现方法之后就可以开始了,先了解哈希表的使用
创建:HashMap<K,V> 哈希表名 = new HashMap<K,V>();
这里的K,V 是变量类型,拿等下使用哈希表的创建举例子
HashMap<String, Integer> hm = new HashMap<String, Integer>();
使用前要把对应关系放进去,用put方法
hm.put("1", 10);
hm.put("11", 100);
hm.put("111", 1000);
hm.put("1111", 10000);
hm.put("", 0);
使用时用get方法:
String key = "";
...//得到对应Key
Integer value = hm.get(key);
4.具体实现
1、放进对应关系
这份代码的权值是没怎么思考过的,可以自己修改权值,而判断情况的种类可以当作参考
private void putinit() {
hm.put("1", 10);
hm.put("11", 100);
hm.put("111", 1000);
hm.put("1111", 10000);
hm.put("2", 10);
hm.put("22", 100);
hm.put("222", 1000);
hm.put("2222", 10000);
hm.put("12", 20);
hm.put("112", 200);
hm.put("1112", 2000);
hm.put("11112", 20000);
hm.put("21", 20);
hm.put("221", 200);
hm.put("2221", 2000);
hm.put("22221", 20000);
hm.put("", 0);
}
2、把各个位置权值放进权值数组
创建一个权值数组,专门用来放每个位置的权值,玩家每走一次便要清零,于是写一个方法AI()用来给权值数组赋值
private void AI() {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (type[i][j] == 0) {// 当前位置是空的
String dir = "";// direction也就是求权值方向
// 对八个方向同求权值
// 向上
dir = "up";
getvalue(i, j, dir);
// 向下
dir = "down";
getvalue(i, j, dir);
// 向左 ...
// 向右 ...
// 左上 ...
// 右上
// 左下
// 右下
}
}
}
}
在这段代码里,我用另一个函数getvalue通过哈希表来获取对应位置的权值,因为方向不同对应的 i 和 j 的加减方式不同,所以要String direction来帮助判断方向
3、从哈希表里获取权值
// 与AI()配套的方法,用于求权值
private void getvalue(int i, int j, String dir) {
String number = "";
int color = 0;
// 对八个方向同求权值
// 以向上举例
if (dir.equals("up")) {// 向上:k=j-1
for (int k = j - 1; k >= 0; k--) {// 记住每个方向的终止条件不同
if (type[i][k] == 0) {// 上面那个也是空的
break;// 两个空的就退出
} else {// 上面那个不是空的
if (color == 0) {// color还没被赋值时,获取这个位置(也就是第二个位置)的棋子颜色
color = type[i][k];
number += type[i][k];
} else if (color == type[i][k]) {// 从第三个位置之后,如果颜色与第二个位置相同则记录
number += type[i][k];
} else {// 颜色出现不同
number += type[i][k];
break;// 可直接退出
}
}
}
} else if (dir.equals("down")) {// 向下:k=j+1
for (int k = j + 1; k < type[j].length; k++) {
if (type[i][k] == 0) {
break;
} else {
if (color == 0) {
color = type[i][k];
number += type[i][k];
} else if (color == type[i][k]) {
number += type[i][k];
} else {
number += type[i][k];
break;
}
}
}
} else if () { // 向左:k=i-1 }
...//八个方向都获取
// 根据code取出hm对应的权值
Integer v = hm.get(number);
if (v != null) {
value[i][j] += v;
}
}
最后paint方法和悔棋的操作就不细说了,到这实现电脑下棋的操作算是完成了