用Python实现五子棋AI:从Alpha-Beta剪枝到评估函数调参实战

发布时间:2026/9/9 5:05:58
用Python实现五子棋AI:从Alpha-Beta剪枝到评估函数调参实战 简介AI-Gomoku是一个基于Python开发的五子棋人工智能程序面向Python开发者和AI算法爱好者可用于学习蒙特卡洛树搜索、卷积神经网络与Alpha-Beta剪枝在棋类博弈中的实践。资源共11个文件压缩包仅18KB包含4个Python源码、4个pyc编译文件、1个Markdown说明、1个Ksh辅助脚本及Git配置源码负责核心逻辑pyc文件为编译后产物可被直接调用README提供快速上手指引整体结构精简便于逐文件查看与调试。已有352人学习下载。通过阅读源码读者可以理解MCTS如何通过模拟随机对局评估棋步CNN如何从棋盘状态提取特征并预测最佳落子以及如何用GUI库搭建人机对战界面并借助棋局评估、裁判逻辑等模块看清从状态判断到决策输出的完整路径。整体资源体量虽小却完整覆盖了棋局评估、裁判逻辑、AI决策与对战测试等模块适合想从零拆解一个AI棋类项目、或借鉴算法思路进行二次开发的开发者。 去年年底我把所有周末时间都投进了一个叫 AI-Gomoku 的项目——一个纯 Python 实现的五子棋 AI 程序。起因特别朴素跟朋友线下切磋五子棋我连输五盘。我不服气觉得一定是自己算得不够远于是决定写个程序替我算。后来它不光能替我下还把我这个作者赢了。这个项目没有用任何付费算力也没有上神经网络靠着经典的极小极大搜索、Alpha-Beta 剪枝和一套手写的评估函数就让 AI 达到了“稳定打赢普通人类”的水平。这篇文章我把完整的技术方案、踩坑经历和调参心得都写出来想动手做博弈 AI 的同学可以直接抄作业。AI-Gomoku 的目标很明确在标准 15×15 棋盘上实现一个走一步棋不超时、不犯低级失误、有基本攻防意识的五子棋 AI。它不需要显卡不需要大数据集所有逻辑都可以用一台普通笔记本跑起来。如果你第一次接触博弈 AI跟着这篇文章你能把搜索树、剪枝、评估函数这些概念彻底搞懂如果你已经写过不少 AI Demo后面那些调优和避坑经验应该也能给你一些启发。1. 项目定位一个“不靠算力”也能赢人的五子棋 AI1.1 MVP 目标先让 AI 能下完一整盘棋很多第一次写棋类 AI 的人一上来就想做一个“不可能输”的程序结果被复杂规则和算法组合劝退。我的建议是先把 MVP最小可行产品定得极低AI 能在 1 秒内给出合法落子并且能下完整盘棋。这个目标听起来很简单但有几个隐含要求棋盘是标准 15×15黑棋先手双方轮流落子落子必须合法不能下到已有棋子的位置每步棋都能在限定时间内返回棋力至少要高于“随机乱下”。满足这四点后再考虑“下得好不好”的问题。我当时第一版就是一个纯随机落子程序跑通之后再逐步加入搜索和评估逻辑。这样做的好处是每一步优化都有可对比的基线比如你在搜索引擎里搜到的“AI 棋力对比”你永远知道自己比随机强多少、比最优解差多少。迭代路径也很清晰随机落子 → 启发式评分选点 → 极小极大搜索 → Alpha-Beta 剪枝 → 评估函数细化 → 迭代加深与置换表。每一层都是在上一层基础上加量不会出现重写整个系统的困境。1.2 为什么不用现成的 AI 库自己写一遍才懂GitHub 上优秀的开源五子棋项目非常多有的是基于蒙特卡洛树搜索MCTS有的是基于深度学习策略网络效果都很惊艳。那你可能会问为什么要自己做我的理由有三条学习价值完全不同。调现成库只需要会写两行调用代码但对搜索树、局面评估这些核心概念的理解几乎为零。自己动手从零写一遍 Alpha-Beta 剪枝那种“原来剪掉无用分支是这种感觉”的顿悟是任何文档都给不了的。控制力强。开源库往往为了通用性做了很多抽象而你自己的项目可以随时改评估函数、调试搜索顺序、加日志统计这是深入优化游戏 AI 的必要条件。成就感不一样。当你知道每一步落子背后都是自己设计的评估逻辑在决定你对这个项目的认同感是完全不同的。不过“不用现成库”不意味着“不参考现有的思路”。Alpha-Beta 剪枝是从教科书《Artificial Intelligence: A Modern Approach》里来的评估函数的棋型分值是参考社区开源项目的经验值再自己调整的。站在前人的肩膀上但代码和推理逻辑要自己走一遍。2. 核心算法拆解极小极大搜索与 Alpha-Beta 剪枝2.1 博弈树的直观理解五子棋的每一次对局都可以展开成一棵巨大的树根节点是当前棋盘AI 每尝试一个落点就产生一个子节点对手再回应一个落点又往下分出一层。理论上只要把整棵树遍历完AI 就能找到必胜或必不败的走法。但 15×15 棋盘上的状态数几乎是天文数字完全遍历不现实。极小极大搜索就是这个树状结构的经典解法。它会交替站在 AI 和对手两个视角AI 视角MAX 层AI 要从所有可能的落子中选一个让局面评分最高的走法对手视角MIN 层对手会选一个让 AI 评分最低的走法来克制它。每一层都取极值逐层向上回溯最终根节点会告诉自己“这一步最好那一步会被对手打得很惨”。用伪代码描述大概是这个样子def minimax(board, depth, is_maximizing): if depth 0 or game_over(board): return evaluate(board) if is_maximizing: best_score -float(inf) for move in get_candidates(board): board.play(move) best_score max(best_score, minimax(board, depth - 1, False)) board.undo(move) return best_score else: best_score float(inf) for move in get_candidates(board): board.play(move) best_score min(best_score, minimax(board, depth - 1, True)) board.undo(move) return best_score理解这个结构之后你会发现所谓“AI 的思考”其实非常机械它就是把所有可能性枚举出来然后按照“每一层对手都会给我找麻烦”的悲观假设来选路。这种悲观假设是博弈 AI 的底层逻辑——在零和游戏里对手永远和你利益相反。2.2 Alpha-Beta 剪枝把搜索深度直接翻倍极小极大搜索有一个致命问题搜索量随深度指数增长。五子棋每层大概有 30~50 个有效候选点搜索六层需要对 30^6 7.29 亿个节点做评估普通电脑直接卡死。Alpha-Beta 剪枝解决的就是这个问题。它的核心思想是如果在搜索某个分支时已经确定这个分支不可能比当前已知的最佳结果更好那就直接剪掉不用继续往下搜。具体来说两个值贯穿整个搜索过程Alphaα当前 MAX 层已经能保证的最低分Betaβ当前 MIN 层已经能给出的最高分上限。当某个子节点的返回分数让局面低于 Alpha 时MAX 层不会选它后续兄弟节点也就不用看了同理当分数高于 Beta 时MIN 层不会让它发生后面的分支也可以剪掉。有效的剪枝能减少约 60% 甚至更多的节点访问量。在五子棋项目中我用了一个关键的优化候选点先排序再搜索如果最容易产生高分的点排在最前面剪枝效率会直线上升。实测下来同样的深度Alpha-Beta 剪枝后的耗时只有朴素极小极大搜索的十分之一左右这直接让我的 AI 搜索深度从 4 层提升到了 8 层。2.3 为什么不直接上 MCTS 和神经网络现在一提到棋类 AI很多人第一反应就是 AlphaGo 和深度学习。为什么我的 AI-Gomoku 走的是传统搜索路线五子棋和围棋不一样。围棋的分支因子极高、局面评估极难所以需要 MCTS 这种渐进式采样的办法来压缩搜索空间。但五子棋棋盘小、规则简单在经典算法加持下搜索深度一旦超过 6 层棋力就已经非常可观完全能打赢业余玩家。MCTS 在五子棋上当然也有效但它的调参难度更大而且对于“判断某个点是否形成冲四”这类精确棋形搜索树反而更直观。神经网络就更不用说了。训练一个能下五子棋的神经网络需要大量对局数据和算力对练手项目来说成本过高。我的目标是“在普通笔记本上 1 秒内落子”经典搜索在这个约束下依然是最优解。3. 工程实现中绕不开的三个环节3.1 棋盘与棋型表示棋盘我用最简单的二维列表表示1 表示黑棋2 表示白棋0 表示空位class Board: def __init__(self, size15): self.size size self.grid [[0] * size for _ in range(size)] self.current_player 1 def play(self, row, col): if self.grid[row][col] ! 0: raise ValueError(非法落子) self.grid[row][col] self.current_player self.current_player 3 - self.current_player def undo(self, row, col): self.grid[row][col] 0 self.current_player 3 - self.current_player有的高性能项目会用位棋盘bitboard来压缩存储和加速判断但对于 Python 项目二维列表的清晰性远大于微小的性能收益。如果你要追求极致性能可以考虑 NumPy 数组不过初期完全没有必要。判断胜负的逻辑也需要单独写。五子棋的胜利条件是横、竖、左斜、右斜四个方向任意一个方向出现连续五个同色棋子。注意这里有个边界情况某些规则是“连成五子或以上”有些规则是“正好五子”。我采用的规则是“五子或以上”因为更通用而且四子被堵住后再补一步不算赢。3.2 候选落子生成把几十个落点缩小到十几个如果每个节点都遍历整个棋盘 225 个空位搜索效率会低到不可接受。但在真实棋局中远离已有棋子的落点几乎不可能成为最佳走法所以可以做一个启发式裁剪只考虑已有棋子周围 radius2 的空格。具体实现思路是def get_candidates(board, radius2): candidates set() for row in range(board.size): for col in range(board.size): if board.grid[row][col] ! 0: for dr in range(-radius, radius 1): for dc in range(-radius, radius 1): r, c row dr, col dc if 0 r board.size and 0 c board.size and board.grid[r][c] 0: candidates.add((r, c)) return list(candidates)实测下来这个裁剪能把候选点数量从 225 个压缩到平均 20~40 个。分支因子直接减少一个量级搜索深度的提升非常明显。不过这个优化也有副作用如果 AI 面临的是一个“远距离伏手”的局面Radius 太小可能会导致它看不到伏手。稳妥一点的做法是在候选点生成时同时检查是否有“活三”或“冲四”在远处形成如果存在就把远处的关键点也加入候选列表。这个技巧在低深度搜索时尤其重要。3.3 评估函数设计给每个棋型定一个价评估函数是整套 AI 的灵魂。它决定了一个棋盘局面“对 AI 有多好”如果没有合理的评估搜索再深也下不过一个会看棋的人。我把棋盘拆分为四个方向横、竖、左斜、右斜上的所有连线段然后对每条线段识别出其中的棋型再把每种棋型映射到分值。棋型定义和初始分值如下棋型分值说明五连10000000已经赢棋必须最高活四1000000两端都没被堵对方无法阻止连五冲四100000一端被堵但依然有威胁活三50000再走一步能成活四眠三5000被堵掉一头威胁小一些活二1000有发展潜力眠二100潜力有限死棋0被双向堵死完全没价值这套分值从何而来其实不是精确计算出来的而是基于经验排序后调整出来的。一个朴素的思路是五连必须远大于其他所有分数的和否则 AI 可能会为了阻挡对方的“眠三”而放弃自己已经形成的“活四”。我选的值保证了这种优先级关系。评估函数一个很重要的细节是攻守兼备。如果你只评估 AI 自己的棋型AI 会变成纯进攻型选手完全不会防守如果你只评估对手的威胁AI 又会变成被动挨打。我的做法是在对手的棋型上加权 0.9 倍作为防守权重让 AI 在判断“我应该进攻还是先堵住对方”时有一个折中。这个 0.9 是我调出来的经验值降低到 0.8 时 AI 倾向于防守后手提高到 1.0 时 AI 变得冒进容易露出破绽。评估代码的简化版本def evaluate(board, ai_player): score 0 for line in get_all_lines(board): score analyze_line(line, ai_player) - 0.9 * analyze_line(line, 3 - ai_player) return score3.4 为什么说评估函数比搜索深度更影响棋力有一段时间我为了追求搜索深度把每步的思考时间放宽到了 3 秒深度跑到了 12 层。结果发现 AI 的棋力并没有想象中那么强反而经常走出“看着计算量很大但思路完全不对”的棋。原因很简单如果对一个局面的“品味”是错的那看得越远就越自信地走错路。评估函数就是 AI 的“品味”。它会告诉搜索树“这个局面到底对我有多有利”。评估函数里没有考虑到的东西搜索再深也补偿不回来。后来我花了很多时间在评估函数上比如加入“双活三”的威胁检测、细分“连活三”“跳活三”的区别、对“待冲四”做特殊加权棋力提升非常明显。更重要的是它比增加搜索深度要省时得多——评估函数只会在每个节点上多花几毫秒而搜索深度每增加一层时间就要翻好几倍。4. 调参实测从“会下棋”到“下得好”的距离4.1 搜索深度、时间与棋力的三角关系搜索深度决定了 AI 能看多远但深度不是越高越好。深度越高单步耗时越长对局体验越差而且在某些局面下短视的“只看一步妙手”反而比“看到十步但错过关键杀棋”要讨喜。我实测了几组参数搜索深度平均单步耗时棋力表现4 层 50ms勉强能看经常被引诱6 层约 150ms已经能防住大多数活三8 层约 600ms棋力明显提升攻守较均衡10 层约 3s能发现很多远距离杀棋但有点慢我的最终选择是固定搜索 8 层配上迭代加深和 1 秒超时保护。意思是启动搜索时先搜 2 层再逐步加深一旦超过 1 秒就使用前一层的结果。这样的好处是AI 在绝大多数局面下都能在 1 秒内做出响应而在残局或关键时刻能自动多搜一两层。4.2 实战中的三个经典坑这里分享几个我在调试过程中真正踩过的坑希望能帮你少走弯路。坑一AI 无视对方的活三死命进攻。原因是搜索树虽然看到了对手成活三但在评估函数里对手的棋型权重不够高AI 觉得“我形成活四能赢你的活三无所谓”。但五子棋中活三往往意味着之后能连成活四和五连如果 AI 在两步内不能立刻取胜就必须优先防守。解决方案是提高防守权重并在评估函数里增加“如果对手已经形成活三且 AI 五步内没有直接杀棋则惩罚 AI 当前局面”。坑二AI 在相同局面下会“来回横跳”。这是因为搜索过程中如果候选点的评估值非常接近AI 会频繁改变选择。常见于深水局面的残局阶段。解决方案是加一个简单的置换表Transposition Table用局面哈希缓存每个局面的评估分数和最佳走法这样重复局面不会重新搜索AI 也会更稳定。坑三开局阶段搜索特别慢。因为棋盘很空候选点和分支因子都大搜索树的宽度会突然膨胀。我的处理方式是在开局前 10 手使用预先计算的“开局书”来限定候选点比如优先下在棋盘中心 7×7 区域这样既不会错过开局布局也能把搜索时间控制住。4.3 提速技巧走法排序、置换表、迭代加深这三个技巧带来的加速非常可观也是把 AI 从“能下”推向“好用”的关键。走法排序搜索前先用评估函数快速给每个候选点打个分然后从高分到低分递归搜索。Alpha-Beta 剪枝的效率严重依赖“先碰到好走法”这一点排序好时剪枝效率能提升好几倍。置换表把搜索过的局面哈希值和分数缓存到字典里后续遇到相同局面直接返回省去重复计算。在复杂中残局阶段效果明显。迭代加深先搜浅层比如 2 层用浅层的结果为深层搜索提供更好的走法排序同时还能作为超时保护。更深层搜索时优先扩展浅层认定的最佳走法剪枝效率更高。这三个优化组合在一起后我的 AI 从“深度 6 层需要 1.5 秒”提升到了“深度 8 层只需要 600ms”。而且这些优化实现的难度都不高即使你是初学者也建议尽早加上。5. 进阶方向从经典搜索走向神经网络5.1 评估函数的手工瓶颈经典搜索方案的极限其实就卡在评估函数上。手写的棋型权重再精细终究是人工经验的外推。遇到复杂的中盘局面比如双方都有多个活二和眠三纠缠在一起时手工打分往往算不清楚。我自己测试过一个典型的残局AI 领先一个“活三”但对方已经有“双活二”的潜在连线。要判断哪个威胁更大需要综合很多局部棋型做全局推演。这种非线性关系手写规则很难建模。神经网络的价值就在于它能从大量对局中自动学习“什么样的局面更优”不需要人去枚举规则。5.2 可行路线MCTS 轻量策略网络如果你想朝深度学习方向延伸我建议不要直接复刻 AlphaGo 的完整架构而是做一个简化版用一个小型卷积神经网络CNN接受当前棋盘状态输出落子概率 P 和局面价值 V然后把 P 作为 MCTS 的先验概率V 作为评估函数的替代。具体来说输入棋盘的 15×15×2 张量或 15×15×4 带历史信息黑棋和白棋各一个通道网络用几层卷积 全连接参数量控制在 50 万以内用自对弈数据训练AI-Gomoku 自己和自己下目标函数是“最大化真实胜率”和“最小化 MCTS 搜索值与 V 的误差”。这条路线的门槛主要在数据生成和训练时间上但不需要顶级算力一张中端游戏显卡就能跑起来。当你看到 MCTS 的学习效果超过手工评分时你会对“搜索 学习”的组合有更深的理解。5.3 部署体验让 AI 程序跑成一个可对战的服务经典搜索版本运行在命令行里体验上差一些。我给它加了一个简单的 HTTP 服务前端用 H5 写了一个 15×15 的可点击棋盘通过 WebSocket 和 AI 通信。每次请求发送当前棋盘和落子方AI 返回最佳落点。这样做还有一个额外的好处你可以把 AI 接到微信群机器人、网页小游戏甚至智能音箱里。训练好的模型如果走深度学习路线可以用 ONNX 导出再用 FastAPI 部署成推理服务。这就是很标准的“AI 应用开发 模型部署”链路了但核心依然是那个搜索和评估框架。我个人在写完这个项目后最大的体会是五子棋 AI 最大的价值不在于赢棋而在于它逼你把一个模糊的“会下棋”的感觉翻译成精确的搜索、评分和剪枝规则。写之前我以为瓶颈在搜索深度真正写完才发现评估函数才是灵魂。如果你也想试试建议先从命令行版开始跑通 15×15 棋盘对弈再一步步加上剪枝、排序、置换表这些进阶优化最后你也会有属于自己的“下得过自己”的 AI。本文还有配套的精品资源点击获取