ARTICLE DETAIL

资讯详情

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

从2048游戏到MDP建模:数学建模竞赛中的启发式搜索与MATLAB实现

从2048游戏到MDP建模:数学建模竞赛中的启发式搜索与MATLAB实现 1. 项目概述从游戏到数学模型的跨越“2048”这款游戏相信很多朋友都玩过甚至为之着迷过。滑动屏幕合并数字目标直指那个看似遥不可及的2048方块。但你是否想过这个简单的滑动合并游戏背后隐藏着深刻的数学原理和策略逻辑这正是当年Mathorcup数学建模竞赛第四届A题的核心魅力所在。它要求参赛者跳出玩家的视角以建模者的身份去解构“2048”的游戏机制并探寻最优的取胜策略。这不仅仅是一个游戏分析更是一次将离散状态、概率、决策树、启发式搜索等数学与计算机科学概念应用于具体场景的绝佳实践。对于学习数学建模、算法设计乃至对强化学习感兴趣的朋友来说这个课题都是一个极好的入门和练手项目。它用最直观的游戏界面引出了最核心的优化问题在充满不确定性的环境中如何做出一系列决策以最大化长期收益达到2048甚至更高分。接下来我将结合当年的赛题思路以及我作为建模指导者的经验为你层层剥开“2048”的数学内核并分享一套可复现的MATLAB求解策略。2. 核心问题拆解与数学模型建立2.1 游戏规则的形式化表述要建立数学模型第一步是将自然语言描述的规则转化为精确的数学或逻辑语言。2048的规则可以分解为以下几个核心组件状态空间 (State Space)游戏状态可以用一个4x4的矩阵S来表示即S ∈ {0} ∪ {2^k | k1,2,...} 其中0代表空位。通常我们研究到2048即k11。状态空间是离散且有限的但数量极其庞大理论上有(12)^16种可能但受合并规则限制实际可达状态少很多仍是一个天文数字这直接决定了我们无法进行穷举搜索。动作空间 (Action Space)玩家在每个回合有四种基本操作上滑(Up)、下滑(Down)、左滑(Left)、右滑(Right)。每个动作会引发一系列确定性的“滑动与合并”规则。我们可以将动作定义为A ∈ {U, D, L, R}。状态转移函数 (State Transition Function)这是模型的核心描述在状态S下执行动作A后如何确定性地转移到新状态S‘。这个过程分为两步滑动与合并对于选定的方向所有非零数字沿该方向“挤”到一边。对于每一行或列取决于方向相邻且相等的数字会合并其值翻倍。合并在一次滑动中只发生一次即4-2-2滑动后变成4-4不会再次合并为8。随机新生在滑动合并后的空白格子中系统会随机选择一个位置通常是均匀随机并放置一个新的数字。这个数字以90%的概率是210%的概率是4。这是游戏中不确定性的唯一来源。奖励函数 (Reward Function)每次合并操作产生的新的数字即为本次动作获得的即时奖励。例如将两个8合并成16则获得16分。总奖励游戏分数是所有合并操作产生数字的总和。终止状态 (Terminal State)当4x4网格被填满且任意相邻的格子都无法合并时游戏结束。达到2048方块通常被视为“获胜”但游戏可以继续向更高分挑战。注意在数学建模中明确“目标”至关重要。赛题中“取胜策略”可以定义为最大化达到2048方块的概率也可以定义为在游戏结束前最大化期望总得分。这两个目标相关但不完全相同需要在一开始就确定。2.2 问题本质马尔可夫决策过程将上述组件组合起来我们发现“2048”游戏完美地符合一个马尔可夫决策过程的框架。MDP由五元组(S, A, P, R, γ)定义S: 状态集合所有可能的4x4棋盘。A: 动作集合上下左右。P(s|s, a): 状态转移概率。由于新生数字的随机性即使在相同状态s下执行相同动作a也可能因新生数字的位置和值2或4不同而进入不同的后继状态s‘。P描述了这种概率分布。R(s, a, s): 奖励函数即从状态s通过动作a转移到s‘所获得的奖励合并得分之和。γ: 折扣因子用于衡量未来奖励的当前价值。在无限期游戏中γ1但在2048这种必然结束的游戏中可以设γ1即不计折扣。我们的目标是找到一个策略π: S - A它能在任何给定的状态s下告诉我们最优的动作a是什么以最大化期望累积奖励总得分或达成特定目标如2048的概率。难点在于状态空间巨大我们无法像解小规模棋盘游戏那样计算精确的最优策略。因此必须借助启发式方法和近似算法。2.3 核心策略方向评估函数的设计既然无法遍历所有状态一个经典的方法是设计一个评估函数V(s)或动作-价值函数Q(s, a)来近似估计某个状态或状态-动作对的“好坏”。然后采用贪婪策略在每个状态s选择使得评估值最高的动作a。评估函数的设计是策略优劣的关键它本质上是对当前棋盘局势的一种“打分”。好的评估函数能准确反映棋盘的潜在价值。常见的评估函数会考虑以下几个启发式特征单调性理想情况下数字应该按大小顺序排列如最大的在角落然后依次递减这样可以为合并创造空间和机会。可以通过检查各行、各列的单调性递增或递减来评分。平滑性相邻格子间的差值越小越好。差值过大意味着形成了“壁垒”阻碍合并。可以计算相邻格子差值的平方和或绝对值和的负值作为评分。空格数量空格越多游戏的可操作空间越大死亡风险越低。通常直接给予空格数量一个正权重。最大数字值拥有更大的数字是取得高分的基础。可以给予最大数字本身一个权重。合并潜力评估相邻且相等的数字对的数量潜在的可合并对越多短期得分机会越大。一个简单的线性评估函数可以是V(s) w1 * 空格数 w2 * 单调性得分 w3 * 平滑性得分 w4 * 最大数字对数其中w1, w2, w3, w4是需要调整的权重参数。实操心得权重参数的调整是策略优化的核心也是一个“玄学”过程。初期可以手动设置比如给“空格数”很高的权重因为生存是第一要务。更高级的方法是用强化学习如时序差分学习来自动学习这些权重或者使用元启发式算法如遗传算法来搜索最优权重组合。这在当年赛题中是一个重要的加分点。3. 经典算法实现与MATLAB代码解析有了理论框架我们进入实战环节。我将介绍两种不同复杂度的策略实现并附上详细的MATLAB代码注释。第一种是基于简单启发式的贪婪搜索第二种是加入了预期最大化思想的Expectimax搜索。3.1 基础策略启发式贪婪算法这个策略的核心是在每一个回合对当前棋盘可以执行的四个动作上下左右进行模拟。对每个动作产生的确定性结果先不考虑随机新生的棋盘用评估函数V(s)打分然后选择得分最高的那个动作。它只向前看一步。MATLAB实现要点棋盘表示用一个4x4的矩阵board表示0代表空。滑动合并函数这是最关键的模块。以向左滑动为例需要处理每一行移除零、合并相邻相同数字、在右侧补零。需要小心处理“一次滑动只合并一次”的规则。function newRow slideLeft(row) % 移除零 nonZeros row(row ~ 0); % 合并 idx 1; while idx length(nonZeros) if nonZeros(idx) nonZeros(idx1) nonZeros(idx) nonZeros(idx) * 2; nonZeros(idx1) []; % 合并后得分可以在这里记录 end idx idx 1; end % 补零 newRow [nonZeros, zeros(1, 4 - length(nonZeros))]; end其他方向可以通过矩阵转置和翻转来复用此函数。评估函数实现前面提到的特征计算。function score evaluateBoard(board) emptyTiles sum(board(:) 0); monoScore calculateMonotonicity(board); smoothScore calculateSmoothness(board); maxTile max(board(:)); % 权重需要反复调试 w_empty 10; w_mono 1.0; w_smooth -0.5; % 平滑性差值为负贡献 w_max 0.1; score w_empty * emptyTiles w_mono * monoScore w_smooth * smoothScore w_max * log2(maxTile); end主循环while ~isGameOver(board) bestScore -inf; bestMove ; moves {左,上,下,右}; for i 1:length(moves) movedBoard moveBoard(board, moves{i}); % 执行滑动合并 if isequal(movedBoard, board) % 移动无效 continue; end currentScore evaluateBoard(movedBoard); if currentScore bestScore bestScore currentScore; bestMove moves{i}; end end % 执行最佳移动 board moveBoard(board, bestMove); % 在随机空位添加2或4 board addRandomTile(board); % 更新显示和分数... end这个策略的优缺点优点实现简单运行速度快能轻松达到2048。缺点目光短浅容易陷入局部最优。例如它可能为了立即获得一个高分合并而破坏棋盘的长期结构。3.2 进阶策略Expectimax搜索算法为了克服贪婪算法的短视我们需要向前多看几步。Expectimax是一种适用于对抗随机性“对手”这里是随机新生数字的搜索算法。它将游戏树中的节点分为三种MAX节点代表玩家决策点选择让评估值最大的动作。CHANCE节点代表随机事件点新生数字计算所有可能随机结果的评估值的期望。由于状态空间爆炸我们无法搜索到游戏结束。通常设置一个固定的搜索深度depth如3-5层。在搜索树的叶子节点我们用评估函数V(s)来估计该状态的价值。算法伪代码function expectimax(state, depth): if depth 0 or state is terminal: return evaluate(state) if its MAXs turn (player move): bestValue -∞ for each action a in actions: newState deterministicMove(state, a) // 滑动合并不新生 value expectimax(newState, depth) // 注意滑动后仍是玩家回合不应切换 bestValue max(bestValue, value) return bestValue else: // CHANCE node (random tile appears) expectedValue 0 emptyTiles getEmptyPositions(state) for each empty pos in emptyTiles: // 考虑新生2 stateWith2 addTile(state, pos, 2) expectedValue 0.9 * expectimax(stateWith2, depth-1) / length(emptyTiles) // 考虑新生4 stateWith4 addTile(state, pos, 4) expectedValue 0.1 * expectimax(stateWith4, depth-1) / length(emptyTiles) return expectedValue关键点在MAX层我们只模拟玩家的确定性移动不添加新块。添加新块的行为被推迟到下一层的CHANCE节点。这样一次“玩家回合”在搜索树中对应一个MAX节点后紧跟一个CHANCE节点。MATLAB实现优化递归实现代码清晰但深度受限。评估函数加速叶子节点的评估函数调用非常频繁必须高度优化。可以使用预计算的查表法或特征值的增量更新。剪枝标准的Expectimax难以进行Alpha-Beta剪枝因为CHANCE节点需要计算期望。但可以设置一个阈值如果某个动作的确定性移动后棋盘完全没变化即该动作无效则直接跳过。深度与性能权衡深度每增加1计算量增长约平均空格数 * 2倍。深度3-4在MATLAB中尚可接受深度5以上就需要很长的思考时间。在实际程序中可以设置一个时间限制。注意事项Expectimax搜索在决策时对于每个可能动作它计算的是“执行该动作后面对后续所有随机新生数字的平均局面价值”。这比贪婪算法只看一眼结果要明智得多。实测中深度为3的Expectimax结合一个良好的评估函数达到2048的概率接近100%并且有相当概率冲击8192。3.3 评估函数的权重优化无论采用贪婪还是Expectimax评估函数V(s)的权重参数w_i都至关重要。手动调参费时费力。我们可以将其转化为一个优化问题目标找到一组权重w使得采用该权重评估函数的策略在大量随机游戏中获得的平均分最高或达到2048的概率最高。我们可以使用遗传算法来求解染色体编码直接将权重向量[w1, w2, w3, w4]作为染色体。适应度函数用该权重对应的策略如深度2的Expectimax运行N局如50局游戏计算平均得分。平均得分即为适应度。遗传操作选择、交叉、变异。迭代运行多代遗传算法种群中的权重会逐渐向更优的方向进化。% 遗传算法优化权重的简化框架 popSize 20; numGenerations 50; weights_pop rand(popSize, 4); % 初始化种群 for gen 1:numGenerations fitness zeros(popSize, 1); for i 1:popSize % 将weights_pop(i, :)赋予评估函数 % 运行多次游戏计算平均分作为fitness(i) end % 根据适应度进行选择、交叉、变异生成新的weights_pop end % 最终取适应度最高的权重作为最优参数这个过程计算量很大但属于“一次训练长期受益”。一旦找到一组好权重你的AI性能将大幅提升。4. 策略性能分析与对比实验设计好算法后我们需要科学地评估其性能。不能只凭感觉说“这个算法好”而要用数据说话。4.1 评估指标成功率在多次独立运行中成功合成2048方块的游戏局数比例。这是最直接的“取胜”指标。平均分数所有游戏局包括失败局的最终得分的平均值。反映了策略的总体得分能力。最大分数/最大方块所有运行中达到的最高分数和合成出的最大数字如4096, 8192。分数分布绘制得分的直方图或箱线图可以直观看到策略的稳定性和上限。平均游戏步数达到终止状态的平均移动次数。步数太多可能意味着策略过于保守步数太少可能意味着过早死亡。4.2 实验设计与MATLAB实现我们需要编写一个自动测试框架。function results benchmarkStrategy(strategyFunc, numGames) % strategyFunc: 函数句柄输入是初始棋盘输出是每一步的决策。 % numGames: 测试游戏局数 success 0; totalScore 0; maxScore 0; maxTile 0; allScores zeros(numGames, 1); for game 1:numGames board initBoard(); % 初始棋盘有两个随机2 score 0; gameOver false; while ~gameOver move strategyFunc(board); % 策略决策 [newBoard, scoreIncrease] executeMove(board, move); % 执行移动并返回得分增量 if isequal(newBoard, board) % 无效移动策略函数应避免此处做保护 % 可能触发游戏结束逻辑 break; end score score scoreIncrease; board newBoard; board addRandomTile(board); gameOver checkGameOver(board); end allScores(game) score; totalScore totalScore score; maxScore max(maxScore, score); currentMaxTile max(board(:)); maxTile max(maxTile, currentMaxTile); if currentMaxTile 2048 success success 1; end end results.avgScore totalScore / numGames; results.successRate success / numGames; results.maxScore maxScore; results.maxTile maxTile; results.scoreStd std(allScores); % 可以绘制 allScores 的直方图 % histogram(allScores); title(得分分布); xlabel(分数); ylabel(频次); end4.3 典型结果分析与解读假设我们对比三种策略策略A随机移动作为基线。策略B启发式贪婪算法3.1节。策略C深度3的Expectimax搜索算法3.2节。运行1000局游戏后可能得到如下表格策略平均分数2048成功率4096成功率最大方块平均步数随机移动1,200 1%0%512~150启发式贪婪15,000~85%5%4096~1200Expectimax(深度3)35,000~99%~40%8192~1800结果解读随机策略作为基线性能极差验证了游戏需要策略。启发式贪婪策略实现了质的飞跃大部分对局能赢达到2048证明了评估函数设计的有效性。Expectimax搜索在各项指标上全面领先。更高的成功率、更高的平均分和最大方块说明向前多思考几步能显著提升长期决策质量。但代价是平均步数增加因为搜索策略更倾向于保持棋盘可控避免过早陷入僵局游戏时间自然变长。成功率与平均分注意到贪婪策略成功率85%但平均分15k而Expectimax成功率99%平均分35k。这说明即使都能赢Expectimax赢得“更漂亮”分数更高因为它更善于向更高分迈进。实操心得性能测试时局数numGames不能太少。策略性能受随机性影响局数太少如10局的结果波动会很大缺乏统计意义。一般至少100局最好500-1000局结果才比较稳定。同时要确保每次测试的随机种子固定或进行多次不同种子的测试以保证结果可复现。5. 高级话题延伸与优化方向如果你已经实现了基础版本的Expectimax并获得了不错的效果那么可以尝试以下进阶优化这些也是数学建模竞赛中冲击高奖的关键。5.1 棋盘对称性约简2048的棋盘是正方形具有旋转和镜像对称性。从策略上看“上”和“下”、“左”和“右”并不是完全等价的因为我们的评估函数可能对方向敏感例如偏好数字按左上角聚集。但旋转对称性是存在的。在搜索过程中我们可以利用这一点进行状态规范化。例如在评估一个棋盘状态时我们可以将其旋转0°、90°、180°、270°然后取评估函数值最高的那个方向所对应的状态作为“规范状态”。在搜索树中如果遇到两个状态经过规范化后是相同的则可以视为同一状态从而进行置换表缓存避免重复计算。这可以显著减少搜索空间尤其是在深度搜索时。5.2 蒙特卡洛树搜索的应用对于更深的搜索Expectimax的复杂度是指数增长的。蒙特卡洛树搜索是处理这类大规模随机决策问题的另一利器。MCTS通过“模拟-评估-回溯”的循环来构建搜索树并不需要展开所有分支而是将计算资源集中在更有希望的状态上。在2048中应用MCTS的基本步骤选择从根节点当前状态开始使用树策略如UCT算法递归地选择子节点直到到达一个未完全展开的节点或叶子节点。UCT公式会在“开发”选择估值高的节点和“探索”选择访问次数少的节点之间取得平衡。扩展如果选择的节点不是终止状态且未被完全展开即还有未尝试过的合法动作则随机选择一个未尝试的动作扩展出一个新的子节点。模拟从新扩展的节点或到达的叶子节点开始使用一个快速策略如随机策略或简单的贪婪策略进行模拟直到游戏结束得到一个模拟结果分数。回溯将模拟得到的分数沿着选择路径反向传播更新路径上所有节点的访问次数和累计价值。经过多次迭代后根节点下访问次数最多的动作通常就是当前最优动作。MCTS的优势它不需要一个精确的评估函数而是通过随机模拟来估计状态价值。对于2048即使使用完全随机的模拟策略只要迭代次数足够多MCTS也能学到非常强大的策略。如果结合一个快速的启发式策略进行模拟收敛速度会更快。5.3 神经网络与强化学习这是目前解决2048这类问题最前沿的方法也最贴合“人工智能”的概念。我们可以将棋盘状态作为输入策略或价值作为输出训练一个神经网络。深度强化学习将2048建模为一个MDP使用深度Q网络或策略梯度方法。状态S4x4棋盘经过卷积神经网络处理输出每个动作A的Q值Q-Learning或直接输出动作概率分布Policy Gradient。智能体通过数百万局的自对弈来学习。监督学习我们可以用强大的传统算法如深度Expectimax搜索作为“教师”生成大量的(状态 最优动作)数据对然后训练一个神经网络来模仿这个策略。训练好的网络决策速度极快一次前向传播虽然可能略逊于“教师”但足以达到很高水平。注意事项神经网络方法需要大量的数据和计算资源GPU其实现复杂度远高于传统搜索算法。在数学建模竞赛中如果选择这个方向重点应放在问题建模、网络结构设计、训练流程描述上并展示对比结果。由于时间限制可能无法从头训练一个SOTA模型但可以设计一个精简网络并展示其潜力。5.4 工程优化技巧即使算法正确低效的实现也会让实验寸步难行。以下是一些MATLAB-specific的优化技巧向量化操作避免在循环中对棋盘元素进行逐个操作。例如计算空格数量用sum(board(:) 0)计算平滑性可以用矩阵差分diff(board, 1, 1)和diff(board, 1, 2)。预计算与查表评估函数会被调用成千上万次。如果特征计算复杂可以考虑预计算。例如对于所有可能的行4个格子每个格子可能为0,2,4,...,2048其单调性得分、合并潜力等是可以预先算好存起来的。但状态太多通常只对“行”这种小单元进行预计算。使用uint16数据类型棋盘数字都是2的幂可以用uint16类型存储节省内存并可能加速运算。剪枝的妙用在Expectimax搜索中虽然不能直接Alpha-Beta剪枝但可以设置“动作排序”。先快速评估所有一步动作的结果按评估值从高到低排序然后按此顺序进行深度搜索。这样好的分支先被搜索如果时间有限可以提前终止至少保证了当前找到的是较优解。并行计算如果你有并行计算工具箱在基准测试运行多局游戏或遗传算法评估种群适应度时这些任务相互独立非常适合用parfor进行并行加速。在我自己的实现中通过将棋盘滑动合并的核心函数用向量化逻辑重写并采用预计算的行特征表Expectimax搜索的深度3决策时间从约0.5秒缩短到了0.1秒以内这使得进行大规模基准测试成为可能。记住在建模竞赛中算法的效率直接决定了你能在有限时间内进行多深入的实验和分析。
返回列表