资讯动态

四子棋AI实战:基于Minimax与Alpha-Beta剪枝的博弈搜索设计

发布时间:2026/9/8 7:29:02 来源:尧图企业网站定制
简介一份基于C的人工智能重力四子棋对抗AI项目适合正在学习博弈树搜索和Alpha-Beta剪枝的学生也适合需要快速搭建棋类AI原型的开发者。压缩包仅7KB共5个文件由3个头文件和2个C源文件组成头文件负责棋盘状态与策略接口等声明源文件实现裁判判定、α-β剪枝搜索和估价函数等核心逻辑代码量小便于逐行研读和二次改造。已有1213人浏览学习读者可从中获得完整的四子棋AI实现思路理解剪枝与评估函数如何配合观察AI在有限搜索深度下如何结合重力规则选择较优落子同时完整的胜负判断与决策流程也可作为课程设计、人工智能实验或同类竞赛项目的参考。项目虽小却展现了对抗类AI从状态表示到搜索决策的完整链路。 四子棋这东西看着就像小学门口的路人局——七列六行红黄棋子往下丢谁先连成四个谁赢。可要是真让你写个AI来跟自己对抗恐怕没那么轻松。我这次做的就是一个人工智能四子棋对抗AI用经典博弈搜索思路不依赖深度学习整个项目跑下来AI能稳定搜索到第5层跟普通人对战胜率相当可观。这篇文章不单是交作业我把完整的设计思路、核心代码、调参踩坑全部摊开讲适合正在做人工智能导论、算法与数据结构大作业的同学也适合所有想入门博弈对抗AI的人参考。1. 先说设计思路为什么四子棋AI该走“搜索”这条路1.1 从需求出发这个AI到底要做什么先说清楚目标。我要做的不是那种能自主学习的神经网络棋手而是一个在有限时间内能做出合理决策的四子棋AI它需要具备三个能力。第一看得懂规则。六行七列、重力落子、先连成四个得胜。第二能判断局面优劣。也就是说在没有任何搜索的前提下它得知道哪个位置更接近胜利。第三会往前多想几步。真正的对抗强度来自多步推演而不是只看眼前棋面。这三个能力对应到技术上就是状态表示、评估函数、博弈树搜索。把这三块拼起来就是一个完整可玩的对抗AI。1.2 方案选型搜索算法为什么比神经网络更合适很多人第一反应是用神经网络或者强化学习但我在这里明确选择传统搜索方案而且不后悔。原因其实很简单。四子棋属于完全信息、确定性、零和博弈。棋盘总共只有7列每一步的选择数量不超过7个分支因子很小。只要能快速判断胜负就可以用极小极大类算法获得确定性的最优解。而神经网络在这类问题上没什么优势反而要准备大量棋谱或者花大量时间做自对弈训练对一个大作业或者个人练手项目来说成本和复杂度都偏高。我选用的是经典的Minimax搜索配合Alpha-Beta剪枝再加一个启发式评估函数。这套组合的好处是可解释性强每一步落子都能回溯到具体的搜索树节点性能可控剪枝后搜索节点数量能减少几个数量级不挑机器任何一台普通电脑都能流畅运行。1.3 技术栈与实现范围整个项目用纯Python实现不依赖任何第三方库。数据结构用二维列表搜索用递归函数UI层暂时用命令行交互展示棋局。选择这种做法是为了让核心逻辑足够干净方便后续迁移到Web或者图形界面。最终的代码组成就是四块棋盘表示与落子逻辑、胜负判定、评估函数、带剪枝的搜索入口。接下来我按这个顺序逐个拆解。2. 棋盘建模与基本规则实现2.1 数据结构与落子逻辑四子棋棋盘是6行7列我直接用嵌套列表来表示board[row][col]0代表空位1代表玩家2代表AI。这里有个容易搞错的地方就是重力规则。棋子在竖直方向会下落落到该列最低的空格里而不是悬浮在任意行。所以每次落子我们要从底部向上找该列第一个空位。ROWS 6 COLS 7 EMPTY 0 PLAYER 1 AI_PLAYER 2 def drop_piece(board, col, piece): for row in range(ROWS - 1, -1, -1): if board[row][col] EMPTY: board[row][col] piece return row, col return None这段逻辑看着简单但它是整个游戏的基础。你想想如果落子位置不对后面所有搜索和评估就全部失真。我还额外做了合法列判断只要某列没有满就可以下子。2.2 胜利判断的四个方向与边界检查胜负判断是高频执行的操作搜索树里的每一个叶子节点都要调用所以要写得干净高效。四子棋的胜利条件是四个方向上的连续四个同色棋子水平、垂直、主对角线、副对角线。我的做法是遍历每个格子从这个格子出发沿四个方向检查。为了简单可靠我写了一个带方向增量的统一函数避免把四个方向的代码重复四遍。def check_win(board, piece): directions [(0, 1), (1, 0), (1, 1), (1, -1)] for row in range(ROWS): for col in range(COLS): if board[row][col] ! piece: continue for dr, dc in directions: er row dr * 3 ec col dc * 3 if not (0 er ROWS and 0 ec COLS): continue count 1 for step in range(1, 4): nr, nc row dr * step, col dc * step if board[nr][nc] piece: count 1 if count 4: return True return False这里最容易出错的是副对角线方向(1, -1)的边界检查。因为列索引会往下减所以终点ec可能变成负数。我每次都用0 ec COLS判断杜绝越界。2.3 评估函数先让AI“看得懂”局面真正让AI有“棋感”的是评估函数。它负责回答一个问题当前棋盘上AI是占优还是吃亏占了多少优势。我从窗口的角度来评估。所谓“窗口”就是棋盘上任意一段连续的四个格子理解成四子棋里的一个潜在连子位置。一个窗口如果同时存在双方棋子说明这个位置已经废了没有任何威胁。如果只包含单方棋子那剩下的空位越多潜力越大。def evaluate_window(window, piece): opp PLAYER if piece AI_PLAYER else AI_PLAYER score 0 if window.count(piece) 4: score 100 elif window.count(piece) 3 and window.count(EMPTY) 1: score 10 elif window.count(piece) 2 and window.count(EMPTY) 2: score 4 if window.count(opp) 3 and window.count(EMPTY) 1: score - 15 return score对称地如果这个窗口是对方的“三连一空”AI就必须防范所以给一个负数权重。实际评估时我遍历棋盘上所有合法窗口把AI视角的分数加起来。有的实现只统计AI窗口不统计对手窗口这种AI防守会很差后面我会具体说这个坑。3. 核心搜索逻辑Minimax与Alpha-Beta剪枝3.1 博弈树上的“你死我活”棋类AI最核心的思想是假设对手永远会选择对自己最有利、对AI最有害的那一步。AI先落子后轮到对手时对手自然要选能让AI分数最低的分支。到了下一层AI再落子时又会在所有分支里挑分数最高的。这就是Minimax这个名字的来源双方轮流“最小化”和“最大化”同一个评估值。这个思路翻译成代码并不复杂但注意评估的立场始终是AI视角分数高代表AI优势大。对手层选的是所有子节点里分数最低的那个AI层选的则是分数最高的那个。如果没有剪枝这个递归搜索会指数级扩展到第5层大概要访问几十万个节点纯Python跑起来非常吃力。3.2 Alpha-Beta剪枝把搜索节点砍掉九成Alpha-Beta剪枝的核心逻辑很朴素如果在某个节点已经发现一条足够好的路径而兄弟节点里出现了比这差得多的路径那就不必继续探索这个兄弟了。剪枝不会影响搜索结果它只是去掉那些确定不会被选中的分支。我把剪枝用两个参数传下去alpha代表AI方目前能保证的最低分beta代表对手方目前能接受的最高分。当alpha beta时当前子树没有继续搜下去的必要。def minimax(board, depth, alpha, beta, maximizing): if check_win(board, AI_PLAYER): return 100000 depth if check_win(board, PLAYER): return -100000 - depth if is_full(board) or depth 0: return evaluate_board(board, AI_PLAYER) if maximizing: max_eval float(-inf) for col in get_valid_cols(board): _, _ drop_piece(board, col, AI_PLAYER) eval_score minimax(board, depth - 1, alpha, beta, False) board[board.index(0) if False else last_row][col] EMPTY max_eval max(max_eval, eval_score) alpha max(alpha, eval_score) if beta alpha: break return max_eval else: min_eval float(inf) for col in get_valid_cols(board): _, _ drop_piece(board, col, PLAYER) eval_score minimax(board, depth - 1, alpha, beta, True) board[last_row][col] EMPTY min_eval min(min_eval, eval_score) beta min(beta, eval_score) if beta alpha: break return min_eval有一处细节必须说清楚在drop_piece之后我们不能简单用board[last_row][col] EMPTY来回滚因为last_row在这里是临时变量不同分支下落位置不同如果直接用会造成回滚错行。稳妥做法是让drop_piece返回落子行索引然后只清空那一层的那个格子。我在实际代码里用了一个辅助类或者返回元组处理这个逻辑避免状态污染。3.3 三个实战优化列排序、迭代加深、深度加分这部分是项目里真正体现性能差距的地方也是我摸索出来的核心经验。第一是列顺序。搜索时优先尝试中间列然后再向两边扩展。原因很简单四子棋棋盘中间列的分支价值通常最高容易在下棋初期控制局势而且好的分支越早被搜索到Alpha-Beta剪枝效果越好。第二是迭代加深。不要一上来就直接搜5层而是从第1层开始每完成一层搜索就用新的深度重来。如果时间不够至少能利用上一层的搜索结果作为备选方案保证随时能给出一个不算差的应对。第三是在胜负分上加深度补偿。搜到的胜利路径越早加分越多这样AI会倾向选择最快获胜的路径失败路径也是同理越晚输分越多这样AI会尽量拖延。这三项优化带来的效果非常明显在完全没优化的版本里搜索第5层动不动就要几秒甚至超时优化完以后配合剪枝第5层搜索基本能控制在能接受的范围而且走子质量明显提升AI已经会主动制造“双线杀”这样的陷阱了。4. 完整实现与实测效果4.1 简洁可直接运行的搜索入口博弈搜索的核心在主入口函数里。AI在思考时遍历所有合法落子列对每一列模拟落子后进入递归搜索然后取分数最高的列作为最终选择。def best_move(board, depth4): valid_cols get_valid_cols(board) best_score float(-inf) best_col valid_cols[0] for col in valid_cols: row, _ drop_piece(board, col, AI_PLAYER) score minimax(board, depth - 1, float(-inf), float(inf), False) board[row][col] EMPTY if score best_score: best_score score best_col col return best_col要注意搜索深度我默认给的是4。这个深度你实际玩起来会觉得AI反应很快但偶尔会漏掉远端杀棋。把深度改成5之后局面判断强了一个档次但计算时间也可能拉长。实际体验下来平台默认用4层做在线对战是稳妥的选择挑战模式里可以手动调5。4.2 搜索深度和响应时间的取舍节点数量和搜索深度是爆炸性增长的关系。实测中3层深度通常只要访问几百到一千个节点算法在毫秒量级就能返回4层深度大概几万个节点响应时间还勉强能接受5层深度时节点数不稳定取决于剪枝效果极端情况下会到几十万甚至更高响应时间开始变得明显。所以我的建议是如果AI参与的是带时间的对抗赛搜索深度优先控制在4层保底不会超时。如果你只是自己研究棋力上限可以调成5层等待几秒换来更好的走子质量。这个项目的核心思路就是“用深度换强度”——每次深度加深一层AI的棋力都会肉眼可见地提升。4.3 人对战实测一个“有脾气”的对手做完命令行交互界面后我测试了几十局整体观测到AI有几个明显特征。AI非常擅长防守。它会把对方三连一空的窗口全部堵死宁可自己放弃进攻也要先补防守。这其实是评估函数里防守权重偏高的结果。AI也有一定的进攻套路。它特别喜欢占中间列因为中间列能让它在横向上拥有更大的覆盖面积。最让我惊讶的是AI在4层深度下就会设置那种“双三”陷阱表面上堵住了一边实际上另一边已经形成必杀局面人是很难看出来的。当然AI并非无懈可击。我试过用连续骚扰边路的打法有时候它能应对有时候会暴露出对边线评估不足的弱点。这也是评估函数继续调优的方向。5. 常见问题与调试经验5.1 高频Bug和解决速查表我把实际调试过程中遇到比较多的问题整理成了一张速查表做同样项目的人可以直接对照排查。问题现象可能原因解决方法棋子悬浮在棋盘中间落子逻辑没找到最底空位强制从底部向上遍历该列斜向四连总是判不出来副对角线边界判断越界检查(1, -1)方向的列索引范围AI总是占同一列棋风机械评估函数对中心列权重过高降低中心加权加入边线收益搜索速度慢到无法忍受未用Alpha-Beta剪枝或列顺序不合理加剪枝从中间列开始搜索AI防守弱经常不堵对方关键手评估函数漏算对手窗口在评估里加入“对方三连扣分”逻辑深度调节后结果异常波动胜负分补偿方向搞反胜利时应depth失败时应-depth表格里每一项都是我真实踩过的尤其是那个落子回滚的问题调了整整一个晚上才找到原因就是因为多层递归共用了一个临时变量。5.2 如何评估你的AI到底强不强做完AI之后我们还需要一套评估体系来量化棋力。我采用的是“左右互搏”模式让当前版本的AI和自己对战100局统计胜率分布。同样深度下如果改动评估函数导致胜率下降说明改动方向有问题。深度不同的两个版本对打往往是深度更大的获胜这也能验证搜索深度的必要性。最直观的方式还是让人来测试因为人能找到算法里的“知识盲区”这也是评估函数最需要补全的地方。我建议做一个自动对战的脚本让AI1执红、AI2执黄循环100局最后统计红黄胜负和平局的分布。这一步虽然简单但能极大提升你的调试效率不用每次都手动落子。6. 从四子棋出发这个项目的可扩展价值6.1 换汤不换药其他棋类怎么迁移写出四子棋的架构以后你会发现这套逻辑几乎可以平移去任何同类型的棋类游戏。五子棋只需要改动两点把棋盘扩大成15×15把胜利方向检查改成五个方向把评估函数里的窗口长度从4改成5。黑白棋则要改掉落子逻辑变成“翻转棋子”规则搜索部分完全通用。井字棋更简单分支因子只有3随便写都能秒回用来练手剪枝再好不过。迁移的过程其实就是三步走重新定义状态表示、重写合法动作生成、重新设计评估函数。搜索框架完全不用动。6.2 再往前走一步可以加什么当前这个版本只是做了最基础的搜索型AI。如果时间充裕我会建议在三个方向上继续扩展。第一个方向是加开局库。把棋谱里最优的前几步走法直接存成表AI开局阶段直接查表省去计算。第二个方向是加难度调节。通过动态控制搜索深度让AI在简单模式下只往前看2层在困难模式下看5层。第三个方向是做可视化界面用WebSocket或者PyQt封装一层这样AI就能变成一个可以分享给别人玩的小产品。我后来还尝试过给评估函数加一个随机扰动项目的就是让AI在多个分数接近的走法中随机挑选而不是每次都走同一个落子。这一改AI就显得更像真人棋风不再机械对局的可玩性高了不少。说实话写完这个四子棋AI我的最大感受是博弈论和搜索算法看起来是本科教材上抽象的理论但真正写成代码以后你会非常直观地理解什么叫“决策”什么叫“权衡”什么叫“在不确定中寻找确定”。如果你正在做一个类似的对抗AI项目我强烈建议你从最小可跑通的版本开始先把规则、搜索、评估、主循环四个模块都跑通然后再花时间去调评估权重、优化剪枝这些细节。一上来就想着做完美AI多半会被一堆边界条件困住。最后再分享一个小经验调试评估函数的时候给AI加上“对战日志”功能把每一步的走子原因和分数变化记录下来你会发现这个小小的日志比任何断点调试都要好用。本文还有配套的精品资源点击获取

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

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

免费获取报价