---
title: 用 Claude3.5 从零写扫雷游戏-实现蜂窝地图
date: '2024-12-17 20:00:00'
tags: ['claude', 'minesweeper']
keywords: ['Claude3.5 辅助前端', '扫雷游戏开发', '在线扫雷游戏']
description: 在完整方格扫雷的基础上，用 Claude3.5 和 Cursor 实现了一个蜂窝版本的扫雷。只要自己想清楚具体实现思路，然后把问题合理拆分，一步步提示，Claude3.5 生成的代码大部分时候还是符合预期的。遇到复杂问题，可以先让 Claude3.5 描述下大致的思路，不要急于写代码，不然容易被错误的代码带偏，浪费不少时间。
---

上一篇 [用 Claude3.5 从零写扫雷游戏-基本功能篇](https://gallery.selfboot.cn/zh/blog/minesweeper)中，在 Claude3.5 的帮助下，我这前端小白也基本完成了一个完整的扫雷游戏。不过只是传统的方格扫雷，如果换成**蜂窝扫雷游戏**，Claude3.5 能实现吗？

先来看成果吧，可以在 [在线扫雷游戏](https://gallery.selfboot.cn/zh/games/minesweeper) 中体验：

![用 Claude3.5 实现的蜂窝状的扫雷地图](https://slefboot-1251736664.file.myqcloud.com/20241217_ai_gallery_minesweeper2_cover.png)

## Claude3.5 蜂窝扫雷实现

考虑到前面已经实现了基本的方格扫雷，并且我们很机智的把逻辑，渲染，UI 组件都分开了。那么实现蜂窝状的扫雷，也可以按照这个思路来。另外，考虑到蜂窝状扫雷除了地图不一样，一些常见操作功能其实和方格扫雷是一样的，所以可以让 Claude 借鉴之前的实现，这样方便我们接入现有的扫雷项目中。

于是第一个提示词就比较简单：

```
这里扫雷目前是正方形的，我想支持正六边形的扫雷。

帮我参考这里实现一个正六边形的扫雷逻辑部分，然后方便我引入。
```

当然这里在 Cursor 中提示的时候，也把方格扫雷的代码文件作为引用文件，这样 Claude3.5 就可以参考之前的实现。

上面提示词简简单单，但 Claude3.5 理解的相当不错。回复也切中了重点，**六边形网格的主要区别在于相邻单元格的计算方式不同**。然后帮我创建了一个新的类 HexMinesweeperGame 来实现六边形蜂窝的逻辑：

```javascript
class HexMinesweeperGame {
  constructor(radius = 4, mines = 10) {
    this.radius = radius;
    this.mines = mines;
    this.gameOver = false;
    this.won = false;
    ...
```

看了下代码，基本符合预期。游戏的核心逻辑如单机翻开格子，以及标旗操作都有，然后使用了新的相邻单元格计算方法。当然除了逻辑部分，还要有对应的渲染部分，以及 UI 组件。于是接着提示：

```
这里在现在的扫雷基础上，怎么增加支持，渲染正六边形的雷。帮我一步步实现，要求代码尽量可维护
```

Claude 先创建了一个新的渲染器类 HexRenderer 来处理六边形的绘制，然后修改了 GameBoard 组件来支持六边形渲染。这里还自己主动在扫雷组件中添加了一个切换不同地图的按钮，这个其实我都没提示到，Claude 还是主动做了。

一切看起来很顺利，整体实现思路符合我的预期。和方格扫雷的实现方法差不多，增加逻辑处理，渲染，组件部分。不过**一次改动了这么多代码，我对结果不报太大期望，不奢望能一次就实现好**。果不其然，这里刷新页面，选择六边形扫雷后，地图根本没反应，还是正方形的扫雷，并且点击方格还报错。

## Claude3.5 蜂窝扫雷问题排查

遇见错误不要慌，直接给 Claude3.5，它写的代码有问题当然要它来解决了。于是直接把基本错误表现和错误堆栈给它：

```
这里选择六边形扫雷地图后没有反应，还是正方形的扫雷。

并且点击后报错：
Uncaught TypeError: renderer.getHexCellFromPoint is not a function
    at handleClick (content.js:131:37)
    at HTMLUnknownElement.callCallback (react-dom.development.js:20565:14)
    at Object.invokeGuardedCallbackImpl (react-dom.development.js:20614:16)
    at invokeGuardedCallback (react-dom.development.js:20689:29)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:20703:25)
    at executeDispatch (react-dom.development.js:32128:3)
    at processDispatchQueueItemsInOrder (react-dom.development.js:32160:7)
    at processDispatchQueue (react-dom.development.js:32173:5)
```

Claude 定位问题还是挺准的，直接告诉我这个是因为渲染器的切换没有正确处理。在切换蜂窝六边形模式后，要使用 HexMinesweeperGame 的逻辑，并给出了修改方案。

后面陆续还有些小问题，比如 HexMinesweeperGame 中的一些方法没有实现。在一次实现大量代码的时候，Claude3.5 还经常会漏掉部分实现，后续可能用到的时候，要慢慢补充。

## 蜂窝扫雷核心逻辑

上面修复完各种小错误后，现在点击切换六边形扫雷，地图看起来是蜂窝了。然后点击后也有翻开格子，不过这里有个比较严重的问题，点击的格子和翻开的格子不一致。比如下图，我点击的红色圆圈内的格子，但翻开的是相隔几个位置的格子。

![蜂窝扫雷地图](https://slefboot-1251736664.file.myqcloud.com/20241217_ai_gallery_minesweeper2_hex.png)

看起来是扫雷格子定位有问题。这里比方格扫雷定位难了很多，方格的定位，只需要计算坐标位置，然后就能很快知道对应哪个格子。六边形格子的布局和定位相对复杂了不少。

这里一开始的时候，自己也没想清楚六边形怎么布局，然后对一个像素位置，要怎么定位到对应的格子。然后只是在各种追问 claude3.5，试图让它帮我解决问题。然后它很努力的给了各种方案，我也无脑尝试，于是遇见了各种奇葩问题，比如：蜂窝格子渲染了一半，点击的时候，又多渲染了不少格子出来。**这中间浪费了不少时间，搞得我一度想放弃了**。

### 尝试让 Claude 解释实现思路

后面还是不甘心，不再直让它写代码，想让它给我解释这里的实现思路，我也来理一理如何实现蜂窝扫雷。提示：

```
你先告诉我这里蜂窝地图怎么绘制蜂窝地图，以及管理这里的数据坐标。
```

Claude 很努力的给我解释，说是可以使用轴向坐标系统管理六边形网格，中心点为(0,0)，向外扩展，每个格子有且仅有6个相邻格子，渲染时使用正六边形的几何特性计算位置，使用 canvas 绘制六边形网格。还给画了个示意图：

```
              /-q-\
           __/     \__
          /  \     /  \
    ____/     \___/    \____
   /    \     /   \    /    \
  /      \___/     \__/      \
  \      /   \     /  \      /
   \____/     \___/    \____/
   /    \     /   \    /    \
  /      \___/     \__/      \
  \      /   \     /  \      /
   \____/     \___/    \____/
        \     /   \    /
         \___/     \__/
```

这里的方法最后渲染出来的蜂窝状地图也是一个大的正六边形，然后通过设置中心点到四个方向的高宽来决定整个蜂窝的大小。看了下 Claude 实现的代码，有点复杂，一时半会难理解。

### 简化问题：矩形蜂窝地图

后面仔细想了下这个问题，其实不用让生成的也是一个大蜂窝。**可以生成矩形地图，指定行数和列数，每行每列的格子数都是一样的，这样简单些**。

简单提示后，**Claude3.5 就理解了我的想法，重新生成代码**。这次移除了之前的轴向坐标系统，使用简单的矩形数组来存储格子，根据行的奇偶性来确定相邻格子的位置（因为偶数行和奇数行的六边形错位排列），每行的格子数量相同，形成矩形区域。重点重写了 getHexCellFromPoint 方法，更精确地处理点击检测。

思路虽然比较清晰了，不过 Claude 实现的 getHexCellFromPoint 还是不对，计算出来的格子位置还是不对。每次计算出来总是错位，中间提示过几次，Claude 每次修改的代码都不正确。

为了根治这个问题，**我决定增加完整的测试代码，用测试代码来方便自己理解，也方便提供给 Claude 来定位**。

## 增加测试让 Claude3.5 更准确的修复

这里测试用例也不用自己写，直接提示 Claude3.5：

```
这里帮我写一个测试用例，根据 calculateCellCenter 计算出来的像素位置，能用 getHexCellFromPoint 反向得到 坐标 row,col
```

于是就实现了一些基本的测试套件，验证从格子坐标到像素再回到格子坐标的准确性，这里测试了不同位置的格子，以及边缘的格子。**添加测试用例的时候，我也加了些日志，方便定位**。运行测试后，很容易就能发现具体出错的地方，直接把报错一起给 Claude3.5：

```
这里计算坐标转换有问题，输出
   测试坐标 (0, 0): 像素坐标 (40, 40)

      at log (src/app/[lang]/games/minesweeper/__tests__/HexRenderer.test.js:37:15)
          at Array.forEach (<anonymous>)

  console.log
    测试坐标 (0, 0): 像素坐标 (40, 40) 反向计算结果 (-1, 0)
```

后续 Claude3.5 就修改了这里的实现，然后顺利通过了测试用例。

不过为了严谨，我又进一步完善了测试用例，对于指定大小的蜂窝地图，遍历每个格子，并且取格子里不同位置的点，然后计算 canvas 中的坐标，最后再根据 canvas 的坐标反向计算格子坐标，验证反向计算的坐标和原坐标是否一致。测试部分如下图：

![测试坐标转换部分的代码](https://slefboot-1251736664.file.myqcloud.com/20241217_ai_gallery_minesweeper2_test.png)

这里定位格子的问题解决后，点击格子，翻开格子就正常了。后面又让 Claude 对蜂窝地雷的核心逻辑部分生成了更多测试用例，这样后续改动心里也有底些。

不过这里顺便提下，**Claude3.5 有时候会生成一些过于肤浅的测试用例，这里还是需要自己去思考怎么设计有效的测试用例**。好在只要想清楚思路，简单提示清楚，基本不用自己写代码，Claude3.5 就能接着生成了。

## Claude3.5 使用技巧总结

在完整方格扫雷的基础上，用 Claude3.5 和 Cursor 我最终还是实现了一个蜂窝版本的扫雷。

总体感觉下来，Claude3.5 还是挺依赖具体提示的。只要自己想清楚具体实现思路，然后把问题合理拆分，一步步提示，Claude3.5 生成的代码大部分时候还是符合预期的。

对于复杂的问题，如果自己没想清楚，可以先让 Claude3.5 描述下大致的思路，不要急于写代码，不然容易被错误的代码带偏，浪费不少时间。

最后再分享一个小妙招，**如果某个函数的实现总是不符合预期，这时候可以添加详细的测试用例，然后用具体的用例和结果来思考，这样就能很快找到问题所在**。