简介
使用Java实现简易五子棋
规则介绍
游戏使用一个标准的15×15方格的棋盘,双方分别用黑白两种颜色的棋子进行对战。黑子先行,双方轮流在空棋盘的交叉点上落子,每人一次只能落一子。游戏的目标是让自己的五颗棋子连成一线,这条线可以是横线、竖线、对角线或斜线。如果一方的五颗棋子按照上述规则连成一线,这一方就获胜并结束游戏。
(1)对局双方各执一色棋子。
(2)空棋盘开局。
(3)黑先、白后,交替下子,每次只能下一子。
(4)棋子下在棋盘的空白点上,棋子下定后不得移动或拿走。
(5)黑方的第一枚棋子必须下在天元点上,即中心交叉点"
功能设计
- 重新开始
用户操作【重新开始】功能,弹窗询问是否确定重新开始,如果是则将所有数据重新初始化,否则什么也不做。 - 悔棋
用户操作【悔棋】功能,恢复上一步的操作,如果已无上一步操作或者游戏结束,不允许悔棋并弹窗提示。 - 退出游戏
用户操作【退出游戏】功能,关闭该应用程序。 - 帮助
菜单栏添加玩法提示,引导用户使用。 - 坐标校准
由于棋子需下在网格线上,交叉点的坐标很小,故鼠标很难精准点击在符合的坐标上,那么就需要对用户点击的坐标进行校准,将其坐标校准为最贴近的符合坐标。 - 坐标可行性及输赢判断
用户落子时,判断该坐标是否可用(是否已有棋子),如果不可行,弹窗提示,否则,判断输赢并且刷新页面绘制棋子。如果某一方获胜,提示游戏结束,禁止继续落下棋子。 - 输赢判断算法
以落下棋子坐标出发,向上下、左右、左上右下、右上左下四个方向延伸,朝一个方向至多延伸五次,若有同色棋子,则计数器加一,最终判断计数器是否大于等于5,如果是则获得胜利。
实现
附上如下实现代码
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 blackboolean 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() {@Overridepublic 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() {@Overridepublic void actionPerformed(ActionEvent e) {regretChess();}});menu.add(regretChess);JMenuItem exit;(exit = new JMenuItem("退出游戏")).addActionListener(new ActionListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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 {@Overridepublic 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);}}