是机器的力量,还是人类的智慧
---分享总结第一个机器学习程序
宛如婴儿来世的第一声啼哭,今天第一个可以称为智能AI的五子棋程序(曙光)诞生了。就像钢琴家赋予每个美丽的音符跳动的生命,程序员用代码构筑了一个梦幻的世界。不得不说这种让冷冰冰的代码让它在自己手中变得鲜活,富有生气,甚至还有点灵性,这种感觉实在是妙不可言。
编写一个具有学习能力的程序的想法从去年就有了,具体可以参照2013年10月6日我发的那篇博客。为了完成编写一个打败熊哥的五子棋,一个具有学习能力的五子棋就是必须的。当时我就萌生了两个想法,机器学习就是其中一个,另一种博弈树是由于当时能力不足,我就只做了一个简单的博弈树。好了废话不多说。下面进入正题。
为了完成一个具有学习能力的五子棋,我们首先需要一些模块。
1.一个可以判断输赢的五子棋的主程序。(最好具有一定的AI)
2.每下一步棋数据的采集和存储。
3.文件的写入还有文件的读出。
4.对文件数据信息的处理与分析程序。
好了有了这些模块我们就可以写一个具有学习功能的五子棋程序了,具体流程图如下:
大家看到这个图后是不是觉得。切~ 这么简单,不就是文件的读写吗。嗯。其实我想说的是思路就是这么简单,丝毫没有一点高大上的东西。好了下面就让我从头开始讲解代码的编写了。这里才是重点,大家看好咯。
首先介绍一下整个算法的核心我们的棋谱
代码如下
package GobongV1_3; @SuppressWarnings("serial") public class Board implements java.io.Serializable { private int board[][]; public Board(int[][] board) { this.board = board; } public int[][] getBoard() { return board; } public void setBoard(int[][] board) { this.board = board; } }
我相信各位看官看到这,一定会满头雾水,不就是个序列化的数组吗,有啥好神奇的。没错我们的核心就是一个序列化的数组,别看它麻雀虽小可是五脏俱全,它所包含的信息已经足够我们折腾了。如果是说相声,这时候捧眼一定就会问,“我们下棋不是需要这些信息:第几步?黑棋还是白棋?它的X,Y坐标。这样才对嘛”。“不错问的好”,这是我就好展开我的思路和算法核心了,“我想反问一句,如果先手下棋,最后赢棋,棋盘上是不是有奇数个棋,如果后手下棋,后手赢棋盘上是不是只有偶数个棋子,当电脑先手时,每次玩家落子对棋盘进行遍历,如果有偶数个棋子,我们就让它匹配先手会赢的棋谱(输出的棋谱根据棋子个数自动分类),反之亦然。所以管它是什么黑棋和白棋赢了就是好奇。所以我们只需要第几步?和XY坐标就行了”。
这里是选择部分的核心算法
if (getchessNO() % 2 == 1// 人先下 && getCount(chessmanual.get(k)) % 2 == 0 // 后手方赢 || getchessNO() % 2 == 0// 电脑先下 && getCount(chessmanual.get(k)) % 2 == 1// 先手方赢 )
知道了核心算法接下来就好说了,先给大家看两张喜闻乐见的流程图
这是存入信息的流程图
核心代码如下
public static void addBoard(Board board) { ArrayList<Board> b = readFile();//将原来的文件数据读取出来 if (b == null) { b = new ArrayList<Board>(); } b.add(board);//将新的棋谱写入序列 saveFile(b);//再次将文件读出 }
读出信息的流程图
核心代码如下
读取棋盘
// 读取文件中的数组,将其添加到chessmanual中进行匹配 public void toArray() { ArrayList<Board> step = FileOperation.readFile(); if (step == null) return; for (int i = 0; i < step.size(); i++) { Board b = step.get(i); int array[][] = b.getBoard(); chessmanual.add(array); array = mirsym(array); chessmanual.add(array); array = horsym(array); chessmanual.add(array); array = mirsym(array); chessmanual.add(array); array = versym(array); chessmanual.add(array); array = mirsym(array); chessmanual.add(array); array = horsym(array); chessmanual.add(array); array = mirsym(array); chessmanual.add(array); } }
这里说明一下,因为五子棋具有极强的对称性,所以我们录入一个棋盘我们可以通过镜像变换,对称变换,得到实际上8个棋谱。感觉是不是很神奇呢?
变换代码如下
// 水平对称 public int[][] horsym(int[][] array) { int[][] temp = new int[N][N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { temp[i][j] = array[N - 1 - i][j]; } } return temp; } // 垂直对称 public int[][] versym(int[][] array) { int[][] temp = new int[N][N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { temp[i][j] = array[i][N - 1 - j]; } } return temp; } // 镜像对称 public int[][] mirsym(int[][] array) { int[][] temp = new int[N][N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { temp[j][i] = array[i][j]; } } return temp; }
匹配棋盘的核心算法
// 返回是棋盘是否成功 public boolean pipeiqipan() { // 得到棋谱 for (int k = 0; k < chessmanual.size(); k++) { int count = 0;// 计数器 // 匹配棋谱 for (int i = 0; i < 15; i++) for (int j = 0; j < 15; j++) { if (getchessNO() % 2 == 1// 人先下 && getCount(chessmanual.get(k)) % 2 == 0 // 后手方赢 || getchessNO() % 2 == 0// 电脑先下 && getCount(chessmanual.get(k)) % 2 == 1// 先手方赢 ) if (data.BOARDSTEP[i][j] != 0 && chessmanual.get(k)[i][j] != 0) { if (chessmanual.get(k)[i][j] == data.BOARDSTEP[i][j]) { count++; } } } // 如果步数满足,就按棋谱下 int step = getchessNO(); if (count == step) { for (int i = 0; i < 15; i++) for (int j = 0; j < 15; j++) { if (chessmanual.get(k)[i][j] == (count + 1)) { step++; data.ARRAY[i][j] = 2; data.BOARDSTEP[i][j] = step; chessboard.appendText("黑:横 " + (i+1) + " 竖 " + (j+1) + " 步数 " + data.BOARDSTEP[i][j]); } } return true; } } return false; }
总结:这次我讲了整个机器学习(五子棋板块)的算法核心,还有流程图。基本上可以给大家提供一个很好的思路;本次程序依旧还有不足,例如,电脑下棋只能下在(8,8)这个点,当人第一步不下在(8,8)这个点,录入的棋谱就是无效数据。还有录入棋谱只能是人工,没有实现机器之间的对战,以便快速得到大量的数据。
最后,还要感谢我的队友 昊神,其中一半的功劳应该归它。