系统设计 既然是JavaApplication,要实现网络对战,故采用C/S模式编写,程序包含7个独立的类文件-ChessWZQ.java、Group.java、Message.java、Player.java、ServerOneClient.java、Server.java、BoardPanel.java。 其中BoardPanel.java主要负责棋盘的初始化,鼠标事件的处理,以及判断胜负条件。ChessWZQ.java定义了面板上的其他元素,包括玩家列表,标题栏等。也包括了事件处理和人工智能。ServerOneClient.java则负责网络对战的处理。其他的类都是又这3个主类延伸出去的。在具体实现的时候再介绍其作用。见图13。 系统实现 棋盘如图1,具体代码如下: String line = "a b c d e f g h i j k l m n o"; char [] rowNum1 = {'1','2','3','4','5','6','7','8','9'}; char [] rowNum2={'1','0','1','1','1','2','1','3','1','4','1','5'}; 这部分为棋盘的边界标识符,是必须要有的。用字符数组存储,最后确定位置放上去即可。 棋盘的绘制: private static int xp; // 棋子 X坐标 private static int yp; // 棋子Y坐标 public void paint(Graphicsgc){ super.paint(gc); //this.setBackground(Color.gray); //this.invalidate(); gc.setColor(Color.blue); //gc.setColor(new Color(255, 255,240)); //画横向标识符 gc.drawString(line,25,15); // 画竖向标识符 for(int i=0;i<9;i++){ gc.drawChars(rowNum1,i,1,10,35+i*30); } for(int i=9,j=0;i<15;i++,j+=2){ gc.drawChars(rowNum2,j,2,10,35+i*30); } // 画棋盘 for (int i = 0; i < 15; i++) { gc.drawLine(30, 30 + i * 30, 450, 30 + i* 30); //行 gc.drawLine(30 + i * 30, 30, 30 + i* 30, 450); //列 } gc.drawLine(25, 25, 455, 25); gc.drawLine(25, 25, 25, 455); gc.drawLine(25, 455, 455, 455); gc.drawLine(455, 25, 455, 455); //面板初始化 for(int i=0;i<15;i++){ for (int j = 0; j < 15; j++) { xp=16+i*30; yp=16+j*30; if (board[i][j] == 1){ gc.setColor(Color.black); gc.fillOval(xp,yp,28,28); //gc.drawImage(black, 16 +i * 30, 16 + j * 30, this); } if (board[i][j] == 2){ gc.setColor(Color.white); gc.fillOval(xp,yp,28,28); //gc.drawImage(white, 16 +i * 30, 16 + j * 30, this); } } } } 确定下子的坐标(xp,yp)画特定大小的椭圆,这里的坐标指棋子相对棋盘的绝对坐标。 private static int xp; // 棋子 X坐标 private static int yp; // 棋子Y坐标 if(col==1) g.setColor(Color.black); elseg.setColor(Color.white); g.fillOval(xp,yp,10,28,28); 要判断四个方向,横向、竖向、以及2个斜向。思想还是比较简单,相同颜色连成五子即胜利,网上有些网友评论说完整的判断胜负条件包括连五和活四,我觉得完全没必要,活四还要检查两边的棋子,虽然运算量不大,但五子棋的标准就是连五即胜,一步之差,我们既要遵守规则,也要简化代码实现尽完整的功能。 我们要事先建立一个盘面数组board[ ][ ],即棋型表,数组的每一个元素对应棋盘上的一个交叉点,用‘0’表示空位,‘1’表示黑棋,‘2’表示白棋。由于代码太多,下面给出了一般状况的判断胜负函数,及以坐标(x,y)为中心的9X9矩形,只能在棋盘的内部,如果超过棋盘,就要另外考虑。下面的代码就是一般情况,整个矩形在棋盘内部的时候的判断胜负条件,如图14。 胜负判断条件以下给出了X方向的代码: protectedboolean judge(int x,int y,int clr){ inti=0,j=0,count=0; // x方向 for(i=0,count=0;x-i>=0&&i<5;i++){ if(clr==board[x-i][y]){ count++; } else{ break; } // System.out.println("("+x+" , "+y+" )"+"count = "+count); if(count==5) return true; } for(i=1;x+i<15&&i<5;i++){ if(clr==board[x+i][y]){ count++; }else{ break; } if(count==5) return true; } returnfalse; } 为保证公平,先下子的就有禁手。但是我们一般没有这个规则限制,都是轮流先下子。理论上是这样的。但很多专家表明,先下子有很大的几率获胜,即使有禁手,先下子的一方还是有很大的优势,我觉得对于我们一般玩家而言,这些规定可以不考虑。 判断胜负的不管是单机还是玩家相互游戏,都必须开服务端,因为判断胜负是放在里面的。如果有一方获胜,弹出提示框,如果确认则清空棋盘继续新游戏。 public void getVictory(Message msg){ JOptionPane.showMessageDialog(null, "You Win The Game", "Congratulations", JOptionPane.INFORMATION_MESSAGE); //继续新游戏 label3.setText("Player2"); newGame(); } 需要注意的一点是落下的棋子如果离任何一方的边界小于4,则以边界为限制判断是否有一方获胜,这样的话也要考虑多种方向,但原理还是和基本情况是一样的。 这部分也属于网络套接字编程的经典应用,根据服务器地址连接特定端口。 网络部分很简单,但是我也做出了自己的特色,就是事件处理,做工可根据具体情况修改,虽然没有多少实用价值,但是也尽量使程序留有扩展性。下面给出了Message的代码。 /** * 消息列表 * type = 1 // 请求连接, msg = 连接者名字 * type = 2 // 放棋子 * type = 3 // 请求和其他人游戏 * type = 4 // 拒绝游戏请求 * type = 5 // 同意请求 * type = 6 // 发送胜利消息 * type = 7 // 断开连接请求 * type = 8 // 保存游戏,但是不放在磁盘上,在下一局开始时将会丢失 * type = 9 // 添加新的玩家到所有客户端的玩家列表 * type = 10// 响应信息 type ==1 * type = 11// 和玩家游戏,msg 保存玩家的名字 ,创建游戏的玩家设置游戏 * type = 12//信息设置 * type = 13// 设置玩家颜色 * type = 14// msg= 接受请求的一方设置 * type = 15// 服务端更新信息 * type = 16// 发送控制或错误信息 * type = 17// 游戏失败 * type = 18// 服务端套接字关闭 * type = 19// 玩家结束游戏及更新 * tyep = 20// 电脑获胜/ 玩家获胜 */ SOCKET监听端口,如果有玩家则更新玩家列表,如果双击除自己以外的玩家,则可以开始游戏,发送游戏请求,如果同意则返回棋子颜色并开始游戏,同时清空玩家列表。每一下子,把坐标传给服务端,并在2端显示出来,并且判断胜负。如果,有一方获胜,则提示消息通知双方,确定则继续开始新游戏。 服务端开启服务监听线程和客户端,如图15,如果有玩家知道服务器IP地址,即可选择在线玩家进行联网游戏,如图16。 双击除自己以外的一个玩家,发出游戏请求,同时要确定自己棋子的颜色,用MSG发回服务端。如果被拒绝,则返回原来的状态。 if(msg.color==1){ ss = new String("white"); bpanel.setColor(2); } else{ ss = new String("black"); bpanel.setColor(1); } 广义上来讲,博弈是指在一定的环境条件和一定的规则约束下,依靠自己所能够掌握的信息,从各自选择的行为或是策略进行选择并加以实施,并从各自取得相应结果或收益的过程。冯·诺伊曼(John von Neumann,1903-1957)和摩根斯坦恩(Oskar Margenstern,1902-1977)在1944年出版了《博弈论与经济行为》(Theory of Games and Economic Behavior)一书中,最早地提出了关于博弈论的概念。但是,对于非合作、纯竞争型博弈,诺伊曼所解决的只有二人零和博弈。在这里所抽象化后的博弈问题是,已知参与者集合(两方),策略集合(所有棋着),和盈利集合(赢子输子),最终是想去找到一个理论上的解或平衡,也就是对参与双方来说都最合理、最优的具体策略。 而在这里狭义的讲,博弈论主要是研究棋手们落子中理性化、逻辑化的部分,并将其系统化为一门科学。换言之,博弈就是研究个体如何在错综复杂的相互影响中得出最合理的策略,博弈论正是衍生于古老的游戏或曰博弈如象棋、扑克等。数学家们将具体的问题抽象化,通过建立自完备的逻辑框架、体系研究其规律及变化。 参考了很多五子棋算法,大部分思想差不多,就是搜索估值确定重要性,然后选取最大的一个点下子。具体做法如下:为电脑和玩家各建立一张表,用来存放棋型数据,比如“20”代表“冲四”的点,用“15”代表“活三”的点,那么在计算重要性时,就可以根据20>15得出前者比后者重要,下子时电脑便会自动选择“冲四”的点,这里还要说明一点的事,还要考虑四个方向。因为有可能有复合棋型,比如“四三”..从第一步起,不管是哪一方下子,电脑都有以这点为中心搜索9X9的矩阵内的所有空白点上棋子的重要性,一颗棋子对棋型影响的大小有9X9。重要看来虽然说进攻和防守的重要性一样的,但是我认为防守更重要。 在估值的时候,必须要考虑棋子的合法落子情况。不同的棋类博弈,其估值必定有极大的差别,各种因为规则而造成的不同因素影响估值的设计。不同的棋类游戏各有所谓的规则,规则中就有博弈双方都可以走哪些着法。某些博弈游戏很容易就找到合理着法,我所实现的五子棋,它就具有很简单的落子规则,即棋盘上所有的空位都可以落子,它们都是合理的着法。但是有些棋类游戏,比如在中国象棋和国际象棋中,情况就有些复杂了,每个棋子都有它特定的着法, 电脑下子要考虑自己和玩家的棋型,优先防守,如果没有要防的棋型,则搜索自己的棋型下子。 设置重要性,即估值模块,下面只给出横向的代码 private voidsetWeight(int x,int y,int tcolor){ inti=RectX1,j=RectY1,value=0,k=0,n=0,flag=0; // '--' 方向 for(i=RectX1,j=y;i<=RectX2;i++){ if(BoardPanel.board[i][j]!=0){ continue; } value=0;flag=0; for(k=1;i-k>=RectX1 &&k<5;k++){ if(BoardPanel.board[i-k][j]==tcolor){ value++; continue; } if(BoardPanel.board[i-k][j]==0){//black space flag++; break; } } for(k=1;i+k<RectX2 &&k<5;k++){ if(BoardPanel.board[i+k][j]==tcolor){ value++; } if(BoardPanel.board[i+k][j]==0){ flag++; break; } } n=weight(value,flag); if(weightBoard[i][j]<n){ weightBoard[i][j]=n; } } } 设定相应空位的重要值以后,选取最大的值下子,代码如下: private voidgetBiggest(int [][] arr,int x,int y){ int [] temp=new int[2]; int swt=arr[0][0],tmp=0; for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ if(arr[i][j]>swt){ temp[0]=i;temp[1]=j; swt=arr[i][j]; } } } x=temp[0]; y=temp[1]; arr[x][y]=0; } 对于特定的棋型,都有一个不同的估值,以此来区别不同棋型的优劣,也以此来决定最终的落子位置。毫无疑问,像已有四子连成一线且还可以继续落子的情况,明显要比只有三个子连成一线的情况要好,或者说优先级要更高,对弈双方对此种棋局,肯定都是把第一种情况放为首要分析的位置上。因此,要使棋手做出这种判断,就要把第一种情况的估值设置得高。 对不同的棋型设置重要值,比如:活四、死四、活三、死三、活二、死二。 同理,和判断胜负一样下子也要考虑边界特殊情况,矩形设置如下: private void setRect(int x,int y){ if(x-4>0) RectX1=x-4; else RectX1=0; if(x+4>14) RectX2=14; else RectX2=x+4; if(y-4>0) RectY1=y-4; else RectY1=0; if(y+4>14) RectY2=14; else RectY2=y+4; if(RectX1>RectY1) RectX1 = x-(y-RectY1); else RectY1 =y-(x-RectX1); if(RectX2>RectY2) RectY2 = y+(RectX2-x); else RectX2 =x+(RectY2-y); } 努力提高电脑AI一直是五子棋游戏关键,第一步的人工智能只是估值和搜索算法的集合,要真正的提高电脑AI还有很多步,例如,我的电脑AI只是片面的分析了双方的器型,没有前瞻性。如果玩家多想几步,电脑就发现不了。不过即使没采用递归算法,要是让电脑先下子的话,你的大部分时间也是花在防守上,可能是我的棋力太菜了,我自己还没有下赢过电脑。 目前有2种方法提高电脑棋力:一是递归算法,二是增加细致的特定棋形的判断,鉴于自己对算法方面欠缺甚多,没有办法完成这个功能,实在是遗憾。我大体说一下这2种算法的思路:递归算法的意思可以说成“今后几步预测法”,首先让电脑分析一个可能的点,如果在这儿下子将会形成对手不得不防守的棋型(例如:“冲四”、“活三”);那么下一步对手就会照你的思路来防守你,如此一来便完成了第一步的预测。这时候在调用盘面分析模块对预测后的棋型进行分析,如果出现了“四三”、“双三”或“双四”等制胜点,那么己方就可以获胜了。否则照同样的方法向下分析,就可以预测出多步。如果盘面上没有对手必须防的棋型,进攻不成的话就得考虑防守了,将自己和对手调换一下位置,然后用上面的方法来预测对手的棋重要防守和攻击都可以平衡,不过缺点是预测的算法量比较大。关于增加细致的特定棋形的判断,前面已经说过,以为不同的棋型,例如“死四”就有不同的几种情况,如果对每一种设置不同的重要值,也可以提高电脑AI,但是要设置合适的分数就要在实践中检验了,因为这个不是大小的问题,而是相差多少。正如前面所说,增加对细致棋型的判断也会提高电脑AI,虽然没有递归算法明显,但的确是一种途径,不过考虑的因素较多。 关于电脑学习,这听起来似乎是算法无法实现的功能。但是在对弈中却是非常有用的,但还只是些理论上的东西,比如棋局结束后,反向搜索,在自己的棋库中设置相关记忆。但我并不以为这是很好的方法。因为用这种方法,很有可能它没有找准原因,又或者进行学习的时候反而把劣等的学习进去;并且这种学习是很片面的,它只会认准一种极相似的情况(或者说一模一样的棋局情况),而不会辨识出相似的棋局情况。 源文件
1.1 程序结构说明
1.1 棋盘及棋子的类
1.1.1 棋盘
1.1.2 棋子
1.2 胜负判断条件
1.1 网络对战
1.1 电脑AI
版权所有© 帮我毕业网 并保留所有权利