基于Java Swing的井字棋小游戏(2)
本文介绍了一个基于MVC架构的井字棋游戏实现。游戏采用Swing组件构建图形界面,支持单人和双人模式,并实现了三个难度级别的AI对手。核心算法采用Minimax递归算法进行最优决策,其中困难模式使用完整Minimax算法,中等模式结合Minimax与启发式策略,简单模式采用随机选择。游戏包含完善的胜负判断机制,通过3x3字符数组存储棋盘状态,使用事件监听器处理用户交互。测试表明该游戏具有良好的兼容
·
2、演示视频
基于Java Swing的井字棋小游戏
3. 设计说明
3.1 整体架构
游戏采用MVC(Model-View-Controller)设计模式:
- Model(模型):使用
char[][] board数组存储游戏状态 - View(视图):使用Swing组件创建图形界面
- Controller(控制器):通过事件监听器处理用户输入
3.2 类结构设计
| 类名 | 功能描述 |
|---|---|
| TicTacToeGame | 主游戏类,继承JFrame,包含游戏逻辑和界面 |
| ButtonClickListener | 内部类,处理按钮点击事件 |
3.3 核心数据结构
// 游戏棋盘,3x3的字符数组
private char[][] board = new char[BOARD_SIZE][BOARD_SIZE];
// 界面按钮,3x3的按钮数组
private JButton[][] buttons = new JButton[BOARD_SIZE][BOARD_SIZE];
// 游戏状态变量
private boolean isXTurn = true; // 是否轮到X玩家
private boolean gameEnded = false; // 游戏是否结束
private boolean isSinglePlayer = true; // 是否单人模式
private boolean isComputerThinking = false; // 电脑是否在思考
private int difficultyLevel = 2; // 难度等级(0-简单,1-中等,2-困难)
4. 算法说明
4.1 Minimax算法
Minimax算法原理:
Minimax是一种递归算法,用于在零和博弈中找到最优策略。算法假设对手也会采取最优策略,通过探索所有可能的游戏状态来选择最佳移动。
算法核心思想:
- 最大化玩家(电脑):选择能获得最大收益的移动
- 最小化玩家(玩家):选择能让对手收益最小的移动
评估函数:
- 电脑获胜:返回正值(10 - depth)
- 玩家获胜:返回负值(depth - 10)
- 平局:返回0
4.2 Minimax算法实现
/**
* Minimax算法实现
* 递归算法,用于计算最佳移动位置
* @param depth 当前搜索深度
* @param isMaximizing 当前是否是最大化玩家回合(true为电脑O,false为玩家X)
* @return 评估值(电脑有利为正,玩家有利为负)
*/
private int minimax(int depth, boolean isMaximizing) {
// 检查当前棋盘状态
char winner = checkWinner();
// 如果有获胜者,返回评估值
if (winner == 'O') return 10 - depth; // 电脑获胜,返回正值(深度越小分越高)
if (winner == 'X') return depth - 10; // 玩家获胜,返回负值(深度越小分越低)
if (winner == 'T') return 0; // 平局,返回0
if (isMaximizing) {
// 最大化玩家(电脑O)回合,寻找最大评估值
int bestScore = Integer.MIN_VALUE;
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] == ' ') {
board[i][j] = 'O'; // 尝试放置O
int score = minimax(depth + 1, false); // 递归调用,轮到最小化玩家
board[i][j] = ' '; // 撤销移动
bestScore = Math.max(score, bestScore); // 更新最佳分数
}
}
}
return bestScore;
} else {
// 最小化玩家(玩家X)回合,寻找最小评估值
int bestScore = Integer.MAX_VALUE;
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] == ' ') {
board[i][j] = 'X'; // 尝试放置X
int score = minimax(depth + 1, true); // 递归调用,轮到最大化玩家
board[i][j] = ' '; // 撤销移动
bestScore = Math.min(score, bestScore); // 更新最佳分数
}
}
}
return bestScore;
}
}
4.3 难度级别算法
游戏实现了三个难度级别,每个级别使用不同的策略:
- 简单模式:完全随机选择空位置
- 中等模式:50%概率使用Minimax,50%使用启发式策略
- 困难模式:使用完整的Minimax算法
5. 测试说明
5.1 功能测试
| 测试项目 | 测试方法 | 预期结果 |
|---|---|---|
| 单人模式 | 点击"单人模式"按钮,然后进行游戏 | 玩家与电脑对战 |
| 双人模式 | 点击"双人模式"按钮,然后进行游戏 | 两名玩家轮流下棋 |
| 难度切换 | 在游戏开始前切换难度选项 | 电脑AI行为符合对应难度 |
| 获胜检测 | 在一行、一列或对角线放置三个相同符号 | 正确识别获胜者并显示结果 |
| 平局检测 | 填满棋盘但无人获胜 | 正确识别平局并显示结果 |
| 游戏重置 | 点击"重置游戏"按钮 | 棋盘清空,游戏重新开始 |
5.2 性能测试
由于井字棋棋盘较小(3x3),Minimax算法的性能表现良好,不会出现明显的延迟。在困难模式下,电脑能够快速计算出最优策略。
5.3 兼容性测试
游戏使用标准Java Swing组件开发,兼容所有支持Java 8及以上版本的操作系统,包括Windows、macOS和Linux。
6. 关键代码
6.1 游戏状态检查
/**
* 检查是否有玩家获胜
* 检查所有可能的获胜条件:行、列、对角线
* @return 获胜玩家的符号('X'或'O'),平局返回'T',游戏继续返回' '
*/
private char checkWinner() {
// 检查每一行是否有获胜者
for (int i = 0; i < BOARD_SIZE; i++) {
if (board[i][0] != ' ' && board[i][0] == board[i][1] && board[i][1] == board[i][2]) {
return board[i][0]; // 返回获胜玩家
}
}
// 检查每一列是否有获胜者
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[0][j] != ' ' && board[0][j] == board[1][j] && board[1][j] == board[2][j]) {
return board[0][j]; // 返回获胜玩家
}
}
// 检查主对角线是否有获胜者(左上到右下)
if (board[0][0] != ' ' && board[0][0] == board[1][1] && board[1][1] == board[2][2]) {
return board[0][0]; // 返回获胜玩家
}
// 检查反对角线是否有获胜者(右上到左下)
if (board[0][2] != ' ' && board[0][2] == board[1][1] && board[1][1] == board[2][0]) {
return board[0][2]; // 返回获胜玩家
}
// 检查是否平局(棋盘已满但无人获胜)
boolean isBoardFull = true;
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] == ' ') {
isBoardFull = false; // 发现空位,棋盘未满
break;
}
}
}
if (isBoardFull) {
return 'T'; // 返回平局标记
}
return ' '; // 游戏继续
}
6.2 电脑AI移动逻辑
/**
* 电脑下棋逻辑(优化版,使用Minimax算法)
* 根据难度级别选择不同的策略
*/
private void makeComputerMove() {
isComputerThinking = true; // 标记电脑正在思考
// 在事件调度线程中执行,避免界面冻结
SwingUtilities.invokeLater(() -> {
try {
Thread.sleep(500); // 延迟0.5秒,让电脑下棋看起来更自然
} catch (InterruptedException ex) {
ex.printStackTrace();
}
int[] bestMove = null; // 存储电脑选择的移动位置
// 根据难度级别选择不同的策略
switch (difficultyLevel) {
case 0: // 简单:随机移动
List emptyPositions = getEmptyPositions();
if (!emptyPositions.isEmpty()) {
// 从空位置中随机选择一个
bestMove = emptyPositions.get((int) (Math.random() * emptyPositions.size()));
}
break;
case 1: // 中等:50%使用minimax,50%随机
if (Math.random() < 0.5) {
// 50%概率使用Minimax算法
bestMove = getBestMove();
} else {
// 50%概率使用启发式策略
// 首先检查电脑是否能获胜
bestMove = findWinningMove('O');
if (bestMove[0] == -1) {
// 如果电脑不能获胜,检查是否需要阻止玩家获胜
bestMove = findWinningMove('X');
}
// 如果没有获胜或阻止的移动,随机选择
if (bestMove[0] == -1) {
emptyPositions = getEmptyPositions();
if (!emptyPositions.isEmpty()) {
bestMove = emptyPositions.get((int) (Math.random() * emptyPositions.size()));
}
}
}
break;
case 2: // 困难:使用minimax算法
bestMove = getBestMove(); // 使用完整的Minimax算法
break;
}
// 执行电脑的移动
if (bestMove[0] != -1) {
executeMove(bestMove[0], bestMove[1], 'O');
} else {
// 如果没有找到合适的移动(理论上不应该发生)
List emptyPositions2 = getEmptyPositions();
if (!emptyPositions2.isEmpty()) {
int[] randomMove = emptyPositions2.get(0);
executeMove(randomMove[0], randomMove[1], 'O');
}
}
isComputerThinking = false; // 标记电脑思考结束
});
}
6.3 按钮点击事件处理
/**
* 按钮点击事件监听器
* 处理玩家点击棋盘按钮的事件
*/
private class ButtonClickListener implements ActionListener {
private int row, col; // 按钮所在的行和列
/**
* 构造函数,保存按钮的行列位置
* @param row 按钮所在行
* @param col 按钮所在列
*/
public ButtonClickListener(int row, int col) {
this.row = row;
this.col = col;
}
/**
* 处理按钮点击事件
* @param e 事件对象
*/
@Override
public void actionPerformed(ActionEvent e) {
// 如果游戏已结束、按钮已被点击或电脑正在思考,则不执行任何操作
if (gameEnded || board[row][col] != ' ' || isComputerThinking) {
return;
}
// 设置当前玩家的符号
char playerSymbol = isXTurn ? 'X' : 'O';
board[row][col] = playerSymbol;
buttons[row][col].setText(String.valueOf(playerSymbol));
buttons[row][col].setEnabled(false);
// 检查是否有玩家获胜
char winner = checkWinner();
if (winner != ' ') {
showGameResult(winner);
return;
}
// 切换玩家回合
isXTurn = !isXTurn;
updateStatus();
// 如果是单人模式,且轮到电脑下棋
if (isSinglePlayer && !isXTurn) {
makeComputerMove();
}
}
}
7. 总结
本井字棋游戏实现了完整的双人对战和人机对战功能,通过Minimax算法实现了智能的电脑AI。游戏界面友好,功能完善,具有良好的用户体验。代码结构清晰,注释详细,易于维护和扩展。
未来可以考虑添加更多功能,如游戏历史记录、在线对战、更丰富的界面主题等,进一步提升游戏体验。
更多推荐


所有评论(0)