简易五子棋

简介

使用Java实现简易五子棋

规则介绍

游戏使用一个标准的15×15方格的棋盘,双方分别用黑白两种颜色的棋子进行对战。黑子先行,双方轮流在空棋盘的交叉点上落子,每人一次只能落一子。游戏的目标是让自己的五颗棋子连成一线,这条线可以是横线、竖线、对角线或斜线。如果一方的五颗棋子按照上述规则连成一线,这一方就获胜并结束游戏。

(1)对局双方各执一色棋子。

(2)空棋盘开局。

(3)黑先、白后,交替下子,每次只能下一子。

(4)棋子下在棋盘的空白点上,棋子下定后不得移动或拿走。

(5)黑方的第一枚棋子必须下在天元点上,即中心交叉点"

功能设计

  • 重新开始
    用户操作【重新开始】功能,弹窗询问是否确定重新开始,如果是则将所有数据重新初始化,否则什么也不做。
  • 悔棋
    用户操作【悔棋】功能,恢复上一步的操作,如果已无上一步操作或者游戏结束,不允许悔棋并弹窗提示。
  • 退出游戏
    用户操作【退出游戏】功能,关闭该应用程序。
  • 帮助
    菜单栏添加玩法提示,引导用户使用。
  • 坐标校准
    由于棋子需下在网格线上,交叉点的坐标很小,故鼠标很难精准点击在符合的坐标上,那么就需要对用户点击的坐标进行校准,将其坐标校准为最贴近的符合坐标。
  • 坐标可行性及输赢判断
    用户落子时,判断该坐标是否可用(是否已有棋子),如果不可行,弹窗提示,否则,判断输赢并且刷新页面绘制棋子。如果某一方获胜,提示游戏结束,禁止继续落下棋子。
  • 输赢判断算法
    以落下棋子坐标出发,向上下、左右、左上右下、右上左下四个方向延伸,朝一个方向至多延伸五次,若有同色棋子,则计数器加一,最终判断计数器是否大于等于5,如果是则获得胜利。

实现

附上如下实现代码

java 复制代码
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Objects;
import java.util.Stack;

public class Gobang extends JPanel {

    boolean op = false; //true-white false black
    boolean win = false;

    static final int SCREEN_WIDTH = 700;
    static final int SCREEN_HEIGHT = 700;

    static final int UNIT_SIZE = 50;
    static final int GAME_UNITS = SCREEN_WIDTH / UNIT_SIZE;

    boolean[][] black;
    boolean[][] white;

    Graphics g;
    Point checkPoint;
    Stack<Point> opStack;
    MouseListener mouseListener;

    JMenuBar menuBar;

    Gobang() {
        this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
        this.setFocusable(true);
        this.addKeyListener(new MyKeyAdapter());
        init();
    }

    /**
     * 初始化网格
     */
    public void initCheckerboard() {
        if (Objects.isNull(g)) {
            return;
        }
        g.setColor(Color.BLACK);
        for (int i = 0; i < SCREEN_WIDTH; i += UNIT_SIZE) {
            g.drawLine(i, 0, i, SCREEN_HEIGHT);
        }
        for (int i = 0; i < SCREEN_HEIGHT; i += UNIT_SIZE) {
            g.drawLine(0, i, SCREEN_WIDTH, i);
        }
    }

    public void initMenu() {
        menuBar = new JMenuBar();
        JMenu menu = new JMenu("菜单");
        JMenuItem restart;
        (restart = new JMenuItem("重新开始")).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int res = JOptionPane.showConfirmDialog(null, "请确定要重新开始吗?", "", JOptionPane.OK_CANCEL_OPTION);
                if (res == 0) {
                    init();
                }
            }
        });
        menu.add(restart);

        JMenuItem regretChess;
        (regretChess = new JMenuItem("悔棋")).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                regretChess();
            }
        });
        menu.add(regretChess);


        JMenuItem exit;
        (exit = new JMenuItem("退出游戏")).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        menu.add(exit);
        menuBar.add(menu);

        JMenu helpMenu = new JMenu("帮助");
        JMenuItem playWay;
        (playWay = new JMenuItem("玩法")).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(null, "游戏使用一个标准的15×15方格的棋盘,双方分别用黑白两种颜色的棋子进行对战。\n" +
                        "黑子先行,双方轮流在空棋盘的交叉点上落子,每人一次只能落一子。\n" +
                        "游戏的目标是让自己的五颗棋子连成一线,这条线可以是横线、竖线、对角线或斜线。\n" +
                        "如果一方的五颗棋子按照上述规则连成一线,这一方就获胜并结束游戏。\n" +
                        "(1)对局双方各执一色棋子。\n" +
                        "(2)空棋盘开局。\n" +
                        "(3)黑先、白后,交替下子,每次只能下一子。\n" +
                        "(4)棋子下在棋盘的空白点上,棋子下定后不得移动或拿走。\n" +
                        "(5)黑方的第一枚棋子必须下在天元点上,即中心交叉点");
            }
        });
        helpMenu.add(playWay);

        JMenuItem about;
        (about = new JMenuItem("关于")).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(null, "无聊时候花一点时间写着玩的");
            }
        });
        helpMenu.add(about);

        menuBar.add(helpMenu);
    }

    /**
     * 游戏数据初始化
     */
    public void init() {
        initCheckerboard();
        initMenu();

        black = new boolean[GAME_UNITS + 1][GAME_UNITS + 1];
        white = new boolean[GAME_UNITS + 1][GAME_UNITS + 1];

        op = false;
        win = false;
        checkPoint = null;

        opStack = new Stack<>();

        super.addMouseListener(mouseListener = new MyMouseAdapter());
        repaint();
    }

    /**
     * 校准鼠标
     *
     * @param x
     * @return
     */
    public double calibration(double x) {
        if (x % UNIT_SIZE > UNIT_SIZE / 2) {
            x = ((int) x / UNIT_SIZE + 1) * UNIT_SIZE;
        } else {
            x = ((int) x / UNIT_SIZE) * UNIT_SIZE;
        }
        return x;
    }

    /**
     * 绘制棋子
     */
    public void drawChessPieces() {
        String tip = null;
        if (Objects.nonNull(checkPoint)) {
            //存记录
            int x = (int) checkPoint.getX();
            int y = (int) checkPoint.getY();
            if (op) {
                white[x / UNIT_SIZE][y / UNIT_SIZE] = true;
            } else {
                black[x / UNIT_SIZE][y / UNIT_SIZE] = true;
            }

            tip = judge();

            op = !op;
            checkPoint = null;
        }

        for (int i = 0; i < black.length; i++) {
            for (int j = 0; j < black.length; j++) {
                if (black[i][j]) {
                    g.setColor(Color.BLACK);
                    g.fillOval(i * UNIT_SIZE - UNIT_SIZE / 2, j * UNIT_SIZE - UNIT_SIZE / 2, UNIT_SIZE, UNIT_SIZE);
                }
            }
        }

        for (int i = 0; i < white.length; i++) {
            for (int j = 0; j < white.length; j++) {
                if (white[i][j]) {
                    g.setColor(Color.WHITE);
                    g.fillOval(i * UNIT_SIZE - UNIT_SIZE / 2, j * UNIT_SIZE - UNIT_SIZE / 2, UNIT_SIZE, UNIT_SIZE);
                }
            }
        }

        if (win && Objects.nonNull(tip)) {
            g.setFont(new Font("Ink Free", Font.BOLD, 40));
            g.setColor(Color.RED);
            g.drawString(tip, (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, SCREEN_HEIGHT / 2);
            super.removeMouseListener(mouseListener);
        }
    }

    public String judge() {
        String tip = op ? tip = "White Win!" : "Black Win!";
        boolean[][] opArr = op ? white : black;
        int x = (int) checkPoint.getX() / UNIT_SIZE;
        int y = (int) checkPoint.getY() / UNIT_SIZE;
        int tempX = x;
        int tempY = y;
        int count = 0;
        //判断横向
        while (x >= 0 && x > tempX - 5) {
            if (opArr[x][y]) {
                count++;
                x--;
                if (x >= 0) {
                    continue;
                }
            }
            x = tempX + 1;
            break;

        }

        while (x <= GAME_UNITS && x < tempX + 5) {
            if (opArr[x][y]) {
                count++;
                x++;
                continue;
            }
            break;
        }
        if (count >= 5) {
            win = true;
            return tip;
        }

        //判断纵向
        x = tempX;
        y = tempY;
        count = 0;
        while (x >= 0 && y >= 0 && x <= GAME_UNITS && y <= GAME_UNITS
                && y > tempY - 5) {

            if (opArr[x][y]) {
                count++;
                y--;
                if (y >= 0) {
                    continue;
                }
            }
            y = tempY + 1;
            break;

        }

        while (x >= 0 && y >= 0 && x <= GAME_UNITS && y <= GAME_UNITS
                && y < tempY + 5) {
            if (opArr[x][y]) {
                count++;
                y++;
                continue;
            }
            break;

        }
        if (count >= 5) {
            win = true;
            return tip;
        }

        //判断左斜向
        x = tempX;
        y = tempY;
        count = 0;
        while (x >= 0 && y >= 0 && x <= GAME_UNITS && y <= GAME_UNITS
                && y > tempY - 5 && x > tempX - 5) {

            if (opArr[x][y]) {
                count++;
                x--;
                y--;
                if (x >= 0 && y >= 0) {
                    continue;
                }
            }

            x = tempX + 1;
            y = tempY + 1;
            break;

        }

        while (x >= 0 && y >= 0 && x <= GAME_UNITS && y <= GAME_UNITS
                && y < tempY + 5 && x < tempY + 5) {
            if (opArr[x][y]) {
                count++;
                y++;
                x++;
                continue;
            }
            break;

        }
        if (count >= 5) {
            win = true;
            return tip;
        }

        //判断右斜向
        x = tempX;
        y = tempY;
        count = 0;
        while (x >= 0 && y >= 0 && x <= GAME_UNITS && y <= GAME_UNITS
                && y > tempY - 5 && x < tempX + 5) {
            if (opArr[x][y]) {
                count++;
                x++;
                y--;
                if (y >= 0 && x <= GAME_UNITS) {
                    continue;
                }
            }
            x = tempX - 1;
            y = tempY + 1;
            break;

        }

        while (x >= 0 && y >= 0 && x <= GAME_UNITS && y <= GAME_UNITS
                && y <= tempY + 5 && x >= tempX - 5) {
            if (opArr[x][y]) {
                count++;
                y++;
                x--;
                continue;
            }
            break;

        }
        if (count >= 5) {
            win = true;
            return tip;
        }

        return null;
    }

    public void regretChess() {
        if (win) {
            JOptionPane.showMessageDialog(null, "游戏结束无法悔棋!");
            return;
        }
        if (opStack.isEmpty()) {
            JOptionPane.showMessageDialog(null, "无效操作,已无上一步棋!");
            return;
        }
        Point point = opStack.pop();
        if (Objects.isNull(point)) {
            return;
        }
        if (op) {
            black[(int) point.getX() / UNIT_SIZE][(int) point.getY() / UNIT_SIZE] = false;
        } else {
            white[(int) point.getX() / UNIT_SIZE][(int) point.getY() / UNIT_SIZE] = false;
        }
        op = !op;
        repaint();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.g = g;

        initCheckerboard();
        drawChessPieces();
    }

    /**
     * 自定义鼠标适配器
     */
    public class MyMouseAdapter extends MouseAdapter {
        @Override
        public void mouseClicked(MouseEvent e) {
            super.mouseClicked(e);
            checkPoint = e.getPoint();
            System.out.println("点击坐标x:" + checkPoint.getX() + " y:" + checkPoint.getY());
            checkPoint.setLocation(calibration(checkPoint.getX()), calibration(checkPoint.getY()));
            System.out.println("校准坐标x:" + checkPoint.getX() + " y:" + checkPoint.getY());

            //去除无效点击
            if (black[(int) checkPoint.getX() / UNIT_SIZE][(int) checkPoint.getY() / UNIT_SIZE]
                    || white[(int) checkPoint.getX() / UNIT_SIZE][(int) checkPoint.getY() / UNIT_SIZE]) {
                JOptionPane.showMessageDialog(null, "无效操作,\n此处已有棋子!");
                return;
            }

            opStack.push(checkPoint);
            repaint();
        }
    }

    /**
     * 自定义按键适配器
     */
    public class MyKeyAdapter extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            int keyCode = e.getKeyCode();
            if (KeyEvent.VK_1 == keyCode) {
                System.out.println("重新开始");
                init();
            }
            if (KeyEvent.VK_2 == keyCode) {
                System.out.println("悔棋");
                regretChess();
            }
            if (KeyEvent.VK_ESCAPE == keyCode) {
                System.out.println("退出游戏");
                System.exit(0);
            }
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setTitle("五子棋");
        Gobang gobang = new Gobang();
        frame.setJMenuBar(gobang.menuBar);
        frame.add(gobang);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

}

效果展示

相关推荐
蚂蚁雅嘿26 分钟前
maven标签日常总结
java·maven
洋不写bug38 分钟前
二叉树(二) 常见基础操作解析|结点数、树高、查找结点、判断完全二叉树
java·开发语言·数据结构·完全二叉树·二叉树结点数·树高·查找结点
菠萝加点糖1 小时前
Maven 坐标系统说明
java·maven
hm宋1 小时前
Canal 升级 jar 包操作文档(以升级 FastJSON 为例)
java·canal·jar
后台模板学习1 小时前
学习的心态高频面试题
java·数据库·学习
用户3126874877201 小时前
线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解
java
cpolar技术支持2 小时前
Kafka Streams 窗口统计怎么验收:本地跑订单流聚合,用 cpolar 给同事看只读结果页
java·docker·kafka·cpolar·kafka streams
爱读源码的大都督2 小时前
DeepSeek面试官问:生产RAG系统回答不准确,该如何定位和优化?这样回答,能让面试官当场给你Offer!
java·后端·python
杨运交2 小时前
[069][公共模块]Spring Boot 全局异常处理与参数校验实战(下):校验异常精细化处理与 WebFlux 适配
java·spring boot·后端
Raas1003 小时前
MAI Gateway(魔芋企业级AI网关)对比分析:AI网关和OpenRouter区别?企业级能力差距一览
java·服务器·网络·人工智能·gateway·ai网关·mai gateway