源码地址:http://download.csdn.net/detail/clx55555/9718406
下载或转载使用请声明原著:但求心安。
基于Java的“多功能五子棋”游戏的设计和实现
引言
随着经济社会的迅速发展,人们生活水平有了很大的提高,人们的生活观念也发生了巨大的改变。高品质的生活、现代化得生活方式成为人们共同追求的目标;休闲、娱乐性活动作为时尚生活中的一部分,而被大多数的人所追捧。棋类项目作为一种智力型项目,可以益智健心、修身养性,也可以满足人们对于娱乐和休闲的新需求。一些项目比如五子棋等等,由于项目本身互动的特点,一直被人们所喜爱,因此得以生存和发展,成为许多人喜爱的休闲娱乐性棋类项目。
而今,以网络技术为核心和计算机技术的现代网络技术已经在现实生活得到了十分的广泛的使用。休闲类网络游戏集合了益智性、趣味性、互动性、娱乐性于一体,已经成为多数人群的选择的休闲方式。 借此,本论文在分析了当前计算机网络的蓬勃发展和人们对于休闲娱乐性生活方式的追求,以java为工具,以精美的界面,以常规安全的socket建立连接,设计更为益智的网络五子棋游戏,增强了趣味性和益智性。
1 需求分析 需求分析作用 软件需求分析是软件开发周期的第一个阶段,也是关系到软件开发成败的最关键一步。只有通过了软件需求分析,才能把软件的功能和性能由总体的概念性和理论性描述转化为具体的规格说明,从而建立软件开发的基础。实践证明,需求分析工作进行得准确程度,在很大程度上决定了软件开发的成败。 软件需求分析的任务就是让用户与开发者明确开发的是一个什么软件。具体的说,就是通过对问题及其环境的理解、分析与综合把握,建立逻辑模型,完成软件的逻辑方案设计。
1.1 基本需求分析
软件的界面简洁明了而不失精美,操作简单容易,功能按钮名称简单易懂,棋盘的大小,棋子的样式,可以换棋盘棋子。
1.2 高级需求分析
网络内部使用TCP/IP 方式利用Socket提供的服务,使用Java Graphics类进行用户图形界面的搭建。系统分成两个部分,分别为服务端程序以及客户端程序。结合实际情况,将服务器端的程序和客户端的程序放在一起,便可以更为方便的建立游戏和参与游戏,有效解决了在局域网中服务器未开启,则无法进行游戏的缺陷。由于将服务器端和客户端程序放在一起,所以可以减少界面绘制的冗余代码,使得客户端和服务器端使用相同的界面。五子棋网络游戏主要分为两个部分,游戏服务端和游戏客户端。服务端负责创建服务器,客户端负责连接服务器.
人机对战:为了是游戏更加益智,增加游戏的趣味性,使用了博弈的思想,用到博弈树,alpha-beta,启发式搜索的思想,让电脑模拟下棋,增加游戏的难度。
聊天窗口,用腾讯云作为服务器,让联机的玩家之间可以进行聊天,就算不在局域网也可以聊天。可以更好地交流,什么时候开战,免得一个人一直在等待。
2 开发环境及运行环境
开发环境:jdk 1.8;eclipse; mysql
运行环境:配置Java路径的计算机, mysql-connector-java-5.0.8.jar的配置环境
3.总体设计
总体设计是软件开发过程中的另一个重要阶段,在这一阶段中将根据需求分析中提出的逻辑模型,合理地完成物理模型的设计。这个阶段的主要目标是将反映了用户信息需求的逻辑方案转换成物理方案,并为下一阶段提供必须的技术资料。 总体设计应遵循以下原则:
★ 整体性:软件是作为统一整体而存在的。因此,在总体设计中要从整个软件的角度去考虑。
★ 灵活性:为保持软件长久的生命力,要求该软件具有很强的环境适应性。为此,该软件必须具有较好的开放性与结构的可变性。
★ 可靠性:可靠性是指软件抵御外界干扰的能力及受外界干扰时的恢复能力。
★ 经济性:经济性是指在满足游戏软件需求的前提下,尽可能地减少对游戏的开销。
3.1 整体设计
玩家可以选择人机,人人联机对战,可以聊天,服务器在我的腾讯云上一直开启状态,让人人间可以聊天。
3.2 用例图设计
玩家的用例图设计,玩家开始后可以选择人机,联机,本机人人,可以在线聊天,聊天和
联机都用的多线程,不影响主线程的使用。
服务器的设计:服务器没有界面只是一个简单的接收发送信息的代码,放在腾讯云服务器上,可以进行聊天。
登陆设计:为了配合聊天的使用,加入了登陆和个人信息,可以清楚地了解个人的信息和游戏分数。
3.3类的设计
CheckFrame继承窗口类,作为整个界面的窗口,一切标签,按钮,棋子棋盘都依赖这个类;
CheckBorder是用来画棋子棋盘的封装完毕,可以调用的方法是paint()可以重画棋盘;
GetC类是CheckBorder的内部类继承Thread类,联机对战中,负责接收对方的下子信息,使用多线程,不会因为对方不下棋,而影响主线程的使用。
Internet和server类是用来联机下棋的,局域网中的联机下棋,一个人为客户端,一个人为服务端,不联网也可以下棋。解决了没网不能下棋的烦恼,封装完毕,对外的方法是setChess(),getChess()用来下子和接受对方的棋子。
Computer是算法类,电脑的人工智能下棋,就是这个类,封装完毕,对外的方法是
Copychess()复制当前棋盘,alphaBeta()用来电脑下子,getSame()是用来判断输赢。
Clock类用来显示当前时间,封装完毕,直接添加在窗口上;
setMenu类是客户用来聊天的界面,封装完毕可以直接添加到窗口上,他的内部类有TCPClient类是用来连接服务器的,recvMSg类是用来发送接收消息的;
Databases类是与数据库连接有关的类。其中既有客户端连接数据库,又储存着注册用户信息资料,检查登录异常等功能的实现。
Login类为登录界面类。该类依赖于数据库类,来检查是否能够成功登录。
resign类为用户注册类,用户在此注册,并将注册信息写入数据库。
Editt类为资料编辑类,用户可以进行自身的资料编辑,并保存至数据库。
类图
3.4时序图的设计
联机对战的时序图
服务器处于建立状态
玩家先登录,可以给其他玩家发送信息,邀请联机,发送接收信息是异步的,不占用主线程。
然后可以自己建立服务器,也可以让对方建立服务器一个人连接别人,连接成功,或者别人连接到你,都会有提示信息,开始联机对战
人机对战时序图
人机对战,可以先选择人机的难度,选择自己的先手后手,开始人机对战,我个人认为这款人机对战还是比较难的,可以模拟6步下子,没有延迟,增加了游戏的乐趣,更加益智
4.五子棋人机alpha算法
简单的五子棋算法:
Q2O代表2个棋子 有一边被堵了,
下面的以此类推,每一次下棋都计算能下棋的位置自己的权值,要从4个方向计算,水平,垂直,45度角,135度角。堵棋是计算对方的权值,两者相加最大值就是下棋的位置。这个水平我认为还算一般,中等偏下。
Computer 类
/*
* 人机对战的算法,对外的接口是getQuan()和getSame(),getQuan()是人机对战是计算出来每个点的权值
* 取最大的的值作为下棋点,getSame()是用来判断输赢的接口
* */
主要代码:
public int getQuan(int i,int j)//每个位置的权值
{
int q=0;
q+=getQuan0(i,j);//4个方向的权值
q+=getQuan90(i,j);
q+=getQuan135(i,j);
q+=getQuan45(i,j);
return q;
}
private int getQuan0(int i,int j)
{
a=1;
b=0;
return getquanALL(i,j);
}private int getquanALL(int i,int j)//计算自己和对方的权值相加就是最大权值
{
int samechesSnums=0;//相同旗子个数
int samechessNumf=0;
int blankNums=0;
int blankNumf=0;
int q=0,qs=0,qf=0;
initc(i,j);
//计算人的权值
samechesSnums=getSamechessNums(1);//得到棋子的数量
if(c[0][0]>=0&&c[0][0]<=14&&c[1][0]>=0&&c[1][0]<=14)//得到空的数量
if(num[c[0][0]][c[1][0]]==0)blankNums++;
if(c[0][1]>=0&&c[0][1]<=14&&c[1][1]>=0&&c[1][1]<=14)
{
if(num[c[0][1]][c[1][1]]==0)blankNums++;
}
qs=getQuanEqual(samechesSnums,blankNums);
//计算自己的权值
initc(i,j);
samechessNumf=getSamechessNums(2);//得到棋子的数量
if(c[0][0]>=0&&c[0][0]<=14&&c[1][0]>=0&&c[1][0]<=14)//得到空的数量
if(num[c[0][0]][c[1][0]]==0)blankNumf++;
if(c[0][1]>=0&&c[0][1]<=14&&c[1][1]>=0&&c[1][1]<=14)
if(num[c[0][1]][c[1][1]]==0)blankNumf++;
qf=getQuanEqual(samechessNumf,blankNumf);
q=qs+qf;
return q;
}
private int getSamechessNums(int id)
//得到棋子的数量
{
int num1=1;
c[0][0]+=a;//右方向
c[1][0]+=b;
while(c[0][0]>=0&&c[0][0]<=14&&num1<5&&c[1][0]>=0&&c[1][0]<=14)
{
if(num[c[0][0]][c[1][0]]!=id)break;
num1++;
c[0][0]+=a;
c[1][0]+=b;
}
//左方向
c[0][1]-=a;
c[1][1]-=b;
while(c[1][1]>=0&&c[1][1]<=14&&num1<5&&c[0][1]>=0&&c[0][1]<=14)
{
if(num[c[0][1]][c[1][1]]!=id)break;
num1++;
c[0][1]-=a;
c[1][1]-=b;
}
return num1;
}
private int getQuanEqual(int chess,int blank)
{
if(chess==2&&blank==1)return Q2O;
else if(chess==2&&blank==2)return Q2;
else if(chess==3&&blank==1)return Q3O;
else if(chess==3&&blank==2)return Q3;
else if(chess==4&&blank==1)return Q4O;
else if(chess==4&&blank==2)return Q4;
else if(chess==5)return Q5;
else return 0;
}
五子棋中难算法: 要说Alpha-beta 算法 就得先说下max_min博弈树 算法,就是模拟电脑下子,要下在对电脑最优的地方,模拟人下子就要下在对人最优的地方,对电脑来说最差的地方 此图中甲是电脑,乙是玩家,那么在甲层的时候,总是选其中值最大的节点,乙层的时候,总是选其中最小的节点。 而每一个节点的分数,都是由子节点决定的,因此我们对博弈树只能进行深度优先搜索而无法进行广度优先搜索。深度优先搜索用递归非常容易实现,然后主要工作其实是完成一个评估函数,这个函数需要对当前局势给出一个比较准确的评分。 1. 在MAX层,假设当前层已经搜索到一个最大值 X, 如果发现下一个节点的下一层(也就是MIN层)会产生一个比X还小的值,那么就直接剪掉此节点。 解释一下,也就是在MAX层的时候会把当前层已经搜索到的最大值X存起来,如果下一个节点的下一层会产生一个比X还小的值Y,那么之前说过玩家总是会选择最小值的。也就是说这个节点玩家的分数不会超过Y,那么这个节点显然没有必要进行计算了。 通俗点来讲就是,AI发现这一步是对玩家更有利的,那么当然不会走这一步。 1. 在MIN层,假设当前层已经搜索到一个最小值 Y, 如果发现下一个节点的下一层(也就是MIN层)会产生一个比Y还大的值,那么就直接剪掉此节点。 关键代码如下:int alphaBeta2(int chess,int depth,int alpha,int beta,int i,int j)//alphaBeta剪枝;极大极小博弈树,人工智能第一步 模拟五步下子 { int best; if( getQuan(i,j,chess%2+1)>=Q5)//五子连珠 { return getComputerQuan()-getPeopleQuan();//电脑的权值减去人的权值 } else if(depth==0)//depth为0 ,搜索的尽头 { //System.out.println(getComputerQuan()-getPeopleQuan()); return getComputerQuan()-getPeopleQuan(); } else { if(chess==2) { int now; for( i = 0;i<= 14;i++) for(j = 0;j<=14;j++) { if(num[i][j]==0) { if(alpha>=beta)return alpha; else if(generator(i,j)==true){ num[i][j]=2; now=alphaBeta2(1,depth-1,alpha,beta,i,j); num[i][j]=0; if(now>alpha)alpha=now; } } } best=alpha; } else { int now; for( i = 0;i<= 14;i++) for(j = 0;j<=14;j++) { if(num[i][j]==0) { if(alpha>=beta)return beta; else if(generator(i,j)==true){ num[i][j]=1; now=alphaBeta2(2,depth-1,alpha,beta,i,j); num[i][j]=0; if(now<beta)beta=now; } } } best=beta; } } return best; } 这么多肯定是不够的最多预测两步,并不是每个点都要搜索如果他的方圆3之内没有棋子,就没必要对他进行深搜。代码如下: public boolean generator(int i,int j)//困难模式 //剪枝有邻居的定义是:想个两步以内至少有一个不为空的点即可 //。比如 b[7,7] 有一个子,那么 b[6,7]是他的邻居,b[5,7] 也是,但是 b[4,7] 就不是,因为相隔了三步 { int min_i,min_j,max_i,max_j; boolean k=false; min_i=i-2>=0?i-2:0; max_i=i+2<=14?i+2:0; min_j=j-2>=0?j-2:14; max_j=j+2<=14?j+2:14; for(i=min_i;i<=max_i;i++) {for(j=min_j;j<=max_j;j++) if(num[i][j]!=0){k=true;break;} } return k; } 可以看出搜索的复杂度是m^n 所以要想多搜几步减少m的值是必要的,m的值怎么减少呢?启发式搜索就是找最优的点有了打分之后,我们就可以按照分数高低进行排序了。具体实现的时候,是根据按照成五,活四,双三,活三,其他 的顺序来排序的。这个难度也比较高,我就按着这个顺序排序,取最优的五个进行下一步循环,大大减少了基数m可以搜索到第六步,多搜索了2步而且时间是几乎相同的,关键代码如下: if(chess==2)//电脑下子 { int now;//一个记录当前值的数, for( i = 0;i<= 14;i++) for(j = 0;j<=14;j++) { if(num[i][j]==0) { if(alpha>=beta)return alpha;//alpha剪枝 else if(generator(i,j)==true){//相邻剪枝 num[i][j]=2; // now=getQuan(i,j,chess); now=getQuan(i,j,1)+getQuan(i,j,2); num[i][j]=0; bestson[cnt]=new struct();//入队 bestson[cnt].i=i; bestson[cnt].j=j; bestson[cnt++].value=now; } } } struct t=new struct(); for(i=0;i<cnt;i++)//冒泡排序 for(j=i+1;j<cnt;j++) if(bestson[i].value<bestson[j].value) { t=bestson[i]; bestson[i]=bestson[j]; bestson[j]=t; } cnt=cnt<5?cnt:5; for(i=0;i<cnt;i++)//启发式搜索,取前五 { num[bestson[i].i][bestson[i].j]=2; now=alphaBeta(1,depth-1,alpha,beta,bestson[i].i,bestson[i].j); if(now>alpha)alpha=now; num[bestson[i].i][bestson[i].j]=0; } best=alpha; } 5.具体软件代码实现见附录 五子棋.jar 6.软件调试和测试 登录界面:有注册,登录成功,登录失败。 人机对战选择让多人来玩这个,并且和网上的人机进行对战,简单模式可以打败4399小游戏中的“超难五子棋”,在多人测试中发现了很多bug,比如都要五个子的话,他会堵玩家,而不是自己下棋,此时加入特判。在不断的测试中,也更改了棋子的权值,使其难度更高,玩家玩起来更有意思。 聊天框的测试:可以聊天,这个没发现bug 联机对战:开始的时候没加多线程,接收对方下子的时候,主线程会等待,加入了接收棋子的线程,下棋之后不会卡着等待。联机对战一个人建立连接,一个人连接对方,建立连接的玩家先下子。 测试的时候才发现bug如此之多,一点点改,比如表加上了,总是使整个界面变卡,人机做的时候bug更多,弄不好权值,一点点改正。没有多线程总会卡主线程,背景图片加不上,等等一系列问题,让我们的队伍头大,从一个只会输入输出的java小菜鸟,做出了能局域网对战,网络聊天,炫彩的界面,智能的人机,我们的团队付出了很大的努力。 7.参考书籍:言川博客(虽然有些思想是言传的,但是我的五子棋可以打败他的。) Java程序设计实用教程(第二版)(人民邮电出版社) 编者注:这是我和队友合作做出来的,有一个用mysql做的登录界面,有需要这个界面的请单独联系楼主,索要源码
public int getSame(int i,int j,int id)//判断输赢
{
int q=0,k=0;
a=1;b=0;initc(i,j);
k=getSamechessNums(id);
q=q
a=0;b=1;initc(i,j);
k=getSamechessNums(id);
q=q
a=1;
b=1;initc(i,j);
k=getSamechessNums(id);
q=q
k=getSamechessNums(id);
q=q
}