
简介人机五子棋游戏是Java课程设计中经典的实践项目适合需要掌握面向对象编程、GUI组建与基础博弈算法的计算机专业学生。该项目从棋盘与棋子的类设计出发完整覆盖Swing界面搭建、落子合法性校验、五子连线判定并基于Minimax与Alpha-Beta剪枝实现简易电脑对手帮助学习者理解从数据模型到交互反馈的完整开发链路。压缩包共19个文件以5个Java源码文件为核心搭配可直接运行的JAR包、设计报告文档、说明文档以及程序截图与工程配置文件整包仅295KB便于下载后快速导入IDE进行源码研读和断点调试。目前已有284人学习下载适合作为课程设计、期末项目或Java综合练习的参考范本。资源内含工程目录、图片素材和说明文档读者可对照源码梳理界面事件、AI估值与棋盘状态管理之间的协作关系并按需扩展悔棋、联机对战等新功能。1. Java课程设计选人机五子棋为什么这是最稳的项目选题每年课程设计季实验室里总能见到两类人一类抱着《Java基础》从头啃另一类对着空白的Eclipse窗口发呆一整天。Java课程设计如果选人机五子棋基本踩中了“性价比最高”的档位——它覆盖了类与对象、Swing图形界面、事件监听、搜索算法这几个核心考点代码量又控制在两千行以内不会做到一半想换题。这份源码包把完整项目都拆好了src目录是全部Java源码chess.jar是编译好的可运行程序设计报告docx可以直接对照改写README.md里写好了运行方式。你需要做的不是从零写一遍而是先把代码读懂再动手改成自己风格的版本。2. 数据模型先行棋盘与棋子的类设计决定后续所有逻辑上手任何游戏项目我的习惯是先把数据模型定死界面和逻辑放在后面。五子棋项目的数据模型就两件事棋盘怎么存储、棋子怎么建模。这两件事如果设计得合理后续的落子、判赢、AI搜索都会顺理成章反之界面画完发现棋盘结构改不动了才会体会到什么叫返工火葬场。这一章把源码里最常见的三种建模方式拆开讲清楚。2.1 二维数组存棋盘为什么不用对象数组绝大多数课程设计版本里棋盘都是一个15×15的二维数组。源码里的ChessBoard类就采用了这个方案public class ChessBoard { public static final int EMPTY 0; public static final int BLACK 1; public static final int WHITE 2; public static final int SIZE 15; private int[][] board new int[SIZE][SIZE]; public boolean placePiece(int row, int col, int color) { if (row 0 || row SIZE || col 0 || col SIZE) { return false; } if (board[row][col] ! EMPTY) { return false; } board[row][col] color; return true; } public int getPiece(int row, int col) { return board[row][col]; } }逻辑说明placePiece是整个棋盘唯一的落子入口它同时拦下两类非法操作——坐标越界和重复落子返回布尔值。之所以把校验收口在同一个方法里是因为界面层和AI层都会调用落子两套校验规则很容易出现“界面能落、AI不能落”的诡异现象。AI模拟落子时也会走这个方法但它会在模拟后调用placePiece(row, col, EMPTY)来撤销否则搜索树就串了。参数说明SIZE 15对应标准五子棋棋盘。三个常量EMPTY/BLACK/WHITE分别代表空位、黑棋、白棋代码里禁止直接写魔法数字。你如果想改成13路棋盘改SIZE一个值就够后续的界面绘制、鼠标换算、AI搜索全部引用SIZE不会出现改漏的情况。为什么不用ChessPiece[][]存棋盘因为AI搜索需要频繁模拟落子int[][]的拷贝开销远低于对象数组量变在几千次搜索后就会变成肉眼可见的卡顿。2.2 ChessPiece类与悔棋栈落子历史记录虽然棋盘用int数组存但摘要里说的ChessPiece类还是要有的——它负责承载“这一步落在哪、什么颜色”的信息是悔棋、复盘、记录走法的核心对象。源码里的设计是把ChessPiece当作一次落子的事件记录public class ChessPiece { private final int row; private final int col; private final int color; public ChessPiece(int row, int col, int color) { this.row row; this.col col; this.color color; } public int getRow() { return row; } public int getCol() { return col; } public int getColor() { return color; } }逻辑说明ChessPiece是纯数据类只有构造和getter没有setter。字段用final修饰一旦创建不可修改。这样做的好处是悔棋栈里保存的每一步记录都不可变不会有“某个历史步骤被意外改掉”的隐患。棋盘本身不用ChessPiece但每次成功落子后控制器会把一个ChessPiece压入栈里悔棋时从这个栈里取出最后一条记录去反向清棋盘。配合GameRecord类实现悔棋栈import java.util.Stack; public class GameRecord { private StackChessPiece history new Stack(); public void recordMove(ChessPiece piece) { history.push(piece); } public ChessPiece undoLastMove(ChessBoard board) { if (history.isEmpty()) { return null; } ChessPiece piece history.pop(); board.placePiece(piece.getRow(), piece.getCol(), ChessBoard.EMPTY); return piece; } public int getMoveCount() { return history.size(); } }逻辑说明undoLastMove把最后一步的记录弹出栈然后通过placePiece把对应位置重置为EMPTY。这里有个隐蔽的设计点placePiece原本会拦截“重复落子”但传入EMPTY时只要该位置当前有棋子它依然能正常操作。悔棋和落子共用同一个数据写入入口避免出现“下棋走A入口、悔棋走B入口”导致状态不一致。参数说明Stack是JCF自带的栈push/pop都是O(1)。如果你觉得Stack太老气可以换成ArrayDeque操作语义一样性能还略好。getMoveCount在界面显示当前步数用也可以在平局判定时配合“棋盘未满”的检查。2.3 行列坐标约定从构造到界面全链路对齐棋盘坐标的约定必须全项目统一。源码采用board[row][col]的写法第一个维度是行从上到下014第二个维度是列从左到右014坐标原点在左上角。这在Swing画棋盘时非常自然因为paintComponent里画网格也是从左上角开始往右下延伸。// 坐标换算的统一封装 public int[] screenToBoard(int mouseX, int mouseY) { int col (mouseX - margin cellSize / 2) / cellSize; int row (mouseY - margin cellSize / 2) / cellSize; return new int[]{row, col}; }逻辑说明screenToBoard把鼠标的像素坐标转成行列下标。返回的数组里第一个元素是行第二个是列与board[row][col]的维度顺序一致。AI搜索部分遍历空位时也是先外层行、再内层列保证所有模块遵循同一套索引顺序。这个约定写在代码注释里特别重要因为你自己两天后回来看大概率也记不清到底谁是行谁是列。参数说明margin是棋盘四周留白像素数cellSize是每个格子的像素边长。这两个值在面板类里是成员变量整个类只有一份定义。屏幕坐标的X对应列、Y对应行换算时先算列再算行但返回时按“行在前、列在后”组装。两套顺序很容易搞混如果发现鼠标点下的棋子总是偏移到对称位置先把这里的返回顺序改成{col, row}试试。3. Swing界面落地自绘棋盘、鼠标事件与控制器串联数据模型设计好后下一层就是界面。五子棋项目的GUI核心不只是“能显示”还要求落子反馈即时、重绘不闪烁、坐标换算准确。这一章从组件的选型讲起逐步给你一套可直接落地的Swing实现。3.1 为什么自绘棋盘而不是JButton网格初学Swing的人第一反应是用JButton[][]模拟棋盘格子每个按钮一个监听器。这在15×15的棋盘上就有225个按钮组件加上每个按钮的边框、字体、背景设置界面渲染的负担很重而且按钮自带的点击动效会不断触发重绘造成肉眼可见的迟钝。源码里用的是继承JPanel、在paintComponent里手绘的方式public class ChessBoardPanel extends JPanel { private ChessBoard board; private int cellSize 36; private int margin 30; public ChessBoardPanel(ChessBoard board) { this.board board; setPreferredSize(new Dimension( ChessBoard.SIZE * cellSize 2 * margin, ChessBoard.SIZE * cellSize 2 * margin)); } Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 画木纹底色 g.setColor(new Color(210, 180, 140)); g.fillRect(0, 0, getWidth(), getHeight()); // 画网格线 g.setColor(Color.BLACK); for (int i 0; i ChessBoard.SIZE; i) { int x margin i * cellSize; g.drawLine(x, margin, x, margin (ChessBoard.SIZE - 1) * cellSize); g.drawLine(margin, x, margin (ChessBoard.SIZE - 1) * cellSize, x); } // 画棋子 for (int row 0; row ChessBoard.SIZE; row) { for (int col 0; col ChessBoard.SIZE; col) { int color board.getPiece(row, col); if (color ChessBoard.EMPTY) continue; int x margin col * cellSize - cellSize / 2; int y margin row * cellSize - cellSize / 2; g.setColor(color ChessBoard.BLACK ? Color.BLACK : Color.WHITE); g.fillOval(x, y, cellSize - 4, cellSize - 4); } } } }逻辑说明整个面板只有一次完整绘制方法不需要为每个格子创建组件。棋子是fillOval画出来的圆形颜色由board.getPiece返回值决定。continue跳过空位比if (color ! EMPTY) { 画棋子 }少一层嵌套。网格线用一条for循环同时画横线和竖线每条线的端点都落在棋盘边缘。参数说明cellSize36在1080p/2k屏上观感正好格子足够大鼠标点击不容易误触。margin30是棋盘到面板边框的余量它给四周留出空间避免棋子被面板边缘裁掉。setPreferredSize必须等于SIZE * cellSize 2 * margin也就是总宽度等于15个格子加左右两个margin。如果这两个值不一致JFrame.pack()后棋盘就会被裁掉或者留出大片空白。3.2 鼠标点击与坐标换算四行公式搞定落子mouseClicked里做的事其实只有三步把像素换算成行列、检查是否合法、执行落子并刷新界面。坐标换算我们在2.3里定义过工具方法这里直接用在监听器里addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { int col (e.getX() - margin cellSize / 2) / cellSize; int row (e.getY() - margin cellSize / 2) / cellSize; if (row 0 || row ChessBoard.SIZE || col 0 || col ChessBoard.SIZE) { return; } controller.onPlayerClick(row, col); } });逻辑说明(e.getX() - margin)先把屏幕坐标换算成相对棋盘原点的距离 cellSize / 2把坐标向最近的交叉点“吸拢”再整除cellSize就得到格子的下标。比如cellSize36时鼠标点到某交叉点向右17像素以内都会归到同一个格子。row 0 || row SIZE时直接return避免在棋盘外点击触发落子。参数说明col和row的顺序不要写反因为board[row][col]是行优先。如果真的写反了会观察到棋子落到了左右镜像的位置。controller是一个中间层它的职责是接收UI事件、调用逻辑层的落子方法、再通知UI刷新——这个模式让代码的扩展性变强后面加AI、加悔棋都只在controller里加方法不用改动监听器。3.3 落子后的流程控制controller如何串联人机回合既然是人机五子棋落子不只是一次棋盘写入还要触发AI决策。简单粗暴的做法是在mouseClicked里先落子、再调用AI方法但AI计算需要时间直接写在事件监听线程里会让界面卡住。所以controller的正确打开方式是public class GameController { private ChessBoard board; private ChessBoardPanel panel; private AIPlayer ai; private boolean isPlayerTurn true; public void onPlayerClick(int row, int col) { if (!isPlayerTurn) { return; } if (!board.placePiece(row, col, ChessBoard.BLACK)) { return; } panel.repaint(); if (board.checkWin(row, col, ChessBoard.BLACK)) { showMessage(你赢了); return; } isPlayerTurn false; // 交给AI走棋用SwingWorker避免阻塞UI new SwingWorkerVoid, Void() { Override protected Void doInBackground() { ai.makeMove(board); return null; } Override protected void done() { panel.repaint(); isPlayerTurn true; } }.execute(); } }逻辑说明onPlayerClick是事件监听器的唯一入口它先判断是否轮到玩家再尝试落子落子成功后立刻重绘检查胜负。随后把AI计算丢进SwingWorker的后台线程done()里再切回事件线程重绘界面——这样AI在想棋的几秒钟内主界面不会变成“未响应”状态。参数说明黑棋永远是玩家、白棋永远是AI所以AIPlayer内部写死了WHITE。SwingWorkerVoid, Void是Swing自带的异步任务类doInBackground运行在后台线程done回到事件分发线程。课程设计阶段如果不想引入这个概念也可以用new Thread(() - { ... }).start()但注意线程里不能直接操作Swing组件必须用SwingUtilities.invokeLater切回来——这也是一个经典的踩坑点。4. 胜负判定与AI搜索四方向扫描、评估函数与Minimax剪枝界面能落子了程序只完成了一半。剩下的一半是两颗“大脑”判赢逻辑和AI决策。这一章的代码量不大但它们决定你这个五子棋项目的答辩深度。把Minimax和Alpha-Beta剪枝讲清楚基本就掌握了这张试卷的最后大题。4.1 五子连珠判定四方向延伸扫描判赢只需要检查最后落下的那颗棋子因为新棋落下之前棋盘上没有五连五连只可能因为这一步而产生。围绕这颗新棋沿着四个方向横、竖、主对角线、副对角线向两边延伸统计连子public boolean checkWin(ChessBoard board, int row, int col, int color) { int[][] directions {{0, 1}, {1, 0}, {1, 1}, {1, -1}}; for (int[] dir : directions) { int count 1; for (int i 1; i 4; i) { int r row dir[0] * i; int c col dir[1] * i; if (isOutOfBound(r, c)) break; if (board.getPiece(r, c) color) count; else break; } for (int i 1; i 4; i) { int r row - dir[0] * i; int c col - dir[1] * i; if (isOutOfBound(r, c)) break; if (board.getPiece(r, c) color) count; else break; } if (count 5) { return true; } } return false; } private boolean isOutOfBound(int r, int c) { return r 0 || r ChessBoard.SIZE || c 0 || c ChessBoard.SIZE; }逻辑说明四条方向的增量分别写死在directions里{0,1}水平、{1,0}垂直、{1,1}主对角线、{1,-1}副对角线。每个方向先向正方向延伸4步再向反方向延伸4步累计同色数达到5就判胜。为什么上限是4因为连成5颗只需要再找出4颗相邻的同色棋超过4的延伸即使存在也不影响判断。参数说明count从1起步落子点自身。延伸遇到异色、空位或越界就break——万一这个方向断了就不用继续数。这套实现的时间复杂度最坏是O(4×4×2)几乎瞬时完成完全不需要用DFS或全盘扫描。有些版本写的“扫描全棋盘找五连”虽然也能用但浪费得很。4.2 AI评估函数给每个棋面打一个分数Minimax搜索需要一个“棋面得分”作叶子节点返回值。评估函数的好坏直接决定AI的战斗力比搜索深度影响还大。先给一个课程设计够用的简单版public int evaluateBoard(ChessBoard board, int aiColor) { int score 0; int[][] dirs {{0, 1}, {1, 0}, {1, 1}, {1, -1}}; for (int row 0; row ChessBoard.SIZE; row) { for (int col 0; col ChessBoard.SIZE; col) { int color board.getPiece(row, col); if (color ChessBoard.EMPTY) continue; score evaluatePoint(board, row, col, dirs, color, aiColor); } } return score; } private int evaluatePoint(ChessBoard board, int row, int col, int[][] dirs, int color, int aiColor) { int total 0; for (int[] dir : dirs) { int count 1; for (int i 1; i 5; i) { int r row dir[0] * i; int c col dir[1] * i; if (isOutOfBound(r, c) || board.getPiece(r, c) ! color) break; count; } for (int i 1; i 5; i) { int r row - dir[0] * i; int c col - dir[1] * i; if (isOutOfBound(r, c) || board.getPiece(r, c) ! color) break; count; } int sign (color aiColor) ? 1 : -1; switch (count) { case 5: total 100000 * sign; break; case 4: total 10000 * sign; break; case 3: total 1000 * sign; break; case 2: total 100 * sign; break; case 1: total 10 * sign; break; } } return total; }逻辑说明AI得分是正向加分玩家得分按同样的规则加负分最后累加成正负可比的整数值。搜索时AI会选总分最大的走法玩家分支会选总分最小的走法。这套评估函数有两个粗糙之处不区分活四和冲四、不区分活三和眠三导致AI偶尔会在“对方堵死自己三子”的位置上浪费步数。但它的优点是实现简单、逻辑清晰足够撑起一个可玩的初级AI。参数说明四个段位的分数量级差10倍权重是10, 100, 1000, 10000, 100000。你想让AI更激进就调大case 4和case 3的正向权重更保守就调大case 2。调参这件事没有标准答案但权重比例必须保持“五连 ≫ 四子 ≫ 三子”否则AI会在胜负关键点上犯浑。4.3 Minimax搜索与Alpha-Beta剪枝AI一步棋怎么想Minimax的核心是一个递归函数AI轮次挑最高分玩家轮次挑最低分直到搜索深度归零返回评估值。Alpha-Beta剪枝给这个递归加了两个边界值——alpha是AI已拿到的最好结果beta是玩家已拿到的最好结果一旦beta alpha就说明当前分支已经不可能影响最终决策直接剪掉public int alphaBeta(ChessBoard board, int depth, int alpha, int beta, boolean isAiTurn, int aiColor) { if (depth 0) { return evaluateBoard(board, aiColor); } int opponentColor (aiColor ChessBoard.BLACK) ? ChessBoard.WHITE : ChessBoard.BLACK; int currentColor isAiTurn ? aiColor : opponentColor; if (isAiTurn) { int maxScore Integer.MIN_VALUE; for (int row 0; row ChessBoard.SIZE; row) { for (int col 0; col ChessBoard.SIZE; col) { if (board.getPiece(row, col) ! ChessBoard.EMPTY) continue; board.placePiece(row, col, currentColor); int score alphaBeta(board, depth - 1, alpha, beta, false, aiColor); board.placePiece(row, col, ChessBoard.EMPTY); maxScore Math.max(maxScore, score); alpha Math.max(alpha, score); if (beta alpha) { return maxScore; } } } return maxScore; } else { int minScore Integer.MAX_VALUE; for (int row 0; row ChessBoard.SIZE; row) { for (int col 0; col ChessBoard.SIZE; col) { if (board.getPiece(row, col) ! ChessBoard.EMPTY) continue; board.placePiece(row, col, currentColor); int score alphaBeta(board, depth - 1, alpha, beta, true, aiColor); board.placePiece(row, col, ChessBoard.EMPTY); minScore Math.min(minScore, score); beta Math.min(beta, score); if (beta alpha) { return minScore; } } } return minScore; } }逻辑说明每一次模拟落子后都紧跟着一行board.placePiece(row, col, ChessBoard.EMPTY)这行代码是用来撤销模拟的。漏掉它的话搜索树会在第一次分支后把棋盘改得乱七八糟后续所有分支拿到的都是坏数据。对深度2来说这套三重循环的总次数大约几千次加上剪枝后实际更少普通机器跑起来没什么压力。参数说明外部调用时depth传2或者3alpha传Integer.MIN_VALUEbeta传Integer.MAX_VALUE。深度2对应的AI棋力相当于新手深度3会明显变强但耗时可能超过2秒。若想提速配合5.3的候选点剪枝——只对“离已落子不超过2格”的空位展开分支可以把搜索量压到原来的十分之一。这就是很多五子棋项目的棋力玄学同一个评估函数把全盘扫描改成邻域扫描速度反而快几倍因为无效分支全被过滤了。5. 避坑指南编译故障、点击错位与AI犯傻的五条实录运行别人给的源码比自己写容易踩坑这套包我完整跑过一遍也看过多位同学在不同环节失败。下面五条是出现频率最高的按“现象→原因→解决”排好顺序就是它出现的概率排序。5.1 jar包双击无反应和“找不到主类”现象双击chess.jar闪退或者没有任何反馈命令行运行报Error: Could not find or load main class。原因八成是MANIFEST.MF没有声明Main-Class或者classpath里缺少依赖。另外JDK版本不一致也会导致UnsupportedClassVersionError——在JDK 17编译的class文件JDK 8的JRE跑不了。解决先检查你电脑上java -version和javac -version是否一致不一致就去配置好JAVA_HOME和PATH环境变量。一致后重新编译打包javac -encoding UTF-8 -d bin src/**/*.java jar cvfe chess.jar MainClassName -C bin .逻辑说明-encoding UTF-8防止源码里中文注释在编译后乱码。MainClassName在你自己的项目里就是包含public static void main的那个类的全限定名含包名。如果源码里用了多级包结构src/**/*.java用通配符一次全部编译。-C bin .表示把bin目录下的所有class打包进jar-e参数顺带在MANIFEST里写入Main-Class。参数说明chess.jar是打包输出文件名可自行修改。如果编译时用了第三方jar包打包还要加Class-Path但这份源码不依赖任何第三方库纯JDK内置Swing所以不用处理依赖。5.2 鼠标点击位置和棋盘格总是错位现象点棋盘的交叉点棋子却落在相邻位置越是靠近边缘越明显。原因margin在paintComponent里和mouseClicked里不一致或者cellSize被调整过但换算公式没同步。解决把cellSize和margin定义成面板成员变量全类只使用这两个值禁止在监听器里写死数字。再检查监听器的换算公式是否包含了cellSize / 2的偏移修正。这是坐标换算的必踩坑之一我每次都会手打一遍检查。5.3 AI一步棋要卡好几秒界面直接假死现象玩家落子后黑屏几秒鼠标变成转圈最后AI才落子。原因AI在事件线程里同步计算阻塞了Swing的事件分发循环。另一个常见原因就是搜索深度为4、全盘扫描所有空位没有预剪枝最坏情况搜几千万个节点不卡才怪。解决把AI放进SwingWorker后台线程同时限制搜索深度为3并在搜索前用候选点过滤提前跳过明显无用的空位。private boolean isCandidate(ChessBoard board, int row, int col) { for (int r row - 2; r row 2; r) { for (int c col - 2; c col 2; c) { if (r 0 r ChessBoard.SIZE c 0 c ChessBoard.SIZE board.getPiece(r, c) ! ChessBoard.EMPTY) { return true; } } } return false; }逻辑说明候选点的思路是离所有已落子超过2格的空位既不可能是必胜点也不可能是关键防守点完全没有必要让评估函数去算分。开局前几步每颗子都是孤立的候选点过滤会把这些“空一圈”的位置全部排除搜索规模直接缩到几十个候选点速度提升一个数量级。代价是AI永远不会走出“远离战场的突然一击”但那种棋在五子棋里本来就是无效棋。参数说明半径2格是经验指标一般不建议改大。半径3格虽然能捕捉更远的外势但候选点数量会从几十膨胀到上百搜索时间几何级增长。如果你的程序做完候选点过滤后AI依然慢就把主函数里的depth从3降到2先保证演示流畅再说棋力。5.4 AI明明能赢却去堵玩家白瞎好局现象AI已有活四差一步成五但它这步棋却下到远处去堵玩家的冲三让活四报废下一手被玩家反杀。原因评估函数不区分“活四”和“冲四”。“差一步就赢”的棋形和“一端被堵的四连”分值一样AI在搜索时可能被玩家那边的威胁带偏。解决在评估函数里对棋形两端分别做“是否可延伸”的判断活四权重应该达到冲四的23倍。课程设计阶段能把这步棋写成“能赢就走了”答辩时就是加分项。常规做法是加一个isAliveFour的检查方法在count 4的分支里额外判断“两端是否为空”两端都空才算活四一端为空只能算冲四。5.5 AI落子历史越积越多内存悄悄涨现象下满半盘后程序越跑越慢内存占用持续上升。原因悔棋栈没清空或者每次落子时controller里不断new Object且旧对象一直有引用。比较隐蔽的是addMouseListener被放在paintComponent里每次重绘都堆一个监听器——这个方法绝不能放。解决把addMouseListener放到面板构造函数里只执行一次。悔棋栈在游戏开始时调用history.clear()不要等到棋盘满了才清。如果用了SwingWorker检查done()里是否真的是事件线程操作组件避免残留任务堆积。6. 进阶调参评估权重怎么改、答辩演示怎么稳项目能用只是及格最后的加分项在调参和现场表现。评估函数里那组权重就是你手里的调音台。6.1 评估函数权重调整法在evaluatePoint的switch里把五个段位的分值按你的风格微调。我常用的一组是五连10万分不可动摇、活四5万分、冲四1万分、活三5000分、眠三500分。如果你发现AI总是无视玩家连续做活三的威胁就把玩家的case 3负权值从1000提高到3000AI会变得保守许多。调参没有一次性调对的多下两盘找感觉每次只改一个数改完不要立刻换另一个否则你根本不知道谁在起作用。6.2 答辩现场的稳定演示法现场演示是不少人的翻车现场。我的习惯是准备两个固定走法先走一个“开局平稳发展”让AI正常应对再走一个“双三必胜局”三招定胜负体现你项目的AI确实有判断力。演示前把这两个走法各跑5遍确认每一步的点位不会误触。如果现场电脑配置差提前把深度设成2放弃棋力保流畅——宁可AI笨一点也不能卡屏。从那以后我每次演示前都强制把“开局→中盘→杀棋”三步棋完整走一遍这个习惯救了我至少三次答辩。完整的源码包和设计报告都在项目里直接用就行按README.md跑一遍再对照着改比从零凑代码省事得多。希望这份源码和踩坑记录能帮你省下几个晚上的调试时间让Java课程设计一次过关。本文还有配套的精品资源点击获取