You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java版Tic-Tac-Toe井字棋AI落子逻辑实现与代码优化

井字棋AI功能实现与代码修正指南

原有代码问题梳理

  • 方法定义错误:switchVal()方法声明返回值为boolean,但方法内无任何返回语句,直接编译失败,需要将返回值类型改为void。
  • 全局变量滥用:row、col、symbol、numPlayers这类仅在单次操作中使用的变量全部定义为类成员变量,多方法调用时很容易出现值被意外篡改的问题,这类变量应当作为局部变量定义在对应方法内,需要跨方法传递时通过参数传入即可。
  • 重复创建对象冗余:play()、singlePlayer()、twoPlayer()方法中都重复新建了TicTacToe实例,导致最初初始化的游戏实例完全没有被使用,额外占用内存还容易出现状态不同步的问题,直接操作当前实例即可。
  • 缺少输入校验:用户输入行列值时,没有校验输入范围是否在1-3之间,也没有校验选中位置是否为空,用户输入非法值时会触发数组越界异常,或者覆盖已有棋子。
  • 单人模式胜负判断缺失:现有单人模式逻辑只判断了玩家获胜和平局两种情况,电脑获胜的场景完全没有处理,电脑赢了也会提示玩家获胜,逻辑错误。
  • 获胜判断逻辑硬编码:8条获胜线的判断全部重复硬编码,可读性差,后续修改成本高,可以抽成通用方法,传入待判断的棋子符号即可返回是否获胜。

AI落子逻辑实现方案

按照要求的优先级实现AI决策,核心思路是先写一个通用的制胜点查找辅助方法:遍历棋盘所有空位,模拟在空位放入指定棋子,检查放入后是否满足获胜条件,如果满足就返回该位置坐标,找不到则返回空。
AI落子的三层判断顺序:

  1. 优先查找AI自身(持'o'棋子)一步获胜的空位,找到则直接落子取胜
  2. 不存在制胜点时,查找玩家(持'x'棋子)一步获胜的空位,找到则落子封堵
  3. 两种情况都不满足时,随机选择空位落子

修正后的完整代码

Driver 主类文件

public class DriverTicTacToe
{
   public static void main(String[] args)
   {
      TicTacToe game = new TicTacToe();
      game.play();
   }
}

TicTacToe 逻辑类文件

import java.util.Scanner;

public class TicTacToe
{  
    // 棋盘、输入Scanner设为成员变量,其他流程变量全部改为局部变量
    private final char[][] board = new char[3][3];
    private final Scanner kb = new Scanner(System.in);
    private static final char EMPTY = '-';
    private static final char PLAYER_SYMBOL = 'x';
    private static final char AI_SYMBOL = 'o';

    // 构造方法初始化空棋盘
    public TicTacToe()
    {
        for(int i = 0; i <3; i++)
        {
            for(int j = 0; j < 3; j++)
                board[i][j] = EMPTY;
        }   
    }

    // 获取玩家数量,增加输入校验
    private int getNumPlayers()
    {
        int input;
        do {
            System.out.println("请选择游玩人数(输入1或2):");
            while (!kb.hasNextInt()) {
                System.out.println("输入非法,请输入数字1或2:");
                kb.next();
            }
            input = kb.nextInt();
        } while (input !=1 && input !=2);
        return input;
    }

    // 打印棋盘
    private void printBoard()
    {
        System.out.println("当前棋盘状态:");
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j<3; j++)
                System.out.print(board[i][j] + " ");
            System.out.println();
        }   
    }

    // 判断棋盘是否还有空位
    private boolean boardNotFilled()
    {
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                if(board[i][j] == EMPTY) 
                    return true;
            }
        }
        return false;
    }

    // 通用获胜判断:传入棋子符号,判断该符号持有者是否获胜
    private boolean checkWin(char symbol)
    {
        // 三行判断
        for (int i = 0; i < 3; i++) {
            if (board[i][0] == symbol && board[i][1] == symbol && board[i][2] == symbol) return true;
        }
        // 三列判断
        for (int j = 0; j < 3; j++) {
            if (board[0][j] == symbol && board[1][j] == symbol && board[2][j] == symbol) return true;
        }
        // 两条对角线判断
        if (board[0][0] == symbol && board[1][1] == symbol && board[2][2] == symbol) return true;
        if (board[0][2] == symbol && board[1][1] == symbol && board[2][0] == symbol) return true;
        
        return false;
    }

    // 查找指定棋子的一步制胜位置,找不到返回null
    private int[] findWinningSpot(char symbol) {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (board[i][j] == EMPTY) {
                    // 模拟落子
                    board[i][j] = symbol;
                    boolean win = checkWin(symbol);
                    // 还原棋盘
                    board[i][j] = EMPTY;
                    if (win) {
                        return new int[]{i, j};
                    }
                }
            }
        }
        return null;
    }

    // 获取用户合法落子位置
    private int[] getPlayerMove() {
        int row, col;
        while (true) {
            System.out.println("请输入落子行号(1-3):");
            while (!kb.hasNextInt()) {
                System.out.println("输入非法,请输入1-3之间的数字:");
                kb.next();
            }
            row = kb.nextInt() - 1;
            System.out.println("请输入落子列号(1-3):");
            while (!kb.hasNextInt()) {
                System.out.println("输入非法,请输入1-3之间的数字:");
                kb.next();
            }
            col = kb.nextInt() - 1;
            // 校验范围和位置是否为空
            if (row >=0 && row <3 && col >=0 && col <3 && board[row][col] == EMPTY) {
                return new int[]{row, col};
            }
            System.out.println("输入位置非法或已被占用,请重新输入!");
        }
    }

    // AI落子逻辑,按优先级决策
    private void compMove() {
        int[] move;
        // 第一步:找自己的制胜点
        move = findWinningSpot(AI_SYMBOL);
        if (move != null) {
            board[move[0]][move[1]] = AI_SYMBOL;
            return;
        }
        // 第二步:找玩家的制胜点封堵
        move = findWinningSpot(PLAYER_SYMBOL);
        if (move != null) {
            board[move[0]][move[1]] = AI_SYMBOL;
            return;
        }
        // 第三步:随机落子
        int row, col;
        do {
            row = (int)(Math.random()*3);
            col = (int)(Math.random()*3);
        } while (board[row][col] != EMPTY);
        board[row][col] = AI_SYMBOL;
    }

    // 单人模式逻辑
    private void singlePlayer()
    {
        boolean isPlayerTurn = true;
        while (true)
        {
            printBoard();
            if (isPlayerTurn) {
                System.out.println("轮到你落子:");
                int[] playerMove = getPlayerMove();
                board[playerMove[0]][playerMove[1]] = PLAYER_SYMBOL;
                // 判断玩家是否获胜
                if (checkWin(PLAYER_SYMBOL)) {
                    printBoard();
                    System.out.println("恭喜你获胜了!");
                    return;
                }
            } else {
                System.out.println("电脑落子中...");
                compMove();
                // 判断电脑是否获胜
                if (checkWin(AI_SYMBOL)) {
                    printBoard();
                    System.out.println("电脑获胜,再接再厉!");
                    return;
                }
                System.out.println("电脑落子完成,轮到你了");
            }
            // 判断平局
            if (!boardNotFilled()) {
                printBoard();
                System.out.println("平局!");
                return;
            }
            isPlayerTurn = !isPlayerTurn;
        }
    }

    // 双人模式逻辑
    private void twoPlayer()
    {
        boolean isP1Turn = true;
        while (true)
        {
            printBoard();
            char currentSymbol = isP1Turn ? PLAYER_SYMBOL : AI_SYMBOL;
            System.out.printf("轮到%s落子:%n", isP1Turn ? "玩家1(x)" : "玩家2(o)");
            int[] move = getPlayerMove();
            board[move[0]][move[1]] = currentSymbol;
            // 判断当前玩家是否获胜
            if (checkWin(currentSymbol)) {
                printBoard();
                System.out.printf("恭喜%s获胜!%n", isP1Turn ? "玩家1" : "玩家2");
                return;
            }
            // 判断平局
            if (!boardNotFilled()) {
                printBoard();
                System.out.println("平局!");
                return;
            }
            isP1Turn = !isP1Turn;
        }
    }

    // 游戏启动入口
    public void play()
    {
        int numPlayers = getNumPlayers();
        if(numPlayers == 1)
            singlePlayer();
        else
            twoPlayer();
        kb.close();
    }
}

额外编码建议

  • 类的成员变量如果不需要对外暴露,尽量加private修饰符,符合封装原则
  • 魔法值(比如'-'、'x'、'o'、3这类重复出现的固定值)可以定义为常量,后续修改的时候只需要改一处
  • 方法功能尽量单一,一个方法只做一件事,不要把输入、落子、判断胜负全耦合在一个方法里,后续维护和改bug会方便很多
  • 涉及用户输入的场景一定要做合法性校验,避免非法输入导致程序崩溃

内容的提问来源于stack exchange,提问作者Leraner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:27:23