【C语言 | 五子棋AI】从零实现智能五子棋对战系统
1. 从零开始为什么用C语言写五子棋AI是个绝佳选择很多刚接触编程的朋友一听到“AI”就觉得高深莫测感觉那是机器学习、神经网络那些高大上的东西。其实AI的起点可以非常朴素和有趣。用C语言来实现一个五子棋的AI对战系统就是我特别推荐给初学者的一个实战项目。它不像那些复杂的库和框架需要你先啃一大堆理论知识。你只需要有C语言的基础比如知道数组、循环、函数怎么用就能跟着我一步步搭起来。这个过程你能亲手摸到游戏逻辑的骨架看到最原始的“智能”是如何从几行判断代码里生长出来的这种成就感是直接调用一个现成的AI接口完全无法比拟的。我当年学C语言的时候就是靠写各种小游戏来巩固知识的。五子棋尤其特别它的规则简单到一句话就能说清横、竖、斜任意方向连成五个子就赢。但正是这种简单的规则背后却藏着非常丰富的策略空间。你写的AI从最初的“随机乱走”到后来能“思考”一步甚至能“算计”好几步这个进化过程就像看着一个数字生命从懵懂到开窍特别有意思。而且C语言足够底层你能清晰地控制每一个字节理解每一个判断背后的计算成本这对于建立扎实的编程思维和算法直觉好处太大了。这个项目适合谁呢我觉得有三类朋友会特别有收获。第一类是C语言的初学者书本上的链表、指针可能有点抽象但用在棋盘表示和搜索算法里一下子就具体了。第二类是对游戏开发感兴趣但被Unity、Unreal这些引擎吓住的朋友从这种纯逻辑的回合制游戏入手能帮你理清最核心的游戏循环和状态管理。第三类就是所有对“智能”感到好奇的人你想知道电脑是怎么“思考”下一步棋的这个项目就是最好的显微镜让你能看到最初级的思考模型。2. 搭建基础框架你的第一个可运行五子棋游戏在让电脑变聪明之前我们得先有一个能玩的游戏。别担心这部分就像搭积木我们把游戏的必要功能一块块拼起来。整个游戏的核心是一个游戏循环它就像心脏一样不停跳动显示棋盘 - 玩家走一步 - 判断输赢 - 电脑走一步 - 判断输赢 - 回到显示棋盘。我们所有的代码都是围绕这个循环展开的。2.1 棋盘的数据心脏二维数组与宏定义棋盘用什么表示最直接的就是一个二维数组。想象一个格子本每个格子可以放白子、黑子或者空着。在C语言里我们用char board[ROW][COL]来表示。这里我强烈建议你使用宏定义#define ROW 15和#define COL 15。为什么不用具体的数字10或者15而要用宏这是血泪教训换来的好习惯。我刚开始写的时候直接把数组大小写成[10][10]后来想改成15路棋盘好家伙我不得不把代码里所有出现10的地方一个个找出来改差点改到崩溃。用了宏定义之后你只需要改ROW和COL那一行所有相关的地方自动生效代码的维护性瞬间提升一个档次。初始化棋盘就是把每个格子都设为空。你可以用两层for循环遍历也可以用memset这个内存操作函数一行搞定。我两种都写过初期用for循环更清晰等你对指针和内存熟悉了用memset会更简洁。这里给你看看两种写法你挑顺手的来。// 方法一for循环初始化 void InitBoard(char board[ROW][COL], int row, int col, char set) { for (int i 0; i row; i) { for (int j 0; j col; j) { board[i][j] set; // set通常是空格 } } } // 方法二使用memset初始化 #include string.h void InitBoard(char board[ROW][COL], int row, int col, char set) { memset(board, set, row * col * sizeof(board[0][0])); }2.2 让棋盘好看点一个清晰的打印函数棋盘初始化好了全是空白我们得把它打印出来让人能看。打印不是简单地把数组内容printf出来那样会挤成一团。我们需要加入一些分隔符比如用|分隔列用---分隔行这样看起来就像一个标准的棋盘了。这里有个小技巧打印行分隔线的时候最后一行下面是不需要的所以要用if (i row - 1)来判断。这个函数写好了你就能实时看到你和电脑的“战场”局势了。void DisplayBoard(char board[ROW][COL], int row, int col) { printf(\n ); // 对齐y坐标 for (int j 0; j col; j) { printf( %2d , j1); // 打印列坐标 } printf(\n); for (int i 0; i row; i) { printf(%2d , i1); // 打印行坐标 for (int j 0; j col; j) { printf( %c , board[i][j]); if (j col - 1) printf(|); } printf(\n ); if (i row - 1) { for (int j 0; j col; j) { printf(---); if (j col - 1) printf(|); } } printf(\n); } }2.3 玩家与电脑的第一次交锋落子与胜负判定玩家落子就是读入两个坐标然后检查这个位置是否在棋盘内且为空。这里要注意一个用户体验细节我们给玩家输入的坐标通常是从1开始的比如第1行第1列但数组下标是从0开始的。所以内部处理时需要把玩家输入的(x, y)转换成board[x-1][y-1]。这个转换逻辑一定要清晰不然会出现棋子下错位置的诡异BUG。电脑落子呢我们第一个版本就让它“傻”一点——完全随机。用rand() % row和rand() % col生成随机坐标如果那个位置是空的就下子。记得在主函数里用srand((unsigned int)time(NULL))设置随机种子不然每次运行电脑下的棋都一样就太没意思了。游戏循环要能结束关键就在于胜负判定。五子棋的胜负逻辑很直观遍历整个棋盘检查每个位置开始在横、竖、左上到右下、右上到左下四个方向上是否有连续五个相同的非空棋子。我建议你把判断胜负的函数IsWin单独写并且设计好返回值。比如玩家赢返回‘O’电脑赢返回‘*’平局棋盘满了返回‘Q’否则返回‘C’表示继续。这样在主循环里判断就非常清晰。char IsWin(char board[ROW][COL], int row, int col) { // 检查横向五连 for (int i 0; i row; i) { for (int j 0; j col - 5; j) { char start board[i][j]; if (start ) continue; int k 1; for (; k 5; k) { if (board[i][j k] ! start) break; } if (k 5) return start; } } // 检查竖向五连... (类似逻辑) // 检查对角线... // 检查平局 if (IsFull(board, row, col)) { return Q; } return C; // 继续 }把这些模块组合起来你就得到了一个可以双人对战只不过一方是随机乱走的电脑的五子棋游戏。虽然AI还很弱智但整个游戏的架子已经搭稳了。这是最重要的一步就像盖房子打好了地基。3. 让AI告别“智障”从随机落子到简单策略如果你的电脑对手只会随机落子那你很快就会发现它简直是在“送温暖”甚至会把子下到完全无关的地方。这种AI玩起来毫无挑战性。我们的第一步优化就是让AI学会“堵枪眼”和“找机会”。这不需要高深的算法只需要一些基于规则的判断逻辑。3.1 优先级策略赢棋、堵截与随机我们可以给AI设计一个简单的决策优先级。它的“思考”顺序应该是这样的第一优先级自己连五。遍历所有空位如果AI‘*’下在这个位置能直接形成五连珠那就立刻下在这里赢得游戏。第二优先级阻止玩家连五。遍历所有空位如果玩家‘O’下在这个位置能直接形成五连珠那AI必须下在这里进行堵截。第三优先级执行简单策略。如果上面两种情况都不存在再使用我们后面会讲的评分函数或者模式匹配找一个“看起来最好”的位置下。保底策略随机落子。如果以上策略都找不到合适位置理论上在开局不会那就退化到随机选择。这个优先级框架非常关键它保证了AI至少不会犯“看着对方赢也不堵”的低级错误。实现起来就是写两个辅助函数FindWinPos和FindBlockPos它们分别检查是否存在能让某一方立即获胜的位置。这本质上是对IsWin函数的一个变种应用只是检查的是“如果下了某个子会不会赢”。3.2 设计一个简单的局面评分函数当没有立即输赢时AI需要评估每个空位的好坏。一个经典又有效的方法是评分函数。我们为棋盘上的每一种“棋型”赋予一个分数。什么是棋型就是沿着一条线横、竖、斜以某个空位为中心两边的棋子构成的状态。比如我们只考虑连续的同色棋子。可以定义成五直接赢了分数最高比如10000分但在优先级里已经处理了。活四两头都没被堵住的连续四个子下一步就能成五威胁极大给高分如5000分。冲四一头被堵住的四连子下一步也能成五但只有一种可能分数次之如1000分。活三两头空的连续三子可以发展成活四分数再低一些如500分。眠三一头堵死的三子如200分。活二、眠二分数依次递减。AI的决策过程就变成了遍历每一个空位假设在这个位置下子无论是自己的子还是对方的子需要分别计算然后以这个子为中心扫描四个方向横、竖、两个斜线找出每个方向上最长的连续同色棋子并根据其“活”性两头是否为空匹配到上述棋型累加分数。最后AI选择对自己最有利自己得分最高、同时对对手最不利阻止对手得分最高的位置落子。// 一个非常简化的评分函数示例 int EvaluatePoint(char board[ROW][COL], int row, int col, int x, int y, char player) { int score 0; // 假设在(x,y)处下player的棋子 board[x][y] player; // 检查四个方向计算棋型并加分 // 这里只是一个示意实际需要详细的模式匹配 score CheckDirection(board, x, y, 1, 0, player); // 横向 score CheckDirection(board, x, y, 0, 1, player); // 纵向 score CheckDirection(board, x, y, 1, 1, player); // 右下斜 score CheckDirection(board, x, y, 1, -1, player); // 右上斜 // 恢复棋盘 board[x][y] ; return score; } void ComputerMove(char board[ROW][COL], int row, int col) { int bestScore -1; int bestX -1, bestY -1; // 1. 检查自己是否能赢 if (FindImmediateWin(board, row, col, *, bestX, bestY)) { board[bestX][bestY] *; return; } // 2. 检查是否需要堵玩家 if (FindImmediateWin(board, row, col, O, bestX, bestY)) { board[bestX][bestY] *; return; } // 3. 基于评分选择最佳点 for (int i 0; i row; i) { for (int j 0; j col; j) { if (board[i][j] ) { // 计算在这个点下子的“好坏”分数 int score EvaluatePoint(board, row, col, i, j, *); // 也可以考虑减去玩家在此下子的潜在分数形成攻防一体 if (score bestScore) { bestScore score; bestX i; bestY j; } } } } if (bestX ! -1) { board[bestX][bestY] *; printf(电脑在 (%d, %d) 落子\n, bestX1, bestY1); } else { // 4. 保底随机 // ... 随机落子代码 } }这个版本的AI虽然还不会“深谋远虑”但已经具备了基本的攻防意识。你会发现它开始会堵你的三连珠也会尝试自己做活二、活三了游戏趣味性大大提升。4. 引入搜索算法教AI“向前看”一步评分函数让AI有了静态的“眼光”能判断单个位置当下的好坏。但下棋是动态的你走一步我走一步。更高明的AI需要具备“向前看”的能力也就是搜索。最简单实用的搜索算法就是极大极小搜索配合Alpha-Beta剪枝。别被名字吓到它的核心思想非常像人的思维我走这一步时要考虑你会怎么应对然后在你所有应对里选对我最不利的极小值我再从我的所有走法里选这个最不利情况中对我最有利的极大值。4.1 理解极大极小搜索的基本思想想象一下AI在决定下一步时会在心里模拟一个“决策树”。树的第一层是AI所有可能的走法第二层是针对AI的每一种走法玩家所有可能的应对第三层又是AI的应对……如此递归下去直到达到设定的搜索深度或者模拟到游戏结束。然后从树的底层叶子节点开始用评分函数评估那个局面对AI的“价值”。在玩家回合的节点MIN层玩家会选择让AI分数最小的走法在AI回合的节点MAX层AI会选择让自己分数最大的走法。这样一层层倒推回来AI就能找到在当前局面下即使对手尽力反击也能获得相对最好结果的走法。4.2 实现一个固定深度的搜索函数我们来实现一个搜索深度为2的极大极小搜索。这意味着AI会思考“如果我走这里对方最好的反击是什么然后我接下来最好的应对又是什么最终局面对我有多好” 深度为2其实就是“AI-玩家-AI”三步棋的推演。// 极大极小搜索函数返回当前局面的评估分数 int Minimax(char board[ROW][COL], int depth, int isMaximizingPlayer, int alpha, int beta) { // 终止条件达到搜索深度或游戏结束 char result IsWin(board, ROW, COL); if (depth 0 || result ! C) { return EvaluateBoard(board, ROW, COL); // 需要一个评估整个棋盘的函数 } if (isMaximizingPlayer) { // AI的回合取最大值 int maxEval -INFINITY; for (每个可能的落子位置) { if (board[i][j] ) { board[i][j] *; // AI落子 int eval Minimax(board, depth - 1, 0, alpha, beta); // 递归 board[i][j] ; // 撤销落子回溯 maxEval (eval maxEval) ? eval : maxEval; alpha (alpha eval) ? alpha : eval; if (beta alpha) break; // Alpha-Beta剪枝 } } return maxEval; } else { // 玩家的回合取最小值 int minEval INFINITY; for (每个可能的落子位置) { if (board[i][j] ) { board[i][j] O; // 玩家落子 int eval Minimax(board, depth - 1, 1, alpha, beta); // 递归 board[i][j] ; // 撤销落子 minEval (eval minEval) ? eval : minEval; beta (beta eval) ? beta : eval; if (beta alpha) break; // Alpha-Beta剪枝 } } return minEval; } }在ComputerMove函数里我们不再只是调用评分函数而是为每个候选位置调用Minimax函数选择返回分数最高的位置落子。这里的EvaluateBoard函数需要评估整个棋盘的分数而不仅仅是单个点。它可以遍历所有位置分别计算玩家和AI的棋型总分然后做差AI分 - 玩家分作为最终评估值。4.3 性能优化与Alpha-Beta剪枝你可能已经发现搜索所有可能位置构成的树节点数量会随着深度指数级增长这就是所谓的“组合爆炸”。15x15的棋盘第一步就有225种可能第二步有224种……计算量瞬间爆炸。Alpha-Beta剪枝就是用来砍掉这棵决策树上明显不需要搜索的树枝的神奇技巧。它的原理是这样的在搜索过程中我们维护两个值alpha和beta。alpha表示AI最大化方至少能保证的分数beta表示玩家最小化方至多允许AI得到的分数。在搜索时如果发现某个分支的结果已经不可能比当前已知的最好选择更优时就果断停止对这个分支的深入搜索。代码中if (beta alpha) break;这一行就是剪枝的关键。通过剪枝我们常常能剪掉超过一半的无用搜索让搜索深度可以增加一两层而计算时间却增加不多。5. 优化与进阶让你的五子棋AI更强大更智能有了搜索算法你的AI已经初具“智能”了。但还有很大的优化空间可以让它更强、更快、更人性化。5.1 启发式搜索与走法排序盲目地搜索所有空位效率太低。我们可以用启发式信息来给走法排序优先搜索“更有希望”的位置。一个简单有效的启发是只搜索有棋子周围的空位。在五子棋中远离所有棋子的位置在短期内几乎不可能成为好棋。我们可以先收集所有落子点周围8个方向一定范围内有棋子的位置只对这些“候选点”进行搜索和评估。这能极大减少搜索的广度。在进入Minimax搜索前我们可以先用快速的评分函数比如前面章节的简单评分对所有候选点进行预评分然后按照分数从高到低排序。在Alpha-Beta剪枝中先搜索好的走法能更早地触发剪枝条件从而大幅提升搜索效率。这个技巧带来的性能提升是立竿见影的。5.2 开局库与终局精确计算对于开局前几步棋盘空旷搜索深度再深也很难算出绝对优劣。这时候可以引入一个开局库。开局库就是预先存储好的、经过验证的高水平开局走法序列。AI在开局时如果当前局面能在开局库中找到就直接采用库中推荐的下法。这不仅能保证开局不落后还能节省大量的计算时间。另一方面当棋盘上棋子达到一定数量剩余空位不多时搜索树会变小。这时我们可以尝试进行终局精确计算即搜索直到游戏结束赢、输或和棋而不是固定深度。这需要配合一个高效的胜负判定算法比如使用位运算或Zobrist哈希的快速判赢算法确保在可接受的时间内完成全盘搜索。当AI能“算清”所有变化时它的走法在局部就是绝对最优的。5.3 提升交互体验与代码可读性一个友好的游戏除了AI强大交互体验也很重要。我们可以为AI的“思考”过程增加一个简单的动画比如打印一个旋转的符号让玩家知道电脑正在计算而不是卡住了。在AI落子后可以清晰地输出它落子的坐标。代码结构上随着功能增加一定要做好模块化。把棋盘操作、胜负判断、AI搜索、评估函数等分别放在不同的.c和.h文件里。使用清晰的函数名和变量名添加必要的注释。例如EvaluateBoard函数内部可以再细分为EvaluateForPlayer和EvaluateForAI。良好的代码组织会让你后续的调试和功能扩展轻松十倍。最后别忘了给你的AI设置不同的难度级别。最简单的方式就是通过调整搜索深度来实现。比如简单只使用优先级策略和简单评分不搜索或深度为1。中等使用Alpha-Beta搜索深度为2或3。困难搜索深度为4或5并启用启发式排序和候选点限制。这样不同水平的玩家都能找到适合自己的挑战。当你一步步实现这些优化看着你的AI从“乱走”到“会堵”再到“能算计”最后甚至能把你逼入绝境时那种亲手创造智能的喜悦是学习编程路上最棒的奖励之一。我建议你在每个阶段都多跟AI下几盘感受它思维方式的进步这本身就是一个非常有趣的过程。

相关新闻

MeanFlow深度解析:何凯明团队如何用平均速度革新单步图像生成

MeanFlow深度解析:何凯明团队如何用平均速度革新单步图像生成

1. 从“一步登天”的梦想说起:为什么单步图像生成这么难? 如果你玩过Stable Diffusion或者DALL-E这类AI绘画工具,肯定对“生成速度”又爱又恨。爱的是它们能创造出令人惊叹的图像,恨的是生成一张图往往需要几十步甚至上百步的计算…

2026/7/3 12:49:33 阅读更多 →
Apache Paimon流数据湖实战:从零搭建到核心功能解析

Apache Paimon流数据湖实战:从零搭建到核心功能解析

1. 初识Apache Paimon:为什么说它是流数据湖的“新宠”? 大家好,我是老张,在数据领域摸爬滚打了十几年,从早期的Hadoop生态一路跟到现在的流批一体。今天想和大家聊聊一个让我眼前一亮的项目——Apache Paimon。你可能…

2026/7/3 12:47:41 阅读更多 →
Linux GDB 调试超详细教程:入门 + 实战

Linux GDB 调试超详细教程:入门 + 实战

在 C/C 开发中,程序崩溃、逻辑错误、内存泄漏等问题时有发生。面对它们,仅靠printf打印日志的传统方式,往往效率低下且力不从心。 而GDB 是 GNU 项目下的开源调试利器,堪称 Linux 环境下 C/C 开发的瑞士军刀。它允许你深入到程序…

2026/7/3 0:47:59 阅读更多 →

最新新闻

如何用KKManager轻松管理14款游戏模组:终极模组冲突检测指南

如何用KKManager轻松管理14款游戏模组:终极模组冲突检测指南

如何用KKManager轻松管理14款游戏模组:终极模组冲突检测指南 【免费下载链接】KKManager Mod, plugin and card manager for games by Illusion that use BepInEx 项目地址: https://gitcode.com/gh_mirrors/kk/KKManager 还在为游戏模组冲突而烦恼吗&#x…

2026/7/3 12:49:45 阅读更多 →
ARI-PREDEX 调压控制器 ARI ARMATUREN Fig 12.705

ARI-PREDEX 调压控制器 ARI ARMATUREN Fig 12.705

在蒸汽、热水、中性流体工艺管网中,自力式压力调节设备是保障系统压力稳定、规避超压爆管、优化能耗的核心部件。德国 ARI ARMATUREN(艾瑞)旗下ARI-PREDEX Fig 12.705直通式隔膜执行超压调压控制器,依靠介质自身压力驱动&#xff…

2026/7/3 12:47:44 阅读更多 →
openeuler/kernel-docs实用教程:快速查找内核会议记录与技术资料

openeuler/kernel-docs实用教程:快速查找内核会议记录与技术资料

openeuler/kernel-docs实用教程:快速查找内核会议记录与技术资料 【免费下载链接】kernel-docs Its used to store kernel documents. 项目地址: https://gitcode.com/openeuler/kernel-docs 前往项目官网免费下载:https://ar.openeuler.org/ar/ …

2026/7/3 12:47:44 阅读更多 →
一键找回丢失的QQ空间记忆:GetQzonehistory完整使用指南

一键找回丢失的QQ空间记忆:GetQzonehistory完整使用指南

一键找回丢失的QQ空间记忆:GetQzonehistory完整使用指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾经翻看QQ空间,发现多年前的说说早已消失不见&am…

2026/7/3 12:47:44 阅读更多 →
ParsecVDisplay:解锁Windows虚拟显示新姿势,告别多屏焦虑

ParsecVDisplay:解锁Windows虚拟显示新姿势,告别多屏焦虑

ParsecVDisplay:解锁Windows虚拟显示新姿势,告别多屏焦虑 【免费下载链接】parsec-vdd ✨ Perfect virtual display for game streaming 项目地址: https://gitcode.com/gh_mirrors/pa/parsec-vdd 你是否曾因物理显示器不足而苦恼?是否…

2026/7/3 12:43:21 阅读更多 →
LosslessCut无损编辑架构:FFmpeg GUI工具的技术革新与多场景应用

LosslessCut无损编辑架构:FFmpeg GUI工具的技术革新与多场景应用

LosslessCut无损编辑架构:FFmpeg GUI工具的技术革新与多场景应用 【免费下载链接】lossless-cut The swiss army knife of lossless video/audio editing 项目地址: https://gitcode.com/gh_mirrors/lo/lossless-cut 在传统视频编辑领域,重编码带…

2026/7/3 12:41:17 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述:为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473,一个关于TLS/SSL协议重协商机制的漏洞,现在提起来还有必要吗?很多运维和开发朋友可能会觉得,这都老掉牙了,现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述:为什么需要双通道远程管理防火墙?在任何一个稍具规模的企业网络里,防火墙都是那个默默守护在边界的关键角色。作为网络工程师,我们不可能每次都跑到机房,插上console线去配置它。远程管理能力,…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述:AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域,同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件,与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻