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。游戏界面友好,功能完善,具有良好的用户体验。代码结构清晰,注释详细,易于维护和扩展。

未来可以考虑添加更多功能,如游戏历史记录、在线对战、更丰富的界面主题等,进一步提升游戏体验。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐