人机版五子棋开发之篇一_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 人机版五子棋开发之篇一

人机版五子棋开发之篇一

 2011/11/8 7:52:46  微Smile  http://792881908-qq-com.iteye.com  我要评论(0)
  • 摘要:到如今,写五子棋也有一段时间了,虽然一开始的人人版并没用多长的时间,但后面的人机对战版可没让人头疼。从对思路的一无所知(在此感谢陆亮小盆友的“精心”指导,呵呵)到了解算法的大概思想,再到真正实现过程中遇到的问题,一路走来,最深的体悟就是,有些错误是想出来而难的调试出来的,而且此种想问题的方法不是对着电脑看着代码想,那样会让人睡觉并且只会感到强烈的挫折感,而是在一个人路上想,去吃饭,去上自习,说不定哪个时候灵光一乍现,突然就发现症结所在了。可惜,经本人不怎么灵泛的大脑
  • 标签:开发

  到如今,写五子棋也有一段时间了,虽然一开始的人人版并没用多长的时间,但后面的人机对战版可没让人头疼。从对思路的一无所知(在此感谢陆亮小盆友 的“精心”指导,呵呵)到了解算法的大概思想,再到真正实现过程中遇到的问题,一路走来,最深的体悟就是,有些错误是想出来而难的调试出来的,而且此种想问题的方法不是对着电脑看着代码想,那样会让人睡觉并且只会感到强烈的挫折感,而是在一个人路上想,去吃饭,去上自习,说不定哪个时候灵光一乍现,突然就发现症结所在了。可惜,经本人不怎么灵泛的大脑,找出问题重见蓝天的同时发现花儿早就谢了,因此也就没有了那种见到新鲜事物时的兴奋激动,唯有的是走出迷雾后的释然。但不管怎么说,现在总算是把那纠结的人机对战五子棋大体完工了。有图有真相,也让自己的思路进入正题。呵呵。



  总体来说,它应该算有三岁小孩的智商,会守会攻,而且以攻为主,虽然这攻守都不怎么高级,但一不小心你也是会输的,呵呵。
  废话讲了许多,下面进入正题,给这一五子棋来个阶段性的总结:
  首先,算法思路:
   并没有用到贪心法系统的算法策略,而只是自创的暴力型算法。
    首先提供数据结构:一个棋盘二维数组,初始值全为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
  • 查看图片附件
发表评论
用户名: 匿名