news 2026/9/9 20:08:50

从零实现Java五子棋:核心算法与界面编程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零实现Java五子棋:核心算法与界面编程实战

简介:面向Java开发者的五子棋游戏实战教程资源包,覆盖棋盘设计、棋子逻辑、玩家交互、AI算法与图形界面构建等核心环节,适合希望通过完整项目入门游戏开发的编程学习者。压缩包共65个文件,包含工程源码、可执行程序、音频与位图素材、配置说明及文档等类型,整体大小仅2.73MB,结构清晰便于按需检索。目前已有204人学习,教程重点拆解了棋盘类、棋子类与玩家类的设计思路,并解释Swing/JavaFX事件驱动模式下的游戏循环流程;同时延伸到Minimax与Alpha-Beta剪枝算法,以及悔棋、存档、难度调节等扩展功能。读者可从中获得完整游戏框架的代码阅读路径、AI优化思路、界面交互细节与常见边界问题排错技巧,系统掌握五子棋从实现到测试优化的完整链路。配合包内说明文档,可对照工程逐步复现项目,尤其适合课程设计与自学练手。

1. 项目来了:为什么我建议你用Java写一个五子棋

做技术这些年,我陆陆续续带过不少新人,也帮很多准备面试的朋友看过项目。聊到练手项目,我每次都会推荐一个几乎人人都听过、但很少真正自己完整写过的程序:五子棋。

这句话放在这里,熟悉的朋友应该已经能猜到我要说什么了。五子棋听上去简单,但把Java的核心知识点几乎全部串上了,包括面向对象设计、二维数组建模、事件监听、图形界面绘制,甚至还能往深处延伸到AI博弈、评分算法、悔棋状态栈这些高级话题。单说性价比,同类练手项目里几乎没有比它更合适的了。

这篇文章不会只讲一个能跑起来的demo,我会把自己实际写这个项目时的完整思路、踩过的坑、调试技巧全部拆开讲。不管你是刚学完Java基础正在找第一个项目的初学者,还是想在简历上补一个"有技术含量的小游戏"的求职者,这套内容都可以直接照着做。

需要说明一下项目的基本定位:我们最终做出来的是一个基于Swing界面的桌面五子棋,支持双人对战,附带悔棋、重新开局、简单的AI落子评分功能。从零开始,完整代码加调试,按我的节奏大概需要一个周末的时间。

2. 动手之前:用二维数组建模棋盘,是整个项目的基石

2.1 棋盘的数据结构选型

写游戏的第一步不是画界面,而是想清楚棋盘在程序里怎么存。很多新手一上来就研究怎么画线、怎么贴图,结果写到胜负判断时发现根本无从下手,因为棋盘数据没有一个清晰的模型。

正确的顺序是先定数据结构,再做界面。

五子棋的棋盘是15×15的网格,最自然的存法就是一个二维数组。我定义成:

private int[][] board = new int[15][15];

数组里每一个值代表一个交叉点的状态:0表示空位,1表示黑子,2表示白子。这套约定贯穿整个项目的所有模块,不管是落子、判断胜负还是AI算分,读写的都是同一个board。

为什么用int数组而不是别的结构?一个很直白的原因:int的默认值是0,天然对应"空位",不需要额外初始化。如果你想做带悔棋功能,二维数组也方便拷贝,深拷贝一行代码就能生成一个历史快照。另外,数组的下标就对应棋盘坐标,后续做范围判断、周围扫描都非常直观,省去了维护复杂坐标对象的成本。

2.2 界面层与数据层怎么分工

Swing界面只做两件事:把board画出来,把鼠标点击坐标换算成数组下标。真正决定游戏状态的永远是board中的数据,界面只是一个可视化外壳。

我用一个自定义的JPanel做棋盘绘制,在paintComponent方法里画网格、画棋子。注意一个细节:Swing的绘制是在事件分发线程(EDT)上执行的,所有对界面组件的修改都应在这个线程里做,否则可能出现界面错乱。实际操作中最稳妥的做法是所有交互逻辑放在监听器或SwingUtilities.invokeLater里跑。

这个"数据与界面分离"的思路,往大了说就是MVC的雏形。面试时如果被问到"你这个项目怎么设计的",能讲清楚这一层,已经比绝大多数只会复制代码的人强很多了。

3. 落子与绘制:事件监听的几个关键坑

3.1 鼠标坐标到棋盘坐标的换算

棋盘落在界面上,每个交叉点之间的间距我设置为30像素(后面统称gridSize),左上角留了30像素的边距(offset)。鼠标点一下,怎么知道点在哪一格?

int col = (e.getX() - offset + gridSize / 2) / gridSize; int row = (e.getY() - offset + gridSize / 2) / gridSize;

这里有个细节很容易被忽略:为什么要加gridSize的一半再除?因为用户很少能精确点到交叉点上,落点只要落在某个格子附近,就应该归到离它最近的交叉点。加了半个格子宽度再整除,本质就是四舍五入,这样鼠标点下去的手感才不会让人觉得"明明点了却落在别处"。

换算完成后,必须做两个校验:一是坐标是否在棋盘合法范围内,二是该位置是否已经有棋子。我见过不少跑起来就崩的五子棋,基本都是漏了越界判断,点边缘就数组下标越界。

if (row < 0 || row >= 15 || col < 0 || col >= 15 || board[row][col] != 0) { return; }

3.2 重绘机制:改了数据怎么让界面刷新

落子的核心动作本质上就三步:修改board数组、判断是否获胜、调用repaint()让界面重新绘制。

很多人第一次写Swing时会在paintComponent里写"画当前棋子"的逻辑,这是错的。正确的做法是paintComponent里无条件地把整个board重新遍历一遍,根据每个位置的值决定画黑子、白子还是不画。这样做的好处是逻辑单一、不会出现残影。每下一子就全量重绘一次,15×15=225个格子的绘制量对现代电脑来说几乎可以忽略不计,根本不存在性能问题。

还有一个小坑要提醒:重绘时棋子画成实心圆,可以直接用:

g.fillOval(x - pieceSize / 2, y - pieceSize / 2, pieceSize, pieceSize);

注意fillOval的前两个参数是外接矩形的左上角坐标,不是圆心。想画在交叉点上就需要减去棋子半径。做黑白棋时记得画完黑子加一个细边框,不然深色背景下观感很差。

4. 胜负判断:五子棋程序的灵魂所在

4.1 为什么说判断逻辑决定整个项目的上限

可以说,任何一个五子棋程序,不管做得再花哨,最终玩家体验都落在胜负判断准不准、快不快上。判断慢半秒,或者出现"明明五颗连在一起却说没赢"的诡异局面,这个游戏基本就没法用了。

初学的时候我见过一个很"暴力"的写法:每次落子后,把整个棋盘所有位置遍历一遍,检查每个位置往四个方向能不能连成五个。这种做法不是不行,但在效率上和代码优雅度上都很差。而且它还有一个隐患:因为全盘扫,你可能在一个棋子落下后错误地判定出两个胜利方向。

正确的思路是:每次只在当前落子位置的基础上,向四个方向延伸检查。因为获胜的五连必然包含刚下的这颗棋子——如果下之前已经赢了,游戏早就结束了,所以只需要检查包含当前点的连线即可。

4.2 四方向检查模板

四个方向分别是水平、垂直、两条对角线。核心逻辑完全一样:从当前棋子出发,往一个方向数连续相同棋子的数量,再从当前点往反方向数,总数加起来大于等于5就赢了。

这里有个值得写的优化点:检查时不用递归也不用复杂算法,只要写一个按方向偏移的循环就行。我封装成下面这样:

private boolean checkWin(int row, int col, int player) { int[][] directions = {{1,0},{0,1},{1,1},{1,-1}}; for (int[] dir : directions) { int count = 1; count += countDirection(row, col, dir[0], dir[1], player); count += countDirection(row, col, -dir[0], -dir[1], player); if (count >= 5) { return true; } } return false; } private int countDirection(int row, int col, int dx, int dy, int player) { int count = 0; int r = row + dx; int c = col + dy; while (r >= 0 && r < 15 && c >= 0 && c < 15 && board[r][c] == player) { count++; r += dx; c += dy; } return count; }

这里directions数组里的四个向量,每个向量加上它的相反方向,就覆盖了游戏所需的全部八个相邻方向。认真看这个设计,你会发现它把问题抽象得非常干净:不管哪个方向,都是同一个逻辑。

边界条件我已经用while循环条件处理了,不需要额外写一堆if判断到底是棋盘上边缘还是下边缘。

4.3 为什么建议判断"当前点附近"而不是"全盘扫描"

除了性能上的考虑,全盘扫描最大的问题是容易误判。举个例子,棋盘上同时存在一条黑子四连和一条白子四连,如果你在全盘扫描后直接判断"有任意四连就赢",就会出现黑白同时获胜的荒谬结果。

所以判定时机也很重要:每落一子,只检查当前落子颜色是否获胜,另一方不做检查。这个时序逻辑听起来很简单,但很多人写着写着就忘了。

5. 进阶玩法:悔棋、重开与简单AI评分

5.1 悔棋功能:用ArrayList当历史栈

悔棋功能是我推荐一定要做的,因为它能培养"栈"这种数据结构在实际项目中的应用意识。

最简单的实现方式:每次落子时,把当前的board快照保存到一个ArrayList里,悔棋时弹出一个历史快照,恢复现场,再重绘。

private ArrayList<int[][]> history = new ArrayList<>(); private void recordHistory() { int[][] snapshot = new int[15][15]; for (int i = 0; i < 15; i++) { snapshot[i] = board[i].clone(); } history.add(snapshot); } private void undo() { if (history.isEmpty()) return; board = history.remove(history.size() - 1); repaint(); }

这里有个让人肉疼的细节:不能直接history.add(board),因为数组存的是引用,后面改board的时候历史记录也会被改,悔棋就完全失效。必须clone每个一维数组,做一次真正的深拷贝。这个坑我当年踩过,第一次写悔棋翻来覆去调试了一晚上,最终恍然大悟是引用传递的问题。

类似的引用陷阱在Java里非常普遍,面试聊起这个项目时,如果你能主动讲清楚"为什么悔棋快照必须深拷贝",会是很大的加分项。

5.2 重新开始与轮次切换

重开功能没什么难点,把board重置为全0、清空历史、当前玩家设为黑棋、重绘即可。真正的坑在于判断游戏状态:因为加入了悔棋和重开,你必须有一个状态变量标明当前是否在对局中,否则就会出现"游戏已经结束还继续落子"的意外。

我常用的做法是加一个boolean gameOver标记,获胜或和棋时置为true,重开时重置为false,落子的监听器第一行就判断这个标记。

5.3 给AI一点面子:基于评分的贪心落子

很多人听到AI就头大,但其实五子棋的初级AI完全可以用很朴素的启发式评分实现,代码量不大,效果却足以"看起来会下棋"。

核心思路是:遍历所有空位,给每个位置打分,然后选最高分的落子。打分规则可以很直白:

  • 某个位置周围有多少颗己方棋子,加分;
  • 有多少颗对方棋子,也要加分,因为要堵;
  • 能形成四连、三连的位置额外加权重。

我用的这套简单评分,虽然谈不上聪明,但对一个练手项目来说足够撑起"人机对战"的面子。更关键的是,写评分的过程能逼着你把"棋形"这个概念抽象成数据,对之后想做更复杂的博弈算法是很好的铺垫。

6. 常见问题排查:那些年我踩过的坑

6.1 问题速查表

现象根源对应解法
鼠标点击没有反应坐标换算越界或落子合法性判断漏了检查行列是否在0~14之间、当前位置是否为0
棋子重叠在同一位置没有判断当前位置是否为空落子前增加board[row][col] != 0判断
胜负判断延迟或漏判方向向量没写全,只查了部分方向检查四个方向是否都覆盖,注意反对角线方向
界面不刷新忘记调用repaint(),或数据与绘制逻辑混杂数据改动后统一调用repaint(),绘制只读取board
悔棋后棋子消失历史记录存了引用,没做深拷贝用clone()逐行拷贝一维数组
游戏结束后还能落子缺少gameOver状态标记添加状态判断,对局结束后直接return
窗口关闭但进程不退没有设置默认关闭操作setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

6.2 调试经验:把棋盘状态打印出来

界面类程序出了问题,最忌讳的就是"肉眼盯着界面看"。我调试五子棋时最常用的手段是在控制台把board按二维数组格式打出来,用符号0/1/2区分空位、黑子、白子。这样能一眼看出数据结构到底对不对,完全不受绘制代码干扰。

这个方法听起来土,但真的能大幅缩小问题范围:如果控制台打印的board是正确的、逻辑也是正确的,那问题多半出在绘制;如果控制台打印本身就是乱的,那就老老实实回到数据结构层面查。

6.3 一个经常被低估的编译问题

开发环境偶尔会出现类找不到或者旧版本缓存导致的问题。项目运行时如果出现奇怪的类加载异常,先不要急着怀疑代码逻辑,试一下mvn clean或者IDE里的"Clear Project Cache"再重新编译,很多诡异问题都只是构建缓存惹的祸。Java项目里这个坑出现频率远比你想的高。

7. 从交作业到拿得出手:扩展方向与我的个人体会

代码跑通、能双人对战、能悔棋、能简单人机对战之后,这个项目已经可以从简历的"项目经历"栏里亮相了。但如果你想让它在一堆项目里显得更有含金量,还有几个方向可以继续加码。

一个是公平性相关的禁手规则,三三禁、四四禁、长连禁,这个做到位就能说明你对五子棋规则有深入理解。另一个是更聪明的AI,把贪心评分升级成极小化极大搜索加Alpha-Beta剪枝,这个就真正触及博弈论与搜索算法的核心了。再有一个是网络对战,用Socket或Netty把两人对战搬到局域网里,告别一台电脑轮着抢鼠标的尴尬。这三个方向任何一个做完,这个项目的技术深度都能再上一个台阶。

最后再说一个我个人的体会:做了这么多年技术、看了这么多简历,真正能体现水平的往往不是你用了多牛的技术栈,而是你对自己做的东西理解得到不到位。五子棋这个项目看起来很朴素,但如果你能把二维数组建模、数据与界面分离、方向向量扫描、深拷贝快照这些点讲清楚,比堆十个人云亦云的微服务项目更有说服力。不要急着追新框架,先把这个小东西吃透,你后面再学什么都会顺很多。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 20:08:42

探访绵阳厨房收纳源头工厂:定制橱柜的板材、五金与动线避坑指南

1. 跑去绵阳看收纳厂&#xff0c;最初只因一个柜子返工三次要不是因为家里厨房的柜子返工三次&#xff0c;我不会专程跑去绵阳看这家厨房收纳厂。上一次装修&#xff0c;定制橱柜装完就出了状况&#xff1a;抽屉面板装歪&#xff0c;怎么调都有半毫米错位&#xff1b;转角柜门打…

作者头像 李华
网站建设 2026/9/9 20:06:37

AI时代程序员两条路:造系统,还是造垃圾?

Cursor首席设计师最近抛了一个挺扎心的判断&#xff1a;AI时代&#xff0c;程序员只剩两条路&#xff0c;造系统&#xff0c;或者造垃圾。这句话这两天在不少技术群里被转疯了&#xff0c;有人焦虑&#xff0c;有人不服&#xff0c;也有人觉得就是标题党。我做了十几年开发&…

作者头像 李华
网站建设 2026/9/9 20:06:19

报障、事件、问题别混淆:从半年6次报障看运维问题管理落地

同一家门店&#xff0c;半年报障6次&#xff0c;每一张事件单都按流程关闭了&#xff0c;可到了第七次故障发生时&#xff0c;我们翻历史记录才发现&#xff0c;所谓“处理完”不过是一次又一次地重启、重置、换线。这个场景在运维圈里太常见了&#xff1a;报障有人接&#xff…

作者头像 李华
网站建设 2026/9/9 20:04:48

解释器与编译器入门:从C嵌入Lua到Python字节码的跨语言实践

我第一次真正把“解释”和“编译”这件事想通&#xff0c;不是在看编译原理教材的时候&#xff0c;而是在折腾 Lua 的 C API 时突然开窍的。那个瞬间我才意识到&#xff1a;一个用 C 语言写出来的 Lua 解释器&#xff0c;可以让我在 C 程序里执行 Lua 脚本&#xff1b;而 Pytho…

作者头像 李华
网站建设 2026/9/9 20:03:32

Matpower 8.0安装配置全攻略:解决路径与运行难题

简介&#xff1a;Matpower 8.0安装包是一款面向电力系统研究与教学场景的常用工具箱&#xff0c;适合使用MATLAB开展潮流计算、最优潮流、连续潮流、状态估计与电网规划仿真的科研人员、工程师及高年级本科生。该版本针对较新版本MATLAB环境做了较好的兼容性调整&#xff0c;下…

作者头像 李华