基于Java(GUI)实现五子棋
五子棋
一、项目简介
背景描述:
五子棋是世界智力运动会竞技项目之一,是一种两人对弈的纯策略型棋类游戏,是世界智力运动会竞技项目之一,通常双方分别使用黑白两色的棋子,下在棋盘直线与横线的交叉点上,先形成 5 子连线者获胜。
许多国家的人对五子棋都有不同的爱称,例如,韩国人把五子棋称为“情侣 棋”,暗示情人之间下五子棋有利于增加情感的交流;欧洲人称其为“绅士棋”, 代表下五子棋的君子风度胜似绅士;日本人则称其为“中老年棋”,说明五子棋 适合中老年人的生理特点和思维方式。
系统描述 :
本系统按照五子棋的比赛规则,添加包括了开始对弈、结束对弈、悔棋、储存棋局进度和读取棋局进度等在内的功能。除了能够进行常规对弈外,本系统还提供了开启/关闭禁手规则的设置选项,更具灵活性。
本项目旨在通过设计五子棋对弈系统,使学生更加系统地掌握 Java 语言知识,了解语言的语法结构,理解类和对象的概念,准确使用各种数据类型,对面向对象中的继承和多态的概念理解、使用,在程序中提高代码的重用性,使设计的程序结构清晰、易于维护。
二、任务分析
五子棋系统总体可以分为两个部分:构图(或者视图)和规则。
其中,构图包括整体布局、棋盘绘制和棋子绘制等其他小部分。规则则需要判断棋子的坐标情况,判断棋子坐标集合中是否有满足五子棋“五子连线”的胜利条件的子集合等。
图 1 系统功能分解图
构图和规则应该是紧密结合的。构图是对外,将结果呈现给用户;而规则对内,是系统内部的判断。在程序执行时,用户的操作转换成图像,即绘图,然后程序对棋局进行分析,判断是否满足程序的规则部分,判断结果影响程序执行的路径。
在主体功能完整的基础上,我们再添加其他细节,例如背景音乐、落子声效等。同时美化程序,添加菜单和背景图片等优化用户体验。
经过分析,我们认为本系统的重点在于如何根据用户操作准确绘图以及如何根据绘图
图 2 程序执行过程简化图
结果分析对弈输赢,本次实践重点也将围绕这两部分展开。
三、系统设计
绘图:
Checkerboard 类:
完成了棋盘和棋子的绘制。其中棋盘直接利用公式绘制。
棋子的绘制是根据 Gframe 中的静态二维数组 piece[i][j],思路是将棋盘看做是一个二维坐标轴,i 和 j 组成坐标,数组中存放的值决定了是否绘制棋子,绘制黑棋子或者白棋子。
piece[i][j]:int 类型的静态二维数组。如果存放的值为 0,则没有棋子,如果存放结果为 1 则代表黑棋,如果存放结果为 2 则代表白棋。
代码实现(以黑棋为例):
if(Gframe.piece[i][j]==1) {int tx=size*j+20;int ty=size*i+20;g.setColor(Color.black);g.fillOval(tx-size/2, ty-size/2, size, size);
}
Gframe 类:
游戏开始的窗口框架,包括选项按钮、信息文本框等。
Gframe 接口了鼠标监听器 MouseListener。在鼠标点击时,记录鼠标点击的坐标,如果坐标在规定范围内,判断该位置是否有棋子,没有棋子则添加棋子,并将坐标、坐标数组标记等一系列信息添加到相关集合内,最后判断是否达成胜利条件。
关于棋手交替问题,这里利用了一个相关数据 operable。
Operable:int 类型,用来记录是是否下棋并完成黑白棋手下棋交替。点击开始按钮,operable 设置为 1,即黑棋手为先手,鼠标点击事件完成后改为 2 同时将数值传给 piece 数组,即交换为白棋手,同样完成后改为 1,依次交替。当点击结束时,设置为 0,再次点击棋盘将不再下棋。
代码实现(以黑棋获胜为例):
if(operable == 1) {piece[pi][pj]=1; //记录坐标的棋子信息list.add(new Point(pi, pj));//存入堆栈,便于悔棋repaint();operable++;//交替棋手if(winOrNot(pi, pj, 1)) {//判断黑棋手是否获胜,后文会阐述具体实现feedback.append("黑子获胜!\n");banbox.setEnabled(true);bgmClip.stop();winClip.setFramePosition(0);winClip.loop(0);feedback.setCaretPosition(feedback.getText().length());operable = 0;//胜利后记录为0,棋盘不可操作}
}
StartFrame 类:
游戏菜单窗口框架,包括开始游戏、游戏设置、游戏信息和退出游戏。
SettingFrame 类:
设置菜单窗口框架,包括关闭音效、关闭 BGM 和开关禁手规则的选项。
规则:
Gframe 类:
WinOrNot 方法:判断是否胜利。情况分为四种情况。分别为列判断、行判断、左斜线判断、右斜线判断。判断需要利用 piece 二维数组。
这里以列判断为例,当下棋时,记录下棋(参考棋子)位置的坐标,然后根据 y 轴坐标依次访问存放上四格和下四格位置信息的 piece 数组。首先判断是否出界,如果出界则访问到边界为止。判断条件根据 piece 中存储的数据,例如,如果参考棋子为黑棋,则统计 piece 数值为 1 的个数。访问结束后,判断数值符合条件的个数,如果统计个数等于大于 6 个(因为要访问两次参考棋子,所以加上一次重复访问,同时要注意,如果开启禁手规则,这里的判断条件要改变),则证明五颗棋子相连,满足胜利条件。
其他情况同理。
代码实现(以列判断为例):
int count = 0;//棋子统计
//列判定
int imin=x-5,imax=x+5;
//出界情况,则访问到边界为止
if(imin<0) imin=-1;
if(imax>14) imax=15;for(int i=x;i>imin;i--) {if(piece[i][y]==key)count++;else break;
}
for(int i=x;i<imax;i++) {if(piece[i][y]==key)count++;else break;
}
if(count>5)return true;
banOrNot 方法:规定了禁手规则。
关于禁手——> 禁手:对局中禁止使用而被判负的行棋手段。连珠中只有黑棋有禁手,有三三禁手、四四禁手、长连禁手、混合禁手等。这是国际连珠比赛基本规则之四。
其中长连禁手只是改变了判断黑棋个数时比较的对象(≥6 改为不得等于 5),较为简单,这里不再阐述。
这里以四四禁手为例。四四禁手规定黑方一子落下,不能同时形成两个或两个以上的四。首先我们要判断是否形成两个及以上的活四(四棋相连)。这里利用一个 int 整型数 four 来统计。在判断无出界问题的前提下,首先依次访问参考棋子的行、列、左斜线和右斜线八个方向。如果在一个方向的统计过程中四连中断则跳过该方向统计。在统计一个方向的过程中如果成功访问到第四个位置的棋子,说明没用中断即棋子四连,four+1。最后统计 four 的值,如果 four 的值超过 1,说明该棋子落下超过了一个四连,需要禁手。
代码实现(单一方向为例):
for(int i=1;i<5;i++) {if(y-i<0)break;if(piece[x][y-i]!=1)break;if(i==4&&y-5>=0&&piece[x][y-5]==0)four++;
}
其次我们要判断是否形成两个及以上的冲四(四个棋子中间缺棋)。这里我们同时
用到了 int 整型数 count 和 int 整型数 four。在判断无出界问题的前提下,首先依次访问参考棋子的行、列、左斜线和右斜线总计八个方向。每次统计完一条线后如果 count 次数等于 4 则 four+1,然后 count 清零,进入下一个方向的统计。最后 four 的判断条件和活四判断规则相同。
代码实现(以两个方向为例,因为冲四的满足条件需要考察一条线而不是单一方向):
count=0;
for(int i=1;i<5;i++) {if(y-i<0)break;if(piece[x][y-i]==1)count++;else break;
}
for(int i=1;i<5;i++) {if(y+i>14)break;if(piece[x][y+i]==1)count++;else break;
}
if(count==4)four++;
其他:
Save 方法和 Read 方法:保存和读取功能。可以实现保存当先棋局和读取棋局。保存和读取的数据利用 piece 数组传递。
悔棋:悔棋利用了堆栈。用 List 存储 piece 各个数据,当执行悔棋操作时,利用 List 的 remove 方法将末尾数据清空。悔棋后,通过判断和改变 operable 的值使悔棋后的交替失效(重复下棋),达到悔棋的效果。
代码实现(以悔棋成功为例):
piece[list.get(list.size()-1).lx][list.get(list.size()-1).ly]=0;
list.remove(list.size()-1);//堆栈弹出
//交替失效
if(operable==1)operable++;
else if(operable==2)operable--;
feedback.append("成功悔棋!\n");
feedback.setCaretPosition(feedback.getText().length());
repaint();
四、运行结果和问题
开始菜单:
设置菜单:
关于:
开始游戏:
读取棋盘:
悔棋:
问题:每次返回菜单后总会默认设置选项,所以每次都要设置是否开启禁手规则,比较繁琐。
五、心得体会
五子棋系统相比其他动作类程序来说,其设计思路更加清晰明白。因为对于棋盘类的程序,大致上都是程序通过分析棋盘上棋子的位置信息来判断是否满足棋盘规则。所以我们组成员在设计五子棋系统时头脑都是清楚的,在编写代码时思路和方向也更加明确。
由于是第一次体验用 Java 来编写游戏,我们组的成员兴致勃勃。虽然如此,由于这是第一次尝试而缺少经验,我们还是需要借鉴其他人的成果来完善我们的思路。同时我们也需要参考之前尝试和接触过的五子棋游戏来思考设计方法。总的来说,一切还算顺利进行。
添加背景音乐和背景图片对我们组的成员来说都是新的尝试,也为编写代码的过程添加了许多乐趣。在挑选 BGM 和图片的过程中有一种创造的快感。事实上,在布局和框架的构建过程中,我们组成员都在享受那种设计的感觉。
对于我们来说,完成基本的要求并不是我们追求的最终结果。我们更加希望在学有余力的情况下完成更多设计,诸如联机操作、人机对弈等许多画龙点睛的功能。尽管短时间内我们可能无法实现,但是通过了解这方面的知识和信息,我们收获颇多。
通过这次实践,我们学会了如何自主动手查阅信息。在设计规则的过程中,我们自己也要去研读关于五子棋规则的文献,尤其是五子棋最为复杂的禁手规则。这一方面我们需要更加深入了解和研究合理对策。同时,我们也明白,要设计一样程序,我们需要从其需求入手,分析完成项目需要的各个部件功能,并逐个解决。任何一个大工程都能分解为很多小项目解决。同时也要了解设计的规则,只有深刻理解其规则背后的本质,我们才能有更加清晰的设计思路和想法。
总的来说,这次实践让我们组受益匪浅。
六、参考文献
《Java 核心技术 卷 I》机械工业出版社(第十版);
CSDN 社区——《关于禁手规则的算法分析》。