到如今,写五子棋也有一段时间了,虽然一开始的
人人版并没用多长的时间,但后面的人机对战版可没让人头疼。从对思路的一无所知(在此感谢陆亮小盆友
的“精心”指导,呵呵)到了解
算法的大概思想,再到真正
实现过程中遇到的问题,一路走来,最深的体悟就是,有些
错误是想出来而难的调试出来的,而且此种想问题的方法不是对着电脑看着代码想,那样会让人睡觉并且只会感到强烈的挫折感,而是在一个人路上想,去吃饭,去上自习,说不定哪个时候灵光一乍现,突然就
发现症结所在了。可惜,经本人不怎么灵泛的大脑,找出问题重见蓝天的同时发现花儿早就谢了,因此也就没有了那种见到新鲜事物时的兴奋激动,唯有的是走出迷雾后的释然。但不管怎么说,现在总算是把那纠结的人机对战五子棋大体完工了。有图有真相,也让自己的思路进入正题。呵呵。
总体来说,它应该算有三岁小孩的智商,会守会攻,而且以攻为主,虽然这攻守都不怎么高级,但一不小心你也是会输的,呵呵。
废话讲了许多,下面进入正题,给这一五子棋来个阶段性的总结:
首先,算法思路:
并没有用到贪心法系统的算法策略,而只是自创的暴力型算法。
首先提供数据结构:一个棋盘二维数组,初始值全为0.用1代表下的黑子,-1代表下的白字。没下一颗子,相应位置上的值改变。 两个权值数组,分别记录玩家的当前棋盘和电脑的当前棋盘。用权值代表该位置的重要性,每放一个棋子,权值数组都重新赋值。
其次就是权值的设置问题,四连的权值最大,其次是活三连,再活二连等等。这里情况分的越细越好,但对本人这类菜鸟,情况越多会把问题搞的越复杂,因此也就作罢。只分了最简单的几种情况:四连,三连,二连,单个子,其中,三连二连每种情况又分双边活和单边活,因此也就六种不同的权值。
最后关键就是找到最大的权值点了。我的思路是,在玩家权值数组中找到权值最大的点,在电脑权值数组中也找到最大的点,再两个比较,谁大电脑就下子在那个权值大的位置。
接下来的问题就是每个权值数组中权值最大点怎么找了。其实无非也就是多次的用两个循环遍历二维数组,在每个点上做四个方向上的判断,求得每个方向上有几个相同 的子,并用四个变量做暂时记录,然后比较这四个变量,在权值数组中赋上这
最多棋子个数对应的权值。
代码如下:
else {//人机对战
System.out.println(MenuJPanel.jc.getSelectedItem());
if(chesses[i][j]==0) {//当前无子时才能放
g.setColor(Color.black);
//玩家下黑子
g.fillOval(x - CHESS_SIZE / 2, y - CHESS_SIZE / 2,
CHESS_SIZE, CHESS_SIZE);
//下子之后该点权值赋值为1
chesses[i][j]=-1;
//电脑找权值最大的点放子
g.setColor(Color.white);
//遍历两棋盘
new MaxValue(VALUES_PALYER).lookforMaxValue(-1);//黑子为-1
new MaxValue(VALUES_COM).lookforMaxValue(1);//白字为1
/在两个权值数组中找最大权值的点
MaxValuep=getMAXVALUE(VALUES_PALYER);
MaxValuec=getMAXVALUE(VALUES_COM);
if(VALUES_PALYER[MaxValuep.y][MaxValuep.x]<=VALUES_COM[MaxValuec.y][MaxValuec.x]) {
//进攻
Max=MaxValuec;
}else {//防守
Max=MaxValuep;
}
int xx = X0+Max.x*SIZE;
int yy = Y0+Max.y*SIZE;//最大权值坐在点的坐标(由下标转化而来)chesses[Max.y][Max.x]=1;
g.fillOval(xx - CHESS_SIZE / 2, yy - CHESS_SIZE / 2,
CHESS_SIZE, CHESS_SIZE);
此段代码中注意:若if(VALUES_PALYER[MaxValuep.y][MaxValuep.x]<=VALUES_COM[MaxValuec.y][MaxValuec.x]) 改成<,则表示当玩家的权值点小于电脑的权值点时才取电脑的权值所在点,则此为以电脑防守为主。但<=则表示以电脑进攻为主。
下面是遍历权值数组:
for(int i=0;i<ROWS;i++ ) {
for(int j=0;j<COLUMNS;j++) {
//给空白处的点赋值
if(chesses[i][j]==0) {
//System.out.println(chesses[i++][j]);
//System.out.println("开始"+ count++);
m=i;
n=j;
num[0]=num[1]=num[2]=num[3]=0;
vacant[0]=vacant[1]=vacant[2]=vacant[3]=0;
//横向向右
while(n<COLUMNS-1&&chesses[m][++n]==color) {
num[0]++;
if(n<COLUMNS-1&&chesses[m][n+1]==0) vacant[0]++;
}
if(j>0&&chesses[i][j-1]==0) vacant[0]++;
n=j;
//横向向左
while(n>0&&chesses[m][--n]==color) {
num[0]++;
if(n>0&&chesses[m][n-1]==0) vacant[0]++;
}
if(j<COLUMNS-1&&chesses[i][j+1]==0) vacant[0]++; n=j;
//纵向向下
while(m<ROWS-1&&chesses[++m][n]==color) { num[1]++;
if(m<ROWS-1&&chesses[m+1][n]==0) vacant[1]++;
}
if(i>0&&chesses[i-1][j]==0) vacant[1]++;
m=i;
//纵向向上
while(m>0&&chesses[--m][n]==color) {
num[1]++;
if(m>0&&chesses[m-1][n]==0) vacant[1]++;
}
if(i<ROWS-1&&chesses[i+1][j]==0) vacant[1]++;
m=i;
//斜向左下
while(n>0&&m<ROWS-1&&chesses[++m][--n]==color) {
num[2]++;
if(m<ROWS-1&&n>0&&chesses[m+1][n-1]==0) vacant[2]++;
}
if(i>0&&j<ROWS-1&&chesses[i-1][j+1]==0) vacant[2]++;
m=i;n=j;
//斜向右上
while(n<COLUMNS-1&&m>0&&chesses[--m][++n]==color) {
num[2]++;
if(m>0&&n<COLUMNS-1&&chesses[m-1][n+1]==0) vacant[2]++;
}
if(i<ROWS-1&&j>0&&chesses[i+1][j-1]==0) vacant[2]++;
m=i;n=j;
//斜向右下
while(m<ROWS-1&&n<COLUMNS-1&&chesses[++m][++n]==color) {
num[3]++;
if(m<ROWS-1&&n<COLUMNS-1&&chesses[m+1][n+1]==0) vacant[3]++;
}
if(i>0&&j>0&&chesses[i-1][j-1]==0) vacant[3]++;
m=i;n=j;
//斜向左上
while(m>0&&n>0&&chesses[--m][--n]==color) {
num[3]++;
if(m>0&&n>0&&chesses[m-1][n-1]==0) vacant[3]++;
}
if(i<ROWS-1&&j<COLUMNS-1&&chesses[i+1][j+1]==0) vacant[3]++;
m=i;n=j;
int max = num[0];
int temp=0;
//找出有相同棋子最多的边,并给该位置赋权值
for(int q=0;q<4;q++ ){
if(max<num[q]) {
max=num[q];
temp=q;
}
}
//给该点设置权值
setValue(values,max,vacant[temp],i,j);
}
下面是给点赋权值代码:
public void setValue(int values[][],int num,int vacant,int i,int j) {
if(num==1) {//一连
if(vacant==1) {//一活
values[i][j]=100;
} else if(vacant==2) {//两活
values[i][j]=500;
}
}else if(num==2) {
if(vacant==1) {//一活
values[i][j]= 200;
}else if(vacant==2) {//两活
values[i][j]=2400;
}
}else if(num==3) {
if(vacant==1) {//一活
values[i][j]=1000;
}else if(vacant==2) {//两活
values[i][j]=5000;
}
}else if(num==4) {
values[i][j]=10000;
}else {
values[i][j]=0;
}
}
此处注意:vacant表示空格数,即要判断死活情况,当vacant=2时,表示两边空(相连棋子的一侧和该点的另一侧),vacant=1表示任何一边活。
下面是找权值数组中的最大值:
//在两个权值数组中找最大权值
public Point getMAXVALUE(int values[][]) {
Point max = new Point();
max.x = 0;
max.y = 0;
for(int i=0;i<ROWS;i++) {
for(int j=0;j<COLUMNS;j++) {
if(values[max.y][max.x]<values[i][j]) {
max.y = i;
max.x = j;
}
}
}
return max;
}
注意:此处可能有细心地朋友注意到,若有两个相同的权值怎么办?这时候应该选哪个点?在此,我是粗略的用遇到大的则覆盖,不比前一个位置的权值大就跳过的办法来处理的。当然此种处理不精确,也会影响到了机器的放子,即决定了他的聪明程度。不过这是第一个
版本,也就暂且这么处理了。
关键的思路就到这了,最后只剩下一个判断输赢问题。需要注意的是,判断输赢肯定是写在一个判断输赢的类中,此时玩家下子后判断和电脑下子后判断传进来的参数不同,玩家下子判断后传进来的时鼠标所点击的位置交叉点的下标,而电脑下子后判断传进来的是电脑找到的最大权值所在位置的下标。
附: 其实,本人这种思路可能是最笨的一个了。完全没有必要用到两个权值数组,这完全是把简单问题复杂话,甚至引起中间有些大的漏洞,就像遇到相同权值的这种情况。另一种稍微简单点的思路是只用一个权值数组表示当前棋局,电脑计算出每个点白子的权值和黑子的权值,把
他们相加,最后找到最大权值所在点放子。这样就省去了中间很多比较的环节。有兴趣的朋友可自己
研究下。
最后,说说这段时间写五子棋过程中所收获的经验,互相学习:
1 在代码布局上,记住“一个类对应一个功能,一个方法实现一个功能”的原则。代码的结构很重要。本次虽然完成了人机对战的大体功能,但在代码布局上还有很大的问题,因此还得有下一个版本的完善。
2 通过这次五子棋的开发,进一步了解了
重绘。
3 在得到画布(需在setVisible(true)之后)与调用画布顺序出冲突时,可以“曲线救国”,重新写一个方法,在该方法中用到画布。
例如:
//添加棋盘
chessboard = new ChessJPanel();
//得到画布对象
g = chessboard.getGraphics();
chesslistener = new ChessListener(g,this);
//添加监听器
public void addListener() {
chessboard.addMouseListener(chesslistener);
}
4 在一个类中要用到某个
接口中的成员,可以直接用接口类.成员,也可以用该类实现该接口,在类中直接使用。
5 学语言得边敲代码边总结,否者像我做玩家对战五子棋中遇到的问题就给忘了。可能下次遇到又得想个老半天了。
码了这么多字,却还不怎么想睡觉。没办法明天还得上课。朋友,晚安。呵呵
- 大小: 72.9 KB
- 大小: 76.5 KB