
本文详解如何在 go 中优雅实现扫雷游戏核心逻辑——遍历二维数组并统计每个非雷格子周围 -1(地雷)的数量,避免边界越界,代码简洁可维护。
本文详解如何在 go 中优雅实现扫雷游戏核心逻辑——遍历二维数组并统计每个非雷格子周围 -1(地雷)的数量,避免边界越界,代码简洁可维护。
在实现控制台版扫雷(Minesweeper)时,一个关键步骤是:对每个非雷格子(即值不为 -1 的单元),统计其八邻域(上下、左右、四个对角线方向)中地雷(-1)的总数,并将该数字填入对应位置;而地雷格子本身保持 -1 不变。直接为每个方向硬编码 if 判断(如 ary[i-1][j-1]、ary[i-1][j] 等)不仅冗长易错,更难以维护和扩展。
更专业、健壮且符合 Go 风格的解法是:以地雷为中心反向扩散更新——即遍历整个数组,一旦发现 -1,就立即更新其所有合法邻域格子的计数值。这样既逻辑清晰,又天然规避了大量重复的边界检查。
✅ 核心思路:地雷驱动 + 边界安全遍历
我们不为每个格子“找邻居”,而是让每个地雷“通知邻居自己存在”。对于位置 (i, j) 的地雷,其邻域坐标 (k, l) 满足:
- k ∈ [i−1, i+1]
- l ∈ [j−1, j+1]
- 同时需确保 0 ≤ k
Go 中没有内置 clamp 或 min/max 的泛型函数,但可借助 math.Max/Min(需转换为 float64)或更轻量的三元风格辅助函数。实践中,使用 max(0, i-1) 和 min(row-1, i+1) 是最常用且高效的方式:
// 辅助函数(可定义在包内)
func max(a, b int) int { if a > b { return a }; return b }
func min(a, b int) int { if a < b { return a }; return b }
// 主逻辑:扫描地雷并更新邻域计数
for i := 0; i < row; i++ {
for j := 0; j < col; j++ {
if ary[i][j] == -1 { // 发现地雷
// 遍历其 3×3 邻域(含自身),但跳过地雷自身
for k := max(0, i-1); k <= min(row-1, i+1); k++ {
for l := max(0, j-1); l <= min(col-1, j+1); l++ {
// 只更新非地雷格子(避免把 -1 改成 0)
if ary[k][l] != -1 {
ary[k][l]++
}
}
}
}
}
}⚠️ 关键注意事项
- 勿修改地雷本身:条件 if ary[k][l] != -1 至关重要。若遗漏,会导致地雷格子被错误累加(例如 -1 + 1 = 0),彻底破坏游戏状态。
- 边界处理不可省略:max(0, i-1) 和 min(row-1, i+1) 确保 k, l 始终落在 [0, row) 和 [0, col) 范围内,完全消除 index out of range panic。
- 时间复杂度合理:虽是四重循环嵌套,但内层两层最多执行 9 次(3×3),整体仍为 O(m×n),与问题规模线性相关,性能无忧。
- 输入数组需为整数切片:确保 ary 类型为 [][]int,且已正确初始化(例如用 make([][]int, row) 并逐行 make([]int, col))。
? 示例验证
原始数组(5×5):
[0 0 0 0 0] [0 0 -1 0 0] [0 0 0 0 0] [0 0 0 -1 -1] [0 0 0 -1 -1]
运行上述逻辑后,得到期望结果:
[0 1 1 1 0] [0 1 -1 1 0] [0 1 2 2 1] [0 0 1 -1 -1] [0 0 1 -1 -1]
其中 (2,2) 值为 2,因其邻域包含两个地雷((1,2) 和 (3,3));(0,0) 为 0,因无邻近地雷——逻辑完全准确。
掌握这一模式,你不仅能完成扫雷计数,还可轻松迁移至其他网格类算法场景,如生命游戏(Game of Life)、图像卷积预处理、局部密度统计等。简洁、安全、可读——这正是 Go 式工程实践的体现。










