五子棋的Java实现 详解

来源:互联网 发布:登录淘宝要脸部拍摄 编辑:程序博客网 时间:2024/05/17 23:52

最近用Java写了一个有人机对战人人对战两种模式的五子棋小游戏,也算是有自己一定的心得,现在把它分享出来。代码我会放在最后面,先把五子棋的思路捋清楚

一、首先上界面图


这是我五子棋的界面,比较简单,大家可以根据自己的想法随意DIY.

二、如何实现界面?

这个五子棋界面,由四个类实现,分别是:
JFrame(作为顶层容器类的JFrame用来添加其他的所有组件)
JPanel(在界面中用来分别添加棋盘界面和按钮界面)
JButton(在界面作为按键如“开始”“认输”“重新开始”按键)
JRadioButton(在界面中作为单选按钮如“人人对战”“人机对战”)

界面中有两个JPanel面板对象,一个在界面的左边用来绘制棋盘,一个在界面的右边用来放置按键。
给出界面实现代码

public class FiveChess extends JPanel{    public Graphics g;                          //五子棋盘里面的Graphics属性用来画图    public static void  main (String args[]){        FiveChess fc=new FiveChess();           //实例化五子棋盘类得到五子棋盘对象fc        fc.go();                                //五子棋盘对象fc调用go()    }    public void setG(Graphics g){               //五子棋盘类用来设置Graphics属性的方法        this.g=g;    }    public void go(){        /*         * 创建一个窗体,设置窗体里的属性         */        JFrame frame=new JFrame();              //实例化一个窗体对象frame        frame.setTitle("五子棋");                  //设置窗体的标题        frame.setSize(800,500);                 //设置窗体的大小        frame.setDefaultCloseOperation(3);        /*         * 添加两个面板 利用board布局 一个放中间 用来画五子棋的棋盘 一个放东边用来放置按钮         */        JPanel buttonpanel=new JPanel();                        //实例化一个面板对象buttonpanel        buttonpanel.setPreferredSize(new Dimension(300,500));//设置buttonpanel大小 只有窗体对象能够用setSize()        buttonpanel.setBackground(Color.white);             //设置buttonpanel对象的背景颜色        /*         * 添加按钮         */         //实例化三个按钮对象            JButton bstart=new JButton("开始");                       JButton bquit=new JButton("认输");        JButton bundo=new JButton("悔棋");        //设置三个按钮对象的大小        bstart.setPreferredSize(new Dimension(200,50));             bquit.setPreferredSize(new Dimension(200,50));        bundo.setPreferredSize(new Dimension(200,50));        //将这三个按钮对象添加到面板对象bp中去        buttonpanel.add(bstart);                                            buttonpanel.add(bquit);        buttonpanel.add(bundo);        //实例化按钮组对象bg        ButtonGroup bg=new ButtonGroup();                       //实例化两个单选按钮对象        JRadioButton bpvp=new JRadioButton("人人对战");         JRadioButton bpvr=new JRadioButton("人机对战");        //设置两个单选按钮对象的大小        bpvp.setPreferredSize(new Dimension(200,50));           bpvr.setPreferredSize(new Dimension(200,50));        //将这两个单选按钮对象设为透明背景        bpvr.setOpaque(false);                                  bpvp.setOpaque(false);        bg.add(bpvp);                                   //将这两个单选按钮对象添加到按钮组对象bg中        bg.add(bpvr);        //将这两个单选按钮对象添加到面板对象buttonpanel中        buttonpanel.add(bpvp);                                          buttonpanel.add(bpvr);          this.setBackground(Color.gray);                 //棋盘对象设置背景色        /*         * 窗体布局,将棋盘面板和按钮面板合理地布局在frame窗体对象上         */        BorderLayout bl=new BorderLayout();             //实例化一个布局对象             frame.setLayout(bl);                            //将窗体对象frame设置为BorderLayout        frame.add(this,BorderLayout.CENTER);            //把棋盘对象放在窗体对象的中间        frame.add(bp,BorderLayout.EAST);                //把按钮面板放在窗体对象的东边        frame.setVisible(true);                         //窗体对象设置为可见        //棋盘对象设置Graphics属性        g=this.getGraphics();       }}

以上这段代码并没有给出如何画出15*15的棋盘
因为棋盘是在左边板上画出来的我们可以利用左边面板的重绘paint()方法画15*15棋盘

   /*     *画五子棋棋盘的方法     */    public void draw(Graphics ag){        Graphics2D g=(Graphics2D)ag;        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                RenderingHints.VALUE_ANTIALIAS_ON);        for(int i=0;i<15;i++){            for(int j=0;j<15;j++){                /*                 * 首先把棋盘重新画一遍                 */                g.setColor(Color.black);                g.drawLine(30,30+i*30,450,30+i*30); //画横线                g.drawLine(30+i*30,30,30+i*30,450); //画竖线            }        }

重写paint()方法

    public void paint(Graphics g){        super.paint(g);        draw(g);    }

有了以上这几段代码我们就能完全实现如图所示的界面了。

有了界面之后,棋子呢?

当我们做好界面之后理所应当地需要在棋盘面板上放置棋子。
Java是一门面向对象的语言,我们可以把棋子作为一个类。
在棋盘上每一个横竖线交叉的地方都应该有一个棋子,在相对应的地方没有下棋的时候,棋子是隐形的棋子状态flag为0,在相对应的位置下完棋之后棋子状态flag为1(白棋)或者2(黑棋)。

public class chess {     public int X,Y;    // X,Y是棋子对应在棋盘上的坐标,我们之后就是利用棋子的坐标,在棋盘上画出它。     public int flag;   //代表棋子的三种状态flag:0 没有落子 flag:1 白子 flag:2 黑子     public int value;  //该棋子的权值      public chess(){         X=0;         Y=0;         flag=0;         value=0;     }     public chess(int x,int y){         X=x;         Y=y;         flag=0;         value=0;     }}

我们可以定义一个15*15的二维数组 数组下标代表棋子在棋盘的相应位置,而棋子的X Y属性代表棋子在棋盘上的坐标值。最左上角的棋子坐标为X:30,Y:30;在二维数组中为行下标为0 列下标为0

for(int i=0;i<15;i++){            for(int j=0;j<15;j++){                chess ch=new chess((j+1)*30,(i+1)*30)                xy[i][j]=ch;            }                       }

**行列下标和棋子坐标对应关系为:
i=xy[][].Y/30-1; j=xy[][].X/30-1;**

如何让棋子在棋盘上出现呢?我们首先要用setGraphics()方法获得棋盘上的“画笔”g,
在设置为对应的白色或黑色之后
然后利用g.fillOval()方法在棋盘界面上画出白色棋子或黑色棋子。
当棋子出现在棋盘上之后,那么棋子的状态flag属性就应该从“0”(没有落子)转变为“1”(白棋)或“2”(黑棋)
在这里还应当设置一个变量color,用来记录和判断当前下的是白棋还是黑棋,假设用color为“1”代表当前下的是白棋,color为“2”代表当前下的是黑棋。在下完黑棋之后应当把color置为“1”,下次就下白棋,下完白棋之后把color设置为“2”,下次就下黑棋。

下完棋子之后呢?

我们在下完棋子之后就应当判断是否有五颗同颜色棋子相连了,我们不需要遍历整个棋盘去判断,只需要在最后落子的地方,从横向,竖向,正对角线,反对角线四个大方向上去判断是否有相同颜色的棋子连成一条线。
先上这部分代码

/* * 方法的参数是棋子对象 */public void checkChess(chess ch){        //实例化一个弹窗对象,后面用来提白方还是黑方获胜        JOptionPane op=new JOptionPane();        /*         * 初始化变量用来存储在 水平 垂直 正对角线 反对角线方向上的连续棋子         */        int count1=1;        int count2=1;        int count3=1;        int count4=1;        /*         * 将棋子的坐标值 转换为对应的数组下标值 xy[0][0] 的坐标是(30 ,30)xy[0][1]的坐标是(60,30)         */        int x=ch.Y/30-1;   //行坐标        int y=ch.X/30-1;   //列坐标        /*         * 水平方向上的检测         */        for(int j=y+1;j<15;j++){                                //行坐标不变 列坐标递增 检测ch右边的落子情况            if(xy[x][y].flag==xy[x][j].flag){ //如果ch右边的棋子和ch颜色一样 计数加一                count1++;        }/如果ch右边的棋子和ch颜色不一样 则退出计数            else                break;                                          //如果不是的话退出判断        }        for(int j=y-1;j>=0;j--){                                //行坐标不变 列坐标递减 检测ch左边的落子情况            if(xy[x][y].flag==xy[x][j].flag){                count1++;            }            else                break;        }        //水平方上的落子情况检测完        if(count1>=5){                                              if(xy[x][y].flag==1)                op.showMessageDialog(null, "白棋胜出");             else                op.showMessageDialog(null, "黑棋胜出");         }        /*         * 垂直方向上的检测         */        for(int i=x-1;i>=0;i--){                                //列坐标不变 行坐标递减 检测ch上面的落子情况                   if(xy[x][y].flag==xy[i][y].flag&&xy[x][y].flag!=0){                count2++;            }            else                break;        }                                                       //列坐标不变 行坐标递加  检测ch下面的落子情况          for(int i=x+1;i<15;i++){                                            if(xy[x][y].flag==xy[i][y].flag&&xy[x][y].flag!=0){                count2++;            }            else                break;        }        // 垂直方向上的棋子情况检测完        if(count2>=5){                                              if(xy[x][y].flag==1)                op.showMessageDialog(null, "白棋胜出");             else                op.showMessageDialog(null, "黑棋胜出");         }        /*         * 正对角线上面的检测         */        for(int i=x-1,j=y+1;i>=0&&j<15;i--,j++){                // 行递减 列递加            if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){                count3++;            }            else break;        }        for(int i=x+1,j=y-1;j>=0&&i<15;j--,i++){                //行递加 列递减            if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){                count3++;            }            else break;        }        //正对角线上检测完毕        if(count3>=5){            if(xy[x][y].flag==1)                op.showMessageDialog(null, "白棋胜出");             else                op.showMessageDialog(null, "黑棋胜出");        }        /*         * 反对角线上的检测              */        for(int i=x+1,j=y+1;i<15&&j<15;i++,j++){                //行递加列递加            if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){                count4++;            }            else break;        }        for(int i=x-1,j=y-1;j>=0&&i>=0;j--,i--){            if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){                count4++;            }            else break;        }        //反对角线上检测完毕        if(count4>=5){            if(xy[x][y].flag==1)                op.showMessageDialog(null, "白棋胜出");             else                op.showMessageDialog(null, "黑棋胜出");        }    }

如何实现按键点击和落子?

当我们是实现了界面之后,理所应当的,接下来就是实现点击按键和落子的功能了,如何达到在点击按键和落子之后五子棋界面会有相应的反应呢?这需要我们用到事件监听机制,为按键加上监听。
按键点击的事件监听,我们要用到ActionListener接口。
在棋盘上落子实际上就是点击棋盘,棋盘上相应的位置画出棋子。我们可以用到MouseAdapter适配器
定义一个继承了MouseAdapter适配器实现了ActionListenner接口的事件监听类listener
给出对应部分代码

package com.ly.fiveChessV2;import java.awt.Color;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.util.HashMap;import javax.swing.JOptionPane;import javax.swing.JPanel;/* * 点击按钮 点击棋盘面板 */public class listener extends MouseAdapter implements ActionListener{    int mode;    int x,y;                    //x y是鼠标在棋盘界面上获得的坐标    private Graphics2D g;       //画笔用来绘图    int chesscount;             //连成一条线的同一颜色棋子数量    public chess [][]xy;        //定义一个棋子数组    int col=1;                  //定义一个颜色标志位 1:白色2:黑色    chess chessnew=new chess(); //用一个棋子代表最近下的棋    public JPanel panel;    /*     * 将棋盘上的画笔g赋值给属性g     */    public void setG(Graphics g){        this.g=(Graphics2D)g;    }    /*     * 把棋盘面板对象传过来     */    public void setP(JPanel panel){        this.panel=panel;    }    /*     * 将棋子数组传入     */    public void setChess(chess [][]xy){        this.xy=xy;    }    /*     *处理按键事件     */    public void actionPerformed(ActionEvent e){        JOptionPane op=new JOptionPane();        String str= e.getActionCommand();        /*         * 认输按键         */        if(str.equals("认输")){            if(col==1){            op.showMessageDialog(null, "白棋认输,黑棋胜");             }            else{                op.showMessageDialog(null, "黑棋认输,白棋胜");                 }            for(int i=0;i<15;i++){                for(int j=0;j<15;j++){                    xy[i][j].flag=0;                }            }            panel.paint(g);        }        /*         * 悔棋按键         */        if(str.equals("悔棋")){               //悔棋应该是利用棋盘界面的重画paint()方法            if(chessnew.flag==1){                col=1;                System.out.println("悔棋的是白棋,白棋再落一子");            }            else if(chessnew.flag==2){                col=2;                System.out.println("悔棋的是黑棋,黑棋再落一子");            }            chessnew.flag=0;                //把最近一次的棋子抹除掉            panel.paint(g);            for(int i=0;i<15;i++){                for(int j=0;j<15;j++){                    if(xy[i][j].flag!=0){                        if(xy[i][j].flag==1){                            g.setColor(Color.white);                            g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20);                        }                        else{                            g.setColor(Color.black);                            g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20);                        }                    }                }            }            System.out.println("悔棋");        }        if(str.equals("人人对战")){            mode=11;        }        if(str.equals("人机对战")){            mode=12;        }    }    /*     * 处理鼠标点击事件     */    public void mouseClicked(MouseEvent e) {        /*         * 画笔抗锯齿         */        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                RenderingHints.VALUE_ANTIALIAS_ON);        /*         * 获取鼠标点击的x和y坐标         */        x=e.getX();        y=e.getY();         /*         * 由鼠标的位置 得到棋子圆心该放的位置         */            for(int i=0;i<15;i++){                for(int j=0;j<15;j++){                      //循环遍历数组找到鼠标点击的最近的格子                    if(Math.abs(x-xy[i][j].X)<10&&Math.abs(y-xy[i][j].Y)<10){                        if(xy[i][j].flag==0){               //判断点击处是否没有落过子                            if(col == 1){                   //判断棋子该下的颜色                                g.setColor(Color.white);                                System.out.println("white");                                g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20);                                xy[i][j].flag=1;            //flag为1表示下的是白棋                                chessnew=xy[i][j];          //将这一步下的棋保存起来                                col=2;                      //下一次下黑旗                            }                            else if(col==2){                                g.setColor(Color.black);                                System.out.println("black");                                g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20);                                xy[i][j].flag=2;            //flag为1表示下的是黑旗                                col=1;                      //下一次下白棋                                        }                        checkChess( xy[i][j]);                        // 落完子 判断输赢之后 电脑落子 判断输赢                        }                           else                                //已经有棋子落下过                        System.out.println("这里不能落子");                    }                }            }        }    }}

在写完事件监听类之后我们应该实例化一个事件监听类对象,并为按键和棋盘添加事件监听。
附上相关代码

        /*         * 实例化一个监听对象         * 为各个组件加上监听         */        listener l=new listener();        l.setG(g);        l.setP(this);        l.setChess(xy);        this.addMouseListener(l);        bquit.addActionListener(l);        bundo.addActionListener(l);        bstart.addActionListener(l);        bpvp.addActionListener(l);        bpvr.addActionListener(l);        this.setG(g);                   //棋盘对象设置Graphics属性

有了以上代码我们的五子棋就能实现人人对战的功能。

如何实现人机对战?

要实现人机对战,那么就要让电脑知道棋子该下到哪一个位置,我们可以通过计算每一个位置上隐形棋子的权值(只有没下过的棋子才需要考虑)。电脑优先下权值大的棋子,然后判断一次输赢。

如何确定棋子的权值?

我的方法是用一个字符串记录棋子水平方向的、竖直方向上的、正对角线方向上、反对角线方向上的落子情况。然后根据落子情况,给该棋子赋值。
我们先要给出隐形棋子各个方向上可能的情况,和这种情况对应的权值。
我们可以利用哈希表实现

//首先实例化一个哈希表对象 HashMap<String,Integer> hm = new HashMap<String,Integer>();

隐形棋子为“0”,白子为“1”,黑子为“2”;这里写图片描述
上图对应的字符串即为“2022”(黑子、隐形棋子、黑子、黑子)

hm.put("2022",10);

我把上图中情况的棋子的权值设置为10,当我们以后检测到隐形棋子周围的落子情况之后,就能取出对应的权值。
比如字符串S为“2022”;

int value=hp.get(S);

那么value的值就是10;
当我们把一个隐形棋子的水平、竖直、正对角线、斜对角线方向上的情况全都统计完并把四个方向上的权值相加,得到该棋子最终的权值,下面给出计算权值的代码方法caculate()

public void caculate(chess ch){        String str="0";     //用一个字符串来记录该棋子旁边的落子情况        int x=ch.Y/30-1;    //把ch棋子对应的横纵坐标转换成在棋子数组里的行和列        int y=ch.X/30-1;            hashInit();        /*         * 计算该位置棋子的权值         * 参数为一个棋子 棋子flag有三种状态 0:还没有落子 1:落白子 2:落黑子         */        if(ch.flag==0){                 //只有没有当棋子没有落下的时候才要计算该棋子的权值            Integer val;             for(int j=y+1;j<15;j++){   //记录棋子右边的落子情况 是列在变                if(xy[x][j].flag==0){   //该棋子右边没有落子                    break;              //不用记录直接退出遍历循环                }                else                     if(xy[x][j].flag==1){                        str=str+1;  //str在左边 往右加                    }                    else if(xy[x][j].flag==2){                        str=str+2;  //str在左边 往右加                        break;                    }            }            // 水平方向上检测棋子 往左看            for(int j=y-1;j>=0;j--){                if(xy[x][j].flag==0){   //该棋子左边没有落子                    break;              //不用记录直接退出遍历循环                }                else if(xy[x][j].flag==1){                    str=1+str; //str在右边 往左加                }                else if(xy[x][j].flag==2){                    str=2+str;  //str在右边 往左加                    break;                }            }             // 到这里之后棋子ch的左右情况就统计完了 str代表统计情况 利用str在哈希表对象中得到对应的键值             val=hm.get(str);            System.out.println("水平方向:"+x+"行"+y+"列棋子 str:"+str);            if(val!=null){                xy[x][y].value+=val;  //把对应的键值加到棋子中去            }            str="0";            //竖直方向上 往上看            for(int i=x-1;i>=0;i--){                if(xy[i][y].flag==0){                    break;                }                else if(xy[i][y].flag==1){                    str=1+str;                }                else if(xy[i][y].flag==2){                    str=2+str;                    break;                }            }            //竖直方向上 往下看            for(int i=x+1;i<15;i++){                if(xy[i][y].flag==0){                    break;                }                else if(xy[i][y].flag==1){                    str=str+1;                }                else if(xy[i][y].flag==2){                    str=str+2;                    break;                }            }            System.out.println("竖直方向:"+x+"行"+y+"列棋子 str:"+str);            val=hm.get(str);                if(val!=null){                    xy[x][y].value+=val;                }            str="0";            //正对角线方向上 向上            for(int i=x-1,j=y+1;i>=0&&j<15;i--,j++){                    if(xy[i][j].flag==0){                        break;                    }                    else if(xy[i][j].flag==1){                        str=1+str;                    }                    else if(xy[i][j].flag==2){                        str=2+str;                        break;                    }            }            //正对角线方向上 向下            for(int i=x+1,j=y-1;i<15&&j>=0;i++,j--){                    if(xy[i][j].flag==0){                        break;                    }                    else if(xy[i][j].flag==1){                        str=str+1;                    }                    else if(xy[i][j].flag==2){                        str=str+2;                        break;                    }            }            System.out.println("正对角线上"+x+"行"+y+"列棋子str:"+str);            val=hm.get(str);            if(val!=null)                xy[x][y].value+=val;            str="0";            //反对角线方向上 向上            for(int i=x-1,j=y-1;i>=0&&j>=0;i--,j--){                    if(xy[i][j].flag==0){                        break;                    }                    else if(xy[i][j].flag==1){                        str=1+str;                    }                    else if(xy[i][j].flag==2){                        str=2+str;                        break;                    }                   }            //反对角线方向上 向下            for(int i=x+1,j=y+1;i<15&&j<15;i++,j++){                    if(xy[i][j].flag==0){                        break;                    }                    else if(xy[i][j].flag==1){                        str=str+1;                    }                    else if(xy[i][j].flag==2){                        str=str+2;                        break;                    }            }            System.out.println("反对角线上"+x+"行"+y+"列棋子str:"+str);            val=hm.get(str);            if(val!=null)                xy[x][y].value+=val;            str="0";            System.out.println(x+"行"+y+"列棋子权值:"+xy[x][y].value);        }    }

有了计算一个棋子权值的方法那么,接下来然计算机决定在哪里落子就很容易了。我们可以再写一个方法AI(),循环遍历棋子数组,在隐形棋子里找出权值最大的一个棋子,然后落子。

public void AI(){        int x,y;        // 定义一个棋子变量        chess ch =new chess();        //遍历二维棋子数组 计算出每一个未落子棋子权值        for(int i=0;i<15;i++){            for(int j=0;j<15;j++){                System.out.println("计算"+i+"行"+j+"列 棋子");                caculate(xy[i][j]);            }        }        //遍历整个二维棋子数组,找出权值最大的棋子          for(int i=0;i<15;i++){            for(int j=0;j<15;j++){                if(xy[i][j].value>=ch.value){                    ch=xy[i][j];                }            }        }        System.out.println("权值最大的棋子 "+(ch.Y/30-1)+"行"+(ch.X/30-1)+"列");        //落子权值最大的棋子        x=ch.Y/30-1;        y=ch.X/30-1;        xy[x][y].flag=2;        g.setColor(Color.black);        g.fillOval(xy[x][y].X-10, xy[x][y].Y-10,20,20);        //清空棋子的权值        for(int i=0;i<15;i++){            for(int j=0;j<15;j++){                xy[i][j].value=0;            }        }    }

哈希表中五子棋的情况怎么给?

有人可能会说,下一盘五子棋那么多的棋子,怎么事先给出棋子的落子情况,和设置对应的落子情况呢?
其实 五子棋的落子情况需要考虑的不过是 活二 跳二 活三 眠三 活四 眠四之类的
我在这里给出活二 跳二 活三 眠三 活四 眠四之类相关介绍的链接 http://game.onegreen.net/wzq/HTML/142336.html

好了到这里最主要的代码已经讲解完了,大家要是有什么不清楚的地方可以给我留言。

原创粉丝点击