JAVA第一个小项目——五子棋的制作(第二篇)

上一篇文章说了怎么制作棋盘、实现下棋的效果以及怎么判断输赢。那么接下来我们就谈谈怎么实现智能——人机大战。其实稍稍懂得计算机的人都知道计算机很笨的,不会有自己的思考,它要做什么必须指定,而且必须明确和唯一。那么要实现人机大战的话,毋庸置疑要用到一些算法,之前老师讲课的时候说了好多种,还包括什么树,自己没听说过,虽然自己在ACM校队,但是主要搞得方向是图论,哎呀都是借口,反正这次我给大家讲的就是最简单也是最容易懂的一个算法——权值法。

权值法是个什么玩意呢?其实他是基于枚举的暴力的方法。之前说过五子棋的棋盘是15*15的。并且赢得情况是5个相同的棋子连成一块,那么大家就可以想到,我们能枚举出所有赢得情况,大约几千种,在我们看来这个数字比较大,但是对于一秒就可以处理数量级到8的计算机来说,小case是不是?那么有了这个理解我们就可以实现两个表,分别是机器和人的胜利表,也就是储存赢得情况的表,那么有了这个表,最终机器决定下棋是要通过权值判断的,至于权值怎么来,先不用管,我们也要用两个表分别储存机器和人的权值的表,贴出代码:

        private GoBangPanel panel;
	private Graphics2D g;
	private int num;
	private Judge j;
	public class struct
	{
		int last;
		//计算x,y坐标
		int[] x = new int[5];
		int[] y = new int[5];
		//记录每个取胜表的棋子相连的情况
		int[] wn = new int[5];
	}
	struct[] winlistp = new struct[800];
	struct[] winlistc = new struct[800];
	int[][] scorep = new int[Row][Coloum];
	int[][] scorec = new int[Row][Coloum];
说一下这几个东西的意思,scorep就是储存人的权值的表,scorec就是储存机器的权值的表。winlist同理是储存赢的情况的表。它是一个结构体数组,其中的x,y分别储存的是五个棋子相连的坐标,last表示这个情况是不是有效,说明一下,比如这是储存白棋赢的一种情况,五个棋子的坐标都已经储存了,但是在下棋的过程中,有一个黑色的棋子下在了这个五个坐标的其中一个,那么白棋就不可能通过这个情况赢了,那么就是无效的。那么大家就可以发现,随着下棋越来越多,无论是计算机还是人的winlist,无效的就会越来越多。

理解了winlist的作用,我们就来说一下score的作用,他就是储存权值的表,每次遍历查找出黑棋和白棋的权值的最大值,我们领白棋是计算机,黑棋是人,那么如果权值最大的就是白棋那么就体现了进攻,如果是黑棋就体现的防守,因为我们现在所说的是针对计算机的,因为我们编这套程序是为了让计算机知道怎么下棋。

怎么计算权值呢?我们先不跳着讲,先看看winlist怎么初始化和更新,初始化就是把所有赢的情况全部储存嘛,贴出代码:

    public GoBangAn(GoBangPanel panel) 
    {
    	//初始化黑棋和白棋各自的评分
    	for(int i = 0; i < Row; i++)
    	{
    		for(int j = 0; j < Coloum; j++)
    		{
    			scorep[i][j] = 0;
    			scorec[i][j] = 0;
    		}
    	}
    	//初始化黑棋和白棋数组的各个元素
    	for(int i = 0; i < 800; i++)
    	{
    		winlistp[i] = new struct();
    		winlistc[i] = new struct();
    	}
    	this.panel = panel;
    	g = (Graphics2D)this.panel.getGraphics();
    	num = 0;
    	//枚举各种情况(有4个大方向)
    	//分别计算人和计算机行的赢的情况
    	for(int i = 0; i < Row; i++)
    	{
    		for(int j = 0; j < 11; j++)
    		{
    			for(int k = j; k < j+5; k++)
    			{
    				winlistp[num].x[k-j] = i;
    				winlistp[num].y[k-j] = k;
    				winlistc[num].x[k-j] = i;
    				winlistc[num].y[k-j] = k;
    				winlistp[num].last = 0;
    				winlistp[num].wn[k-j] = 0;
    				winlistc[num].last = 0;
    				winlistc[num].wn[k-j] = 0;
    			}
    			num++;
    		}
    	}
    	//分别计算人和计算机列的赢的情况
    	for(int i = 0; i < Coloum; i++)
    	{
    		for(int j = 0; j < 11; j++)
    		{
    			for(int k = j; k < j+5; k++)
    			{
    				winlistp[num].x[k-j] = k;
    				winlistp[num].y[k-j] = i;
    				winlistc[num].x[k-j] = k;
    				winlistc[num].y[k-j] = i;
    				winlistp[num].last = 0;
    				winlistp[num].wn[k-j] = 0;
    				winlistc[num].last = 0;
    				winlistc[num].wn[k-j] = 0;
    			}
    			num++;
    		}
    	}
	
    	//分别计算人和计算机右上对角线赢的情况
    	for(int i = 1; i < 11; i++)
    	{
    		for(int x = i,y = 0; x < 11; x++,y++)
    		{
    			for(int kx = x,ky = y; kx < x+5; kx++,ky++)
    			{
    				winlistp[num].x[kx-x] = ky;
    				winlistp[num].y[kx-x] = kx;
    				winlistc[num].x[kx-x] = ky;
    				winlistc[num].y[kx-x] = kx;
    				winlistp[num].last = 0;
    				winlistp[num].wn[kx-x] = 0;
    				winlistc[num].last = 0;
    				winlistc[num].wn[kx-x] = 0;
    			}
    			num++;;
    		}
    	} 
    	//分别计算人和计算机左下对角线的情况
    	for(int i = 1; i < 11; i++)
    	{
    		for(int y = i,x = 0; y < 11; y++,x++)
    		{
    			for(int kx = x,ky =y; ky < y+5; kx++,ky++)
    			{
    				winlistp[num].x[kx-x] = ky;
    				winlistp[num].y[kx-x] = kx;
    				winlistc[num].x[kx-x] = ky;
    				winlistc[num].y[kx-x] = kx;
    				winlistp[num].last = 0;
    				winlistp[num].wn[kx-x] = 0;
    				winlistc[num].last = 0;
    				winlistc[num].wn[kx-x] = 0;
    			}
    			num++;
    		}
    	}

因为代码很长但是不难,所以就贴出一部分,大家懂了之后剩下的代码自己补上绝对不是问题。看一下代码应该清楚,枚举所有赢的情况。分别是横着的方向和竖着的方向以及两个斜着的方向,这样就可以通过for循环直接全部初始化。score数组的初始化当然是0啦。

那么怎么更新winlist数组呢,很简单,假如更新白棋,那么就遍历棋盘找黑棋的位置,然后遍历winlist表,看看这个黑棋的坐标被哪些情况包含了,那么这个情况就无效了,贴出代码:

 //分别更新人和计算机的取胜表的情况
    public void update(int sitx,int sity)
    {
    	if(array[sitx][sity]%2 == 0)
    	{
    		for(int i = 0; i < num; i++)
    		{
    			if(winlistp[i].last == 0)
    			{
	    			for(int j = 0; j < 5; j++)
	    			{
	    				if((winlistp[i].x[j] == sitx) && (winlistp[i].y[j] == sity))
	    				{
	    					winlistp[i].last = 1;
	    					break;
	    				}		    				
	    				
	    			}
    			}
    			if(winlistc[i].last == 0)
				{
    				for(int j = 0; j < 5; j++)
    				{
	    				if((winlistc[i].x[j] == sitx) && (winlistc[i].y[j] == sity))
	    				{
	    					winlistc[i].wn[j] = 1;
	    					break;
	    				}
    				}
				}
    		}
    	}else 
    	{
    		for(int i = 0; i < num; i++)
    		{
    			if(winlistc[i].last == 0)
    			{
    				for(int j = 0; j < 5; j++)
    				{
	    				if((winlistc[i].x[j] == sitx) && (winlistc[i].y[j] == sity))
	    				{
	    					winlistc[i].last = 1;
	    				}
    				}
    			}
    			if(winlistp[i].last == 0)
    			{
    				for(int j = 0; j < 5; j++)
    				{
    					if((winlistp[i].x[j] == sitx) && (winlistp[i].y[j] == sity))
    					{
	    					winlistp[i].wn[j] = 1;
    					}
    				}
    			}
    		}
    	}

根据我说的再去看看代码就很简单了是不是?

那么接下来说一下怎么更新score数组,一个很显然的情况就是如果这个坐标被winlist里面的情况包含的越多那么下棋在这个位置的用处就越大。这是一个方面,为什么winlist还有一个wn数组呢,那就是判断是不是3个棋子连在一起了?4个棋子连在一起了??等等。因为是权值,每种情况都要有一定的数值,比如活三就给的大一点,活四直接认输就好了等等,权值怎么给确实很大的影响结果,自己到现在也没有找到很好的分配方式。这个怎么给大家自己决定吧,剩下的我就不多说了,每次更行权值列表,挑出最大的位置下棋就好了,不过因为把所有的情况都考虑了,之前有一个人说过,如果计算机先手不能保证赢但是能保证不输。哈哈哈不过我没有实现的这个程度,还是要加强的。那五子棋就讲到这里了。


你可能感兴趣的:(JAVA学习专栏)