• 简述

这是本学期上完Java课后老师给出的课程设计题目,目的是:熟悉与掌握GUI编程;实现五子棋棋盘和棋子的绘制;实现游戏AI以及对二维数组的使用。

  • 界面效果图

电脑先行,玩家输赢图:

javafx五子棋悔棋 java五子棋ai_java

javafx五子棋悔棋 java五子棋ai_界面设计_02

玩家先行,玩家输赢图:

javafx五子棋悔棋 java五子棋ai_java_03

javafx五子棋悔棋 java五子棋ai_java_04

  • 整体设计

界面设计部分

这里实现的是框架的主要界面设计(由4366中的在线五子棋修改而来),除棋盘之外的所有部分都在这里完成,即标签,图片,按钮的添加,框架边框的去除,实现框架边框去除后的拖动事件,按钮的点击事件响应等。(这里用到的图片是在GoBang项目中的image文件夹中,后面用到的音乐是在GoBang项目中的music文件夹中,在编写该程序时由程序员自己添加)

代码如下:

/**
* 2017年6月24日GoBang_main.java我和奥巴马
*/
package GoBang;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
 
/**
*@author我和奥巴马
*@date 2017年6月24日
*  @filename GoBang_main.java
*  @descriptionTODO
*/
public class GoBang_main extends JFrame{
finalint X_LOCATION=300;
finalint Y_LOCATION=5;
finalint ROWS=16;
finalint COLS=16;
finalint R_SIZE=40;
int xOld,yOld;
staticjl[]=new JLabel[9];
static boolean FLAG=false;
=new"image/White.png");
staticicBlack=new"image/Black.png");
=new"image/stop.png");
=new"image/play.png");
="music"+File.separator+"IF YOU-BIGBANG.wav";
public GoBang_main(){
this.setBounds(X_LOCATION,Y_LOCATION, COLS*R_SIZE+170,ROWS*R_SIZE+90);
this.setLayout(null);
this.getContentPane().setBackground(new Color(51,51,51));
        
//棋谱面板
gochess=new//中央黄色面板
gochess.setBounds(25,70,COLS*R_SIZE,ROWS*R_SIZE);
this.add(gochess);
        
//五子棋标签
jl[0]=new"五子棋",JLabel.CENTER);//中央上方标签
jl[0].setFont(new"华文行楷",Font.BOLD,25));//字体
jl[0].setForeground(Color.WHITE);//文本颜色
jl[0].setBounds(25, 20,COLS*R_SIZE, 40);//位置大小
jl[0].setOpaque(true);//设置背景透明
jl[0].setBackground(new//设置背景色
this.add(jl[0]);
        
//右边功能
jl[1]=newnew"image/Computer.png"));//电脑图片
jl[1].setBounds(685, 100, 50, 50);
jl[2]=newicBlack);//黑棋
jl[2].setBounds(745, 110, 30, 30);
     
f=new"华文楷体",Font.BOLD,15);//右边文本颜色
jl[3]=new"电脑黑棋,先行",JLabel.LEFT);
jl[3].setBounds(685, 150, 115, 50);
     
jl[4]=newnew"image/User.PNG"));//用户图片
jl[4].setBounds(685, 230, 50, 50);
jlw=newicWhite);//白棋
jlw.setBounds(745, 240, 30, 30);
         
        jl[5]=new"玩家白棋,后行",JLabel.LEFT);
jl[5].setBounds(685, 280, 115, 50);
        
jl[6]=new"开始游戏",JLabel.CENTER);
jl[6].setBounds(685, 360, 115, 30);
jl[6].addMouseListener(new//是电脑先
publicvoide){  //鼠标移开事件监听
jl[6].setBackground(new Color(186,160,10));
            }
publicvoide){ //鼠标进入事件监听
jl[6].setBackground(new Color(214,200,100));
            }
publicvoide){ //鼠标点击事件监听
for(inti=1;i<=COLS;i++){
for(intj=1;j<=ROWS;j++){
                            GoChess.blank(i,j); //棋谱置空
                        }
                    }
                    repaint();
FLAG=true;//给标志给JPanel让其响应鼠标点击落点
jlw.setIcon(icWhite);
jl[2].setIcon(icBlack);
jl[3].setText("电脑黑棋,先行");
jl[5].setText("玩家白棋,后行");
                    GoChess.sureMove(COLS/2,ROWS/2,GoChess.computerColor);//电脑第一颗棋的位置
                    GoChess.audio(GoChess.strClick);
                    repaint();
jl[6].setText("重新开始");//点击开始后,换内容
            }
        });
jl[7]=new"玩家先行",JLabel.CENTER);
jl[7].setBounds(685, 430, 115, 30);
jl[7].addMouseListener(new//玩家先
publicvoide){
jl[7].setBackground(new Color(186,160,10));
            }
publicvoide){
jl[7].setBackground(new Color(214,200,100));
            }
publicvoide){
for(inti=1;i<=COLS;i++){
for(intj=1;j<=ROWS;j++){
                        GoChess.blank(i,j); //棋谱置空
                    }
                }
                repaint();
FLAG=true;//给标志给JPanel让其响应鼠标点击落点
jlw.setIcon(icBlack);//对应的棋色互换
jl[2].setIcon(icWhite);
jl[3].setText("电脑白棋,后行");//标签提示互换
jl[5].setText("玩家黑棋,先行");
            }
        });
        
jlm=newnew"image/music.PNG"));//音乐图片
jlm.setBounds(685, 550, 50, 50);
jlp=newicst);//暂停图片
jlp.setBounds(745, 558, 40, 40);
jlp.addMouseListener(new MouseAdapter(){
publicvoide){
if(e.getClickCount()==1){
jlp.setIcon(icpl);//换成暂停图标
                    GoChess.audio(strmu);
                }
            }
        });
        
jl[8]=new"退出",JLabel.CENTER);//退出标签
jl[8].setBounds(685, 500, 115, 30);
jl[8].addMouseListener(new MouseAdapter(){
publicvoide){
jl[8].setBackground(new Color(186,160,10));
            }
publicvoide){
jl[8].setBackground(new Color(214,200,100));
            }
publicvoide){
                System.exit(0);
            }
        });
for(inti=1;i<9;i++){//加组件
jl[i].setFont(f);
if(i==6||i==7||i==8){
jl[i].setOpaque(true);
jl[i].setBackground(new Color(186,160,10));
//设置边框
jl[i].setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, new Color(186,160,10)));
            }
this.add(jl[i]);
this.add(jlp);
this.add(jlm);        
this.add(jlw);
if(i==3||i==5)
jl[i].setForeground(Color.WHITE);//设置字体白色
        }
        
this.setUndecorated(true);
this.setVisible(true);
this.setResizable(false);
this.addMouseListener(new MouseAdapter(){
publicvoide){ //鼠标压监听
xOld=e.getX();//获得x
yOld=e.getY();//获得y
            }
        });
this.addMouseMotionListener(new//框架拖动
publicvoide){ 
intxOnScreen=e.getXOnScreen();//获得屏幕上的点x坐标
intyOnScreen=e.getYOnScreen();//获得屏幕上的点y坐标
intxNew=xOnScreen-xOld;//得到的新点x
intyNew=yOnScreen-yOld;//得到的新点y
this.setLocation(xNew,yNew);//实现框架的拖动
            }
        });
    }
publicstaticvoidargs[]){
new GoBang_main();
    }
}
五子棋绘制部分
这里包括给出棋谱的绘制(在Jpanel 子类中完成),棋子的绘制,评估函数(给每种情况打分,然后可以统计棋盘上的优劣情况),判断输赢等一系列函数,是整个项目中最重要部分。(绘制棋盘和棋子,棋盘分为15行15列,棋子分黑白两种,用颜色去区分,用paint函数去绘制棋盘和棋子,再用一个2维数组去存储棋盘中的棋子的颜色值(在这里空子为0,黑色为1,白色为2))。
代码如下:
/**
* 2017年6月24日.java我和奥巴马
*/
package GoBang;
import java.io.File;
import java.awt.Color;
importjava.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
importjava.net.MalformedURLException;
import javax.swing.JPanel;
importsun.audio.AudioPlayer;
importsun.audio.AudioStream;
 
/**
*@author我和奥巴马
*@date 2017年6月24日
*  @fileROWSame GoChess.java
*  @descriptionTODO
*/
publicclassGoChessextends JPanel{
finalstaticint ROWS=16;
finalstaticint COLS=16;
finalint R_SIZE=40;
staticint BLACK=1;
staticint WHITE=2;
staticint EMPTY=0;
staticint userColor=WHITE;
staticint computerColor=BLACK;
staticint table[][]=newint[COLS+1][ROWS+1];   
staticint i,j;
booleanFLAG1=false;
booleanFLAG2=false;
staticstrClick="music"+File.separator+"sale.wav";
="music"+File.separator+"success.wav";
="music"+File.separator+"failure.wav";
public GoChess(){
this.addMouseListener(new MouseAdapter(){
publicvoide){
intx=e.getX();
inty=e.getY();
if(x>=20&&x<COLS*R_SIZE-20&&y>=20&&y<ROWS*R_SIZE-20){//设置鼠标响应范围==左半边和右半边点击时没反应
i=(x-20)/40+1;//映射成相应的x坐标
j=(y-20)/40+1;//映射成相应的y坐标
                }
//添加响应函数
            }    
        });
    }
publicvoid mouseClick(){
if(GoBang_main.FLAG){//必须点击《开始游戏》或者《玩家先行》才有效
if(isEmpty(i,j)){    
                sureMove(i,j,userColor);//用户落子
intyn=isEnd(i,j,userColor);//判断用户赢
if(yn!=0){
FLAG1=true;//响应paint里的画字符串
                    repaint();
jl[6].setText("开始游戏");//赢了就把重新开始换为开始游戏
return;
                }
                repaint();
intcomputer[]=Computer.getNext(computerColor);//电脑落子位置
                sureMove(computer[0],computer[1],computerColor);//电脑落子
                audio(strClick);//提示电脑落子声音 ===海浪
yn=isEnd(computer[0],computer[1],computerColor);
if(yn!=0){
FLAG2=true;
                    repaint();
jl[6].setText("开始游戏");
return;
                }
                repaint();
            }
        }
    }
publicstaticvoidstr){  //添加音乐函数
file=null;
try {
file=newstr);
            AudioStreamas=newfile);    
            AudioPlayer.player.start(as);
 catch) {
e.printStackTrace();
 catch) {
e.printStackTrace();
        }
    }
    
publicstaticbooleaninti,intj){//判断是否为空
returntable[i][j]==EMPTY;
    }
publicstaticvoidinti,intj,intcolor){//落棋子
table[i][j]=color;
    }
publicstaticvoidinti,intj){//撤回棋子
table[i][j]=EMPTY;
    }        
    
publicvoidintcolor,intx,inty,Graphicsg){ //画棋子
if(GoBang_main.jl[2].getIcon()==GoBang_main.icBlack){//电脑黑棋,颜色为1
if(color==BLACK){
g.setColor(new Color(0,0,0));
//左右半径19,上下为39/2
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360); //((x-20)/40+1)*RIZE-19
            }                
        }
else{//电脑白棋,颜色为2
if(color==BLACK){
g.setColor(new Color(255,255,255));
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360);
            }
        }
if(GoBang_main.jl[2].getIcon()!=GoBang_main.icBlack){//电脑白棋,白色
if(color==WHITE){
g.setColor(new Color(0,0,0));
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360); 
            }                
        }
else{//电脑黑棋,颜色黑
if(color==WHITE){
g.setColor(new Color(255,255,255));
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360);
            }
        }
    }
    
publicvoidg){
g.setColor(new Color(208,152,69));
g.fillRect(0,0,COLS*R_SIZE,ROWS*R_SIZE);
g.setColor(Color.DARK_GRAY);
for(inti=1;i<COLS;i++){
g.drawLine(R_SIZE*i,R_SIZE,R_SIZE*i, (ROWS-1)*R_SIZE);//画竖线
        }
for(intj=1;j<ROWS;j++){
g.drawLine(R_SIZE,R_SIZE*j,(COLS-1)*R_SIZE,R_SIZE*j);//画横线
        }
g.setColor(new Color(0,0,0));
//画五个定位点
g.fillArc(8*R_SIZE-5, 8*R_SIZE-5, 10, 10, 0, 360); //中
g.fillArc(4*R_SIZE-5, 4*R_SIZE-5, 10, 10, 0,360); //左上
g.fillArc(12*R_SIZE-5, 4*R_SIZE-5, 10, 10, 0, 360); //右上
g.fillArc(4*R_SIZE-5, 12*R_SIZE-5, 10, 10, 0, 360); //左下
g.fillArc(12*R_SIZE-5, 12*R_SIZE-5, 10, 10, 0, 360); //右下
        
for(inti=1;i<=COLS;i++){
for(intj=1;j<=ROWS;j++){
if(!isEmpty(i,j))
table[i][j],i,j,g);//画棋子
            }
        }
if(FLAG1){
g.setColor(Color.RED);
g.setFont(new"隶书",Font.BOLD,50));//红色提示赢
g.drawString("你赢了", 1*R_SIZE-5,8*R_SIZE-5);
            audio(strSuccess);//赢了的wav
FLAG1=false;
FLAG=false;
        }
if(FLAG2){
g.setColor(Color.LIGHT_GRAY);
g.setFont(new"隶书",Font.BOLD,50));
g.drawString("你输了", 1*R_SIZE-5,8*R_SIZE-5);
            audio(strFailure);
FLAG2=false;
FLAG=false;
        }
    }
 
publicstaticintintcolor) {//评估函数
intdx[] = {1, 0, 1, 1};//右,下,右下,右上
intdy[] = {0, 1, 1, -1};
intans = 0;
for(intx=1;x<ROWS;x++) {
forintyyCOLS;y++) {
iftable[x][y] != color)
continue;
intnum[][] =newint[2][10];//计数
forintii++) {
intsum = 1;
intflag1flag2//falg1表示一头死,falg2两头活
 
inttxxdx[i];
inttyydy[i];
 
whiletx>0&&tx<ROWS&&ty>0&&ty<COLS&&table[tx][ty]==color) {
txdx[i];
tydy[i];
sum;
                    }
 
if(txtxROWStytyCOLStable[tx][ty] ==EMPTY)
flag1 = 1;
 
txxdx[i];
tyydy[i];
whiletxtxROWStytyCOLStable[tx][ty] ==color) {  //回找
tx-=dx[i];
ty-=dy[i];
sum;
                    }
if(txtxROWS&&tytyCOLStable[tx][ty] ==EMPTY)
flag2 = 1;
 
if(flag1flag2 > 0)
num[flag1flag2- 1][sum];
                }
/*成5:即构成五子连珠
                活4:即构成两边均不被拦截的四子连珠
                                死4:一边被拦截的四子连珠
                                活3:两边均不被拦截的三字连珠
                                死3:一边被拦截的三字连珠
                                活2:两边均不被拦截的二子连珠
                                死2:一边被拦截的二子连珠*/
//成5
if(num[0][5]>0 ||num[1][5]> 0) //num[0][5]+num[1][5]>0
ans = Math.max(ans, 100000);
//活4 |双死四 |死4活3
else if(num[1][4] > 0||num[0][4]> 1||(num[0][4]> 0 &&num[1][3] > 0))
ans = Math.max(ans, 10000);
//双活3
else if(num[1][3] > 1)
ans = Math.max(ans, 5000);
//死4
else if(num[0][4] > 0)
ans = Math.max(ans, 1000);
//死3活3
else if(num[1][3] > 0 &&num[0][3] > 0)
ans = Math.max(ans, 500);
//单活3
else if(num[1][3] > 0)
ans = Math.max(ans, 200);
//双活2
else if(num[1][2] > 1)
ans = Math.max(ans, 100);
//死3
else if(num[0][3] > 0)
ans = Math.max(ans, 50);
//单活2
else if(num[1][2] > 0)
ans = Math.max(ans, 10);
//死2
elseif(num[0][2] > 0)
ans = Math.max(ans, 5);
elseif(num[1][1]>0)
ans=Math.max(ans,1);
            }
        }
return ans;
    }
publicstaticintintcolor){//找棋子数
int num=0;
for(int i=1;i<COLS;i++){
for(int j=1;j<ROWS;j++){
if(!isEmpty(i,j)&&table[i][j]==color)
num++;
if(num>0)
return num;
            }
        }
return num;
    }
/*判断局面是否结束 0未结束 1 WHITE赢 2 BLACK赢 */
    publicstaticintintx,int y,int color) {
        intdx[] = {1, 0, 1, 1};
        intdy[] = {0, 1, 1, -1};
        forintii++) {
           intsum = 1;
           inttxxdx[i];
           inttyydy[i];
           whiletxtxCOLStytyROWStable[tx][ty] ==color) {  //当前点去遍历
dx[i];
dy[i];
sum;
           }
xdx[i];
ydy[i];
           whiletxtxCOLStytyROWS&&table[tx][ty] ==color) {  //返回来遍历
dx[i];
dy[i];
sum;
           }
           if(sum >= 5)
               returncolor;
       }
       return 0;
   }
}
电脑返回走法部分
电脑根据带有alpha-beta剪枝的MinMax搜素算法(递归求解)给出电脑的走法,这里主要是借助评估算法来给出电脑的具体走法。
代码如下:
/**
* 2017年6月25日Computer.java我和奥巴马
*/
package GoBang;
 
import java.util.Random;
/**
*@author我和奥巴马
*@date 2017年6月25日
*  @filename Computer.java
*  @descriptionTODO
*/
publicclass Computer {
staticint depth=1;
staticint computerColor=GoChess.BLACK;
/*alpha_beta剪枝搜索,寻找着点
    Alpha,即搜索到的最好值,任何比它更小的值就没用了,因为策略就是知道Alpha的值,任何小于或等于Alpha的值都不会有所提高
    Beta,即对于对手来说最坏的值。这是对手所能承受的最坏的结果,因为我们知道在对手看来,他总是会找到一个对策不比Beta更坏的。
    如果搜索过程中返回Beta或比Beta更好的值,那就够好的了,走棋的一方就没有机会使用这种策略了*/
publicstaticintintdepth,intalpha,intbeta,intcolor,intx,inty){
if(depth>Computer.depth||GoChess.isEnd(x,y,color%2+1)!=0){
intans =GoChess.reckon(computerColor)-GoChess.reckon(computerColor%2+1);
if(depth%2==0)
ans=-ans;
return ans;
        }
for(inti=1;i<GoChess.COLS;i++){
for(intj=1;j<GoChess.ROWS;j++){
if(!GoChess.isEmpty(i,j))
continue;
                GoChess.sureMove(i,j,color);
intval=-alpha_betaFind(depth+1,-betaalpha,color%2+1,i,j);//ans的值给val
                GoChess.blank(i,j);
if(val>=beta)
returnbeta;//返回比beta好的值val=(-ans)>=-beta====beta<=-val //所以加个 -号
if(val>alpha)
alpha=-val;//返回比alpha更坏的值val=(-ans)<-alpha====val>alpha
            }
        }
return alpha;
    }
 
publicstaticint[] getNext(intcolor){
intrel[]=newint[2];
int ans=-100000000;
random=new Random(47);
if(GoChess.number(GoChess.BLACK)<1){
if(GoChess.table[GoChess.COLS/2][GoChess.ROWS/2]!=computerColor){//电脑后手需定位
if(GoChess.isEmpty(GoChess.COLS/2,GoChess.ROWS/2)){//中点
rel[0]=GoChess.COLS/2;
rel[1]=GoChess.ROWS/2;
                }
else{
rel[0]=GoChess.COLS/2+1;//向右占位
rel[1]=GoChess.ROWS/2;
                }
            }
else{        
for(intx=1;x<GoChess.COLS;x++){
for(inty=1;y<GoChess.ROWS;y++){
if(!GoChess.isEmpty(x,y))
continue;
                    GoChess.sureMove(x,y, color);  //黑棋落子
intval=-alpha_betaFind(0,-100000000,100000000,color%2+1,x,y);//判断白棋局面
intran=random.nextInt(100);//100是不包含在内的,只产生0~100之间的数
if(val>ans||val==ans&&ran>50){//val(-递归返回值)<-ans=====val>ans ||ans一直被刷新
=val;
rel[0]=x;
rel[1]=y;
                    }
                    GoChess.blank(x,y);
                }
            }
        }
return rel;
    }
}
想要看源码的小伙伴点这里
注:
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
报错解决办法如下:
Windows->preference->java->complier->errors/warning->deprecated and restricted API把 Forbidden reference 的Error改成warning 即可
  • 总结

其实这次是对自己的一个很大的挑战,因为以前只是随便了解了一下AI,现在自己有在没有经验的情况下自己编写代码来实现人机对弈,实在是一次很大的进步。由于能力有限,只能实现简单模式的人机对弈,在默认情况下,电脑的第一个落子是固定的,玩家多次对战后能基本掌握电脑的走法,所以这是本程序的一个漏洞,电脑没有每次随机出招,没有记忆功能和自学习功能,导致这只是一个初级的AI游戏。后续的话,我打算自己再次去探索AI编程,以及寻找更好的算法来支撑我的程序,让玩家体验一个更智能的五子棋游戏。