Java五子棋源码深度解析:从事件监听到AI评估的完整课设指南

发布时间:2026/10/31 17:09:35

Java五子棋源码深度解析:从事件监听到AI评估的完整课设指南 简介Java实现的经典五子棋小游戏源码定位于Java课程设计、入门练手与小游戏开发学习场景面向有一定Java基础或对游戏开发感兴趣的学生和开发者。压缩包共24个文件其中8个java源文件与8个class编译文件一一对应既可直接阅读详细实现也能运行体验完整游戏另有3个xml工程配置文件、2张png游戏界面截图对局界面与获胜界面、1份README说明文档整包仅90KB非常轻量几乎免配置即可导入开发环境。源码模块划分清晰GobangMain作为程序入口GobangListener负责鼠标落子等事件监听Chess维护棋子状态Gobang实现棋盘绘制Judge完成五子连珠胜负判定同时EasyAi与DifficultAI分别对应简单与困难两种机器对手覆盖了界面绘制、事件响应、规则判断与基础博弈AI等关键环节适合系统学习JavaSwing图形编程与人机对战设计。项目结构规范src目录放源码、out目录放编译结果、imgs目录存界面截图便于对照调试。目前已有84人学习浏览可作为课程设计直接参考也可作为二次开发起点继续扩展联机对战、悔棋或更强AI。1. 五子棋这套Java源码课程设计选它到底值不值五子棋在Java课程设计里算是个被低估的选择界面不复杂但事件监听、二维数组、算法评估、面向对象拆分全都碰得到。这个源码包正好落在这条线上——入口、棋子、棋盘、监听、胜负判定、两档AI全部拆成独立类直接打开就能编译运行。如果你正在挑Java小游戏课设或者在准备Java实习面试想拿个能讲清的项目这份代码比贪吃蛇、推箱子更能往深里挖。后面我会把每个类的职责边界、判定算法和AI评估函数拆开讲最后给出一版能答辩的改造方案。2. 源码结构拆解八个Java文件的职责边界与主循环2.1 入口与装配GobangMain和Gobang的分工拿到源码包先看src目录八个.java文件按名字能直接猜出大半职责GobangMain是程序入口Gobang是棋盘主逻辑Chess是棋子对象GobangListener是鼠标监听器Judge是胜负判定EasyAi和DifficultAI是两档AI剩下一个Exam是校验入口。这种命名方式对课程设计答辩很友好——老师问“这个类干嘛的”看名字就能答上来。GobangMain只做两件事创建主窗口、把棋盘面板挂进去。常见做法是Gobang继承JPanel负责绘制棋盘和棋子GobangMain继承JFrame负责窗口外壳两者通过setContentPane或add组装。窗口初始化的代码大致长这样// GobangMain.java —— 入口只做装配不掺业务逻辑 public class GobangMain extends JFrame { public GobangMain() { setTitle(五子棋 - Java版); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Gobang panel new Gobang(); // 棋盘面板持有数据模型 setContentPane(panel); // 把面板装进窗口 pack(); // 根据面板首选尺寸自适应窗口 setLocationRelativeTo(null); // 窗口居中 setResizable(false); // 禁止拉伸防止棋盘变形 } public static void main(String[] args) { SwingUtilities.invokeLater(() - { GobangMain window new GobangMain(); window.setVisible(true); }); } }注意SwingUtilities.invokeLater这一层包装。Swing 的界面操作必须跑在事件分发线程EDT上main方法里直接new JFrame虽然也能跑但偶尔会出现界面绘制不全的玄学问题。从main进invokeLater是把任务丢进EDT队列这是规范写法面试问到 Swing 线程模型时这是个能主动讲出来的点。setResizable(false)不是随便加的。五子棋棋盘按固定格子数绘制一旦窗口被拉伸棋盘比例会跟着变MouseListener里的坐标换算就全错了。实测踩过这个坑不锁窗大小拖一下窗口边缘落子位置就偏了一格。2.2 棋盘与棋子的建模二维数组、交叉点坐标与重绘机制下棋本质是往一个二维数组里写值。15×15 的五子棋棋盘有 225 个交叉点对应模型层就是一个int[15][15]数组0 表示空1 表示黑子2 表示白子。Chess.java这个类一般只包三个字段横坐标、纵坐标、棋子颜色再加一个构造方法。它是纯数据载体不承担绘制职责这个拆分是Java基础里“单一职责”的体现。棋盘绘制在Gobang的paintComponent里完成先画网格线和星位再遍历数组把非空位置的棋子画出来。每次落子后调用repaint()触发重绘这是 Swing 2D 绘图的标准套路说白了就是“改数据 → 通知界面刷新”// Gobang.java —— 绘制棋盘与棋子棋盘数据也放在这里 public class Gobang extends JPanel { public static final int BOARD_SIZE 15; // 棋盘格数 public static final int CELL_SIZE 36; // 每格像素 public static final int MARGIN 25; // 窗口边缘留白 private int[][] board new int[BOARD_SIZE][BOARD_SIZE]; private Color pieceColor Color.BLACK; // 当前轮到谁落子 Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 画网格从左上角第一个交叉点开始向右下画横竖线 for (int i 0; i BOARD_SIZE; i) { int pos MARGIN i * CELL_SIZE; g.drawLine(MARGIN, pos, MARGIN (BOARD_SIZE - 1) * CELL_SIZE, pos); g.drawLine(pos, MARGIN, pos, MARGIN (BOARD_SIZE - 1) * CELL_SIZE); } // 画棋子只遍历数组里有值的坐标 for (int row 0; row BOARD_SIZE; row) { for (int col 0; col BOARD_SIZE; col) { if (board[row][col] 0) continue; g.setColor(board[row][col] 1 ? Color.BLACK : Color.WHITE); int x MARGIN col * CELL_SIZE - 15; int y MARGIN row * CELL_SIZE - 15; g.fillOval(x, y, 30, 30); } } } Override public Dimension getPreferredSize() { // 面板首选尺寸两倍留白 14个格子间距 return new Dimension(MARGIN * 2 (BOARD_SIZE - 1) * CELL_SIZE, MARGIN * 2 (BOARD_SIZE - 1) * CELL_SIZE); } }getPreferredSize和pack()配合窗口大小不用手工算由布局管理器自动取面板尺寸。很多新手把窗口尺寸硬编码成 600×600棋盘实际占不满或溢出这个动态算尺寸的做法更稳。Chess类在落子和悔棋时会用到。悔棋要“撤销上一次落子”就得知道上一次棋子的位置和颜色所以Chess至少要存横纵坐标和颜色三个字段。这个后面第6章讲悔棋时会具体展开。2.3 胜负判定Judge类四方向扫描最容易写错的地方在哪Judge.java是这类项目里“看起来简单、写起来容易翻车”的部分。五连判定大多数人第一反应是落子后遍历整个棋盘找有没有五连但更高效也更好写的做法是只围绕“刚落的这颗子”向四个方向水平、垂直、两条对角线的正反两端延伸数连续同色棋子数到五颗就判胜。// Judge.java —— 以刚落子为中心向四个方向延伸检查 public class Judge { // 四个方向水平、垂直、主对角线、副对角线 private static final int[][] DIRS {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; public static boolean checkWin(int[][] board, int row, int col, int player) { for (int[] d : DIRS) { int count 1; // 当前落子自身算一颗 // 沿正方向数连续同色棋 for (int i 1; i 5; i) { int nRow row d[0] * i; int nCol col d[1] * i; if (nRow 0 || nRow board.length || nCol 0 || nCol board[0].length) { break; } if (board[nRow][nCol] player) { count; } else { break; } } // 沿反方向数连续同色棋 for (int i 1; i 5; i) { int nRow row - d[0] * i; int nCol col - d[1] * i; if (nRow 0 || nRow board.length || nCol 0 || nCol board[0].length) { break; } if (board[nRow][nCol] player) { count; } else { break; } } if (count 5) { return true; } } return false; } }这段代码的边界检查是关键。方向数组DIRS四个元素分别代表{1,0}向右、{0,1}向下、{1,1}向右下、{1,-1}向右上。注意副对角线方向用的是{1,-1}行坐标加一行、列坐标减一列一旦把符号写反斜向五连永远判不出来。循环只跑到i 5是因为五子棋判胜只需要数到五颗。即使一条线上有六颗甚至七颗连子“达到五颗”这个条件已经满足继续数下去没有意义反而要小心数组越界。这个里头的常见错误是for (int i 0; i 5; i)从0开始结果把自身重复数了一遍四颗连子被误判成五连。Exam.java在这个包里更像是回归校验入口——把一组摆好的棋局数据丢给Judge验证黑子白子各自的判定结果是否符合预期。课程设计里留这么个类很加分答辩时现场跑一遍测试数据比嘴上解释“我的判定逻辑是对的”有说服力得多。3. 事件驱动与控制反转GobangListener的鼠标坐标换算与落子状态机3.1 鼠标点击到交叉点的坐标换算容差、越界与就近取整Swing 的MouseListener只是事件源它不关心业务规则。“点击屏幕的某个像素点”要变成“棋盘第几行第几列”中间隔着一层坐标换算。这层换算放在GobangListener里是整个交互流程的第一道关卡。// GobangListener.java —— 坐标换算与落子入口 public class GobangListener implements MouseListener { private Gobang panel; // 持有棋盘面板引用才能读写棋盘数据 public GobangListener(Gobang panel) { this.panel panel; } Override public void mouseClicked(MouseEvent e) { // 棋盘像素坐标 → 网格索引 int col (e.getX() - Gobang.MARGIN Gobang.CELL_SIZE / 2) / Gobang.CELL_SIZE; int row (e.getY() - Gobang.MARGIN Gobang.CELL_SIZE / 2) / Gobang.CELL_SIZE; // 越界检查点在窗口内但不在棋盘交叉点范围 if (col 0 || col Gobang.BOARD_SIZE || row 0 || row Gobang.BOARD_SIZE) { return; } // 占位检查这里已经有棋子了就不能再落 if (panel.getBoard()[row][col] ! 0) { return; } // 交给主逻辑完成落子、判胜、AI回应 panel.placePiece(row, col); } }核心是(e.getX() - MARGIN CELL_SIZE / 2) / CELL_SIZE这条公式。直觉做法是(e.getX() - MARGIN) / CELL_SIZE但这样点击点落在格子左半边缘时会出现“偏了一格”的感觉。加上CELL_SIZE / 2再做整数除法等价于四舍五入取最近的交叉点手感会准很多。这属于参数微调里很影响体验的一处很多源码包里就是少这个半格偏移被老师说“点不准”。两个return分支都是防御性写法。越界检查防的是玩家点击窗口边缘空白区域时报负数索引数组直接ArrayIndexOutOfBoundsException占位检查防的是同一交叉点重复落子把对方棋子覆盖掉。这两个分支缺一个玩不了几手就会崩。坐标换算公式里的MARGIN和CELL_SIZE必须和Gobang里绘制棋盘用到的常量完全一致。绘制时格子起点从MARGIN开始监听器计算时也从MARGIN开始两个类共用同一份常量否则画出来的棋子和点击命中区域会整体错位。3.2 落子流程与换手状态机一局棋从开始到结束的事件时序监听器只负责“点击事件”真正的游戏规则在Gobang的placePiece方法里。这里是个典型的状态机当前该谁走、棋盘是否已分出胜负、落子后是否要调用AI回手。把这几个状态理清整个游戏循环就通了。// Gobang.java —— 落子主流程 public void placePiece(int row, int col) { // 状态检查已经结束了就不再响应落子 if (gameOver) { return; } // 当前玩家落子 board[row][col] currentPlayer; Chess lastMove new Chess(row, col, currentPlayer); moveHistory.push(lastMove); // 记录历史悔棋用 repaint(); // 判胜如果当前玩家赢了结束对局 if (Judge.checkWin(board, row, col, currentPlayer)) { gameOver true; JOptionPane.showMessageDialog(this, (currentPlayer 1 ? 黑棋 : 白棋) 获胜); return; } // 换手 AI回应 currentPlayer (currentPlayer 1) ? 2 : 1; // 简单规则黑先白后白棋由AI自动回应 if (currentPlayer 2 !gameOver) { int[] aiMove ai.getMove(board, 2); board[aiMove[0]][aiMove[1]] 2; moveHistory.push(new Chess(aiMove[0], aiMove[1], 2)); repaint(); if (Judge.checkWin(board, aiMove[0], aiMove[1], 2)) { gameOver true; JOptionPane.showMessageDialog(this, AI获胜); } currentPlayer 1; // AI走完换回人类 } }这段流程有几个边界值得细说。第一所有状态修改集中在placePiece里监听器不直接改board数组。这是“控制反转”在Swing里的实际落点——事件源鼠标把控制权交给业务逻辑而不是业务逻辑反过来被事件牵着走。Java面试里常问的“依赖倒置”“回调机制”这个结构就是活例子。第二AI落子放在人类落子之后同步执行。ai.getMove(board, 2)返回一个int[2]数组作为落子坐标这在单线程模型下没问题因为EasyAI和DifficultAI的计算量都很小几十毫秒内能返回结果。如果以后要上深度搜索比如10层递归加Alpha-Beta剪枝就得把AI计算丢到后台线程否则界面会卡住——这个包目前没做多线程是合理的取舍。第三moveHistory这个栈结构现在看不出用处它给第6章的悔棋留了接口。落子前先记录悔棋时从栈顶弹出两步AI一步、人类一步就能回到上个人类回合。这种“为扩展留结构”的做法在答辩时讲出来是加分项。me4. 双AI从入门到进阶贪心打分、评估函数与难度差异的落地4.1 简单AI为什么“好赢”活三扫描与随机兜底EasyAi.java这个类的定位是给新手玩家练手。它不需要多聪明但要“像个人在下棋”不能像乱下的新手一样满盘瞎点。常见的实现是遍历所有空位对每个空位做一次简单打分优先选分数最高的位置。// EasyAi.java —— 简易打分数连续子和己方棋型 public class EasyAi { public int[] getMove(int[][] board, int player) { int bestScore -1; int bestRow -1, bestCol -1; for (int row 0; row 15; row) { for (int col 0; col 15; col) { if (board[row][col] ! 0) continue; int score evaluatePosition(board, row, col, player); if (score bestScore) { bestScore score; bestRow row; bestCol col; } } } // 兜底棋盘点全空时走中间 if (bestScore -1) { return new int[]{7, 7}; } return new int[]{bestRow, bestCol}; } private int evaluatePosition(int[][] board, int row, int col, int player) { // 只看这个点周围8个方向里有多少颗我方连子 int score 0; int[][] dirs {{1,0},{0,1},{1,1},{1,-1}}; for (int[] d : dirs) { int count 1; for (int i 1; i 2; i) { // 只向前后各看两格 int nr row d[0] * i; int nc col d[1] * i; if (nr 0 nr 15 nc 0 nc 15 board[nr][nc] player) { count; } } if (count 3) score 50; // 能凑出三连优先 else if (count 2) score 10; } return score; } }EasyAI 的评分策略其实很粗暴主力放在“和自己已有棋子凑三连”上完全不评估对方棋型。所以它会被“双三”“冲四”这类简单战术骗到——你搭个双活三它根本意识不到威胁还在忙着连自己的子。这正是难度设计的巧妙之处不是让AI乱下而是让AI“只顾自己、不管别人”玩家稍微动点脑筋就能赢。evaluatePosition里的方向数组只用4个方向因为对一个空位来说前后对称的连子数已经在正反两个方向叠加了。count 3给50分、count 2给10分这些权重拍脑袋定的。EasyAI 本身就允许“不太聪明”权值不用太讲究。4.2 困难AI的评估函数棋型权值表怎么设计才算“聪明”DifficultAI.java才是这套源码的技术核心。它的评分逻辑不再是“我自己能连几颗”而是“我把子落在这里能形成什么棋型同时能破坏对方什么棋型”。这就引入了五子棋AI里最经典的概念——棋型权值表。// DifficultAI.java —— 评估函数棋型权值表 攻守加权 public class DifficultAI { // 棋型权值表数值越大说明这个棋型越危险/越有价值 private static final int SCORE_FIVE 100000; // 五连直接赢 private static final int SCORE_LIVE_FOUR 10000; // 活四两端都没被堵 private static final int SCORE_DIED_FOUR 1000; // 冲四一端被堵 private static final int SCORE_LIVE_THREE 800; // 活三再走一步成活四 private static final int SCORE_DIED_THREE 200; // 眠三 private static final int SCORE_LIVE_TWO 50; // 活二 private static final int SCORE_DIED_TWO 10; // 眠二 public int[] getMove(int[][] board, int player) { int bestScore Integer.MIN_VALUE; int bestRow -1, bestCol -1; int opponent (player 1) ? 2 : 1; for (int row 0; row 15; row) { for (int col 0; col 15; col) { if (board[row][col] ! 0) continue; // 分别计算“落这里对己方的价值”和“落这里对对方的破坏” int myScore evaluateMove(board, row, col, player); int opScore evaluateMove(board, row, col, opponent); // 总价值 己方进攻价值 对方防守价值 × 0.9 // 0.9 是攻守权重略微偏进攻但对方有杀棋时优先防守 int total myScore (int) (opScore * 0.9); if (total bestScore) { bestScore total; bestRow row; bestCol col; } } } return new int[]{bestRow, bestCol}; } }这套打分逻辑的核心理念就一句话评估的不只是“落子后自己的局面”还要算“如果这个位置被对方占了会怎样”。myScore负责进攻opScore * 0.9负责防守权值系数 0.9 意味着AI在“自己发展”和“堵对方”之间略微偏向进攻。为什么要乘0.9而不是完全取最大值如果取myScore opScoreAI会过度防守——对方只要有个活二它就屁颠屁颠去堵完全不发展自己的局面下到最后全是防守没有进攻。如果只取myScoreAI又成了 EasyAI 的加强版只会攻不会守。0.9 是经验值既保留进攻性又能在对方出现“冲四活三”这类必杀棋型时及时堵截。evaluateMove内部需要把以该点为中心的横竖斜四条线分别拿出来做棋型识别把连续同色子的数量和端点是否被堵组合成“活四、冲四、活三”等棋型再查表打分。真正的棋型识别比这段逻辑复杂不少但权值表是它的骨架。4.3 攻守权重与棋型分值的调整方向:怎么调出让玩家“有压力”的AI拿到源码后我最推荐你自己动的地方是权值表和攻守系数。这两个参数直接决定AI“脾气”SCORE_DIED_FOUR和SCORE_LIVE_THREE的相对大小决定了AI在“冲四威胁”和“活三发展”之间怎么选攻守系数 0.9 调成 0.6AI会明显变激进。我调参时的一个经验先固定棋型表不动只调opScore的系数。从 0.5 到 1.5 每隔 0.1 跑五局记录胜负比。多数情况下 0.9 到 1.0 之间AI胜率最高低于0.7容易被人用双三杀穿高于1.1则经常出现“AI宁可放弃自己活四也要堵对方一个眠二”的迷惑行为。这部分属于五子棋AI的“玄学”区间不同实现手感差异很大必须实测。另一个可以调的是棋型表里SCORE_LIVE_FOUR与SCORE_FIVE的比值。理论上活四下一步就是五连但在代码里活四和五连的分值差距决定了AI是否会“贪心留一步”。有些实现里SCORE_FIVE只设成SCORE_LIVE_FOUR 1AI就会在能直接赢时毫不犹豫走赢棋如果差距拉大AI反而可能出现“明明能赢但去堵对方”的怪棋。这个包的AI没做搜索深度纯靠评估函数打分所以权值表的合理性几乎决定了一切。5. 运行与调试排查五条高频踩坑记录与解决顺序5.1 编译报“找不到符号”或主类缺失入口与包名对不上现象拿到源码用javac编译报一堆找不到符号或者编译过了但java GobangMain直接提示错误: 找不到或无法加载主类。原因这个包的.java文件没有包名声明直接放在src根目录下。如果你把文件拖进某个带package的工程目录里编译器会按包结构找类入口路径全乱。IDEA里直接Open整个源码包通常没问题但从别的工程里“复制代码进去”很容易踩这个坑。解决先看第一行有没有package声明。没有就把所有.java保持在同一个目录下在src目录里执行javac src/*.java -d out java -cp out GobangMain逻辑说明javac src/*.java一次编译所有源文件省去手动处理依赖顺序的麻烦-d out指定编译产物目录java -cp out GobangMain指定类路径为输出目录再指定完整类名。这里不用带.class后缀这是Java命令最容易踩的小坑。5.2 中文乱码Windows控制台和源文件编码不一致现象运行后弹窗标题、按钮文字全是黑棋之类的乱码或者在控制台打印日志时中文变问号。原因源码文件是 UTF-8 编码Windows 控制台默认用 GBK 读取两边对不上。IDEA 里跑可能正常命令行java跑就乱。解决编译和运行时都显式指定编码javac -encoding UTF-8 src/*.java -d out java -Dfile.encodingUTF-8 -cp out GobangMain逻辑说明-encoding UTF-8让编译器按 UTF-8 读源码-Dfile.encodingUTF-8让 JVM 按 UTF-8 输出字符串。两步都要做只做前者运行时界面照样乱。这个坑在现场答辩时最容易暴露——教室电脑控制台编码没配置一运行满屏乱码印象分会大打折扣。5.3 点击棋盘没反应监听器没挂上或占位检查误伤现象窗口能弹出来棋盘画得也没问题但鼠标点哪都不落子一点反应都没有。原因GobangMain或者Gobang的构造函数里忘了调用addMouseListener。这个包里的GobangListener是单独一个类如果只new了它没绑定到面板上事件根本不会被派发。解决在Gobang构造函数里补上绑定代码// Gobang.java 构造函数内 GobangListener listener new GobangListener(this); addMouseListener(listener);逻辑说明addMouseListener的参数是实现MouseListener接口的对象内部由 Swing 的事件分发机制回调。这里常犯的错误是给JFrame加了监听器但棋盘绘制在JPanel上鼠标点在面板区域时事件派发不到JFrame上。先确认是给谁加的监听器再确认this指向的是不是真正接收鼠标事件的面板对象。5.4 AI不落子或直接卡死评估函数里的数组越界与死循环现象人类落子后界面卡住不动CPU 占用率飙升过半天AI才回手或者干脆崩溃报ArrayIndexOutOfBoundsException。原因评估函数里遍历空位时evaluateMove内部对某个方向的延伸检查没有做边界判断。棋盘索引跑到 -1 或 15 就会抛异常异常没被捕获直接打断事件回调线程界面当然没反应。死循环则是棋型识别里while循环缺少终止条件正方向数完没把索引恢复到原位接着数反方向时就死循环。解决给AI的坐标遍历加一个统一边界守卫在getMove入口处先做一轮全盘检查// DifficultAI.java getMove 开头 for (int row 0; row 15; row) { for (int col 0; col 15; col) { if (board[row][col] ! 0) continue; // 只评估棋盘范围内的点 if (row 0 || row 15 || col 0 || col 15) continue; // ... 原有评分逻辑 } }逻辑说明越界检查必须放在最前面先于任何数组访问操作。实际调这类问题我一般先在evaluateMove里加一行System.out.println(row , col)跑两步看日志输出到哪个坐标卡住定位到具体方向数组再修比对着代码一行行猜效率高得多。5.5 窗口拖拽后棋盘错位固定尺寸与动态布局的冲突现象运行正常但拖动窗口边缘改变大小后棋盘的一部分被裁掉或者落子位置和棋子显示位置明显偏了一格。原因前面第2章讲了pack()和getPreferredSize的配合。如果setResizable(false)这行被删了Swing 的布局管理器会根据窗口大小重新计算面板尺寸但MARGIN和CELL_SIZE还是写死的绘制和监听的坐标就全乱了。解决在GobangMain构造函数里保留setResizable(false)或者给Gobang的paintComponent加一个基于当前面板尺寸的动态缩放。课程设计里直接锁窗口是最省事的方案不用引入复杂的布局计算。检查依据是窗口标题栏右侧的“最大化”按钮是否是置灰状态——不是置灰就说明setResizable(false)没生效。6. 把它改成一版能答辩的课设悔棋栈、计时器和演示话术6.1 悔棋只需要一个栈人类悔一步AI也跟着退回一步很多课程设计版本没有悔棋功能答辩时老师最爱问的就是“能悔棋吗”。加上它其实不难——利用前面placePiece里留的moveHistory栈悔棋就是回退两个棋子// Gobang.java 新增悔棋方法 public void undoMove() { if (moveHistory.size() 2) return; // 没有足够步数可悔 // 弹出AI的落子 Chess aiMove moveHistory.pop(); board[aiMove.getRow()][aiMove.getCol()] 0; // 弹出人类玩家的落子 Chess humanMove moveHistory.pop(); board[humanMove.getRow()][humanMove.getCol()] 0; currentPlayer humanMove.getColor(); // 回到人类回合 gameOver false; // 如果原来赢了悔棋后继续 repaint(); }逻辑说明悔棋必须成对弹出——人类悔一步AI自动悔一步否则会变成“人类多走一手”的不公平局面。gameOver false这行很多人会漏胜利对话框弹完之后棋盘状态还锁着悔棋也不响应。这个细节是答辩时能现场演示的亮点赢了之后点悔棋对局状态正常恢复。功能加完后配合项目里imgs目录下的界面.png和获胜.png两张截图做演示。开场先切到界面图讲整体布局跑一局后让AI获胜再切到获胜图展示弹窗效果最后点悔棋演示“赢了也能回到上一手”这个演示节奏三分钟内能讲完。6.2 计时器与难度切换答辩现场的两个加分项计时器和难度切换是另外两个低成本高回报的改动。计时器用javax.swing.Timer每1秒触发一次在监听器里更新时间显示难度切换是在界面上加两个单选按钮切换时替换Gobang持有的EasyAi和DifficultAI实例。这两个功能加完这份源码基本覆盖了课程设计评分标准里“功能完整、交互良好”的档位。我个人的习惯是拿到项目先不改任何业务代码第一遍只是从头到尾跑通跑的过程里用备忘录记下“这里报错了、那里手感奇怪”。第二遍再动手改功能改完一定把每个功能走一遍回归。从那以后我每次给课设项目做改造都强制先跑通原版再动代码否则你永远分不清报错是你改出来的还是原版自带的坑。希望这篇拆解能让你少走几趟弯路把时间花在真正能讲出东西的AI评估函数和项目结构上。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/31 17:09:35

VLA模型π0实战指南:机器人控制与多模态架构部署

1. VLA模型π0速通指南:从原理到实战部署 在机器人控制领域,通用策略模型正经历革命性突破。Physical Intelligence公司最新发布的π0模型(代号pi-zero)代表了当前最先进的视觉-语言-动作(VLA)多模态架构&a…

2026/10/31 17:09:35

轻量级资源聚合插件 ponytail:扫描、匹配、聚合告别散装资源

上个月我在整理前端项目的静态资源时,被一堆散落在各个目录里的图标、字体和配置片段逼得受不了,干脆自己做了一个叫 ponytail 的轻量级聚合插件。这篇博文就把 ponytail 插件是什么、能解决什么问题、怎么安装、怎么配置,以及我实际使用过程…

2026/10/31 17:09:35

从技能拆解到刻意练习:构建属于你的高效学习系统

Skill这个词,这几年被各种平台讲烂了——一会儿说“核心竞争力”,一会儿说“复合型人才”,一会儿又说“AI时代要学提示词工程”。但真正把“学技能”这件事当成一门技能来研究的人,少之又少。我自己做了几年项目,带过新…

2026/10/31 18:04:37

SpringBoot考研互助平台开发实践与架构解析

1. 项目背景与核心价值考研备考是一场持久战,很多考生在这个过程中会遇到各种问题:资料获取困难、学习进度难以把控、疑难问题无人解答、备考压力无处释放。传统的考研论坛和QQ群已经难以满足当下考生的需求——前者功能单一缺乏互动性,后者信…

2026/10/31 18:04:37

PostgreSQL 15 shared_buffers参数优化指南

1. PostgreSQL 15中的shared_buffers参数解析shared_buffers是PostgreSQL中最重要的内存配置参数之一,它定义了数据库服务器用于缓存数据的内存区域大小。这个参数直接影响到数据库的查询性能和整体吞吐量。在PostgreSQL 15中,这个参数的配置方式与之前版…

2026/10/31 18:04:37

Oracle APEX 4.2.5 开发实战:环境搭建与表单动态交互

简介:《Oracle APEX深入浅出》是一份面向Oracle APEX初学者与中级开发者的技术文档,侧重解决从环境搭建到企业级应用开发中的知识断层问题。资源为单个doc文档,压缩包约8.11MB,内容以开发指南形式组织,适合作为系统学习…

2026/10/31 18:04:37

GDPO与VideoAuto-R1:多目标强化学习与视频推理的技术突破

1. 论文核心内容解析:GDPO与VideoAuto-R1的技术突破 1.1 GDPO:多奖励强化学习的解耦归一化策略 在多目标强化学习场景中,传统方法通常采用加权求和的方式处理多个奖励信号。这种简单粗暴的合并方式存在三个致命缺陷: 不同奖励量…

2026/10/31 17:59:37

技术直播拆解:硬核知识可视化与信任经济新形态

1. 雷军拆车直播现象解析5小时不间断的汽车拆解直播,这个看似硬核枯燥的内容形式却在雷军的操盘下成为现象级传播案例。这场直播之所以能够突破圈层引发广泛讨论,关键在于它精准抓住了当前内容市场的三个核心痛点:硬核知识的可视化呈现&#…

2026/10/30 20:55:10

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/30 20:55:10

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/30 20:55:09

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/31 0:02:41

猫狗目标检测数据集:VOC/COCO/YOLO三格式一体化实战包

简介:本资源是一套面向计算机视觉初学者与YOLO目标检测实践者的猫狗图像数据集及配套开发工具包,专为快速上手目标检测模型训练而设计。资源包含1000张真实场景高清猫狗图片,标注质量高,同时提供VOC(XML)、…

2026/10/31 0:02:41

HDU2017多校标程复现:算法竞赛工程化真题沙盒

简介:本资源为2017年HDU多校联合训练第一场官方配套资料,面向ACM程序设计竞赛备赛学生、算法爱好者及高校编程集训队成员,旨在提供权威解题参考与完整测试验证环境。压缩包共40个文件,含15份C标程(覆盖全部赛题&#x…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑