简介
使用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 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);
}
}