AI智能人机对战五子棋(Java实现图形界面)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_39453325/article/details/76091391
  • 简述

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

  • 界面效果图

电脑先行,玩家输赢图:

玩家先行,玩家输赢图:

  • 整体设计

界面设计部分

这里实现的是框架的主要界面设计(由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;

    static JLabel jl[]=new JLabel[9];

    static boolean FLAG=false;

    Icon icWhite=new ImageIcon("image/White.png");

    static IconicBlack=new ImageIcon("image/Black.png");

    Icon icst=new ImageIcon("image/stop.png");

    Icon icpl=new ImageIcon("image/play.png");

    String strmu="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));

        

        //棋谱面板

        GoChessgochess=new GoChess();//中央黄色面板

        gochess.setBounds(25,70,COLS*R_SIZE,ROWS*R_SIZE);

        this.add(gochess);

        

        //五子棋标签

        jl[0]=new JLabel("五子棋",JLabel.CENTER);//中央上方标签

        jl[0].setFont(new Font("华文行楷",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 Color(156,102,31)); //设置背景色

        this.add(jl[0]);

        

        //右边功能

        jl[1]=new JLabel(new ImageIcon("image/Computer.png"));//电脑图片

        jl[1].setBounds(685, 100, 50, 50);

        jl[2]=new JLabel(icBlack);//黑棋

        jl[2].setBounds(745, 110, 30, 30);

     

        Fontf=new Font("华文楷体",Font.BOLD,15);//右边文本颜色

        jl[3]=new JLabel("电脑黑棋,先行",JLabel.LEFT);

        jl[3].setBounds(685, 150, 115, 50);

     

        jl[4]=new JLabel(new ImageIcon("image/User.PNG"));//用户图片

        jl[4].setBounds(685, 230, 50, 50);

        JLabeljlw=new JLabel(icWhite);//白棋

        jlw.setBounds(745, 240, 30, 30);

         

        jl[5]=new JLabel("玩家白棋,后行",JLabel.LEFT);

        jl[5].setBounds(685, 280, 115, 50);

        

        jl[6]=new JLabel("开始游戏",JLabel.CENTER);

        jl[6].setBounds(685, 360, 115, 30);

        jl[6].addMouseListener(new MouseAdapter(){ //是电脑先

            publicvoid mouseExited(MouseEvente){ //鼠标移开事件监听

                jl[6].setBackground(new Color(186,160,10));

            }

            publicvoid mouseEntered(MouseEvente){ //鼠标进入事件监听

                jl[6].setBackground(new Color(214,200,100));

            }

            publicvoid mouseClicked(MouseEvente){ //鼠标点击事件监听

                    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("玩家先行",JLabel.CENTER);

        jl[7].setBounds(685, 430, 115, 30);

        jl[7].addMouseListener(new MouseAdapter(){ //玩家先

            publicvoid mouseExited(MouseEvente){

                jl[7].setBackground(new Color(186,160,10));

            }

            publicvoid mouseEntered(MouseEvente){

                jl[7].setBackground(new Color(214,200,100));

            }

            publicvoid mouseClicked(MouseEvente){

                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("玩家黑棋,先行");

            }

        });

        

        JLabeljlm=new JLabel(new ImageIcon("image/music.PNG"));//音乐图片

        jlm.setBounds(685, 550, 50, 50);

        JLabeljlp=new JLabel(icst);//暂停图片

        jlp.setBounds(745, 558, 40, 40);

        jlp.addMouseListener(new MouseAdapter(){

            publicvoid mouseClicked(MouseEvente){

                if(e.getClickCount()==1){

                    jlp.setIcon(icpl);//换成暂停图标

                    GoChess.audio(strmu);

                }

            }

        });

        

        jl[8]=new JLabel("退出",JLabel.CENTER);//退出标签

        jl[8].setBounds(685, 500, 115, 30);

        jl[8].addMouseListener(new MouseAdapter(){

            publicvoid mouseExited(MouseEvente){

                jl[8].setBackground(new Color(186,160,10));

            }

            publicvoid mouseEntered(MouseEvente){

                jl[8].setBackground(new Color(214,200,100));

            }

            publicvoid mouseClicked(MouseEvente){

                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(){

            publicvoid mousePressed(MouseEvente){ //鼠标压监听

                xOld=e.getX();//获得x

                yOld=e.getY();//获得y

            }

        });

        this.addMouseMotionListener(new MouseMotionAdapter(){ //框架拖动

            publicvoid mouseDragged(MouseEvente){

                intxOnScreen=e.getXOnScreen();//获得屏幕上的点x坐标

                intyOnScreen=e.getYOnScreen();//获得屏幕上的点y坐标

                intxNew=xOnScreen-xOld;//得到的新点x

                intyNew=yOnScreen-yOld;//得到的新点y

                GoBang_main.this.setLocation(xNew,yNew);//实现框架的拖动

            }

        });

    }

    publicstaticvoid main(Stringargs[]){

        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;

    static StringstrClick="music"+File.separator+"sale.wav";

    String strSuccess="music"+File.separator+"success.wav";

    String strFailure="music"+File.separator+"failure.wav";

    public GoChess(){

        this.addMouseListener(new MouseAdapter(){

            publicvoid mouseClicked(MouseEvente){

                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坐标

                }

                mouseClick();//添加响应函数

            }    

        });

    }

    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();

                    GoBang_main.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();

                    GoBang_main.jl[6].setText("开始游戏");

                    return;

                }

                repaint();

            }

        }

    }

    publicstaticvoid audio(Stringstr){ //添加音乐函数

        FileInputStreamfile=null;

        try {

            file=new FileInputStream(str);

            AudioStreamas=new AudioStream(file);    

            AudioPlayer.player.start(as);

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

    

    publicstaticboolean isEmpty(inti,intj){//判断是否为空

        returntable[i][j]==EMPTY;

    }

    publicstaticvoid sureMove(inti,intj,intcolor){//落棋子

        table[i][j]=color;

    }

    publicstaticvoid blank(inti,intj){//撤回棋子

        table[i][j]=EMPTY;

    }        

    

    publicvoid drawPieces(intcolor,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);

            }

        }

    }

    

    publicvoid paint(Graphicsg){

        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))

                    drawPieces(table[i][j],i,j,g);//画棋子

            }

        }

        if(FLAG1){

            g.setColor(Color.RED);

            g.setFont(new Font("隶书",Font.BOLD,50));//红色提示赢

            g.drawString("赢了", 1*R_SIZE-5,8*R_SIZE-5);

            audio(strSuccess);//赢了的wav

            FLAG1=false;

            GoBang_main.FLAG=false;

        }

        if(FLAG2){

            g.setColor(Color.LIGHT_GRAY);

            g.setFont(new Font("隶书",Font.BOLD,50));

            g.drawString("输了", 1*R_SIZE-5,8*R_SIZE-5);

            audio(strFailure);

            FLAG2=false;

            GoBang_main.FLAG=false;

        }

    }

    publicstaticint reckon(intcolor) {//评估函数

        intdx[] = {1, 0, 1, 1};//右,下,右下,右上

        intdy[] = {0, 1, 1, -1};

        intans = 0;

        for(intx=1;x<ROWS;x++) {

            for (inty = 1;y <COLS;y++) {

                if (table[x][y] != color)

                    continue;

                intnum[][] =newint[2][10];//计数

                for (inti = 0;i < 4; i++) {

                    intsum = 1;

                    intflag1 = 0,flag2 = 0; //falg1表示一头死,falg2两头活

                    inttx =x + dx[i];

                    intty =y + dy[i];

                    while (tx>0&&tx<ROWS&&ty>0&&ty<COLS&&table[tx][ty]==color) {

                        tx +=dx[i];

                        ty +=dy[i];

                        ++sum;

                    }

                    if(tx > 0 &&tx <ROWS &&ty > 0 && ty <COLS &&table[tx][ty] ==EMPTY)

                        flag1 = 1;

                    tx =x - dx[i];

                    ty =y - dy[i];

                    while (tx > 0 && tx <ROWS &&ty > 0 && ty <COLS &&table[tx][ty] ==color) { //回找

                        tx-=dx[i];

                        ty-=dy[i];

                        ++sum;

                    }

                    if(tx > 0 &&tx <ROWS&&ty > 0 && ty <COLS &&table[tx][ty] ==EMPTY)

                        flag2 = 1;

                    if(flag1 +flag2 > 0)

                        ++num[flag1 +flag2- 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;

    }

    publicstaticint number(intcolor){//找棋子数

        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赢 */

    publicstaticint isEnd(intx,int y,int color) {

        intdx[] = {1, 0, 1, 1};

        intdy[] = {0, 1, 1, -1};

        for (inti = 0;i < 4; i++) {

           intsum = 1;

           inttx =x + dx[i];

           intty =y + dy[i];

           while (tx > 0 &&tx <COLS &&ty > 0 && ty <ROWS &&table[tx][ty] ==color) { //当前点去遍历

              tx +=dx[i];

              ty +=dy[i];

              ++sum;

           }

           tx =x - dx[i];

           ty =y - dy[i];

           while (tx > 0 &&tx <COLS &&ty > 0 && ty <ROWS&&table[tx][ty] ==color) { //返回来遍历

              tx -=dx[i];

              ty -=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更好的值,那就够好的了,走棋的一方就没有机会使用这种策略了*/

    publicstaticint alpha_betaFind(intdepth,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,-beta ,-alpha,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;

        Randomrandom=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一直被刷新

                        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编程,以及寻找更好的算法来支撑我的程序,让玩家体验一个更智能的五子棋游戏。



猜你喜欢

转载自blog.csdn.net/weixin_39453325/article/details/76091391