五子棋人人对战完善版

421 阅读5分钟

针对上一个缺陷版,这次的代码进行了许多方面的完善。以下就亮点进行说明,最后从总体上综述项目内容。

亮点:

1:用ArrayList存储棋子坐标位置->存储棋子对象

原本我是用一个ArrayList列表存储每个棋子坐标,就像{{1,2},{3,2},{4,3},{5,1}}这样顺次add,这样能够判断该位置能否下棋以及取出最后一子用于悔棋,但实操中也有功能缺陷,比如:不知道哪种颜色是最后一子,对棋子其它属性无法刻画等等。于是后来改用存储Chess对象,对象属性为坐标和颜色。

2:原本画棋盘和画棋子分开操作,棋盘一次画好不再更改->更新棋子时更新棋盘

原本的逻辑是好理解的,初始化界面的时候画好棋盘,之后画棋子,清空棋子,重画棋子的操作虽都在此棋盘画板上,但理应于棋盘无关了。但实操中发现,chessPanel里不容易区分初始化时画的棋盘,和之后画的棋子,这两种一个是初始化时自动跑的paint(自动产生的画笔),一个是从画板手动产生画笔,赋给监听器,再用画笔画的。我尝试用repaint方法重画棋盘,再手动重画棋子,这样尝试区分开两步骤,但发现repaint方法有点毛病:底层代码在repaint后的重画棋子早早进行完毕,但界面上的重画棋子一直跑不出来,猜测是repaint跑的时间很长。

另外当我们把这个界面最小化或者拖到屏幕以外,界面上的棋子就消失了。猜测也是先跑了一遍repaint,再重新完成之前的画棋子操作,但因为repaint莫名其妙的完不了,就导致来不及画棋子。

最后的操作是:在原本画棋盘的方法中同样更新棋子,也就是在想要大改棋子的时候直接重画棋盘。这样做一方面不用区分开画棋盘和画棋子(因为棋盘是随着棋子而重画的),另一方面,绑定在一起后,最小化界面和拖动不会单一的使棋盘画完而棋子来不及画(为什么会这样?可能是repaint在画完后有其他工作要做)。

3:原本刻画棋子占位的二维数组从对象属性->全局变量

原本的二维数组是在监听器类下的属性,每次鼠标点击后更新数组内容,调用时也是在监听器类中调用——悔棋和检查有无下过棋子。但因为在画棋盘的时候也要用到此二维数组(ChessPanel类),于是构思把此数组作为接口类下的公用变量(为什么是公用?而不是每个实现类都拥有一个?——

static既然没用,那猜测默认有static。)

4:数据的使用:从纯粹的数字->用静态变量代替

代码:

1:GoBangUI类

public class GoBangUI {

GoListener goListener=new GoListener();
JFrame jf=new JFrame("五子棋");
    public void initUI(){
    try {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        jf.setSize(900, 800);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setResizable(false);
        jf.setLocationRelativeTo(null);
        SwingUtilities.updateComponentTreeUI(jf);
    }catch (Exception e){
        e.printStackTrace();
    }
        JPanel btnPanel =new JPanel();
        btnPanel.setBackground(Color.ORANGE);
        btnPanel.setPreferredSize(new Dimension(110,100));
        btnPanel.setLayout(new FlowLayout());

        JLabel jla=new JLabel("五子棋");
        jla.setHorizontalAlignment(SwingConstants.CENTER);
        jla.setPreferredSize(new Dimension(100,100));
        jla.setFont(new Font(null,Font.ITALIC,20));
        Icon icon=new ImageIcon("https://pic.ntimg.cn/20140411/18441126_192718566176_2.jpg");
        jla.setIcon(icon);
        btnPanel.add(jla);
        jf.setVisible(true);

        String[] strs={"开始游戏","悔棋","退出游戏","历史记录"};
        for (int i = 0; i < strs.length; i++) {
            JButton jb=new JButton(strs[i]);
            jb.setBackground(Color.WHITE);
            //jb.setBounds(5,125+i*50,95,45);  //setBounds直接设置了位置
            jb.setPreferredSize(new Dimension(95,45));  //setSize后按钮会随着字多少而变大变小?
            btnPanel.add(jb);
            jb.addActionListener (goListener);//
        }

        ChessPanel cp=new ChessPanel();
        cp.addMouseListener(goListener);

        jf.add(btnPanel,BorderLayout.EAST);
        jf.add(cp,BorderLayout.CENTER);
        //jf.add(cp,BorderLayout.WEST);    //west显示不出来

        jf.setVisible(true);
        goListener.ui=this;
        goListener.g=cp.getGraphics();
        goListener.chessPanel=cp;
    }

    public static void main(String[] args) {
        GoBangUI ui=new GoBangUI();
        ui.initUI();
    }
}

GoListener类

public class GoListener implements ActionListener, MouseListener,GoData {
    
    int chessFlag=0;
    Graphics g;
    ChessPanel chessPanel;
    GoBangUI ui;
    ArrayList<Chess> chessList=new ArrayList<>();
    int blackWin=0;
    int whiteWin=0;
    JButton btn;
    @Override
    public void actionPerformed(ActionEvent e) {
        String actionStr=e.getActionCommand();
        btn=(JButton) e.getSource();
        if(actionStr.equals("开始游戏")){
            chessFlag=1;
            btn.setText("结束游戏");
        }
        else if(actionStr.equals("结束游戏")){
            for (int i = 0; i < COLS; i++) {
                for (int j = 0; j < ROWS; j++) {
                    CHESS_ARRAY[i][j]=0;
                }
            }
            chessList.clear();
            chessPanel.paint(g);  //这个画笔?????chessPanel和g都需要指定具体的,在此只是用声明的对象进行操作
            btn.setText("开始游戏");
            chessFlag=0;
        }else if(actionStr.equals("悔棋")){
            if (chessList.size()==0){
                JOptionPane.showMessageDialog(null,"没有棋子可以悔棋了");
                return;
            }
            Chess chess=chessList.get(chessList.size()-1);
            //System.out.println(chess.c+"@@@"+chess.r);
            CHESS_ARRAY[chess.c][chess.r]=9;
            chessFlag=chess.chessFlag;
            chessList.remove(chessList.size()-1);

            chessPanel.paint(g);

        } else if (actionStr.equals("退出游戏")) {
            ui.jf.setVisible(false);
        }else if(actionStr.equals("历史记录")){
            JOptionPane.showMessageDialog(null,"黑方总计获胜次数:"+blackWin+"\n白方总计获胜次数:"+whiteWin);
        }
    }

    public boolean check(int x, int y) {
        int ans = CHESS_ARRAY[x][y];
        System.out.println("x:"+x+"y:"+y);
        try {
            for (int i = 0; i < 5; i++) {
                if (CHESS_ARRAY[x - 4 + i][y] == ans && CHESS_ARRAY[x - 3 + i][y] == ans && CHESS_ARRAY[x - 2 + i][y] == ans && CHESS_ARRAY[x - 1 + i][y] == ans &&CHESS_ARRAY[x+i][y]==ans) {
                    System.out.println("横着的是:"+i);
                    return true;
                }
            }
            for (int i = 0; i < 5; i++) {
                if (CHESS_ARRAY[x][y - 4 + i] == ans && CHESS_ARRAY[x][y - 3 + i] == ans && CHESS_ARRAY[x][y - 2 + i] == ans && CHESS_ARRAY[x][y - 1 + i] == ans && CHESS_ARRAY[x][y+i]==ans) {
                    System.out.println("竖着的是:"+i);
                    return true;
                }
            }
            for (int i = 0; i < 5; i++) {
                if (CHESS_ARRAY[x - 4 + i][y - 4 + i] == ans && CHESS_ARRAY[x - 3 + i][y - 3 + i] == ans && CHESS_ARRAY[x - 2 + i][y - 2 + i] == ans && CHESS_ARRAY[x - 1 + i][y - 1 + i] == ans && CHESS_ARRAY[x+i][y+i]==ans) {
                    System.out.println("捺的是:"+i);
                    return true;
                }
            }
            for (int i = 0; i < 5; i++) {
                if (CHESS_ARRAY[x - 4 + i][y + 4 - i] == ans && CHESS_ARRAY[x - 3 + i][y + 3 - i] == ans && CHESS_ARRAY[x - 2 + i][y + 2 - i] == ans && CHESS_ARRAY[x - 1 + i][y + 1 - i] == ans &&CHESS_ARRAY[x+i][y-i]==ans) {
                    System.out.println("撇的是"+i);
                    return true;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
            return false;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        int x=e.getX();
        int y=e.getY();

        int r=(y-Y+SIZE/2)/SIZE;
        int c=(x-X+SIZE/2)/SIZE;
        if(chessFlag==0){
            JOptionPane.showMessageDialog(null,"还没开始");
            return;
        }
        if(r<0||c<0||r>ROWS||c>COLS){
            JOptionPane.showMessageDialog(null,"不能在边界外下棋");
            return;
        }
        if(CHESS_ARRAY[c][r]!=0){
            return;
        }
        CHESS_ARRAY[c][r]=chessFlag;
        chessList.add(new Chess(c,r,chessFlag));

        if(chessFlag==1){
            g.setColor(Color.BLACK);
            chessFlag=2;
        }else if(chessFlag==2){
            g.setColor(Color.WHITE);
            chessFlag=1;
        }
        int cx=c*SIZE+X-SIZE/2;
        int cy=r*SIZE+Y-SIZE/2;

        g.fillOval(cx,cy,SIZE,SIZE);

        if (check(c,r)){
            String ans;
            if(CHESS_ARRAY[c][r]==1){
                ans="黑方";
                blackWin++;
            }else{
                ans="白方";
                whiteWin++;
            }
            int a=JOptionPane.showConfirmDialog(null,new Object[]{"再来一局?"},"游戏结束,胜者是"+ans,JOptionPane.YES_NO_OPTION);

            if(a==0){
                for (int i = 0; i < ROWS; i++) {
                    for (int j = 0; j < COLS; j++) {
                        CHESS_ARRAY[i][j]=0;
                    }
                }
                chessList.clear();
                chessPanel.paint(g);//这个画笔?????
                btn.setText("开始游戏");
                chessFlag=0;

            }
            else{
                ui.jf.setVisible(false);
            }
            }

        for (int i = 0; i < CHESS_ARRAY.length; i++) {
            for (int j = 0; j < CHESS_ARRAY[i].length; j++) {
                System.out.print(CHESS_ARRAY[j][i]);
            }
            System.out.println(" ");
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {

    }

    @Override
    public void mouseReleased(MouseEvent e) {

    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }
}

ChessPanel类

public class ChessPanel extends JPanel implements GoData{

    public void paint(Graphics g){
        g.setColor(Color.lightGray);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(Color.BLACK);

        for (int i = 0; i < ROWS; i++) {
            g.drawLine(X,Y+i*SIZE,X+GRID_NUM*SIZE,Y+i*SIZE);
        }
        for (int i = 0; i < COLS; i++) {
            g.drawLine(X+i*SIZE,Y,X+i*SIZE,Y+GRID_NUM*SIZE);
        }
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                int xDraw,yDraw;
                xDraw=X+j*SIZE-SIZE/2;
                yDraw=Y+i*SIZE-SIZE/2;
                if(CHESS_ARRAY[j][i]==1){
                    g.setColor(Color.BLACK);
                    g.fillOval(xDraw,yDraw,SIZE,SIZE);
                } else if (CHESS_ARRAY[j][i]==2) {
                    g.setColor (Color.WHITE);
                    g.fillOval (xDraw ,yDraw,SIZE,SIZE);
                }
            }
        }
    }

}

GoData接口

public interface GoData {
    int X=75;
    int Y=75;
    int SIZE=40;
    int ROWS=16;
    int COLS=16;
    int GRID_NUM=15;
    static int[][] CHESS_ARRAY=new int[COLS][ROWS];

}

综述:

代码首先完成了UI界面设计,JFrame打底,多个JPanel上添加组件。之后监听器监听两种行为,分别是点击组件的ActionListener和鼠标点击的MouseListener。

行为监听包括开始游戏命令(这里用一个变量和一个弹窗完成),悔棋命令(重画了棋盘和之前的棋子),退出命令(将界面设置不可见),成绩记录命令(用两个变量记录双方输赢)。

鼠标监听中得到的xy坐标是界面的像素值,匹配到易于操作和理解的坐标值后,记录到数组,同时完成画棋子。