从贪婪到智能:优化 Block Blast Solver 的评分引擎

发布: (2026年1月19日 GMT+8 13:35)
8 min read
原文: Dev.to

Source: Dev.to

我写了一篇关于为什么为 Block Blast 构建求解器的文章——一个简单的贪心算法,让我领悟到了关于空间和策略的惊人教训。它是一个很好的起点,但它有一个致命缺陷:目光短浅。

贪心求解器只关注下一步。它根据立即清除的行数和一个粗略的 “空闲空间” 计数来为放置打分,挑选得分最高的选项,然后继续前进。它还能……算是起作用了。通常它会先清除两行,但随后在放下两个方块后就会导致一个无法挽回的局面。它赢得了局部的战斗,却输掉了整体的战争。

于是我不再仅仅是求解,而是开始思考:一个战略性走法应该是什么样的? 我该如何让算法把棋盘看作是静态的格子,而是视为未来可能性的系统?

简单贪婪机器人 的局限性

下面是我最初评分函数的核心。它很天真,但算是一个起点:

// The Old, Greedy Way
function simpleScore(newBoard) {
    let score = 0;
    score += countClearedLines(newBoard) * 100;   // Clear lines = good!
    score -= countIsolatedCells(newBoard) * 20;   // Holes = bad!
    return score;
}

这种逻辑在细微之处会失效。它喜欢立刻消除一行,即使这意味着在某个角落堆高块。它讨厌单格洞,但对未来可能出现的大型 3×3 块无法放置的区域视而不见。它只会避免下一步失误,却没有十步之后的计划。

构建策略启发式

目标并不是暴力前瞻(那是以后要做的 minimax 实验)。目标是一个更聪明的评分启发式——一个函数,能够在一次评估中估计棋盘的长期健康程度。

我把 “棋盘健康” 拆解为具体、可量化的组成部分:

组件权重思路
未来灵活性空白空间是一种资源,但并非所有空间都等价。一个干净的 3×3 区域价值极高。countPotentialPlacements() 用来检查下一步可能的块形有多少可以理论上放入棋盘。能够保留更多选项的落子得分很高。
连锁潜力中等清除一行还行;为下一块设置两行或三行的完成条件就很棒。comboSetupScore() 分析一次落子后,棋盘上是否留下多条只差一个块就能完成的行。
危险检测关键经典的致命因素是单格空洞,但更微妙的致命因素是“高柱”。calculateColumnHeightVariance() 对列高差进行惩罚。平坦的棋盘灵活;起伏大的棋盘则是死亡陷阱。
可达性小幅度的惩罚。对完全围住空格、只能通过完美且极不可能的块形才能填充的落子进行惩罚。

新的评分函数成为这些策略因素的加权和:

// The New, Strategic Heuristic
function strategicBoardScore(newBoard, nextBlockShapes) {
    let score = 0;

    // Core Strategy Weights
    score += calculateFlexibilityScore(newBoard, nextBlockShapes) * 0.4; // 40 % weight on future options
    score += calculateComboPotential(newBoard) * 0.3;                    // 30 % on setting up big plays
    score -= calculateBoardDanger(newBoard) * 0.2;                     // -20 % for creating risk
    score -= calculateInaccessibleAreas(newBoard) * 0.1;                 // -10 % for trapping spaces

    // Bonus for immediate clears (but less important)
    score += countClearedLines(newBoard) * 50;

    return score;
}

对这些权重(0.4、0.3 等)进行调优花费了整个下午的反复试验,观察求解器玩了成千上万局游戏。感觉这不像在写代码,更像是在训练一个非常简单的 AI。

“啊哈!”时刻在代码中

真正的考验并不是最终得分,而是特定的棋盘状态。旧的贪婪机器人面临一个选择:

  • 选项 A: 放置一个 L 形块,立即消除 1 行。
  • 选项 B: 将相同的 L 形块放在别处,此时不消除任何行。

旧的逻辑痴迷于即时消除,总是选择选项 A。然而,选项 A 会在右侧形成一个高堆。

新的启发式算法看到别的东西。选项 B 虽然没有立即消除,但保持了棋盘的完全平整,并留下了一个漂亮的 4×2 矩形空位。calculateFlexibilityScore() 为它提供了巨大的加分,而 calculateBoardDanger() 则将选项 A 标记为风险。

首次,求解器牺牲了即时奖励以换取长期健康。它做出了一个耐心、类人的决定。这就是“啊哈!”时刻——算法学会了游戏的核心原则。

结果与实时演练场

差距非常明显。与之前的贪心算法相比,策略求解器的存活时间 consistently 延长了30‑40 %。它的平均得分大幅提升,且它的移动看起来更有目的性,仿佛在朝着某个目标前进,而不仅仅是被动反应。

如果你想亲眼看到这些原理的实际效果,或自行分析棋盘状态,欢迎访问我的在线版本:Block Blast Solver。挑选一个棘手的棋盘,依据上面的启发式方法猜测求解器的“推理”。这是一种逆向工程自己直觉的有趣方式。

接下来是什么?极小极大视野

策略启发式是一次巨大的飞跃,但它仍然是一种近似。真正的前沿是实现 带剪枝的极小极大算法——让求解器在做决定之前实际模拟 2、3 或 4 步。

这就是下一章的内容。它是一个更重的计算挑战,但在看到优秀启发式的威力后,我确信它是解锁真正最优玩法的关键。从贪婪到聪明的旅程才刚刚开始。

Tags: javascript #algorithms #gamedev #optimization #puzzle

Back to Blog

相关文章

阅读更多 »

Rapg:基于 TUI 的密钥管理器

我们都有这种经历。你加入一个新项目,首先听到的就是:“在 Slack 的置顶消息里查找 .env 文件”。或者你有多个 .env …

技术是赋能者,而非救世主

为什么思考的清晰度比你使用的工具更重要。Technology 常被视为一种魔法开关——只要打开,它就能让一切改善。新的 software,...

踏入 agentic coding

使用 Copilot Agent 的经验 我主要使用 GitHub Copilot 进行 inline edits 和 PR reviews,让我的大脑完成大部分思考。最近我决定 t...