极小化极大与Alpha-Beta剪枝:四子棋对抗AI实战解析
2026/9/7 13:47:34 网站建设 项目流程

简介:这是一份面向人工智能算法学习者与博弈游戏开发者的重力四子棋对抗AI实现,围绕四子棋的竖直堆叠规则,采用α-β剪枝与自适应估价函数,在有限搜索深度内做出较优决策,解决了重力规则下如何平衡搜索效率与落子质量的问题。压缩包共5个文件,包含3个头文件与2个C++实现文件,头文件分别用于策略接口声明、坐标点定义和搜索相关数据结构,源文件则承载核心搜索逻辑与游戏判胜流程,模块划分清晰,便于阅读和二次开发。资源包仅7KB,体积小巧,可快速集成到课程设计、实验报告或小型对抗项目中。目前已有1213人学习下载,对于正在学习博弈树搜索的学生,是理解α-β剪枝实际效果的直观样本;对于想快速搭建四子棋对战AI的开发者,也能直接编译运行并在此基础上继续调优。通过源码还能体会估价函数对棋型、攻防权重的设计细节,以及重力机制带来的落点搜索特点,是算法实践与项目复盘的有价值参考,适合课程设计、毕业设计或AI竞赛备赛使用。

1. 项目概述与技术选型思路

“人工智能四子棋对抗AI”是我在做人工智能课程大作业时选的一个题目,属于经典的博弈类AI项目。四子棋的规则和我小时候玩的五子棋很像,只是把棋盘从平面网格改成了7列6行的纵向格子,双方轮流落子,谁先把四颗棋子连成一条线(横、竖、斜都算)谁就获胜。

这个项目说大不大,说小不小。如果只是做一个能随机落子的AI,半天时间就能写完;但如果要做成一个经验丰富的玩家都很难击败的对抗AI,需要认真设计搜索算法和评估函数。我在定方案时给自己设定了三个目标:一是AI要有一定棋力,至少是普通人类玩家很难赢的水平;二是整个系统要能在普通配置的电脑上流畅运行,落子思考时间不超过三秒;三是代码结构要清晰,方便后续扩展和调整策略。

在算法选型上,我优先考虑的是经典搜索算法而非深度强化学习,这样既保证了训练数据少、训练时间短,又让整个项目在缺少高端显卡的环境中也能跑得动。最终我采用极小化极大算法(Minimax)加Alpha-Beta剪枝作为核心搜索框架,评估函数方面设计了基于位置权重的综合打分机制。这套方案是博弈类AI中最经典的组合,代码量适中,效果却有保障——实测在搜索深度4到6层时,AI就已经具备相当强的棋力。

1.1 为什么选四子棋作为对抗AI的载体

选四子棋有几个很实际的原因。首先是规则简单,四子棋的状态空间比围棋、中国象棋小几个数量级,方便在单个文件中完成搜索与评估的全部逻辑,但搜索树的规模又比三子棋(井字棋)大得多,适合体现算法优化的价值。井字棋搜索空间只有不到30万种状态,暴力穷举就能赢;围棋的状态复杂度是10的170次方级,个人开发者连基础框架都搭建困难。四子棋一个6行7列的棋盘,合法着法排列组合起来,就算限制了回合数,实际搜索树深度也足够检验算法效率。

其次是“对抗”的属性特别明显。四子棋没有运气成分,不涉及随机手牌或掷骰子,纯粹是两名棋手搜索深度的比拼。这意味着极小化极大算法的思路可以被直接应用:我(当前玩家)取对我最有利的局面,对方取对我最不利的局面。这种“在互相对抗中寻找最优解”的模式,是很多真实决策系统的雏形。

另外四子棋的胜负判定非常直观,四子连线即可胜出,便于我在开发过程中快速定位是搜索逻辑的问题还是评估函数的问题。每一层的评分与胜负信息可以直接打印在终端里对照,调试体验远好于围棋这类需要长期判断五五开的项目。

1.2 算法选型:极小化极大与蒙特卡洛的取舍

在调研阶段我仔细比较了几种常见的博弈搜索方案。极大极小算法是最直观、最经典的一类——扩展当前局面的所有可能落子,假设双方都采取最优应对,构建一棵搜索树,当前玩家拿最大值,对方拿最小值,最终选出评分最高的分支作为决策。

Alpha-Beta剪枝是对极大极小算法的优化,它能在不影响最终决策的前提下跳过大量“肯定不会选”的分支。通俗点说就像你在网购时看到一件商品好评率99%,已经准备加入购物车了,这时候不需要再翻后面几十页的差评——反正不影响决定。这个比喻用来理解Alpha-Beta剪枝的核心思路再合适不过了。

蒙特卡洛树搜索(MCTS)是另一种常见方案,通过随机模拟大量对局来估算每个着法的胜率,AlphaZero等知名AI系统采用的就是这类思路。MCTS在复杂棋类中表现亮眼,但四子棋搜索深度较浅、分支因子较小,极小化极大配合剪枝已经能在极短时间内算出非常优的结果,不需要引入随机模拟带来的不确定性。考虑到课程大作业的周期和调试成本,我选择了更快见效的极小化极大方案。

2. 核心算法原理与实现解析

2.1 极小化极大算法:从“我赢你输”的建模说起

极小化极大算法的核心是一个假设:对手和我一样聪明,每次都会选对自己最有利的着法。那么在某个局面下,我落子后要评估的不是“这一手看起来好不好”,而是“如果双方都走最优路径,最终我能赢多少”。

用四子棋来举例。假设AI是红方(先手),轮到红方落子,AI有一个评估函数F,输入一个盘面,返回一个数值。数值越大代表对红方越有利(红方越接近胜利),数值越小代表对蓝方越有利。放在搜索树的视角里,红方节点会选择子节点中F值最大的分支,蓝方节点会选择子节点中F值最小的分支。

算法递归的伪代码如下:

def minimax(board, depth, maximizing_player): if depth == 0 or game_over(board): return evaluate(board) if maximizing_player: max_eval = -float("inf") for move in legal_moves(board): board.make_move(move) eval = minimax(board, depth - 1, False) board.undo_move(move) max_eval = max(max_eval, eval) return max_eval else: min_eval = float("inf") for move in legal_moves(board): board.make_move(move) eval = minimax(board, depth - 1, True) board.undo_move(move) min_eval = min(min_eval, eval) return min_eval

在实现时要注意剪枝并且正确识别“当前轮到谁走”。如果有一步棋能直接让对方形成“三连且两端畅通”的形,而这步棋本身又没有立即获胜的价值,那这步棋通常是被优先剪掉的分支。真正决定AI棋力的,正是对“对手意图”的模拟深度。

2.2 Alpha-Beta剪枝:让搜索树瘦身70%

Alpha-Beta剪枝并不改变极小化极大的结果,它只是去掉那些“反正不会选”的搜索分支。原理是维护两个值:alpha表示当前最大化玩家已经确保能获得的最低分,beta表示当前最小化玩家已经确保能获得的最高分。

当某个分支的返回分数突破了当前alpha-beta区间时,后续分支就不用再搜了。以最大化节点为例,如果它已经找到一个评分为10的分支,而接下来某个子节点作为最小化节点返回了5,由于5小于10,这个子节点不会影响父节点的选择,那么它剩下的孙节点都可以剪掉。

搜索顺序对剪枝效率影响极大。剪枝最理想的情况是:先搜索高分值的着法,这样alpha会迅速抬高,后续低分值的分支能大面积剪掉。我在代码里先对方子、我方子分别按列中心距离排序,优先搜索靠近中央位置的着法,实测能剪掉60%-85%的节点。

2.3 评估函数:这是决定AI棋力的核心因素

如果说搜索算法决定了AI“看得多远”,评估函数则决定了AI“看到的局面到底好不好”。评估函数的设计直接决定AI的棋风——是激进进攻还是稳健防守。

我的评估函数由三部分组成:

第一部分是基础连线检测。遍历所有横、竖、斜方向上的四格窗口,统计窗口内红蓝棋子的分布情况。如果某个窗口中全是红方棋子(我执红),说明已经获胜,返回极大值;如果窗口中有三种颜色棋子混合,则该窗口价值为0;如果窗口里只有一种颜色的棋子且存在空位,按连子数量打分。我采用的权重是:四连=10000分、三连且两端有棋=100分、两连=10分、一连=1分。

第二部分是位置权重。中心列通常是四子棋的战略要地——中心列的棋子能同时参与横向、两种斜向的连线组合。我设定了简单的列权重矩阵,中心两列权重最高,越靠边权重越低。

第三部分是防守加分。如果对手在某列已经堆叠了三个棋子且再放一个就能获胜,我方必须在该列封堵。这里的评估方式是:计算对手所有可能成四的“威胁”数量,我方评分中减掉防守惩罚分。

完整的评估函数大致如下:

def evaluate(board): score = 0 for window in get_windows(board): red_count = window.count(RED) blue_count = window.count(BLUE) empty_count = window.count(EMPTY) if red_count > 0 and blue_count > 0: continue if red_count == 4: score += 10000 elif red_count == 3 and empty_count == 1: score += 100 elif red_count == 2 and empty_count == 2: score += 10 if blue_count == 4: score -= 10000 elif blue_count == 3 and empty_count == 1: score -= 100 elif blue_count == 2 and empty_count == 2: score -= 10 score += position_bonus(board) return score

3. 实操过程与调优记录

3.1 第一版:命令行版基础AI

我第一版实现是在命令行下交互的。棋盘用二维数组表示,用户输入1-7的数字选择列,AI调用搜索函数后输出落子列。这个版本的核心代码不到200行,全部使用Python实现。

在搜索深度设为4时,AI已经能应对大多数普通玩家——它能看到自己的直接胜手,也能及时堵住对手的直接胜手。但棋力有局限:遇到“两步之后形成的双威胁”这种需要纵深搜索的情况处理不好。比如对手在第三列堆了两个棋子,同时第五列也堆了两个棋子,两边都可能构成三连,AI顾此失彼,因为它没看到两步后的交叉威胁。

3.2 第二版:加入Alpha-Beta剪枝与迭代加深

第二版我实现了Alpha-Beta剪枝,同时引入了迭代加深机制:先在深度1搜索,然后深度2、3逐渐加深,把时间控制在一定阈值内,时间到了就返回当前深度的最优着法。这种策略保证了响应时间可控,同时尽可能利用剩余算力探索更深的层。

搜索深度的选择我做了几组测试(测试电脑配置为i5处理器+16GB内存,Python实现):

搜索深度单步耗时(秒)剪枝后扩展节点数棋力表现
40.3-0.5约2.8万能挡住直接威胁,偶尔失误
61.8-3.2约65万能处理双威胁,棋力接近老手
812秒以上约680万棋力很高但等待时间过长

我最终将线上深度限制在6层,加上时间阈值3秒的迭代加深控制,实测在Python版本下运行良好,用户等待时间可以接受。

3.3 第三版:启发式着法排序与性能优化

Alpha-Beta剪枝的效果严重依赖搜索顺序,所以我实现了着法排序:先搜索近期落子位置附近的列,优先搜索能形成己方四连或能堵住对方四连的列。实际观察中,这个简单的排序让扩展节点数从没有排序的约950万降低到了约65万,效果非常明显。

这背后是搜索树“先探索高价值分支”的核心方法论——用常见的性能分析工具,比如Python的cProfile,可以看到sorted和排序模块的调用占比,但相对于剪枝减少的节点数来说,这点排序开销不值一提。

3.4 界面版:从命令行到可视化

命令行版本便于调试和测试,但作为课程大作业的展示,最终还是要有一个可视化界面。我用Python的Pygame库实现了一个简单版本:

  • 窗口大小设为700x600,棋盘区域每格大小为100像素
  • 鼠标点击某一列时,在当前列最低空位落子
  • AI思考时显示“AI思考中...”的提示
  • 胜负判定后弹出结果提示并支持重新开局

界面代码不算复杂,但有一个细节需要注意:四子棋的落子有重力效果——棋子必须落到该列最下面的空位,不属于自由选位。所以AI搜索时需要先判断该列是否已满,再确定落子的行位置。

3.5 参数调优:那几次“拍脑袋”的调整

在调参过程中我踩过一些坑,也总结出几个有效策略:

第一,评估函数的权重调整不能“拍脑袋”。我最初把三连的权重设得过高——比两连高50倍,结果AI只执着于进攻,忽视了对手在边缘列慢慢积累优势。后来我把三连设为两连的10倍,并把防守威胁的权重调升,AI的风格就均衡了不少。核心的教训就是:把权重变化写入配置文件,反复跑自对弈来验证。

第二,搜索深度4的AI碰上搜索深度6的AI,几乎必败,差距在于看得不够远。但如果AI能提前“看到”对手形成双威胁的战术,会优先破坏这种局面,而不是盲目进攻。这一步优化靠的是在评估函数中加入防守点权重,而非单纯增加搜索深度。

第三,四子棋有一个天然先手优势:先手(红方)的第一步如果下在正中间,胜率会明显升高。我在AI的先手逻辑里做了硬编码优先处理,实测这能让AI胜率提升5%左右。

4. 常见问题与排查技巧实录

4.1 为什么AI有时会无视对方的直接威胁

这是我调试过程中遇到最多的问题。现象是:AI已经在某一列上存在三连,下一步就能获胜,但AI却走了别的位置——然后被对手反杀。排查后发现原因在于搜索深度为偶数时,最底层节点的视角和根节点视角不一致。原本这手棋确实能赢,但在更深层的递归中它可能被对手的“反手致胜”抵消,导致评分反而降低。

这个问题的本质是评估函数的“地平线效应”——搜索深度有限,看不到更深层的威胁。我的解决方法是增加“威胁检测”模块:在搜索开始前,直接检查是否存在一子定胜负的位置,如果有则立刻落子,不进入搜索流程。这个方法牺牲了一点“战略规划”能力,但能杜绝低级的漏杀问题。

4.2 搜索时间过长,界面卡死

问题出在最大搜索深度设置过高,深度8及以上Python版搜索耗时超过10秒。我的解决办法有两条:一是用迭代加深配合时间预算,当某层搜索超出时间预算时直接返回上一层的结果;二是引入缓存表——用一个字典记录已评估过的盘面的哈希值,同一盘面再次出现时直接返回分数。实际效果是平均搜索耗时降低了40%左右。

这里提醒大家注意:棋盘状态哈希时一定要包含当前轮到谁走这个信息。同一个盘面,红方走和蓝方走是两种完全不同的局面,如果混用缓存会导致评估错误。

4.3 评估函数权重怎么调才合理

这个问题没有标准答案,但有一个有效的检查方法:设计几个标准测试局面,比如“红方已有三连且两端均为空位”“蓝方有两个分散的两连”“双方在中心列各有两子”,记录评估函数给出的分数,看是否符合直觉判断。

我在调优时用了一个更高效的方式:让两个不同权重的AI自对弈,快速跑100局,统计胜率和平均步数。如果A权重显著占优,说明权重方向正确;如果胜负接近,说明权重基本均衡。这种方法把“感觉”转化为“数据”,调参效率提高了很多。

4.4 命令行版本下输入不合法字符导致的崩溃

这是个编码习惯问题,但也值得提到。因为四子棋的输入是1-7的数字,新手容易输入0、8或字母。我在代码里加了异常处理,非法输入时提示重新输入而不是直接抛异常。看起来不起眼,但在给别人演示项目时,这个细节能节省大量解释时间。

搜索逻辑中用到的move排序也需要小心:排序依据应是当前棋局下该列的潜在价值,而不是固定的列序号。中心列在棋局初期价值最高,但棋局进入中盘后,靠近当前局势焦点区域的列价值可能超过中心列。

5. 项目扩展与后续思考

这个项目做完之后还可以从几个方向继续延伸。

一是提高搜索效率,做并行搜索。Python的GIL限制了CPU多核利用,但可以通过多线程管理对手的搜索过程,实现“一边想下一步,一边准备应对”的效果;或者用C++重写搜索核心,通过Python调用,单步耗时能再降一个量级。

二是引入机器学习。可以在极小化极大搜索的基础上,用强化学习训练评估函数——让AI自我对弈数千局,根据胜负结果用梯度下降更新评估函数的权重。这个过程虽然训练时间较长,但在本地用CPU也能跑,可以作为一个进阶研究方向。

三是把游戏移植到Web端。用Flask或FastAPI搭建后端,前端用HTML5 Canvas绘制棋盘,代码量不大,但展示效果远比命令行版本吸引人。如果是课程设计,给老师演示时这类有交互感的东西很加分。

我之前还试过让这个四子棋AI和另一个用MCTS实现的AI对弈,在各自限定步时1秒的条件下,极小化极大版本的胜率约为6成,但MCTS在复杂局面的风格更多变,很难被针对。

最后说说我做完这个项目的体会。四子棋虽然规则简单,但它覆盖了博弈AI的核心问题:怎样搜索、怎样评估、怎样控制搜索成本。这些思路放到五子棋、黑白棋、国际象棋上基本通用,唯一要变的是评估函数的设计和搜索树的剪枝策略。如果你想入门博弈类人工智能,四子棋是一个性价比很高的练手项目——规则简单但思考深度足够,代码量适中又能学到完整的设计思路。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询