Java编程入门:手把手教你做五子棋小游戏

你是否想过,作为Java初学者就能开发出可运行的小游戏? 今天就带大家从零开始,拆解五子棋小游戏的实现过程,哪怕没有学过Java,跟着步骤走也能看懂原理哦~ 现在让我们正式开始学习如何实现五子棋小游戏吧

一、核心原理:五子棋需要哪些“零件”?

在写代码之前,我们先想清楚五子棋的基础功能:要有一个游戏窗口、能看到棋盘、能落黑白两色的棋子、判断谁赢了,还要有开始游戏/进行悔棋/重新开始等功能按钮以及计时计步等功能。对应到Java里,我们会用到Swing组件(做窗口和按钮)、Graphics类(画棋盘棋子)、数组(存棋子位置和判断输赢)……这些知识点在后面的教学中都会边做边进行讲解。

二、创建游戏窗口(窗体)

窗体是游戏的“容器”,就像装棋盘的盒子一样。Java里常用JFrame类来做窗口,下面一步步看代码怎么编写。

1.核心代码(GameUI类)

import javax.swing.*;
import java.awt.*;

public class GameUI {
    //显示游戏界面的方法
    public void initUI() {
        //1.创建窗口对象
        JFrame jf = new JFrame();
        //2.设置窗口大小:宽500像素,长400像素
        jf.setSize(500, 440);
        //3.设置窗口标题
        jf.setTitle("五子棋游戏");
        //4.关闭窗口时退出程序
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //5.让窗口显示在屏幕的正中间
        jf.setLocationRelativeTo(null);
        //6.设置窗口布局
        jf.setLayout(new BorderLayout());
        //7.让窗口显示出来
        jf.setVisible(true);
      }
    
     //程序入口(主函数)
     public static void main(String[] args) {
        GameUI ui = new GameUI();//创建GameUI对象
        ui.initUI();//调用方法显示窗口
    }
}

2.运行效果

此时运行代码,会弹出一个空白的窗口,这就是我们的游戏容器啦~

三、绘制棋盘和棋子

有了窗口之后,接下来就需要画棋盘(横线+竖线)和棋子(黑白色)。这里会用到  JPanel(画板)和  Graphics(画笔)。

1.先做棋盘:创建GamePanel类

棋盘要先画在“画板”上,JPanel 就是专门用来绘图的面板,我们需要重写它自带的 paint 方法(相当于在画板上画画的步骤)

核心代码(GamePanel类)

import javax.swing.*;
import.java.awt.*;

public class GamePanel extends JPanel {
    //棋盘参数:X0/Y0是棋盘左上角坐标,SIZE是每格边长,LINE是棋盘行数(15列15行)
    public int X0=25 , Y0=25 , SIZE=25 , LINE=15;
    //关联监听器(用于获取棋子信息)
    public GameListener listener;

    //重写paint方法
    public void paint(Graphics g ){
        //1.保留父类功能(必须加,不然画板会乱)
        super.paint(g);
        //2.设置画笔颜色为黑色(用于画棋盘线条)
        g.setColor(Color.BLACK);
        //3.画棋盘线
        for (int i=0;i<LINE;i++){
            //4.画横线:从左到右,共LINE条
            g.drawLine(X0,Y0+i*SIZE,(LINE-1)*SIZE+X0,Y0+i*SIZE);
            //5.画竖线:从 上到下,共LINE条
            g.drawLine(X0+i*SIZE,Y0,X0+i*SIZE,(LINE-1)*SIZE+Y0);
        }
    }
}

2.再画棋子:通过鼠标点击落子

棋子不是一开始就画好的,需要点击棋盘落子。这里就需要用到 MouseListener(鼠标监听器),监听鼠标的“点击”动作,然后在点击的位置画出棋子。

核心代码( GameListener 类:处理鼠标点击和棋子绘制)

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class GameLIstener implements MouseListener {
    //棋子参数:CHESS是棋子的直径
    public int X0 = 25 , Y0 = 25 , SIZE = 25 , LINE = 15 , CHESS = 15;
    //画笔对象(用来画棋子)
    public Graphics g;
    //棋盘画板(关联到GamePanel,方便重绘)
    private GamPanel panel;
    //棋子计数器(判断黑白棋:偶数画黑棋,奇数画白棋)
    public int count = 0;
    //二维数组:存所有棋子信息
    public int[][] chessArray = new int[LINE * LINE][3];
    //二维数组:存棋盘落子情况
    public int[][] broad = new int[LINE][LINE];
    //控制是否可以下棋
    private boolean canPlay = flase;

    //构造方法:初始化属性
    public GameListener(Graphics g, GamePanel panel) {
        this.g = g;
        this.panel = panel;
    }
    
    //设置是否可以下棋的方法
    public void setCanPlay(boolean canPlay){
        this.canPlay = canPlay;
    }

    //鼠标点击时执行的方法(控制落子)
    public void mouseClicked(MouseEvent e) {
        //没点“开始”就点击棋盘,弹出提示
        if (!canPlay) {
            JOptionPane.showMessageDialog(panel,"点击【开始游戏】开始对局!");
            return;
        }

        //1.获取鼠标点击的坐标
        int x = e.getX();
        int y = e.getY();
        
        //2.计算 棋子落在棋盘的哪个交叉点
        //计算棋子X轴交点值
        if ((x - X0) % SIZE > SIZE / 2) {  //靠右
            chessX = (x - X0) / SIZE + 1;
        } else {  //靠左
            chessX = (x - X0) / SIZE;
        }

        //计算棋子Y轴交点值
        if ((y - Y0) % SIZE > SIZE / 2) {  //靠下
            chessY = (y - Y0) / SIZE + 1;
        } else {  //靠上
            chessY = (y - Y0) / SIZE;
        }
        
        //3.限制棋子在棋盘内
        chessX = Math.min(LINE - 1, Math.max(0, chessX));
        chessY = Math.min(LINE - 1, Math.max(0, chessY));

        //4.防止重复落子
        //判断当前位置是否已有棋子
        if (board[chessX][chessY] != 0) {
            return;//已有棋子则不处理
        }

        //5.记录棋子颜色(1=黑棋,2=白棋)
        int colorType = count % 2; 
        int boardColor = colorType + 1;
        board[chessX][chessY] = boardColor;

        //6.保存棋子信息到数组
        chessArray[count][0] = chessX;
        chessArray[count][1] = chessY;
        chessArray[count][2] = count % 2;
        count++;//每落一子,计数器+1

        //触发重绘
        panel.repaint();
}

3.把棋盘和监听器加到窗口里

完成上面的步骤之后,就差最后一步就能正常下棋啦。我们需要回到 GameUIinitUI 方法,添加下列代码(将棋盘面板和监听器关联起来)

核心代码

//在jf.setLayout(new BorderLayout());后面添加下列代码
//1.创建棋盘面板
GamePanel chessPanel = new GamePanel();
chessPanel.setBackground(Color.ORANGE);  //棋盘背景设为橙色
chessPanel.setPreferredSize(new Dimension(425,425);  //棋盘大小
jf.add(chessPanel,BorderLayout.CENTER);  //把棋盘放在窗口中间

//2.创建画笔对象(从棋盘面板获取对象)
Graphics g = chessPanel.getGraphics();

//3.创建监听器对象(把画笔和棋盘传递进去) 
GameListener listener = new GameListener(g,chessPanel);
chessPanel.listener = listener;   //让棋盘监听器
chessPanel.addMouseListener(listener);  //给鼠标加鼠标监听器

4.重要知识点

二维数组

在五子棋游戏中,棋盘通常用二维数组表示。一维表示行数,二维表示列数。二维数组board相当于“棋盘地图”,用0、1、2标记每个位置是否有棋子,避免在同一个地方重复落子。

public int[][] board = new int[15][15]; // 15x15的棋盘
 
二维数组的遍历与棋子检测

通过嵌套循环遍历二维数组,可以检测棋子的位置所在或判断输赢:

for (int i = 0; i < board.length; i++) {
    for (int j = 0; j < board[i].length; j++) {
        if (board[i][j] == 1) {
            System.out.print(" 黑棋 "); // 打印黑子
        } else if (board[i][j] == 2) {
            System.out.print(" 白棋 "); // 打印白子
        } else {
            System.out.print(" 空位 "); // 空位
        }
    }
    System.out.println();
}

5.运行效果

点击“开始游戏”之后,在棋盘上点击会落下黑白交替的棋子,且不能在同一位置重复落子

四、实现棋盘和棋子的重绘

为什么需要“重绘”呢?比如当用户在玩游戏时将窗口最小化后再打开,之前下的棋子会消失不见,甚至棋盘也会消失不见,这时候就需要重新把所有棋子画出来。这就是“重绘”了。

1.核心代码

import javax.swing.*;
import java.awt.*;


public class GamePanel extends JPanel {
    public int X0=25 , Y0=25 , SIZE=25 , LINE=15;
    public GameListener listener;

    //重写paint方法
    public void paint(Graphics g ){
        //保留绘制组件的功能
        super.paint(g);
        //绘制棋盘
        for (int i=0;i<LINE;i++){
            g.setColor(Color.black);//黑色棋盘线
            g.drawLine(X0,Y0+i*SIZE,(LINE-1)*SIZE+X0,Y0+i*SIZE);
            g.drawLine(X0+i*SIZE,Y0,X0+i*SIZE,(LINE-1)*SIZE+Y0);
        }
        //绘制所有棋子(实现交替重绘)
        if (listener!=null){
            int radius = listener.CHESS/2;
            //遍历所有已落下的棋子(i从0到listener.count-1)
            for (int i=0;i<listener.count;i++){
                //从数组中获取棋子的坐标和颜色
                int x = listener.chessArray[i][0];
                int y = listener.chessArray[i][1];
                int color = listener.chessArray[i][2];

                //计算棋子绘制坐标
                int drawX = X0 + x*SIZE - radius;
                int drawY = Y0 + y*SIZE - radius;

                if(color == 0){  //黑棋
                    g.setColor(Color.BLACK);
                }else{ //白棋
                    g.setColor(Color.WHITE);
                }
                //黑棋填充
                g.fillOval(drawX,drawY,listener.CHESS,listener.CHESS);

                //白棋填充加黑框
                if (color == 1){
                    g.setColor(Color.BLACK);
                    g.drawOval(drawX,drawY,listener.CHESS,listener.CHESS);
                }
            }
        }
    }
}

2.重要知识点

  • 我们把所有棋子的信息存在 chessArray 数组里,重绘时只要遍历这个数组,把每个棋子重新画一遍就行。
  • fillOval 是画填充圆,drawOval 是画圆边框,白棋加黑边框是为了和橙色棋盘进行区分,使棋子颜色更为明显。

五、用二维数据判断游戏的输赢

我们都知道五子棋获胜的条件是“同一颜色的棋子在横、竖、斜四个方向中,有任意一个方向连成5颗”。在这里我们可以用二维数组遍历来判断,即每次落子之后,检查落子位置的四个方向(水平、竖直、两个对角线)有没有连续5颗相同颜色的棋子。

1.核心代码

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class GameLIstener implements MouseListener {
    //棋子参数:CHESS是棋子的直径
    public int X0 = 25 , Y0 = 25 , SIZE = 25 , LINE = 15 , CHESS = 15;
    //画笔对象(用来画棋子)
    public Graphics g;
    //棋盘画板(关联到GamePanel,方便重绘)
    private GamPanel panel;
    //棋子计数器(判断黑白棋:偶数画黑棋,奇数画白棋)
    public int count = 0;
    //二维数组:存所有棋子信息
    public int[][] chessArray = new int[LINE * LINE][3];
    //二维数组:存棋盘落子情况
    public int[][] broad = new int[LINE][LINE];
    //控制是否可以下棋
    private boolean canPlay = flase;

    //构造方法:初始化属性
    public GameListener(Graphics g, GamePanel panel) {
        this.g = g;
        this.panel = panel;
    }
    
    //设置是否可以下棋的方法
    public void setCanPlay(boolean canPlay){
        this.canPlay = canPlay;
    }

    //鼠标点击时执行的方法(控制落子)
    public void mouseClicked(MouseEvent e) {
        //没点“开始”就点击棋盘,弹出提示
        if (!canPlay) {
            JOptionPane.showMessageDialog(panel,"点击【开始游戏】开始对局!");
            return;
        }

        //1.获取鼠标点击的坐标
        int x = e.getX();
        int y = e.getY();
        
        //2.计算 棋子落在棋盘的哪个交叉点
        //计算棋子X轴交点值
        if ((x - X0) % SIZE > SIZE / 2) {  //靠右
            chessX = (x - X0) / SIZE + 1;
        } else {  //靠左
            chessX = (x - X0) / SIZE;
        }

        //计算棋子Y轴交点值
        if ((y - Y0) % SIZE > SIZE / 2) {  //靠下
            chessY = (y - Y0) / SIZE + 1;
        } else {  //靠上
            chessY = (y - Y0) / SIZE;
        }
        
        //3.限制棋子在棋盘内
        chessX = Math.min(LINE - 1, Math.max(0, chessX));
        chessY = Math.min(LINE - 1, Math.max(0, chessY));

        //4.防止重复落子
        //判断当前位置是否已有棋子
        if (board[chessX][chessY] != 0) {
            return;//已有棋子则不处理
        }

        //5.记录棋子颜色(1=黑棋,2=白棋)
        int colorType = count % 2; 
        int boardColor = colorType + 1;
        board[chessX][chessY] = boardColor;

        //6.保存棋子信息到数组
        chessArray[count][0] = chessX;
        chessArray[count][1] = chessY;
        chessArray[count][2] = count % 2;
        count++;//每落一子,计数器+1
 
        //7.判断是否赢了
        if (checkWin(chessX, chessY, boardColor)) {
            String winner = (colorType == 0) ? "黑棋" : "白棋";
            //获胜后不让继续下棋
            canPlay = false;
            //获胜后弹出窗口提示
            JOptionPane.showMessageDialog(panel,(winner + "五子连珠,取得胜利!" ));
            isGameOver = true;
         }

        //触发重绘
        panel.repaint();
    }
        //判断输赢
    private boolean checkWin(int x, int y, int color) {
        //方向数组:水平,竖直,两个对角线
        int[][] dirs = {
                {1, 0}, //向右检查
                {0, 1}, //向下检查
                {1, 1}, //向右下检查
                {1, -1} //向左下检查
        };

        //遍历每个检查方向
        for (int[] dir : dirs) {
            int sameCount = 1;//当前位置已经有1颗目标颜色棋子,初始为1
            int dx = dir[0];//x方向每次走几步
            int dy = dir[1];//y方向每次走几步

            //1.正向检查(顺着当前方向走,最多查4步,因为起始1颗共五颗)
            for (int i = 1; i < 5; i++) {
                int nx = x + dx * i;//新的x坐标
                int ny = y + dy * i;//新的y坐标
                //检查新坐标是否在棋盘内,且棋子颜色相同
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;//超出棋盘,停止检查
                }
                if (board[nx][ny] != color) {
                    break;//颜色不同,停止检查
                }
                sameCount++;//颜色相同,计数+1
            }

            //2.反向检查(逆着当前方向走,最多查4步)
            for (int i = 1; i < 5; i++) {
                int nx = x - dx * i;
                int ny = y - dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //3.每个方向检查完后,立即判断是否连成5颗
            if (sameCount >= 5) {
                return true;
            }
        }
        //所有方向都没有到5颗,则没有获胜
        return false;
    }
}

六、功能按钮的添加

上面实现了基本的五子棋基本功能,在上面所述的基础上可以进一步完善五子棋游戏的功能。我们可以知道五子棋游戏需要3个关键按钮:开始游戏重新开始进行悔棋

1.把按钮“放”到界面上

要让按钮显示在屏幕上,需要先给它们在窗体上安排位置,这就需要用到 Java Swing 的布局管理器。这里需要用到两种布局管理器(具体运用可参考【Java编程入门:Java Swing简易画图工具的优化指南(1)和(2)】

  • BorderLayout(边框布局):把整个游戏窗口分为“东、西、南、北、中”五个部分,我们把“棋盘”放在中间(CENTER),“按钮面板”放在右边(EAST)。
  • FlowLayout(流式布局):让按钮在面板里居中排列,还可以设置按钮之间的间距,使按钮与按钮之间看起来更整齐。

核心代码

//在GameUI的initUI中添加

//1.创建按钮面板
    JPanel buttonPanel = new JPanel();
    //将面板改为橙色
    buttonPanel.setBackground(Color.ORANGE);
    //按钮面板里的内容垂直排列
    buttonPanel.setLayout(new BoxLayout(buttonPanel,BoxLayout.Y_AXIS));
    buttonPanel.setPreferredSize(new Dimension(100,0));

//2.创建一个子面板专门放置按钮,保持按钮原有居中排列样式
    JPanel btnSubPanel = new JPanel();
    //将面板统一颜色
    btnSubPanel.setBackground(Color.ORANGE);
    //居中排列,上下间距为10
    btnSubPanel.setLayout(new FlowLayout(FlowLayout.CENTER,5,10));
    btnSubPanel.setPreferredSize(new Dimension(100,50));

//3.创建三个按钮,设置文字和大小
    //“开始游戏”按钮
    JButton startBtn = new JButton("开始游戏");
    startBtn.setPreferredSize(new Dimension(90,30));
    //“进行悔棋”按钮
    JButton undoBtn = new JButton("进行悔棋");
    undoBtn.setPreferredSize(new Dimension(90,30));
    //“重新开始”按钮
    JButton restartBtn = new JButton("重新开始");
    restartBtn.setPreferredSize(new Dimension(90,30));

//4.把按钮放置在子面板,再将子面板放置在右边的大面板里
    btnSubPanel.add(startBtn);
    btnSubPanel.add(restartBtn);
    btnSubPanel.add(undoBtn); 
    //将按键子面板和计时框添加到总按钮面板上(垂直排列)
    buttonPanel.add(btnSubPanel);
    buttonPanel.add(Box.createHorizontalStrut(1));//添加垂直间距,让按钮与计时框之间有空隙    

//5.将按钮面板放置在主窗体上
    jf.add(buttonPanel,BorderLayout.EAST);

2.给按钮添加动作监听器

根据上面的操作可以使按钮正常显示出来,但要使其达到我们的目的,就需要添加动作监听器,使用户点击按钮之后就执行对应的操作。

Java中用 addActionListener 实现监听,点击时触发 actionPerformed 方法里的代码。

(1)“开始游戏”按钮

点击“开始游戏”之后,可以实现的目的:一是允许玩家进行游戏,二是启动计时器(后面进行补充)。

//在GameUI的jf.add(buttonPanel,BorderLayout.EAST);后面补充添加
 
//开始按钮
      startBtn.addActionListener(new ActionListener() {
        @Override
           public void actionPerformed(ActionEvent e) {
               //1.告诉程序可以开始运行(listener是控制下棋逻辑的对象)
               listener.setCanPlay(true);
               //2.如果计时器没有启动,就对其进行启动(避免重复计时)
               if (!timeUpdateTimer.isRunning()){
                   timeUpdateTimer.start();
               }
           }
       });

重要知识点:

setCanPlay(true) 就像按钮的开关,如果没点这个按钮就点棋盘的话,程序无法正常运行,会提示用户点击开始游戏按钮之后才能开始游戏(后面会对其进行补充)。

(2)“进行悔棋”按钮

点击“进行悔棋”按钮之后,可以实现的目的:判断有没有棋子可以悔、确认用户是否要进行悔棋、以及清空最后一步的记录并重绘棋盘。

//悔棋按钮事件
      undoBtn.addActionListener(new ActionListener() {
         @Override
          public void actionPerformed(ActionEvent e) {
              //调用listener里的悔棋方法
              listener.undoLastStep(); 
              //重新绘制棋盘,让撤回的棋子清空
              chessPanel.repaint();
           }
       });

这里要重点看 undoLastStep() 方法里的代码(在 GameListener 类中)

//在 GameListener的public void mouseClicked(MouseEvent e)后面进行补充
   
//悔棋功能
    public void undoLastStep() {
        //1.检查是否能悔棋,如果还没有下过棋,就弹出提示“无棋可悔”
        if (count <= 0) {
            //如果已经没有棋可以悔,弹出提示
            JOptionPane.showMessageDialog(null, "没有可悔的棋了哦~");
            return;
        }

        //2.显示悔棋确认弹窗,弹出确认框,让用户进行确认(避免误选)
        int choice = JOptionPane.showConfirmDialog(null, "是否悔棋?", "确认悔棋", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        //用户选择"否"时,就不进行悔棋
        if (choice == JOptionPane.NO_OPTION) {
            return;
        }
        //3.减少总步数:将最后一步的记录清空
        //棋子计数器减1
        count--;
        //步数减1
        stepCount--;
        //获取最后一步棋的坐标
        int lastX = chessArray[count][0];//获取x坐标
        int lastY = chessArray[count][1];//获取y坐标

        //清空最后一步记录
        board[lastX][lastY] = 0;
        chessArray[count][0] = 0;
        chessArray[count][1] = 0;
        chessArray[count][2] = 0;

        //4.提示悔棋成功,指明下一个该是谁下
        String nextPlayer = (count % 2 == 0) ? "黑方" : "白方";
        JOptionPane.showMessageDialog(null, "悔棋成功,请" + nextPlayer + "继续下棋");

        //确保游戏状态为未结束
        isGameOver = false;
        //重绘棋盘
        panel.repaint();
    }

重要知识点

  • JOptionPane 类 :使用此类可以弹出提示框、确认框,不用自己写弹窗,直接调用现成的方法。这样使代码更为简洁与方便。
  • %:此符号代表取余的意思,可以用于判断多个项目的选择。如:当面临悔棋后需判断该下黑棋还是白棋时可以使用 count %2 (count是棋子的总数,偶数时下黑棋,奇数时下白棋)。
(3)“重新开始”按钮

点击“重新开始”按钮之后,可以实现的目的:把所有数据全部恢复到初始值,在点击“开始游戏”按钮之后才能继续游戏。

//重新开始按钮
        restartBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //调用listener里的重置方法,清空棋盘和所有步数
                listener.restartGame();
                //重新绘制棋盘,去掉所有棋子
                chessPanel.repaint(); 
                //重置计时框显示为初始状态
                timeLabel.setText("当前用时:0分0秒");
                //停止计时更新定时器
                timeUpdateTimer.stop();
            }
        });

restartGame() 方法的核心逻辑(GameListener 类中)

//重新开始功能
    public void restartGame() {
        //1.清空棋盘:把棋盘数组里的所有位置设为0
        for (int i = 0; i < LINE; i++) {
            for (int j = 0; j < LINE; j++) {
                board[i][j] = 0;
            }
        }
        //2.清空棋子记录
        count = 0;
        //清空步数
        stepCount = 0;
        //3.游戏结束判断
        isGameOver = false;
        //重新开始时设置为不可下棋状态
        canPlay = false;
        //4.停止计时器
        timer.stop();
        panel.repaint();
    }

七、计时器和计步器的实现

在游戏过程中,用户需要知道对局时长多久以及下了多少步棋子,进而思考下一步棋应该怎么下。所以计时器与计步器的实现在极大程度上能够提升玩家的游戏体验。我们要实现的两个功能:实时显示用时记录总步数。

1.计时器

计时器的核心是 Timer 类,它能做到每隔1秒,就让总时长加1秒,再更新界面上的时间。

(1)创建计时器和时间显示框

首先,需要在界面上创建一个时间显示框( JLabel ),再创建一个 Timer ,让其能够实时更新时间

//在GameUI类的btnSubPanel.add(undoBtn);添加

   //1.创建时间显示框
   timeLabel = new JLabel("当前用时:0分0秒");
   //设置文字居中
   timeLabel.setHorizontalAlignment(SwingConstants.CENTER);
   //设置背景颜色
   timeLabel.setBackground(Color.WHITE);
   //让计时框支持显示颜色
   timeLabel.setOpaque(true);
   //垂直方向居中对齐
   timeLabel.setAlignmentX(Component.CENTER_ALIGNMENT);


   //2.创建计时器:1000毫秒触发1次 
     //1秒触发一次,实现实时效果
        Timer timeUpdateTimer = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int totalSec = listener.totalSeconds;
                //计算分钟和秒
                int minute = totalSec / 60;
                int second = totalSec % 60;
                //更新计时框文本,显示实时数据
                timeLabel.setText("当前用时:" + minute + "分" + second + "秒");
            }
        });

   //3.把时间显示框放到右边的按钮面板上
   buttonPanel.add(timeLabel);
(2)控制计时器的启动与停止
  • 点击“开始游戏”按钮:计时开始,且不重复计时
  • 点击“重新开始”按钮:计时暂停,并将时间显示框中文字恢复为“0分0秒”
  • 有一方获胜:计时暂停,显示获胜总时长
(3)totalSeconds 变量

totalSeconds 是 GameListener 里的变量,用来记录总秒数。

public class GameListener extends MouseAdapter{
    //棋子数据
    public int X0 = 25, Y0 = 25, SIZE = 25, LINE = 15, CHESS = 15;
    //保存传递过来的画笔对象
    public Graphics g;
    //保存当前棋子在棋盘上的交点值
    public int chessX, chessY;
    //棋子计数器
    public int count = 0;
    //用二维数组保留所有棋子信息
    public int[][] chessArray = new int[LINE * LINE][3];
    //用二维数组记录落子情况:0=空,1=黑棋,2=白棋
    public int[][] board = new int[LINE][LINE];
    //关联棋盘画板
    private GamePanel panel;
    //标记游戏是否结束
    private boolean isGameOver = false;
    //控制是否可以下棋
    private boolean canPlay = false;
    //计时器(每秒走1次)
    private Timer timer;
    //总用时(单位:秒)
    public int totalSeconds = 0;
    //总步数(每下一颗棋+1)
    public int stepCount = 0;

    //构造方法初始化属性
    public GameListener(Graphics g, GamePanel panel) {
        this.g = g;
        this.panel = panel;
        //初始计时器(1000毫秒 = 1秒,每秒触发1次事件)
        timer = new Timer(1000,e -> totalSeconds++);
    }

    //设置是否可以下棋的方法
    public void setCanPlay(boolean canPlay){
        this.canPlay = canPlay;
        //开始游戏是启动计时器(避免重复多次计时)
        if(canPlay && !timer.isRunning()){
            timer.start();
        }
    }

    //点击鼠标落子
    public void mouseClicked(MouseEvent e) {
        //未点击开始按钮,则不处理点击
        if (!canPlay) {
            JOptionPane.showMessageDialog(panel,"点击【开始游戏】开始对局!");
            return;
        }

        //如果游戏已结束,则不处理点击
        if (isGameOver){
            JOptionPane.showMessageDialog(panel,"对局已结束,请重新开始哦!");
            return;
        }

        System.out.println("点击!!");
        int x = e.getX();
        int y = e.getY();

        //计算棋子X轴交点值
        if ((x - X0) % SIZE > SIZE / 2) {  //靠右
            chessX = (x - X0) / SIZE + 1;
        } else {  //靠左
            chessX = (x - X0) / SIZE;
        }

        //计算棋子Y轴交点值
        if ((y - Y0) % SIZE > SIZE / 2) {  //靠下
            chessY = (y - Y0) / SIZE + 1;
        } else {  //靠上
            chessY = (y - Y0) / SIZE;
        }

        //限制在棋盘范围内
        chessX = Math.min(LINE - 1, Math.max(0, chessX));
        chessY = Math.min(LINE - 1, Math.max(0, chessY));

        //判断当前位置是否已有棋子
        if (board[chessX][chessY] != 0) {
            return;//已有棋子则不处理
        }

        //记录棋子颜色(1=黑棋,2=白棋)
        int colorType = count % 2;
        int boardColor = colorType + 1;
        board[chessX][chessY] = boardColor;

        //保存棋子信息
        chessArray[count][0] = chessX;
        chessArray[count][1] = chessY;
        chessArray[count][2] = count % 2;
        count++;
        stepCount++;

        //判断是否赢了
        if (checkWin(chessX, chessY, boardColor)) {
            String winner = (colorType == 0) ? "黑棋" : "白棋";
            //获胜后停止计时
            timer.stop();
            //获胜后不让继续下棋
            canPlay = false;
            //计算用时
            int minute = totalSeconds / 60 ;
            int second = totalSeconds % 60 ;
            String timeStr = minute + "分" + second + "秒";
            //弹出窗口显示胜负、步数、用时
            JOptionPane.showMessageDialog(panel,(winner + "五子连珠,取得胜利!" + "总共走了:"+ stepCount + "步!" +"总用时" + timeStr));
            isGameOver = true;
        }

         //触发重绘
        panel.repaint();
    }

    //悔棋功能
    public void undoLastStep() {
        //检查是否能悔棋
        if (count <= 0) {
            //如果已经没有棋可以悔,弹出提示
            JOptionPane.showMessageDialog(null, "没有可悔的棋了哦~");
            return;
        }

        //显示悔棋确认弹窗
        int choice = JOptionPane.showConfirmDialog(null, "是否悔棋?", "确认悔棋", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        //用户选择"否"时才返回
        if (choice == JOptionPane.NO_OPTION) {
            return;
        }
        //减少总步数
        count--;
        stepCount--;
        //获取最后一步棋的坐标
        int lastX = chessArray[count][0];//获取x坐标
        int lastY = chessArray[count][1];//获取y坐标

        //清空最后一步记录
        board[lastX][lastY] = 0;
        chessArray[count][0] = 0;
        chessArray[count][1] = 0;
        chessArray[count][2] = 0;

        //提示悔棋成功,指明下一方
        String nextPlayer = (count % 2 == 0) ? "黑方" : "白方";
        JOptionPane.showMessageDialog(null, "悔棋成功,请" + nextPlayer + "继续下棋");

        //确保游戏状态为未结束
        isGameOver = false;
        //重绘棋盘
        panel.repaint();
    }

    //重新开始功能
    public void restartGame() {
        //清空棋盘
        for (int i = 0; i < LINE; i++) {
            for (int j = 0; j < LINE; j++) {
                board[i][j] = 0;
            }
        }
        //清空棋子记录
        count = 0;
        //步数重置
        stepCount = 0;
        //游戏结束判断
        isGameOver = false;
        //重新开始时设置为不可下棋状态
        canPlay = false;
        //停止计时器
        timer.stop();
        panel.repaint();
    }

    //判断输赢
    private boolean checkWin(int x, int y, int color) {
        //方向数组:水平,竖直,两个对角线
        int[][] dirs = {
                {1, 0}, //向右检查
                {0, 1}, //向下检查
                {1, 1}, //向右下检查
                {1, -1} //向左下检查
        };

        //遍历每个检查方向
        for (int[] dir : dirs) {
            int sameCount = 1;//当前位置已经有1颗目标颜色棋子,初始为1
            int dx = dir[0];//x方向增量
            int dy = dir[1];//y方向增量

            //1.正向检查(顺着当前方向走,最多查4步,因为起始1颗共五颗)
            for (int i = 1; i < 5; i++) {
                int nx = x + dx * i;
                int ny = y + dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //2.反向检查(逆着当前方向走,最多查4步)
            for (int i = 1; i < 5; i++) {
                int nx = x - dx * i;
                int ny = y - dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //3.每个方向检查完后,立即判断是否连成5颗
            if (sameCount >= 5) {
                return true;
            }
        }
        return false;
    }
}
(4)重要知识点

e -> totalSeconds++ 是Java中的 Lambda 表达式。表达式结构:(参数)-> {表达式或代码块}。此表达式的意思是“每次计时器触发时,就执行 totalSeconds 加1”,这样比写完整方法更为简洁。

2.计步器

计步器的原理在于每成功下出一颗棋子,就让步数加1,最终在获胜时显示出来。

核心代码
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class GameListener extends MouseAdapter{
    //棋子数据
    public int X0 = 25, Y0 = 25, SIZE = 25, LINE = 15, CHESS = 15;
    //保存传递过来的画笔对象
    public Graphics g;
    //保存当前棋子在棋盘上的交点值
    public int chessX, chessY;
    //棋子计数器
    public int count = 0;
    //用二维数组保留所有棋子信息
    public int[][] chessArray = new int[LINE * LINE][3];
    //用二维数组记录落子情况:0=空,1=黑棋,2=白棋
    public int[][] board = new int[LINE][LINE];
    //关联棋盘画板
    private GamePanel panel;
    //标记游戏是否结束
    private boolean isGameOver = false;
    //控制是否可以下棋
    private boolean canPlay = false;
    //计时器(每秒走1次)
    private Timer timer;
    //总用时(单位:秒)
    public int totalSeconds = 0;
    //总步数(每下一颗棋+1)
    public int stepCount = 0;

    //构造方法初始化属性
    public GameListener(Graphics g, GamePanel panel) {
        this.g = g;
        this.panel = panel;
        //初始计时器(1000毫秒 = 1秒,每秒触发1次事件)
        timer = new Timer(1000,e -> totalSeconds++);
    }

    //设置是否可以下棋的方法
    public void setCanPlay(boolean canPlay){
        this.canPlay = canPlay;
        //开始游戏是启动计时器(避免重复多次计时)
        if(canPlay && !timer.isRunning()){
            timer.start();
        }
    }

    //点击鼠标落子
    public void mouseClicked(MouseEvent e) {
        //未点击开始按钮,则不处理点击
        if (!canPlay) {
            JOptionPane.showMessageDialog(panel,"点击【开始游戏】开始对局!");
            return;
        }

        //如果游戏已结束,则不处理点击
        if (isGameOver){
            JOptionPane.showMessageDialog(panel,"对局已结束,请重新开始哦!");
            return;
        }

        System.out.println("点击!!");
        int x = e.getX();
        int y = e.getY();

        //计算棋子X轴交点值
        if ((x - X0) % SIZE > SIZE / 2) {  //靠右
            chessX = (x - X0) / SIZE + 1;
        } else {  //靠左
            chessX = (x - X0) / SIZE;
        }

        //计算棋子Y轴交点值
        if ((y - Y0) % SIZE > SIZE / 2) {  //靠下
            chessY = (y - Y0) / SIZE + 1;
        } else {  //靠上
            chessY = (y - Y0) / SIZE;
        }

        //限制在棋盘范围内
        chessX = Math.min(LINE - 1, Math.max(0, chessX));
        chessY = Math.min(LINE - 1, Math.max(0, chessY));

        //判断当前位置是否已有棋子
        if (board[chessX][chessY] != 0) {
            return;//已有棋子则不处理
        }

        //记录棋子颜色(1=黑棋,2=白棋)
        int colorType = count % 2;
        int boardColor = colorType + 1;
        board[chessX][chessY] = boardColor;

        //保存棋子信息
        chessArray[count][0] = chessX;
        chessArray[count][1] = chessY;
        chessArray[count][2] = count % 2;
        count++;
        stepCount++;

        //判断是否赢了
        if (checkWin(chessX, chessY, boardColor)) {
            String winner = (colorType == 0) ? "黑棋" : "白棋";
            //获胜后停止计时
            timer.stop();
            //获胜后不让继续下棋
            canPlay = false;
            //计算用时
            int minute = totalSeconds / 60 ;
            int second = totalSeconds % 60 ;
            String timeStr = minute + "分" + second + "秒";
            //弹出窗口显示胜负、步数、用时
            JOptionPane.showMessageDialog(panel,(winner + "五子连珠,取得胜利!" + "总共走了:"+ stepCount + "步!" +"总用时" + timeStr));
            isGameOver = true;
        }

         //触发重绘
        panel.repaint();
    }

    //悔棋功能
    public void undoLastStep() {
        //检查是否能悔棋
        if (count <= 0) {
            //如果已经没有棋可以悔,弹出提示
            JOptionPane.showMessageDialog(null, "没有可悔的棋了哦~");
            return;
        }

        //显示悔棋确认弹窗
        int choice = JOptionPane.showConfirmDialog(null, "是否悔棋?", "确认悔棋", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        //用户选择"否"时才返回
        if (choice == JOptionPane.NO_OPTION) {
            return;
        }
        //减少总步数
        count--;
        stepCount--;
        //获取最后一步棋的坐标
        int lastX = chessArray[count][0];//获取x坐标
        int lastY = chessArray[count][1];//获取y坐标

        //清空最后一步记录
        board[lastX][lastY] = 0;
        chessArray[count][0] = 0;
        chessArray[count][1] = 0;
        chessArray[count][2] = 0;

        //提示悔棋成功,指明下一方
        String nextPlayer = (count % 2 == 0) ? "黑方" : "白方";
        JOptionPane.showMessageDialog(null, "悔棋成功,请" + nextPlayer + "继续下棋");

        //确保游戏状态为未结束
        isGameOver = false;
        //重绘棋盘
        panel.repaint();
    }

    //重新开始功能
    public void restartGame() {
        //清空棋盘
        for (int i = 0; i < LINE; i++) {
            for (int j = 0; j < LINE; j++) {
                board[i][j] = 0;
            }
        }
        //清空棋子记录
        count = 0;
        //步数重置
        stepCount = 0;
        //游戏结束判断
        isGameOver = false;
        //重新开始时设置为不可下棋状态
        canPlay = false;
        //停止计时器
        timer.stop();
        panel.repaint();
    }

    //判断输赢
    private boolean checkWin(int x, int y, int color) {
        //方向数组:水平,竖直,两个对角线
        int[][] dirs = {
                {1, 0}, //向右检查
                {0, 1}, //向下检查
                {1, 1}, //向右下检查
                {1, -1} //向左下检查
        };

        //遍历每个检查方向
        for (int[] dir : dirs) {
            int sameCount = 1;//当前位置已经有1颗目标颜色棋子,初始为1
            int dx = dir[0];//x方向增量
            int dy = dir[1];//y方向增量

            //1.正向检查(顺着当前方向走,最多查4步,因为起始1颗共五颗)
            for (int i = 1; i < 5; i++) {
                int nx = x + dx * i;
                int ny = y + dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //2.反向检查(逆着当前方向走,最多查4步)
            for (int i = 1; i < 5; i++) {
                int nx = x - dx * i;
                int ny = y - dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //3.每个方向检查完后,立即判断是否连成5颗
            if (sameCount >= 5) {
                return true;
            }
        }
        return false;
    }
}

八、完整代码展示

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class GameUI {
    //计时框组件,用于显示实时时间
    private JLabel timeLabel;
    //保存GameListener 对象,用于访问totalSeconds
    private jh0826.GameListener listener;

    //显示游戏界面
    public void initUI() {
        //JFrame 默认是边框布局
        JFrame jf = new JFrame();
        jf.setSize(500, 440);
        jf.setTitle("五子棋游戏");
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setLocationRelativeTo(null);
        jf.setLayout(new BorderLayout());

        //创建棋盘面板
        GamePanel chessPanel = new GamePanel();
        chessPanel.setBackground(Color.ORANGE);
        chessPanel.setPreferredSize(new Dimension(425,425));
        jf.add(chessPanel,BorderLayout.CENTER);

        //创建按钮面板
        JPanel buttonPanel = new JPanel();
        buttonPanel.setBackground(Color.ORANGE);
        buttonPanel.setLayout(new BoxLayout(buttonPanel,BoxLayout.Y_AXIS));
        buttonPanel.setPreferredSize(new Dimension(100,0));

        //创建一个子面板专门放按钮,保持按钮原有居中排列样式
        JPanel btnSubPanel = new JPanel();
        btnSubPanel.setBackground(Color.ORANGE);
        btnSubPanel.setLayout(new FlowLayout(FlowLayout.CENTER,5,10));
        btnSubPanel.setPreferredSize(new Dimension(100,50));

        //开始按钮
        JButton startBtn = new JButton("开始游戏");
        startBtn.setPreferredSize(new Dimension(90,30));
        //悔棋按钮
        JButton undoBtn = new JButton("进行悔棋");
        undoBtn.setPreferredSize(new Dimension(90,30));
        //重新开始按钮
        JButton restartBtn = new JButton("重新开始");
        restartBtn.setPreferredSize(new Dimension(90,30));

        //按钮添加到按钮子面板
        btnSubPanel.add(startBtn);
        btnSubPanel.add(restartBtn);
        btnSubPanel.add(undoBtn);

        //计时框
        //设置初始显示文本
        timeLabel = new JLabel("当前用时:0分0秒");
        //设置文字居中
        timeLabel.setHorizontalAlignment(SwingConstants.CENTER);
        //设置背景颜色
        timeLabel.setBackground(Color.WHITE);
        //让计时框支持显示颜色
        timeLabel.setOpaque(true);
        //垂直方向居中对齐
        timeLabel.setAlignmentX(Component.CENTER_ALIGNMENT);

        //将按键子面板和计时框添加到总按钮面板上(垂直排列)
        buttonPanel.add(btnSubPanel);
        buttonPanel.add(Box.createHorizontalStrut(1));//添加垂直间距,让按钮与计时框之间有空隙
        buttonPanel.add(timeLabel);

        //按钮面板添加到主窗体
        jf.add(buttonPanel,BorderLayout.EAST);

        //获取画笔对象
        Graphics g = chessPanel.getGraphics();
        this.listener = new GameListener(g,chessPanel);
        //GameListener listener = new GameListener(g,chessPanel);
        chessPanel.listener = listener;
        chessPanel.addMouseListener(this.listener);

        //实时更新定时器
        //1秒触发一次,实现实时效果
        Timer timeUpdateTimer = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int totalSec = listener.totalSeconds;
                //计算分钟和秒
                int minute = totalSec / 60;
                int second = totalSec % 60;
                //更新计时框文本,显示实时数据
                timeLabel.setText("当前用时:" + minute + "分" + second + "秒");
            }
        });

        //开始按钮
        startBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                listener.setCanPlay(true);
                //启动计时更新定时器
                if (!timeUpdateTimer.isRunning()){
                    timeUpdateTimer.start();
                }
            }
        });

        //悔棋按钮事件
        undoBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                listener.undoLastStep();
                chessPanel.repaint();
            }
        });

        //重新开始按钮
        restartBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                listener.restartGame();
                chessPanel.repaint();
                //重置计时框显示为初始状态
                timeLabel.setText("当前用时:0分0秒");
                //停止计时更新定时器
                timeUpdateTimer.stop();
            }
        });

        //设置可见
        jf.setVisible(true);
    }

    public static void main(String[] args) {
        GameUI ui = new GameUI();
        ui.initUI();
    }
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;


public class GameListener extends MouseAdapter{
    //棋子数据
    public int X0 = 25, Y0 = 25, SIZE = 25, LINE = 15, CHESS = 15;
    //保存传递过来的画笔对象
    public Graphics g;
    //保存当前棋子在棋盘上的交点值
    public int chessX, chessY;
    //棋子计数器
    public int count = 0;
    //用二维数组保留所有棋子信息
    public int[][] chessArray = new int[LINE * LINE][3];
    //用二维数组记录落子情况:0=空,1=黑棋,2=白棋
    public int[][] board = new int[LINE][LINE];
    //关联棋盘画板
    private GamePanel panel;
    //标记游戏是否结束
    private boolean isGameOver = false;
    //控制是否可以下棋
    private boolean canPlay = false;
    //计时器(每秒走1次)
    private Timer timer;
    //总用时(单位:秒)
    public int totalSeconds = 0;
    //总步数(每下一颗棋+1)
    public int stepCount = 0;

    //构造方法初始化属性
    public GameListener(Graphics g, GamePanel panel) {
        this.g = g;
        this.panel = panel;
        //初始计时器(1000毫秒 = 1秒,每秒触发1次事件)
        timer = new Timer(1000,e -> totalSeconds++);
    }

    //设置是否可以下棋的方法
    public void setCanPlay(boolean canPlay){
        this.canPlay = canPlay;
        //开始游戏是启动计时器(避免重复多次计时)
        if(canPlay && !timer.isRunning()){
            timer.start();
        }
    }

    //点击鼠标落子
    public void mouseClicked(MouseEvent e) {
        //未点击开始按钮,则不处理点击
        if (!canPlay) {
            JOptionPane.showMessageDialog(panel,"点击【开始游戏】开始对局!");
            return;
        }

        //如果游戏已结束,则不处理点击
        if (isGameOver){
            JOptionPane.showMessageDialog(panel,"对局已结束,请重新开始哦!");
            return;
        }

        System.out.println("点击!!");
        int x = e.getX();
        int y = e.getY();

        //计算棋子X轴交点值
        if ((x - X0) % SIZE > SIZE / 2) {  //靠右
            chessX = (x - X0) / SIZE + 1;
        } else {  //靠左
            chessX = (x - X0) / SIZE;
        }

        //计算棋子Y轴交点值
        if ((y - Y0) % SIZE > SIZE / 2) {  //靠下
            chessY = (y - Y0) / SIZE + 1;
        } else {  //靠上
            chessY = (y - Y0) / SIZE;
        }

        //限制在棋盘范围内
        chessX = Math.min(LINE - 1, Math.max(0, chessX));
        chessY = Math.min(LINE - 1, Math.max(0, chessY));

        //判断当前位置是否已有棋子
        if (board[chessX][chessY] != 0) {
            return;//已有棋子则不处理
        }

        //记录棋子颜色(1=黑棋,2=白棋)
        int colorType = count % 2;
        int boardColor = colorType + 1;
        board[chessX][chessY] = boardColor;

        //保存棋子信息
        chessArray[count][0] = chessX;
        chessArray[count][1] = chessY;
        chessArray[count][2] = count % 2;
        count++;
        stepCount++;

        //判断是否赢了
        if (checkWin(chessX, chessY, boardColor)) {
            String winner = (colorType == 0) ? "黑棋" : "白棋";
            //获胜后停止计时
            timer.stop();
            //获胜后不让继续下棋
            canPlay = false;
            //计算用时
            int minute = totalSeconds / 60 ;
            int second = totalSeconds % 60 ;
            String timeStr = minute + "分" + second + "秒";
            //弹出窗口显示胜负、步数、用时
            JOptionPane.showMessageDialog(panel,(winner + "五子连珠,取得胜利!" + "总共走了:"+ stepCount + "步!" +"总用时" + timeStr));
            isGameOver = true;
        }

         //触发重绘
        panel.repaint();
    }

    //悔棋功能
    public void undoLastStep() {
        //检查是否能悔棋
        if (count <= 0) {
            //如果已经没有棋可以悔,弹出提示
            JOptionPane.showMessageDialog(null, "没有可悔的棋了哦~");
            return;
        }

        //显示悔棋确认弹窗
        int choice = JOptionPane.showConfirmDialog(null, "是否悔棋?", "确认悔棋", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        //用户选择"否"时才返回
        if (choice == JOptionPane.NO_OPTION) {
            return;
        }
        //减少总步数
        count--;
        stepCount--;
        //获取最后一步棋的坐标
        int lastX = chessArray[count][0];//获取x坐标
        int lastY = chessArray[count][1];//获取y坐标

        //清空最后一步记录
        board[lastX][lastY] = 0;
        chessArray[count][0] = 0;
        chessArray[count][1] = 0;
        chessArray[count][2] = 0;

        //提示悔棋成功,指明下一方
        String nextPlayer = (count % 2 == 0) ? "黑方" : "白方";
        JOptionPane.showMessageDialog(null, "悔棋成功,请" + nextPlayer + "继续下棋");

        //确保游戏状态为未结束
        isGameOver = false;
        //重绘棋盘
        panel.repaint();
    }

    //重新开始功能
    public void restartGame() {
        //清空棋盘
        for (int i = 0; i < LINE; i++) {
            for (int j = 0; j < LINE; j++) {
                board[i][j] = 0;
            }
        }
        //清空棋子记录
        count = 0;
        //步数重置
        stepCount = 0;
        //游戏结束判断
        isGameOver = false;
        //重新开始时设置为不可下棋状态
        canPlay = false;
        //停止计时器
        timer.stop();
        panel.repaint();
    }

    //判断输赢
    private boolean checkWin(int x, int y, int color) {
        //方向数组:水平,竖直,两个对角线
        int[][] dirs = {
                {1, 0}, //向右检查
                {0, 1}, //向下检查
                {1, 1}, //向右下检查
                {1, -1} //向左下检查
        };

        //遍历每个检查方向
        for (int[] dir : dirs) {
            int sameCount = 1;//当前位置已经有1颗目标颜色棋子,初始为1
            int dx = dir[0];//x方向增量
            int dy = dir[1];//y方向增量

            //1.正向检查(顺着当前方向走,最多查4步,因为起始1颗共五颗)
            for (int i = 1; i < 5; i++) {
                int nx = x + dx * i;
                int ny = y + dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //2.反向检查(逆着当前方向走,最多查4步)
            for (int i = 1; i < 5; i++) {
                int nx = x - dx * i;
                int ny = y - dy * i;
                if (nx < 0 || nx >= LINE || ny < 0 || ny >= LINE) {
                    break;
                }
                if (board[nx][ny] != color) {
                    break;
                }
                sameCount++;
            }

            //3.每个方向检查完后,立即判断是否连成5颗
            if (sameCount >= 5) {
                return true;
            }
        }
        return false;
    }
}
import javax.swing.*;
import java.awt.*;

public class GamePanel extends JPanel {
    public int X0=25 , Y0=25 , SIZE=25 , LINE=15;
    public GameListener listener;

    //重写paint方法
    public void paint(Graphics g ){
        //保留绘制组件的功能
        super.paint(g);
        //绘制棋盘
        for (int i=0;i<LINE;i++){
            g.setColor(Color.black);//黑色棋盘线
            g.drawLine(X0,Y0+i*SIZE,(LINE-1)*SIZE+X0,Y0+i*SIZE);
            g.drawLine(X0+i*SIZE,Y0,X0+i*SIZE,(LINE-1)*SIZE+Y0);
        }
        //绘制所有棋子(实现交替重绘)
        if (listener!=null){
            int radius = listener.CHESS/2;
            for (int i=0;i<listener.count;i++){
                int x = listener.chessArray[i][0];
                int y = listener.chessArray[i][1];
                int color = listener.chessArray[i][2];
                //计算棋子绘制坐标
                int drawX = X0 + x*SIZE - radius;
                int drawY = Y0 + y*SIZE - radius;
                if(color == 0){  //黑棋
                    //g.setColor(Color.BLACK);
                    GradientPaint black3d = new GradientPaint(drawX,drawY,new Color(80,80,80),drawX + listener.CHESS,drawY + listener.CHESS,new Color(0,0,0));
                    ((Graphics2D)g).setPaint(black3d);
                }else{ //白棋
                    //g.setColor(Color.WHITE);
                    GradientPaint white3d = new GradientPaint(drawX,drawY,new Color(255,255,255),drawX + listener.CHESS, drawY + listener.CHESS,new Color(220,220,220));
                    ((Graphics2D)g).setPaint(white3d);
                }
                //g.fillOval(drawX,drawY,listener.CHESS,listener.CHESS);
                ((Graphics2D)g).fillOval(drawX,drawY,listener.CHESS,listener.CHESS);
                //白棋加黑框
                if (color == 1){
                    g.setColor(Color.BLACK);
                    g.drawOval(drawX,drawY,listener.CHESS,listener.CHESS);
                }
            }
        }
    }
}

九、运行效果展示

十、补充拓展建议

1.将登录界面和五子棋游戏结合起来

2.将五子棋棋子进行优化使其更有立体感

3.添加人机对战

4.游戏窗体进一步装饰使其更为美观

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值