ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多智能体搜索实战:从Minimax到Alpha-Beta剪枝与评估函数设计

多智能体搜索实战:从Minimax到Alpha-Beta剪枝与评估函数设计 搞这个项目之前我建议你先想清楚一个问题你真的理解什么叫“多智能体”吗CS188 Project 2: Multi-agents本质上是让你在一个对抗性环境中同时考虑多个决策者。这个项目里的Pacman不是一个人在战斗它面对的是一群有“恶意”的幽灵。你写的每一个搜索算法都得在“自己怎么吃到豆子”和“怎么不被幽灵抓住”之间做权衡。这不是单机通关这是博弈。很多第一次接触这门课的同学做完Project 1之后会觉得搜索不过如此BFS、DFS、A*一跑就完事。但到了Project 2画风突变——你不再是单向搜索而是要模拟对手的反应做带对抗性质的决策。这个转变恰恰是AI从“寻路”走向“决策”的第一道坎。这篇文章我会围绕这个项目从整体思路、核心算法、评估函数设计、性能优化这几个维度完整拆解一遍最后附上我实际调试时踩过的坑。无论你是正在做这个大作业、还是想系统理解多智能体搜索这篇文章都值得你花五分钟读完。1. 项目整体设计与思路拆解1.1 这个项目到底在考察什么CS188作为伯克利的AI导论课Project 2的设计非常巧妙。它没有直接给你一堆公式让你背而是用Pacman这个游戏让你亲手实现三种核心搜索算法Minimax、Alpha-Beta剪枝、Expectimax外加一个评估函数设计。这个项目表面上是四个小任务实际上是一个递进式的认知链路第一个任务实现Minimax理解对抗性搜索的基本框架。第二个任务在Minimax上做Alpha-Beta剪枝理解如何通过剪枝减少搜索空间。第三个任务引入随机性改用Expectimax应对不确定对手。第四个任务设计评估函数学习如何把游戏状态量化成一个分数。这个链路走完你基本就理解了游戏AI、棋类AI的最底层逻辑。像国际象棋的Deep Blue、围棋的AlphaGo其核心决策树框架就是从这些基础算法延伸出去的。1.2 为什么偏偏用Pacman来教多智能体其实用游戏来教学不是CS188的首创但Pacman这个载体选得极其精准。第一Pacman的规则足够简单状态空间却足够复杂。你不会被复杂的规则淹没但又有足够的搜索深度让你感受到计算开销的压力。第二这个游戏天然存在“非合作博弈”。幽灵的目标是抓住你你的目标是吃豆子并躲避幽灵双方目标冲突这构成了典型的零和博弈场景正好是Minimax的适用场景。第三Pacman里存在不确定性的变体——当幽灵被吃掉后会回到初始位置这种行为有随机性又可以适配Expectimax的教学需求。一个游戏承载三个算法这个设计效率非常高。1.3 任务拆解与评分逻辑整个项目分了五个小问每个问题都有独立的分数权重。我按实际难度做个梳理Q1Minimax基础实现要求能在小地图上跑通不算难。Q2Alpha-Beta剪枝表面上是Q1的优化版但处理不好容易逻辑混乱。Q3Expectimax增加随机节点比Minimax多一层逻辑难度略升。Q4评估函数设计自由度高但想拿满分需要反复调参。Q5可选加分题用概率推理预测幽灵位置属于进阶内容。Q1到Q3本质上是同一个递归框架的不同变体。如果你能把递归的状态转移、取最大值/最小值/期望值的逻辑理清楚这三个问题最多花你一个晚上的时间。真正拉开差距的是Q4评估函数的特征选择、权重设计、归一化处理每一项都在考验你对游戏本身的理解深度。2. 核心算法逐个击破从Minimax到Expectimax2.1 Minimax多智能体博弈的地基Minimax的核心思想一句话就能说清在零和博弈中双方都采取最优策略时你的收益是多少。具体在Pacman场景中你Pacman是最大化者要追求最高收益所有幽灵是同一阵营的最小化者它们会选择一个让你的收益最低的行动。算法从当前状态出发递归模拟到某个深度然后在博弈树的每一层轮流取最大或最小值。这里的递归实现有几个关键点要注意第一终止条件不只是搜索深度达到上限还包括游戏结束状态。如果Pacman被幽灵抓住这个状态的效用值应该是负无穷或者一个极大的负数如果成功吃到所有豆子就是正无穷或极大正数。第二幽灵的行动选择需要遍历它所有可能的移动方向。注意幽灵不能原地不动这一点和Pacman的行为有细微差别。第三多个幽灵意味着多个最小化层。它们的顺序可以任意排列但每多一个幽灵计算量就指数级增长。这也是为什么Minimax在小地图上能跑大地图上就会卡死。代码层面的递归框架大概是这样的def minimax(state, depth, agentIndex): # 终止条件 if state.isWin() or state.isLose() or depth 0: return evaluationFunction(state) # 最大化层Pacman的回合 if agentIndex 0: value float(-inf) for action in state.getLegalActions(agentIndex): successor state.generateSuccessor(agentIndex, action) value max(value, minimax(successor, depth - 1, 1)) return value # 最小化层幽灵的回合 else: value float(inf) nextAgent (agentIndex 1) % state.getNumAgents() for action in state.getLegalActions(agentIndex): successor state.generateSuccessor(agentIndex, action) if nextAgent 0: value min(value, minimax(successor, depth - 1, 0)) else: value min(value, minimax(successor, depth, nextAgent)) return value注意多个幽灵的情况下depth的递减时机很讲究。只有轮到Pacman行动时depth才减一幽灵回合不应该消耗搜索深度。这是很多人容易出错的地方也是跑出来的结果“怪怪的”的常见原因。2.2 Alpha-Beta剪枝把指数级搜索压下来Alpha-Beta剪枝不是新算法它是在Minimax基础上做的一项优化思想非常朴素如果当前节点已经知道某个分支不可能影响最终决策就跳过它不再往下搜。具体来说Alpha是最大化者目前能保证的最小收益下界Beta是最小化者目前能保证的最大收益上界。当一个节点的值已经超出这个区间时继续搜索没有意义。在实现层面你需要给递归函数额外传两个参数alpha和beta并在每次递归返回后更新边界。这个原理说起来简单但实现时有一个高频错误很多人把alpha和beta的更新放错了位置或者忘记了在递归调用时传递最新的边界值。正确的自顶向下传递逻辑是进入子节点时把父节点目前能接受的区间传下去在子节点中每找到一个候选值就尝试更新本层的alpha或beta一旦发现alpha beta立即剪枝返回。值得注意的是节点的遍历顺序直接影响剪枝效率。如果分支的评估值是从好到坏排列的剪枝效率最高如果排列混乱剪枝可能几乎不生效。这一点在后面讲评估函数时可以结合起来看一个准确的评估函数能让搜索顺序更合理自然也就更快。2.3 Expectimax当对手不再“聪明”Minimax假设对手永远会做出对你不利的决策这在很多真实场景中并不成立。幽灵的移动策略并不完全理性有了随机成分后处理方式就得变化。Expectimax的思路是引入机会节点chance node。在这个节点上不再取最小值而是计算所有可能结果的期望值。实现上就是把“取最小”换成“对所有子节点取平均”加权系数取决于每个结果的概率。在CS188的标准设定中幽灵的移动方向是随机选择的所以每个行动的权重是相同的1/合法行动数。如果你要自定义概率模型只需要修改权重即可。Expectimax有一个明显的特点它的评估值永远不会像Minimax那样极端保守因为它考虑的不是最坏情况而是平均情况。这个差异在评估函数中体现得非常明显如果你发现Pacman的行动“太怂”或者“太浪”多半是搜索策略和评估函数对风险的判断不一致。2.4 三种算法的取舍对比算法适用场景计算开销风险偏好核心局限Minimax对手完全理性高极度保守搜索深度有限时容易错判Alpha-Beta对手完全理性且搜索空间大中极度保守受节点遍历顺序影响大Expectimax对手行为随机更高风险中性期望值可能掩盖致命风险我个人的经验是在项目最基础的跑通阶段用Alpha-Beta如果你希望Pacman在一个随机幽灵的地图上有更好的存活率Expectimax往往表现更自然。两者可以组合使用比如用Alpha-Beta做剪枝框架、在叶节点用概率模型做评估这种混合策略在真实项目中很常见。3. 评估函数设计从“能跑”到“跑得好”3.1 评估函数为什么是分水岭很多同学做完Q1到Q3觉得项目不过如此代码一跑就通过了。但到了Q4才真正感觉到什么叫“调参调到怀疑人生”。评估函数的作用是给非终止状态打一个分让搜索算法在无法穷尽博弈树时有一个“直觉”。这个直觉的质量直接决定了Pacman的聪明程度。一个糟糕的评估函数哪怕搜索深度拉到10它的表现也可能不如一个优秀评估函数深度为2的效果。设计评估函数的核心问题是从游戏状态中提取哪些特征以及每个特征该赋予多大权重。3.2 特征选择的经典组合根据CS188课程中的标准方案和社区里的高分经验一套稳定好用的评估函数通常包含这几类特征第一食物相关特征。最简单的做法是统计剩余食物数量或者计算Pacman到最近食物的距离。前者反应进度后者反应效率。注意如果只统计食物数量Pacman会倾向于先把近处的豆子吃光但不会主动规划路线如果只计算最近食物距离它会为了“接近食物”而冒进。两者需要搭配。第二幽灵相关特征。核心指标是Pacman与每个幽灵之间的最短距离曼哈顿距离即可。当幽灵距离较远时风险不大可以忽略当幽灵距离较近时必须施加巨大惩罚。这里的“近”怎么界定需要你根据地图尺寸调试。还有一个容易被忽略的点幽灵被吃掉之后会处于“恐惧状态”蓝色透明状态这时幽灵变成了食物。如果你不做区分评估函数会把恐惧状态的幽灵也当作威胁导致Pacman明明有机会吃掉幽灵却选择逃跑。第三胶囊Power Pellet相关特征。胶囊的有效期有限吃不吃、什么时候吃对局势影响很大。一个好的评估函数应该考虑胶囊剩余数量和Pacman与最近胶囊的距离。第四当前游戏是否处于“可进攻”状态。这个特征是进阶玩法需要判断幽灵是否处于恐惧状态如果有多只幽灵在恐惧状态评估函数应该鼓励Pacman主动出击。3.3 权重调优的实操方法PDF格式的评分标准里面Q4的得分是从多个随机地图上跑出来的平均胜率计算的。因此你不需要在某个特定地图上做到极致而是要保证在绝大多数地图上表现稳定。我的建议是分两步调参第一步先把特征提取正确。你用debug模式跑几局打印出每个状态的各特征值确认你的距离计算、恐惧状态判断没有出bug。这一步骤很多人跳过了直接跳到调权重结果调了半天发现是特征算错了浪费时间。第二步从一组保守的初始权重开始小步调整。比如食物权重为正的小数、距离食物权重为负的小数、幽灵距离权重为负的大数。跑一局观察Pacman的行为倾向再针对性调整。这个过程类似调参炼丹没有标准答案但有方法论。这里分享一个我常用的“逃命优先”策略给幽灵距离设置一个安全阈值比如5格。当Pacman与最近幽灵的距离小于阈值时评估函数的幽灵惩罚项应该呈指数级上升而不是线性上升。这样做的原因是幽灵的移动速度与Pacman一致特殊模式下幽灵可能更快在狭小通道中距离差1格可能就是生与死的差别。3.4 评估函数与搜索深度的联动评估函数不是孤立的它和搜索深度互相影响。当搜索深度较浅比如1或2时Pacman只能看到未来一步或两步评估函数需要承担大部分“智商”工作。当搜索深度较深比如4或5时算法能模拟出幽灵的大部分移动路径此时评估函数起到的作用相对降低但绝不能被忽略。我实测下来绝大多数地图上Alpha-Beta配合深度3和一套中等质量的评估函数就能拿到不错的分数。再往上加深边际收益会大幅递减反而可能因为搜索时间过长导致超时。有一个Hack可以试试在评估函数中加入一个微小的高斯噪声范围±0.001这样可以打乱某些对称局面下搜索的固定偏好往往能微幅提升胜率。这个方法在对抗性搜索中叫“打破对称性”属于实战小技巧。4. 性能优化剪枝再多也不够用4.1 计算瓶颈在哪里如果你跑过完整的地图比如mediumClassic应该能明显感受到深度一旦超过3每次行动的决策耗时就开始让人难受了。这里面的计算瓶颈有两个第一状态生成器的开销。generateSuccessor会复制整个GameState包括所有的食物、胶囊、幽灵位置。复制一个状态的开销非常可观搜索树上的节点数以万计时这个开销会被放大。第二合法行动的遍历。每层节点都要枚举Pacman或幽灵的所有合法移动方向这些方向数量虽然不大最多4个但和搜索深度叠加后整体展开的节点数量仍然是指数级的。4.2 我实测有效的优化手段首先缓存历史结果。Python的lru_cache装饰器可以直接套在评估函数上但需要注意GameState对象是否可哈希。如果不可哈希你需要自己实现一个状态编码函数把所有关键信息打包成元组再作为缓存的key。其次提前终止。如果当前状态中Pacman已经无路可逃周围全是幽灵且没有胶囊可以救场可以直接返回一个极低分数不用继续往下搜索。这个“逃生预判”虽然写得有点暴力但在很多地图上能省掉大量无效搜索。第三限制幽灵数量。在搜索树中每多一个幽灵当前层的最小化分支数量就翻倍。如果你确认某个地图上某些幽灵离Pacman特别远可以跳过它们只对近处的幽灵做最小化搜索。这种做法理论上会损失部分安全性但实测下来对胜率影响很小。第四优化状态表示。不要存储整个副本而是用坐标差值计算距离。很多评估函数里需要反复算Pacman到最近食物的距离如果你每次都是全图扫描效率太低了。可以预先算好一个距离矩阵或者用BFS做一次预处理。4.3 超时问题的排查思路CS188的自动评分有超时限制。如果你在某几个大地图上超时多半是搜索深度设置过高或者剪枝逻辑写错了。排查方法很简单先在smallClassic上跑深度从2开始往上加每加一档记录耗时。如果深度2耗时是0.1秒深度3是0.8秒深度4是6秒那你就能大致估算出这个地图的搜索树增长倍率。如果这个倍率远高于4倍说明你的剪枝并没有生效需要回去检查alpha和beta的更新逻辑。另外注意Python的递归有深度限制默认1000层你的搜索深度也就是4到5层不会踩线但如果代码里存在递归传递错误导致无限递归就会触发RecursionError。出现这个错误时优先检查depth有没有在正确的位置递减。5. 实操过程与核心环节实现5.1 开发环境准备与项目结构这个项目的代码框架本身是基于Python 2的但现代环境建议直接用Python 3运行兼容性基本没问题只有个别打印语法需要调整如果官方包没适配的话。我推荐的环境组合是Python 3.8 VS Code pytest可选一个轻量环境足够跑起来了。项目文件结构需要清楚pacman/ ├── pacman.py # 游戏主逻辑 ├── game.py # GameState核心定义 ├── ghostAgents.py # 幽灵行为模型RandomGhost可以改 ├── multiAgents.py # 你要实现的核心文件 ├── evaluationFunction # 需要填写的评估函数 ├── minimax / alphabeta / expectimax └── layouts/ # 地图文件multiAgents.py是唯一需要你动手改的文件。官方框架里ReflexAgent已经给出一个简单的评估函数作为示范而MinimaxAgent、AlphaBetaAgent、ExpectimaxAgent则留了getAction接口让你实现。5.2 一个可直接复现的Minimax实现在动手之前先跑一次python pacman.py -p ReflexAgent -l openClassic建立基线。ReflexAgent的表现在多数地图上大概是有吃有跑但不够聪明的水平之后我们做的所有改进都要和它做对比。下面是一个可直接运行的MinimaxAgent实现基于多幽灵结构进行设计。建议你看懂后再自己盲写一遍不要直接抄进作业里——这门课的评分系统有查重机制风险自负。class MinimaxAgent(MultiAgentSearchAgent): def getAction(self, gameState): actions gameState.getLegalActions(0) bestAction actions[0] bestValue float(-inf) for action in actions: successor gameState.generateSuccessor(0, action) value self.minimax(successor, self.depth, 1) if value bestValue: bestValue value bestAction action return bestAction def minimax(self, state, depth, agentIndex): if state.isWin() or state.isLose() or depth 0: return self.evaluationFunction(state) legalActions state.getLegalActions(agentIndex) if len(legalActions) 0: return self.evaluationFunction(state) nextAgent (agentIndex 1) % state.getNumAgents() nextDepth depth - 1 if nextAgent 0 else depth if agentIndex 0: # 最大化 value float(-inf) for action in legalActions: successor state.generateSuccessor(agentIndex, action) value max(value, self.minimax(successor, nextDepth, nextAgent)) return value else: # 最小化 value float(inf) for action in legalActions: successor state.generateSuccessor(agentIndex, action) value min(value, self.minimax(successor, nextDepth, nextAgent)) return value注意这里的深度递减逻辑当nextAgent回到0也就是Pacman的回合时才说明完成了一整轮“Pacman一次行动 所有幽灵各行动一次”此时depth减一。这个设计是保证多个幽灵时搜索深度不偏斜的关键。5.3 Alpha-Beta的正确打开方式基于上面的MinimaxAlpha-Beta需要传入两个额外参数并在每一层更新阈值class AlphaBetaAgent(MultiAgentSearchAgent): def getAction(self, gameState): actions gameState.getLegalActions(0) bestAction actions[0] alpha float(-inf) beta float(inf) bestValue float(-inf) for action in actions: successor gameState.generateSuccessor(0, action) value self.alphabeta(successor, self.depth, 1, alpha, beta) if value bestValue: bestValue value bestAction action alpha max(alpha, bestValue) return bestAction def alphabeta(self, state, depth, agentIndex, alpha, beta): if state.isWin() or state.isLose() or depth 0: return self.evaluationFunction(state) legalActions state.getLegalActions(agentIndex) if len(legalActions) 0: return self.evaluationFunction(state) nextAgent (agentIndex 1) % state.getNumAgents() nextDepth depth - 1 if nextAgent 0 else depth if agentIndex 0: value float(-inf) for action in legalActions: successor state.generateSuccessor(agentIndex, action) value max(value, self.alphabeta(successor, nextDepth, nextAgent, alpha, beta)) if value beta: return value alpha max(alpha, value) return value else: value float(inf) for action in legalActions: successor state.generateSuccessor(agentIndex, action) value min(value, self.alphabeta(successor, nextDepth, nextAgent, alpha, beta)) if value alpha: return value beta min(beta, value) return value这里的关键是剪枝条件的判断在最大化层一旦当前值超过beta说明对手在上一层已经有更好的选择没必要继续探索在最小化层一旦当前值低于alpha同理。5.4 Expectimax实现与“风险中性”行为Expectimax的代码逻辑几乎和Minimax一致唯一的区别是最小化层换成了平均层class ExpectimaxAgent(MultiAgentSearchAgent): def getAction(self, gameState): actions gameState.getLegalActions(0) bestAction actions[0] bestValue float(-inf) for action in actions: successor gameState.generateSuccessor(0, action) value self.expectimax(successor, self.depth, 1) if value bestValue: bestValue value bestAction action return bestAction def expectimax(self, state, depth, agentIndex): # 终止条件同上 if state.isWin() or state.isLose() or depth 0: return self.evaluationFunction(state) legalActions state.getLegalActions(agentIndex) if len(legalActions) 0: return self.evaluationFunction(state) nextAgent (agentIndex 1) % state.getNumAgents() nextDepth depth - 1 if nextAgent 0 else depth if agentIndex 0: value float(-inf) for action in legalActions: successor state.generateSuccessor(agentIndex, action) value max(value, self.expectimax(successor, nextDepth, nextAgent)) return value else: values [] for action in legalActions: successor state.generateSuccessor(agentIndex, action) values.append(self.expectimax(successor, nextDepth, nextAgent)) return sum(values) / len(values)运行一下Expectimax对比Minimax你会发现一个有趣的现象在某些地图里Expectimax因为不假设幽灵“永远聪明”反而会选择一些冒险路径从而在部分布局上拿到更好的胜率。5.5 评估函数实战从零到可用的代码以一个稳定能过评分的评估函数为例。以下特征是经过我多次调试后保留的版本几条都不算复杂但组合起来足够应付绝大多数地图def betterEvaluationFunction(currentGameState): # 当前位置 pacmanPos currentGameState.getPacmanPosition() # 剩余食物 foodList currentGameState.getFood().asList() # 胶囊 capsules currentGameState.getCapsules() # 幽灵状态 ghostStates currentGameState.getGhostStates() score currentGameState.getScore() # 食物部分 if len(foodList) 0: minFoodDist min(manhattanDistance(pacmanPos, f) for f in foodList) score 10.0 / (minFoodDist 1) score - 4.0 * len(foodList) # 胶囊部分 if len(capsules) 0: minCapDist min(manhattanDistance(pacmanPos, c) for c in capsules) score 6.0 / (minCapDist 1) # 幽灵部分 for g in ghostStates: dist manhattanDistance(pacmanPos, g.getPosition()) if g.scaredTimer 0: # 恐惧状态幽灵是猎物 if dist 5: score 20.0 / (dist 1) else: if dist 5: score - 50.0 / (dist 0.1) return score这个评估函数的逻辑是分数本身作为基线食物距离越近加分越多用倒数是为了平滑剩余食物数量作为惩罚项剩余越多越不利激励Pacman加快进度胶囊提供了另一种激励幽灵的处理区分了正常状态和恐惧状态。值得注意的两个细节一个是10.0 / (minFoodDist 1)这个结构分母1是为了避免除零另一个是幽灵惩罚的50.0 / (dist 0.1)这个惩罚项在高距离时衰减得比较快但距离小于1时数值非常恐怖能有效阻止Pacman在幽灵脸上晃悠。6. 常见问题与排查技巧实录6.1 幽灵“鬼打墙”明明没被抓住却一直在原地打转这种问题基本可以锁定在评估函数的“最近食物距离”特征上。如果你用了minFoodDist作为正向激励当Pacman和食物之间的距离保持不变时它会觉得原地不动也不错。尤其当食物分布在过道两侧时Pacman会在食物之间来回摆动。解决办法是给Pacman当前的移动方向一个小的惩罚或奖励或者引入“已经访问过的位置”惩罚滞留在同一格的时间越长惩罚越大。6.2 Minimax结果与预期不符总是往幽灵方向走大概率是符号问题最大化层写成了最小值或者评估函数的方向反了。排查时用一个只有两个幽灵、一块食物、无胶囊的小地图单步调试打印每个状态下搜索树的顶层几个值看是否符合直觉。还有一种情况是多幽灵时depth递减时机不对。如果幽灵回合也消耗了depth那么在深层搜索中轮到幽灵决策的次数会被压缩评估结果自然偏移。6.3 Alpha-Beta与Minimax结果不一致排除代码语法错误后最可能的原因是剪枝条件写反了。注意只有“值大于等于beta”时才能剪掉最大化层的分支只有“值小于等于alpha”时才能剪掉最小化层的分支。如果你用代替不会出大错但会少减一些分支如果方向写反结果就会完全不同。6.4 超时问题深度加到3就炸了这种情况优先怀疑剪枝没有生效或者评估函数里用了全图扫描之类的重操作。用一个计数装饰器打印搜索函数被调用的次数对比Minimax和Alpha-Beta的调用次数就能确认剪枝是否正常工作。正常来说Alpha-Beta应该比Minimax少30%以上的节点访问。6.5 幽灵之间的距离与“包围圈”如果你发现Pacman经常被两只幽灵从两个方向堵死那说明评估函数没有考虑“多幽灵协同”。一个改进思路是在幽灵惩罚项中加入临近幽灵数量的连续性惩罚。比如当两只幽灵都在距离Pacman三步以内时额外施加一个大的惩罚值因为这通常意味着无路可逃。6.6 一张速查表帮你快速定位问题现象可能原因排查方向Pacman原地打转评估函数缺少位置变化激励检查最近食物距离是否稳定总往幽灵方向走最大化/最小化逻辑写反对照伪代码逐层走查剪枝后结果不同alpha/beta传递顺序错误单步调试打印区间值搜索超时剪枝不生效或评估函数过重打印节点访问数对比幽灵恐惧状态下不敢吃缺少scaredTimer分支确认评估函数对幽灵状态做了区分多幽灵地图表现差最小化层逻辑未覆盖多幽灵检查depth递减时机7. 写在最后的一些经验这个项目做到最后我的一个很深的体会是写代码本身不难真正难的是想明白每个决策背后在优化什么。Minimax优化的是最坏情况下的收益Expectimax优化的是平均收益Alpha-Beta只是加速手段评估函数是这些优化目标的量化表达。四者之间的关系一旦理清后面的所有问题都是顺着这个框架延伸出来的。如果你做完这个项目还有余力我建议你试着把深度调到5以上并用graphics模式去看Pacman的实时行为。你会看到它在吃豆子前会“思考”——先在原地顿一下再掉头那就是Alpha-Beta在计算。这个过程某种程度上就是在观察一个决策系统的白盒运行。还有一个容易踩的坑是不要在deadline前才把所有代码一次写完。这个项目最好分两天做第一天把Minimax和Alpha-Beta写完跑通基础测试第二天专心调评估函数和参数。拖延到最后通宵调试评估函数那种感觉真的很难受。如果你在调试过程中遇到了其他奇怪的问题欢迎在评论区描述你的复现步骤、异常行为和期望结果。我看到后会尽量帮你定位。
返回列表