资讯动态

从零实现五子棋AI:博弈树与α-β剪枝算法详解

发布时间:2026/8/30 14:14:46 来源:尧图企业网站定制
简介本资源是一套基于C实现的五子棋AI人机对战系统源码面向算法初学者、计算机专业学生及AI入门开发者聚焦博弈论核心思想在经典棋类中的工程落地。项目以博弈树为决策骨架集成α-β剪枝优化搜索效率支持四层深度局面推演在保证响应速度的同时显著提升AI策略性与对抗强度。压缩包共47个文件含3个头文件.h定义核心数据结构与接口、3个源文件.cpp实现AI逻辑与主程序流程、1个Visual Studio解决方案.sln及配套工程配置文件辅以调试符号.pdb、编译中间产物.obj、.tlog等总大小2.64MB结构完整、开箱可编译运行。目前已有74人学习下载读者可直接构建可执行程序体验人机对弈深入理解博弈树构建、极小化极大搜索、剪枝边界维护等关键机制并基于现有四层框架拓展更深搜索、启发式评估函数或哈希表优化具备清晰的学习路径与二次开发基础。1. 项目概述从零构建一个会“思考”的五子棋AI最近在整理旧项目时翻出了一个当年让我印象深刻的“硬骨头”——一个基于C、使用博弈树和α-β剪枝算法实现的五子棋AI。这个项目的核心目标很明确打造一个能与人进行对弈的智能程序并且要求AI至少能“向前看”四步棋。听起来是不是有点像让计算机学会下棋没错这本质上是一个经典的博弈问题而我们的武器就是博弈树搜索和优化它的“神技”——α-β剪枝。对于刚接触游戏AI或者算法优化的朋友来说这个项目堪称一个完美的练手案例。它不涉及复杂的神经网络和庞大的数据训练而是用清晰的逻辑和确定的算法让你亲手赋予程序“策略性思考”的能力。无论你是想深入理解搜索算法还是希望给自己的C项目增加一个有趣的AI模块这个实现都能提供一条从理论到实践的清晰路径。2. 核心思路拆解博弈、搜索与剪枝的艺术2.1 博弈树AI的“决策沙盘”要让AI下棋首先得教会它如何评估局面并做出选择。博弈树就是这个过程的数学模型。你可以把它想象成一棵巨大的“可能性之树”。树的根节点代表当前棋盘状态轮到AI走棋。从根节点出发AI会考虑所有合法的落子点每个落子点生成一个子节点代表走完那一步后的新棋盘状态。接着轮到对手玩家走棋在每个子节点下对手也会考虑他的所有合法落子生成下一层子节点。如此反复交替一层代表AI的决策一层代表玩家的应对不断向下延伸。理论上如果一直搜索到游戏结束有一方五子连珠或棋盘下满AI就能找到一条从当前状态到必胜的路径。但五子棋的棋盘有15*15225个点即使只考虑中盘分支数量也极其庞大形成“组合爆炸”。穷举所有可能直到终局所需的计算量是现代计算机也无法承受的。因此我们必须设定一个搜索深度限制比如本项目要求的四层。这意味着AI只向前推算接下来的4步棋AI一步玩家一步AI一步玩家一步然后对第4层即搜索终点的棋盘局面进行评分。注意这里的“四层”通常指的是“四层博弈树”包含了根节点第0层当前局面和后续三层交替的走棋。更常见的表述是“搜索深度为4”即向前看4步棋。在实现时务必明确自己深度计算的起点避免混淆。2.2 局面评估函数AI的“价值判断”当搜索到达指定深度后面对一个未结束的棋局如何判断优劣这就需要局面评估函数。这个函数是AI棋力的核心它像一个裁判给当前棋盘状态打一个分数。分数越高对AI越有利分数越低对玩家越有利。一个简单但有效的五子棋评估函数通常会扫描整个棋盘识别各种棋型并赋分。例如连五直接返回极大/极小值表示胜负已分。活四下一步就能形成连五威胁极大给予非常高的分数。冲四只有一个点能形成连五分数次之。活三可以形成活四有发展潜力。死三、活二、死二依次递减分数。评估函数需要同时计算AI的棋型和玩家的棋型最后得分可以是AI总分 - 玩家总分。设计一个好的评估函数需要大量棋谱经验和调试是调整AI棋力“风格”激进或保守的关键。2.3 α-β剪枝给搜索树“瘦身”的利器如果只是简单地构建一棵深度为4的博弈树并逐一评估计算量依然不小。α-β剪枝算法的目的就是在不影响搜索结果正确性的前提下砍掉那些明显不需要搜索的分支极大提升效率。其核心思想是“传递乐观与悲观估计”。在搜索过程中我们维护两个值α阿尔法 当前路径上AI方至少能保证得到的最好分数下界。初始值为负无穷。β贝塔 当前路径上对手方至少能保证让AI得到的最差分数上界。初始值为正无穷。当AI极大层搜索时它会更新α值。如果某个子节点返回的分数 β这意味着对手在前面某一层极小层已经有了一条更好的路径对AI更不利的路径可以迫使AI不会走到当前这个分支因此当前分支剩余未搜索的子节点可以全部“剪掉”无需再查。 同理当对手极小层搜索时它会更新β值。如果某个子节点返回的分数 α这意味着AI在前面某一层极大层已经有了一条更好的路径对手不会给你这个机会走到当前分支因此也可以剪枝。这个过程就像两个人谈判AI说“我在这条路上至少能拿到α分。”对手说“我在这条路上最多让你拿到β分。”一旦发现“对手承诺的最高分β”比“AI已知的最低保障α”还低β α这笔交易这条搜索路径就肯定谈不拢了后面的细节子节点就不用再谈了。通过α-β剪枝最佳情况下可以将搜索复杂度从O(b^d)降低到O(b^(d/2))其中b是平均分支因子d是深度。这意味着搜索效率可能呈平方根级提升对于深度为4的搜索效果非常显著。3. 核心模块设计与实现要点3.1 棋盘与棋局表示高效的数据结构是基础。一个15x15的棋盘最简单的是用二维数组如int board[15][15]表示0为空1为AI黑2为玩家白。但频繁的边界检查会影响性能。一个常见的优化是使用更大的数组如19x19但只使用中间的15x15区域这样在检查连珠时可以减少边界判断。更高级的表示法是“位棋盘”即用两个unsigned long long或数组分别表示黑子和白子的位置每一位对应棋盘上一个点。这种表示法能利用位运算进行快速模式匹配和评估性能极高但实现复杂度也更高。对于入门和深度为4的搜索二维数组已完全足够。class GomokuBoard { public: static const int SIZE 15; enum Piece { EMPTY 0, AI 1, PLAYER 2 }; Piece board[SIZE][SIZE]; // 初始化、落子、判断胜负、获取空位等方法... bool isWin(int x, int y, Piece player) const; // 判断落子(x,y)后player是否获胜 std::vectorstd::pairint, int getEmptyPositions() const; // 获取所有空位 };3.2 博弈树搜索与α-β剪枝实现这是项目的算法核心。我们需要实现一个递归的搜索函数。通常采用负极大值搜索形式它统一了极大层和极小层的代码通过交替传递负分来模拟双方博弈。// 负极大值搜索 with α-β Pruning int negamax(GomokuBoard board, int depth, int alpha, int beta, int color) { // color: 1 for AIs turn (maximizing), -1 for Players turn (minimizing) // 但在负极大值中我们始终站在当前行棋方的视角最大化分数 // 对于对手的棋型评估函数会返回负分 // 终止条件达到深度限制或游戏结束 if (depth 0 || board.isGameOver()) { return color * evaluateBoard(board); // 评估函数对AI为正 } std::vectorstd::pairint, int moves board.getCandidateMoves(); // 获取候选落子点 // 对落子点进行排序启发式排序能大幅提升剪枝效率 orderMoves(moves, board); int bestValue -INFINITY; for (const auto move : moves) { board.makeMove(move.first, move.second, color 0 ? GomokuBoard::AI : GomokuBoard::PLAYER); int value -negamax(board, depth - 1, -beta, -alpha, -color); // 关键递归时取负并交换α/β board.undoMove(move.first, move.second); // 回溯 if (value bestValue) { bestValue value; } if (value alpha) { alpha value; } if (alpha beta) { break; // α-β 剪枝 } } return bestValue; }关键点解析递归与回溯每次递归调用makeMove落子递归返回后必须undoMove撤销恢复棋盘状态这是深度优先搜索的常规操作。负极大值value -negamax(...)这行代码是精髓。它使得无论当前是AI还是玩家代码逻辑都是“最大化当前分数”。上一层返回的分数对于下一层就是相反数完美模拟了零和博弈中一方收益即另一方损失的特性。α-β参数传递递归时传入-beta和-alpha并交换了位置。这是因为在负极大值框架下角色的优势和劣势是交替的。剪枝条件if (alpha beta) break;当“已知的最好结果α”已经好于“对手能接受的最坏结果β”时对手绝不会让这条路发生后续分支无需搜索。3.3 启发式移动排序大幅提升剪枝效率α-β剪枝的效率极度依赖于子节点走法的搜索顺序。如果最好的走法能最先被搜索到那么就能更早地更新α或β值从而剪掉更多后续的无用分支。对于五子棋简单的启发式排序规则非常有效胜负手优先如果某一步能让自己直接获胜连五必须最先搜索。防御急所优先如果某一步能阻止对手下一步获胜堵住对手的活四或冲四优先级极高。攻击要点优先能形成自己活四、冲四、活三的落子点。棋盘中心优先在开局阶段靠近棋盘中心的点通常价值更高。利用历史启发维护一个历史表History Heuristic记录在以往搜索中某个走法在类似深度下引发剪枝的效果好坏。效果好的走法在未来的搜索中优先尝试。这是中级以上AI常用的优化手段。在代码中orderMoves函数就负责实现这个排序逻辑。一个排好序的走法列表能让剪枝效果提升数倍。3.4 评估函数的设计细节评估函数evaluateBoard的准确性直接决定AI的棋力。一个基础的实现需要遍历所有横、竖、斜两种对角线方向检测连续的棋子。int evaluateBoard(const GomokuBoard board) { int aiScore 0; int playerScore 0; // 遍历所有可能形成五子连珠的线段行、列、对角线 for (每个扫描方向) { // 使用一个滑动窗口长度为5进行检测 for (窗口在线上滑动) { int aiCount 窗口内AI棋子数; int playerCount 窗口内玩家棋子数; int emptyCount 5 - aiCount - playerCount; // 只有一方有棋子的窗口才有评估价值 if (playerCount 0) { aiScore getScoreByPattern(aiCount, emptyCount); // 根据棋型查表得分 } else if (aiCount 0) { playerScore getScoreByPattern(playerCount, emptyCount); } // 如果窗口内双方都有子则这个窗口无价值 } } return aiScore - playerScore; }getScoreByPattern函数根据连续棋子数和两端的空位情况判断是活四、冲四、活三还是死三等并返回预设的分数。这些分数值需要精心调整是调参的重点。例如活四的分数应该设置得足够高比如10000以确保AI能立即看到胜利活三的分数比如1000应显著高于活二比如100以体现威胁程度的差异。4. 项目整合与实战调试4.1 主程序与对战循环将上述模块整合形成一个简单的控制台或图形界面游戏循环。核心流程如下初始化清空棋盘决定谁先手。游戏循环玩家回合接收玩家输入坐标检查合法性落子判断玩家是否获胜。AI回合调用negamax搜索函数传入当前棋盘、深度如4、α-∞、β∞。函数会返回所有可能走法中评估分数最高的一个坐标。AI在该坐标落子判断AI是否获胜。终局判断任何一方获胜或棋盘下满则游戏结束。在AI回合可以打印出搜索到的最佳分数和思考时间增加互动感。4.2 性能优化与调试技巧深度与时间的权衡深度4在15路棋盘上如果分支过多比如搜索所有空位思考时间可能较长。可以限制每层的搜索分支数例如只评估当前所有棋子周边3格范围内的空位即“邻域”这能极大减少分支因子在深度不变的情况下加快搜索。迭代加深这是一种实用策略。先以深度1搜索得到最佳走法和分数再以深度2搜索并利用深度1的结果进行移动排序依次增加深度直到时间用完。这样既能保证在规定时间内返回一个结果随时可以中断又能利用浅层搜索优化深层搜索的顺序。Zobrist哈希与置换表这是高级优化。为每个棋盘状态生成一个几乎唯一的哈希值并将搜索深度和评估结果存入一个“置换表”。当再次遇到相同的棋盘状态时可以直接查表获取结果避免重复搜索。这对于有较多对称性或重复局面的搜索提速明显。调试评估函数这是最耗时的部分。可以写一个测试程序加载一些经典棋局如“三三禁手”形状、冲四活三形状让AI评估分数并与你的直觉判断对比。不断调整棋型分数直到AI的行为符合预期。4.3 常见问题与排查实录在开发过程中你几乎一定会遇到以下问题问题现象可能原因排查与解决方案AI走棋明显愚蠢送子给对方赢。1.评估函数权重失衡防守棋型堵对手分数过低。2.搜索深度太浅深度1或2的AI就是“近视眼”。3.α-β剪枝逻辑错误特别是负极大值实现中α、β的传递和取反出错。1. 提高对对手活三、冲四等威胁的防守评分。2. 确保搜索深度至少为3或4。使用调试器单步跟踪看AI是否真的在搜索指定深度。3.重点检查递归调用时-beta, -alpha的顺序和-negamax的负号。打印每一步的α、β值观察剪枝是否发生在合理时机。AI思考时间过长甚至卡死。1.分支爆炸每层搜索了全部200多个空位。2.评估函数过于复杂全盘扫描且计算量大。3.递归死循环终止条件有误深度未递减或游戏结束判断错误。1. 实现启发式移动生成只搜索有棋子的邻格如周围2格内的空点。开局第一手可以限定在中心区域。2. 优化评估函数避免重复扫描。可以考虑增量评估只计算落子点影响到的几条线。3. 在递归函数开头打印深度确认深度在递减。强化isGameOver()函数的正确性。同一局面AI两次走棋选择不同。1.随机因素如果移动排序没有完全确定可能存在多个相同分数的走法选择策略不同。2.哈希冲突如果使用了置换表不同的棋盘哈希到了同一个值导致错误读取历史评估。1. 在移动排序的最后对于分数完全相同的走法可以按坐标字典序固定选择如选择行号小、列号小的确保确定性。2. 检查Zobrist哈希的随机数质量和哈希表大小。实现时加入棋盘完整性校验在查表命中后对比当前棋盘与哈希键对应的棋盘是否真的一致。AI不会主动进攻一味防守。评估函数攻击性不足对自己形成进攻棋型如活三、活二的奖励分数太低远低于防守对手棋型的分数。调整评估函数中进攻和防守棋型的分数比例。确保形成一个自己的“活三”和堵住对手的一个“活三”分数接近或前者略高以鼓励进攻。可以通过与自己对弈来观察AI的风格变化。实操心得调试博弈树AI可视化和日志是关键。不要只盯着最终走法。我习惯让AI在思考时打印出它搜索的主要分支、评估分数以及α/β值的变化。对于关键回合甚至可以输出它搜索的完整树结构深度浅时。这能帮你直观理解AI的“思考过程”快速定位是评估不准还是搜索逻辑有bug。5. 超越基础可能的优化与扩展方向当你的基础AI能稳定运行并具备一定棋力后可以考虑以下方向进行深化开局库与残局库人类棋手有定式。可以为AI建立一个开局库前几步直接采用经典开局走法避免开局浪费计算时间在无意义的搜索上。对于某些必然获胜或必和的固定残局形状可以直接查表走棋。更高级的搜索算法如MTD(f)算法它基于零窗口搜索能更高效地找到确切的极小化极大值通常比标准的α-β搜索更快。并行化搜索博弈树的搜索天生适合并行。可以将不同的根节点子任务即不同的第一步走法分配到多个CPU核心同时进行搜索最后汇总结果。这需要处理线程同步和共享数据访问。与机器学习结合使用蒙特卡洛树搜索MCTS代替传统的α-β搜索。或者用神经网络来构建评估函数价值网络和指导移动排序策略网络这就是AlphaGo Zero的核心思想之一。虽然复杂度飙升但这是现代游戏AI的前沿。实现一个深度为4的α-β剪枝五子棋AI是一个里程碑式的项目。它扎实地涵盖了博弈论、搜索算法、启发式优化和性能调优等多个核心计算机科学概念。当你看到AI成功堵住你的“活三”或者自己走出一个“冲四活三”的绝杀时那种亲手创造智能的成就感是无与伦比的。这个项目的源码不仅仅是一份可运行的程序更是一个理解经典AI决策过程的绝佳窗口。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价