Java 五子棋课程设计
项目概述
本项目旨在使用 Java 语言开发一个功能完整、界面友好的五子棋游戏,游戏将支持双人对战模式,具备图形用户界面,并实现胜负判定、悔棋、重新开始等核心功能,通过本项目,可以综合运用 Java 面向对象编程、Swing/AWT 图形界面开发、事件处理等核心技术,提升软件工程实践能力。
项目目标
-
核心功能实现:

- 实现 15x15 或 19x19 的标准五子棋棋盘。
- 实现玩家轮流下棋(黑子先行)。
- 实现胜负判定逻辑:当任意一方在横、竖、斜方向上连成五子时,判定该方获胜。
- 实现游戏结束后的提示功能。
-
增强功能实现:
- 悔棋功能:允许玩家撤销上一步操作。
- 重新开始:允许玩家随时清空棋盘,开始新的一局。
- 显示当前玩家:在界面上明确提示当前轮到哪位玩家下棋。
- 落子音效(可选):增加游戏体验感。
-
技术目标:
- 熟练运用 Java Swing 或 JavaFX 构建图形用户界面。
- 掌握 Java 事件处理机制(如
MouseListener,ActionListener)。 - 运用面向对象思想设计游戏模型、视图和控制器,实现代码模块化和可维护性。
- 掌握基本的算法实现,如二维数组遍历、胜负判定算法。
技术选型
- 开发语言:Java 8 或更高版本。
- 开发工具:IntelliJ IDEA, Eclipse, VS Code 等。
- 核心库:Java Swing (用于构建 GUI),Swing 是 Java 的标准 GUI 工具包,组件丰富,易于上手,非常适合课程设计。
- 数据结构:
int[][]二维数组:用于存储棋盘状态。0代表空位,1代表黑子,2代表白子。java.util.Stack:用于实现悔棋功能,存储每一步的落子位置。
系统设计
1 功能模块划分
系统可分为以下几个核心模块:
-
棋盘模块:

- 负责绘制棋盘网格和棋子。
- 管理棋盘上的所有棋子状态(二维数组)。
- 处理鼠标点击事件,将点击位置转换为棋盘坐标。
-
游戏逻辑模块:
- 管理游戏当前状态(进行中、黑方胜、白方胜、平局)。
- 管理当前轮到哪位玩家下棋。
- 核心功能:
checkWin(int row, int col),在落子后检查是否获胜。 - 辅助功能:
undo(),实现悔棋。
-
用户界面模块:
- 包含棋盘面板、游戏信息面板(如当前玩家提示)、控制按钮(悔棋、重新开始)。
- 负责响应用户的按钮点击事件。
-
主程序模块:
创建游戏窗口,初始化各个模块,启动游戏。

2 类结构设计 (面向对象)
建议创建以下几个类,使结构清晰:
| 类名 | 职责 | 主要属性/方法 |
|---|---|---|
GomokuGame (主类) |
程序入口,创建并显示主窗口。 | main(String[] args) |
ChessBoard (棋盘面板) |
继承 JPanel,负责绘制棋盘和棋子,处理鼠标事件。 |
board[][] (棋盘状态), paintComponent(), mouseClicked() |
GameLogic (游戏逻辑) |
封装所有游戏规则和状态。 | currentPlayer, gameState, checkWin(), placePiece(), undo() |
MainFrame (主窗口) |
继承 JFrame,作为整个游戏的顶层容器。 |
ChessBoard, JButton, JLabel |
Position (可选) |
一个简单的数据类,用于存储 (row, col) 坐标。 |
row, col |
3 数据流设计
-
用户落子:
- 用户在
ChessBoard面板上点击鼠标。 ChessBoard的mouseClicked事件被触发。ChessBoard将鼠标坐标转换为棋盘坐标(row, col)。ChessBoard调用GameLogic的placePiece(row, col)方法。GameLogic检查该位置是否为空、游戏是否进行中。- 如果合法,更新
board二维数组,并调用checkWin(row, col)。 checkWin返回胜负结果。GameLogic更新游戏状态和当前玩家。ChessBoard监听到GameLogic状态变化,重新绘制自身(repaint()),显示新的棋子和游戏信息。
- 用户在
-
用户悔棋:
- 用户点击“悔棋”按钮。
- 按钮的
actionPerformed事件被触发。 - 事件处理器调用
GameLogic的undo()方法。 GameLogic从Stack中弹出上一步的坐标,将board对应位置重置为空,并切换回上一个玩家。ChessBoard重新绘制,棋子消失。
详细实现步骤
步骤 1:创建项目与主窗口
- 创建一个新的 Java 项目。
- 创建
MainFrame.java类,设置窗口标题、大小和关闭操作。
// MainFrame.java
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
public MainFrame() {
setTitle("Java 五子棋课程设计");
setSize(800, 700);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null); // 居中显示
// TODO: 在这里添加 ChessBoard 和控制按钮
}
public static void main(String[] args) {
// 使用 SwingUtilities 确保在事件分发线程中创建 GUI
SwingUtilities.invokeLater(() -> {
MainFrame frame = new MainFrame();
frame.setVisible(true);
});
}
}
步骤 2:实现棋盘面板 ChessBoard
- 创建
ChessBoard.java,继承自JPanel。 - 定义棋盘大小、格子大小等常量。
- 使用
int[][] board来存储棋盘状态。 - 重写
paintComponent(Graphics g)方法来绘制棋盘和棋子。 - 实现
MouseListener接口,处理鼠标点击事件。
// ChessBoard.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class ChessBoard extends JPanel implements MouseListener {
private static final int BOARD_SIZE = 15; // 15x15 棋盘
private static final int GRID_SIZE = 40; // 每个格子的大小
private static final int MARGIN = 30; // 棋盘边距
private int[][] board; // 0: 空, 1: 黑, 2: 白
private GameLogic gameLogic; // 引用游戏逻辑对象
public ChessBoard(GameLogic logic) {
this.gameLogic = logic;
this.board = new int[BOARD_SIZE][BOARD_SIZE];
addMouseListener(this);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 1. 绘制棋盘背景
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
// 2. 绘制网格线
g.setColor(Color.BLACK);
for (int i = 0; i < BOARD_SIZE; i++) {
// 横线
g.drawLine(MARGIN, MARGIN + i * GRID_SIZE, MARGIN + (BOARD_SIZE - 1) * GRID_SIZE, MARGIN + i * GRID_SIZE);
// 竖线
g.drawLine(MARGIN + i * GRID_SIZE, MARGIN, MARGIN + i * GRID_SIZE, MARGIN + (BOARD_SIZE - 1) * GRID_SIZE);
}
// 3. 绘制棋子
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] != 0) {
int x = MARGIN + j * GRID_SIZE;
int y = MARGIN + i * GRID_SIZE;
if (board[i][j] == 1) {
g.setColor(Color.BLACK);
} else {
g.setColor(Color.WHITE);
}
g.fillOval(x - GRID_SIZE / 2, y - GRID_SIZE / 2, GRID_SIZE, GRID_SIZE);
// 为白子加一个黑色边框,使其在浅色背景下更清晰
if (board[i][j] == 2) {
g.setColor(Color.BLACK);
g.drawOval(x - GRID_SIZE / 2, y - GRID_SIZE / 2, GRID_SIZE, GRID_SIZE);
}
}
}
}
}
@Override
public void mouseClicked(MouseEvent e) {
// 将鼠标点击位置转换为棋盘坐标
int x = e.getX();
int y = e.getY();
int col = Math.round((float) (x - MARGIN) / GRID_SIZE);
int row = Math.round((float) (y - MARGIN) / GRID_SIZE);
// 检查坐标是否在棋盘范围内
if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE) {
// 调用游戏逻辑来处理落子
if (gameLogic.placePiece(row, col)) {
// 落子成功,更新棋盘数据并重绘
board[row][col] = gameLogic.getCurrentPlayer();
repaint();
}
}
}
// 其他 MouseListener 方法暂时留空
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
}
步骤 3:实现游戏逻辑 GameLogic
- 创建
GameLogic.java类。 - 定义
currentPlayer和gameState。 - 实现
placePiece(int row, int col)方法,检查落子是否合法。 - 核心:实现
checkWin(int row, int col)方法,这是五子棋的灵魂。
// GameLogic.java
public class GameLogic {
public static final int EMPTY = 0;
public static final int BLACK = 1;
public static final int WHITE = 2;
private int currentPlayer;
private boolean gameOver;
// 使用栈来存储历史落子位置,用于悔棋
private java.util.Stack<int[]> history;
public GameLogic() {
reset();
}
public void reset() {
this.currentPlayer = BLACK;
this.gameOver = false;
this.history = new java.util.Stack<>();
}
public int getCurrentPlayer() {
return currentPlayer;
}
public boolean isGameOver() {
return gameOver;
}
public boolean placePiece(int row, int col) {
if (gameOver) {
return false;
}
// TODO: 这里 ChessBoard 需要访问 board,所以逻辑可能需要调整。
// 更好的设计是 ChessBoard 只负责显示,所有状态都由 GameLogic 管理。
// 为了简化,我们先假设 board 在 GameLogic 中。
// let's assume we have a board in GameLogic for now.
// int[][] board = ...;
// if (board[row][col] != EMPTY) return false;
// 简化版:我们暂时不在这里检查棋盘,而是由 ChessBoard 保证
// 记录历史
history.push(new int[]{row, col});
// 检查是否获胜
if (checkWin(row, col)) {
gameOver = true;
return true;
}
// 切换玩家
currentPlayer = (currentPlayer == BLACK) ? WHITE : BLACK;
return true;
}
// 悔棋功能
public boolean undo() {
if (history.isEmpty() || gameOver) {
return false;
}
history.pop();
currentPlayer = (currentPlayer == BLACK) ? WHITE : BLACK;
return true;
}
/**
* 胜负判定核心算法
* @param row 最后落子的行
* @param col 最后落子的列
* @return 是否获胜
*/
private boolean checkWin(int row, int col) {
int player = currentPlayer; // 获取当前玩家(也就是刚刚落子的玩家)
int[][] directions = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 水平、垂直、主对角线、副对角线
for (int[] dir : directions) {
int count = 1; // 从当前落子点开始算一个
// 正向检查
for (int i = 1; i < 5; i++) {
int newRow = row + dir[0] * i;
int newCol = col + dir[1] * i;
// if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE || board[newRow][newCol] != player) break;
// 简化版,假设棋盘无限大,或者由调用者保证边界
if (/* ... get piece at (newRow, newCol) ... */ != player) break;
count++;
}
// 反向检查
for (int i = 1; i < 5; i++) {
int newRow = row - dir[0] * i;
int newCol = col - dir[1] * i;
// if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE || board[newRow][newCol] != player) break;
// if ( ... get piece at (newRow, newCol) ... != player) break;
count++;
}
if (count >= 5) {
return true;
}
}
return false;
}
}
注意:上述 checkWin 中的棋盘访问是简化的,在实际项目中,GameLogic 应该持有一个 board 的引用,或者 ChessBoard 在落子前通过 GameLogic 查询该位置是否为空。
步骤 4:整合所有组件
修改 MainFrame.java,将 ChessBoard、GameLogic、按钮和标签整合起来。
// MainFrame.java (修订版)
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFrame extends JFrame {
private ChessBoard chessBoard;
private GameLogic gameLogic;
private JLabel statusLabel; // 用于显示当前玩家或游戏结果
private JButton undoButton;
private JButton restartButton;
public MainFrame() {
setTitle("Java 五子棋");
setSize(800, 700);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
// 1. 初始化游戏逻辑
gameLogic = new GameLogic();
// 2. 创建棋盘,并传入游戏逻辑
chessBoard = new ChessBoard(gameLogic);
// 3. 创建控制面板和按钮
statusLabel = new JLabel("黑方回合", SwingConstants.CENTER);
undoButton = new JButton("悔棋");
restartButton = new JButton("重新开始");
// 4. 添加按钮事件监听器
undoButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (gameLogic.undo()) {
// 悔棋成功,需要更新棋盘显示
// 这需要 ChessBoard 能从 GameLogic 获取最新状态,或者 GameLogic 通知 ChessBoard
// 为了简化,我们直接重绘 ChessBoard,但更优雅的方式是通知模式
chessBoard.updateBoard(gameLogic.getBoardState()); // 假设有这个方法
statusLabel.setText(gameLogic.getCurrentPlayer() == GameLogic.BLACK ? "黑方回合" : "白方回合");
}
}
});
restartButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
gameLogic.reset();
chessBoard.reset(); // ChessBoard 也需要重置自己的状态
statusLabel.setText("黑方回合");
}
});
// 5. 布局
setLayout(new BorderLayout());
add(chessBoard, BorderLayout.CENTER);
JPanel controlPanel = new JPanel(new FlowLayout());
controlPanel.add(statusLabel);
controlPanel.add(undoButton);
controlPanel.add(restartButton);
add(controlPanel, BorderLayout.SOUTH);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
MainFrame frame = new MainFrame();
frame.setVisible(true);
});
}
}
注意:上面的整合代码中,ChessBoard 和 GameLogic 的数据同步是一个需要仔细处理的问题,一个更清晰的设计是让 GameLogic 持有 board 的全部状态,ChessBoard 只负责根据 GameLogic 提供的状态进行绘制,当落子时,ChessBoard 调用 GameLogic 的方法,GameLogic 内部更新状态,然后通过一个观察者模式(GameLogic 状态变化后调用 chessBoard.update())来通知 ChessBoard 重绘。
步骤 5:完善与优化
- 数据同步问题:如上所述,重构
ChessBoard和GameLogic的交互。ChessBoard不再持有board,而是通过GameLogic提供的getBoardState()方法获取状态进行绘制。 - 完善
checkWin:确保checkWin方法正确处理边界条件,并且只检查与最后落子点相关的连线,提高效率。 - 增加游戏结束提示:当
gameOver为true时,用JOptionPane.showMessageDialog弹出提示框。 - 代码注释:为所有关键类和方法添加详细的 JavaDoc 注释。
- 代码风格:遵循 Java 编码规范,保持代码整洁。
扩展功能建议
如果项目要求更高,可以考虑以下扩展:
- 人机对战:实现一个简单的 AI 算法,例如基于评分的贪心算法或极小化极大算法。
- 网络对战:使用 Java Socket 编程,实现一个客户端/服务器架构,让两个玩家可以在不同电脑上对弈。
- 保存/加载游戏:将当前棋盘状态保存到文件,并支持从文件加载。
- 更美观的 UI:使用图片作为棋盘和棋子背景,增加动画效果等。
本课程设计提供了一个从零开始构建 Java 五子棋游戏的完整蓝图,核心在于合理划分模块、运用面向对象思想以及正确处理事件和状态,通过完成这个项目,学生不仅能巩固 Java 基础知识,还能对 GUI 开发和软件设计有更深入的理解,建议从核心功能入手,逐步迭代,确保每个模块都能独立正确运行后再进行整合。
标签: 评估函数 极大极小算法 AlphaBeta剪枝