该程序是基于Java的GUI图形界面,实现的双人版五子棋小游戏。该程序拥有简洁美观的图形化界面,且界面主要由棋盘、标题和游戏操作的按钮三部分组成。
目的:使学生巩固和加深以Java语言为基础的面向对象编程技术理论知识的理解,提高实际动手编程能力的培养,掌握以Java为核心的应用软件开发方案,达到能独立阅读、编制和调试一定规模的Java程序的水平。
背景:五子棋是大家喜闻乐见的小游戏,规则简单,变化多端,非常有趣味性,通过编写五子棋小游戏的程序,进一步锻炼java图形编程技巧和编写程序的能力。
从用户角度考察系统应具有哪些功能及非功能性需求。这里实现了双人对下的五子程序。
总体功能:1、该游戏界面的大小是不可变的,程序会自动获取电脑屏幕的大小信息,并且计算出合适的位置居中显示。2、标题位于界面的最上方;棋盘为19*19的围棋棋盘,位于左下方。3、按钮包括:“开始游戏”、“游戏说明”、“游戏设置”、“认输”、“认输”、“悔棋”、“关于”、“退出”。共7个,位于棋盘的右侧。4、 在单击鼠标时,在相应的位置显示棋子并且播放出下棋的音效 ,还能够显示轮到哪一方下棋(规定黑子先下)。5、可以保存棋局,即保存之前下过的棋子。6、能够判断游戏胜负,弹出窗口提示并且播放音乐。7、实现计时功能,设置一盘和全局计时。8、游戏采用积分制,最后以积分的多少来判断输赢。9、只有玩家点击棋盘下棋子后才会开始计时。
生成可执行文件与安装包摆脱对开发环境的依赖:利用生成工具exe4j,将jar软件包与转为exe 可执行程序。最后通过Windows 安装程序制作软件InnoSetup 将生成的exe文件与程序的源文件打包成安装包。
JDK: Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。
Eclipse:是一个免费的、基于Java的可扩展开发平台,它允许开发人员开发和测试并用其他编程语言编写代码。 eclispe提供了一个用于处理工具的通用用户界面模型,目的是运行在多个操作系统上。 对于学习java的人来说Eclipse并不陌生,它是一个开放源代码的、基于Java的可扩展开发平台,说到底它是可扩展软件开发应用程序框架,工具和运行时的开源平台。
exe4j:是一个帮助你集成Java应用程序到Windows操作环境的java可执行文件生成工具,无论这些应用是用于服务器,还是图形用户界面(GUI)或命令行的应用程序。如果你想在任务管理器中及Windows XP分组的用户友好任务栏里以你的进程名取代java.exe的出现,那么exe4j可以完成这个工作。exe4j帮助你以一种安全的方式启动你的 java应用程序,来显示本地启动画面,检测及发布合适的JRE和JDK,以及进行启动时所发生的错误处理等,以至于更多。
Inno Setup:用Delphi写成,其官方网站同时也提供源程序免费下载。它虽不能与Installshield这类恐龙级的安装制作软件相比,但也当之无愧算是后起之秀。Inno Setup是一个免费的安装制作软件,小巧、简便、精美是其最大特点,支持pascal脚本,能快速制作出标准Windows2000风格的安装界面,足以完成一般安装任务。
Photoshop:由Adobe Systems开发和发行的图像处理软件。 Photoshop的专长在于图像处理,而不是图形创作。 图像处理是对已有的位图图像进行编辑加工处理以及运用一些特殊效果,其重点在于对图像的处理加工;图形创作软件是按照自己的构思创意,使用矢量图形来设计图形。
对程序进行基本的设置、如设置标题、窗口大小、加载标题栏图片和背景图片、注册鼠标事件监听、启动线程等。
Public void init() {
this.setTitle("欢乐五子棋"); //设置窗口标题
this.setSize(700, 700); //设置窗口大小和位置,在屏幕中间位置显示
this.setLocationRelativeTo(null); // 窗体居中
//加载背景图片
try {
bg = ImageIO.read(new File("bg8.png"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setIconImage(Toolkit.getDefaultToolkit().createImage("bt.png")); //添加标题栏图片
//将时间值归零,开始计时
blackTime=0;
whiteTime=0;
tempTime=0;
t.start(); //启动计时器线程
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗口可关闭
this.setResizable(false);//设置窗口大小不可变
this.setVisible(true);//设置窗口可见
this.addMouseListener(this); //注册鼠标事件
this.addMouseMotionListener(this);
}
界面的上方绘制处倒计时和下棋方,字体为黑体,大小为16。在中间绘制出19x19的棋盘,鼠标在棋盘上移动到的位置以该格交线为中心绘制出半径为格子一半大小的红色边框。在界面的底部,绘制处玩家的积分和全局时间,字体为宋体,大小为18。
public void paint(Graphics g) {
//双缓冲,防止闪屏
BufferedImage bi = new BufferedImage(700, 700, BufferedImage.TYPE_INT_ARGB);
Graphics g2 = bi.createGraphics();
g2.setColor(Color.BLACK); //设置初始颜色
g2.drawImage(bg,1, 20, this); //绘制背景
g2.setFont(new Font("黑体",Font.BOLD,16)); //输出下棋方
g2.drawString("下棋方:", 310, 145);
if(isBlack) {
g2.drawString("黑方", 385, 145);
}
else {
g2.drawString("白方", 385, 145);
}
//如果未设置时间,则默认为20,如果设置为空或零,则无限制
if(maxTempTime>0) {
g2.drawString("倒计时: "+(maxTempTime-tempTime)+" 秒",310,120);
}
else {
g2.drawString("倒计时: 无限制 ",310,120);
}
//画出黑白方的信息
g2.setFont(new Font("宋体",Font.BOLD,18));
g2.drawString(String.valueOf(blackWonNum)+" 积分",153,637); //画出获得的积分
g2.drawString(String.valueOf(whiteWonNum)+" 积分",370,637);
g2.drawString(blackTime/3600+":"+blackTime%3600/60+":"+blackTime%60, 150, 664); //画出全局总用时
g2.drawString(whiteTime/3600+":"+whiteTime%3600/60+":"+whiteTime%60, 370, 665);
//画棋盘
int j=0;
for(int i=158;j<19;i+=25){
j++;
g2.drawLine(32, i, 485, i);
}
j=0;
for(int i=32;j<19;i+=25){
j++;
g2.drawLine(i, 159, i, 608);
}
//画出棋盘上的九个点
g2.fillOval(105, 230, 5, 5);
g2.fillOval(105, 381, 5, 5);
g2.fillOval(105, 530, 5, 5);
g2.fillOval(405, 530, 5, 5);
g2.fillOval(405, 230, 5, 5);
g2.fillOval(405, 381, 5, 5);
g2.fillOval(254, 230, 5, 5);
g2.fillOval(254, 381, 5, 5);
g2.fillOval(254, 530, 5, 5);
//在鼠标移动到的位置,画出红色边框,以便观察确认下棋的位置
g2.setColor(Color.red);
g2.drawLine(moveX-12+32,moveY+158-12,moveX-12+32,moveY+158+12); //竖边框
g2.drawLine(moveX+12+32,moveY+158-12,moveX+12+32,moveY+158+12);
g2.drawLine(moveX-12+32,moveY+158-12,moveX+12+32,moveY+158-12); //横边框
g2.drawLine(moveX-12+32,moveY+158+12,moveX+12+32,moveY+158+12);
g2.setColor(Color.black);
//画棋子
for(int i=0;i<19;i++)
for(j=0;j<19;j++) {
if(chess[i][j]==1) {
g2.fillOval((32+i*25-10), (158+j*25-10), 20, 20);
}
else if(chess[i][j]==2) {
g2.setColor(Color.white);//设置白色
g2.fillOval((32+i*25-10), (158+j*25-10), 20, 20);
g2.setColor(Color.black);//还原黑色
}
}
g.drawImage(bi, 0, 0, this);
this.isWon();//画完后判断输赢
}
为了方便观察、找准下棋的位置,该程序添加了红色选择框的绘制,通过mouseMoved();方法来监听,当鼠标移动到棋盘上时,或根据所获得的坐标,在以该格交线为中心绘制出半径为格子一半大小的红色边框。在鼠标点击时,先判断该位置是否已有棋子,即chess[x][y]==0,如果下棋方为黑方,则chess[x][y]=1,白方则chess[x][y]=2,同时播放下棋声。如果chess[x][y]!=0则不进行任何操作。最后调用repaint();重新绘制棋盘。并且每一次绘制都会调用isWon()方法进行五子连珠的判断。
//在鼠标移动到的位置,画出红色边框,以便观察确认下棋的位置
g2.setColor(Color.red);
g2.drawLine(moveX-12+32,moveY+158-12,moveX-12+32,moveY+158+12); //竖边框
g2.drawLine(moveX+12+32,moveY+158-12,moveX+12+32,moveY+158+12);
g2.drawLine(moveX-12+32,moveY+158-12,moveX+12+32,moveY+158-12); //横边框
g2.drawLine(moveX-12+32,moveY+158+12,moveX+12+32,moveY+158+12);
g2.setColor(Color.black);
//画棋子
for(int i=0;i<19;i++)
for(j=0;j<19;j++) {
if(chess[i][j]==1) {
g2.fillOval((32+i*25-10), (158+j*25-10), 20, 20);
}
else if(chess[i][j]==2) {
g2.setColor(Color.white);//设置白色
g2.fillOval((32+i*25-10), (158+j*25-10), 20, 20);
g2.setColor(Color.black);//还原黑色
}
}
//下棋
if(x>=32&&x<=482&&y>=158&&y<=608) {
x = (x-32+12)/25;
y = (y-158+12)/25;
if(chess[x][y]==0) {
con=false;
if(isBlack) {
chess[x][y] = 1;
isBlack = false;
tempTime=0;
}
else {
chess[x][y] = 2;
isBlack = true;
tempTime=0;
}
lastX=x;lastY=y;//记录上一步下棋的坐标
playMusic("下棋声");//播放下棋音效
this.repaint();//重新绘制
con=true;
begin=true;
}
}
//画红色边框
public void mouseMoved(MouseEvent e) {
int moveXX,moveYY;
moveXX = e.getX(); moveYY = e.getY();//获得鼠标移动到的位置,以便画出红色边框
if(moveXX>=32&&moveXX<=482&&moveYY>=158&&moveYY<=608&&con) {
moveX = (moveXX-32+12)/25*25;
moveY = (moveYY-158+12)/25*25;
this.repaint();//重新绘制
}
}
从斜向左、斜向右、水平、竖直四个方向检测,用count计算连珠的棋子个数,每一个方向扫描前,进行判断count值是否大于等于5。是则不扫描其他方向,否则将count置零并开始下一个方向的扫描。以此来降低算法的时间复杂度。如果出现了五子连珠的情况,则停止计时,弹出提示框,同时播放胜利的音乐。然后将该方的积分加2,并进入下一盘。
//判断是否五子连珠
public void isWon() {
for(int i=0;i<19;i++)
for(int j=0;j<19;j++) {
if(chess[i][j]!=0){
int thschessNum=chess[i][j];
int count=1;
int ii=i,jj=j;
//斜向左方向是否五子连珠,连珠则加2分,并开始下一盘
while(--ii>=0&&++jj<19)
{
if(chess[ii][jj]==thschessNum) count++;
else break;
}
//斜向右方向是否五子连珠,连珠则加2分,并开始下一盘
if(count<5) {
count=1;ii=i;jj=j;
while(--ii>=0&&--jj>=0)
{
if(chess[ii][jj]==thschessNum)
count++;
else break;
}
}
//水平方向是否五子连珠,连珠则加2分,并开始下一盘
if(count<5) {
count=1;ii=i;jj=j;
while(++ii<19)
{
if(chess[ii][jj]==thschessNum)
count++;
else break;
}
}
//竖直方向是否五子连珠,连珠则加2分,并开始下一盘
if(count<5) {
count=1;ii=i;jj=j;
while(++jj<19)
{
if(chess[ii][jj]==thschessNum)
count++;
else break;
}
}
//有五子连珠则加两分,并进入下一盘
if(count>=5)
{
con=false;
playMusic("五子连珠");
if(chess[i][j]==1)
{
JOptionPane.showMessageDialog(this, "黑方五子连珠!\n加两分");
blackWonNum+=2;
}else {
JOptionPane.showMessageDialog(this, "白方五子连珠!\n加两分");
whiteWonNum+=2;
}
con=true;
this.clearChess();
}
}
else continue;
}
}
开始游戏按钮在按钮栏第一个位置,当玩家鼠标点击该位置时,停止计时,弹出是否重新开始游戏的提示框,点击确认后调用startGame();方法重新开始游戏,并继续计时。
3.5.1.2效果图
//开始游戏
if(y>=175&&y<=198&&x>=543&&x<=621) {
con=false;
if(JOptionPane.showConfirmDialog(this, "是否重新开始游戏?")==0)
this.startGame();
con=true;
}
//重新开始游戏
public void startGame() {
//将数据全部初始化
this.clearChess();
blackRegret=true;
whiteRegret=true;
blackTime=0;
whiteTime=0;
blackWonNum=0;
whiteWonNum=0;
tempTime=0;
begin=false;
}
游戏说明按钮在按钮栏第二个位置,当玩家鼠标点击该位置时,停止计时,弹出游戏说明的介绍框,点击确认后继续计时。
//游戏说明
if(y>=221&&y<=243&&x>=543&&x<=621) {
con=false;
JOptionPane.showMessageDialog(this, "1、黑白双方依次落子,由黑先下\n2、五子连珠则加2分\n3、一人每盘只可以悔棋一次\n4、一盘里超时则扣1分,全局超时则扣两分并且游戏结束\n5、认输则该盘棋对方获胜,并进入下一盘","游戏说明",JOptionPane.PLAIN_MESSAGE);
con=true;
}
游戏设置按钮在按钮栏第三个位置,当玩家鼠标点击该位置时,停止计时,弹出游戏设置的输入框,将用户输入的内容赋值给和maxTempTime和maxTime,并用try{}catch{}捕捉异常,如果输入的不是数字,则进行异常提示,输入的是数字才会赋值给时间变量。最后点击确认后继续计时。
//游戏设置
If(y>=266&&y<=286&&x>=543&&x<=618) {
try {
con=false;
String t1 = JOptionPane.showInputDialog(this, "输入数字设置每一步的时间(秒)限制");
String t2 = JOptionPane.showInputDialog(this, "输入数字设置全局的时间(秒)限制");
int t11 = Integer.parseInt(t1);
int t22 = Integer.parseInt(t2);
if(t11<0||t22<0) {
JOptionPane.showMessageDialog(this, "输入的数字无意义!");
}
else {
maxTempTime = t11;
maxTimeNum = t22;
int yes = JOptionPane.showConfirmDialog(this, "时间设置成功,是否重新开始游戏?");
if(yes==0) {
startGame();
}
}
}catch(NumberFormatException e2){
JOptionPane.showMessageDialog(this, "请输入数字");
}finally {
con=true;
}
}
认输按钮在按钮栏第四个位置,当玩家鼠标点击该位置时,停止计时,弹出游戏设置的是否认输的确认框,在玩家点击确认后,另一方加2分,并进入下一盘。最后点击确认后继续计时。
//认输
if(y>=310&&y<=328&&x>=562&&x<=603) {
con=false;
int yes = JOptionPane.showConfirmDialog(this, "确定要认输吗?");
if(yes==0) {
if(isBlack) {
JOptionPane.showMessageDialog(this, "黑方认输,白方加2分","认输",JOptionPane.PLAIN_MESSAGE);
whiteWonNum+=2;
this.clearChess();
}
else {
JOptionPane.showMessageDialog(this, "白方认输,黑方加2分","认输",JOptionPane.PLAIN_MESSAGE);
blackWonNum+=2;
isBlack=true;
this.clearChess();
}
}
con=true;
}
悔棋按钮在按钮栏第五个位置,当玩家鼠标点击该位置时,停止计时,弹出悔棋消息框,并将上一步的chess值赋为0,以此实现悔棋,最后点击确认后继续计时。一盘棋里,每个玩家只能悔棋一次,如果已经悔过棋,再点击悔棋则弹出一盘只能悔棋一次的消息框。
//悔棋
if(y>=349&&y<=368&&x>=562&&x<=600) {
con=false;
if(!begin) {
JOptionPane.showMessageDialog(this, "清先下棋","悔棋",JOptionPane.PLAIN_MESSAGE);
}
else {
if(!isBlack) {
if(blackRegret) {
blackRegret=false;
JOptionPane.showMessageDialog(this, "黑方悔棋一次","悔棋",JOptionPane.PLAIN_MESSAGE);
chess[lastX][lastY]=0;
}
else
JOptionPane.showMessageDialog(this, "一盘只能悔棋一次","提示",JOptionPane.WARNING_MESSAGE);
}
else {
if(whiteRegret) {
JOptionPane.showMessageDialog(this, "白方悔棋一次","悔棋",JOptionPane.PLAIN_MESSAGE);
chess[lastX][lastY]=0;
isBlack=false;
}
else
JOptionPane.showMessageDialog(this, "一盘只能悔棋一次","提示",JOptionPane.WARNING_MESSAGE);
}
}
con=true;
}
}
关于按钮在按钮栏第六个位置,当玩家鼠标点击该位置时,停止计时,弹出作者、指导、版本、更新时间的消息框,点击确认后继续计时。
//关于
if(y>=386&&y<=408&&x>=563&&x<=602) {
con=false;
JOptionPane.showMessageDialog(this, "作者:谢敏杰\n指导:朱文\n版本:1.7\n更新时间:2021年7月8日","关于",JOptionPane.PLAIN_MESSAGE);
con=true;
}
退出按钮在按钮栏第七个位置,当玩家鼠标点击该位置时,停止计时,退出游戏的确认框,点击确认后弹出获胜者的消息框,最后关闭程序。
//退出
if(y>=426&&y<=448&&x>=564&&x<=602) {
con=false;
//退出游戏确认
if(JOptionPane.showConfirmDialog(this, "是否退出?")==0){
gameOver();//判断玩家的分数,分多的为胜利者,输出结果后退出
}
con=true;
}
使用线程实现计时器,设置了maxTime、maxTempTime、blackTime、whiteTime变量,分别是一盘的最大时间、全局的最大时间、黑方的时间、白方的时间。实现Runnable接口重写了run();方法,用sleep(1000);是线程休眠,从而实现间隔一秒的计时。 当黑白方的一步的时间和全局时间超过给定的最大值时,弹出提示框并播放音乐,如果超过一步的时间,则扣一分,超过全局时间则扣2分并且游戏结束。在线程休眠一秒后,用repaint();方法重新绘制棋盘。以实现每一秒的计时效果,
为了避免游戏还没开始,或未在下棋过程中时计时器仍在计时的情况,设置了begin变量,在计时之前,判断是否计时或重新绘制棋盘。
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//计时,如果计时的标志位false则不计时。
if(isBlack&&con&&begin) {
blackTime++;
tempTime++;
}
else if(!isBlack&&con&&begin){
whiteTime++;
tempTime++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//超时处理
If(maxTempTime>0&&tempTime>=maxTempTime) {
playMusic("超时");
JOptionPane.showMessageDialog(this, "快点儿吧,我等到花都谢了~\n扣1分");
if(isBlack) blackWonNum--;
else whiteWonNum--;
tempTime=0;
}
if(maxTimeNum>0&&blackTime>=maxTimeNum) {
playMusic("结束");
JOptionPane.showMessageDialog(this, "黑方超时!白方加2分,游戏结束。");
whiteWonNum+=2;
gameOver();
}
if(maxTimeNum>0&&whiteTime>=maxTimeNum) {
playMusic("结束");
JOptionPane.showMessageDialog(this, "白方超时!白方加2分,游戏结束。");
blackWonNum+=2;
gameOver();
}
if(con)this.repaint();
}
}
音效的添加可以提升玩家的体验,增加游戏的乐趣。该程序有四个音效,分别在下棋、五子连珠、一盘里超时、全局超时四个情况下播放。在播放之前,设置con=flase来暂停计时,结束后设置con=true继续计时。
//播放音乐的方法
public void playMusic(String musicName){
try {
URL uuu;
File f = new File("music/"+musicName+".wav");//导入音频文件
uuu = f.toURL();
AudioClip bf;
bf = Applet.newAudioClip(uuu);//加载音频
bf.play(); //播放音频
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
playMusic("下棋声"); //鼠标点击并下棋时播放音乐
playMusic("五子连珠"); //五子连珠时播放音乐
playMusic("超时"); //一盘里超时播放音乐
playMusic("结束"); //全局超时播放音乐
为了更好的游戏体验,增强界面的个性化展示。背景图片通过Photoshop软件,对图片的图像、色彩、各个按钮、游戏标题与信息、以及分辨率大小进行了设计。在右上角加了棋子图像,让玩家一目了然。在最上方的游戏标题用方正舒体,字体颜色与背景相融。背景采用中国画来增强中国博弈文化元素,不透明度为28%。在右下角添加了矩形功能按钮框与致胜小妙招。在最下方是玩家的信息。
为了摆脱用户对程序开发环境的依赖,提升程序的实用性与体验感。该程序通过exe生成根据exe4j与程序打包工具Inno Setup对程序就行了包装。
详情请看:如何将Java程序转换为exe可执行文件并生成安装包
献上源码、图片与音乐资源:链接:GoBang
提取码:8hge