上一篇 用 Claude3.5 从零写扫雷游戏-基本功能篇中,在 Claude3.5 的帮助下,我这前端小白也基本完成了一个完整的扫雷游戏。不过只是传统的方格扫雷,如果换成蜂窝扫雷游戏,Claude3.5 能实现吗?
先来看成果吧,可以在 在线扫雷游戏 中体验:
Claude3.5 蜂窝扫雷实现
考虑到前面已经实现了基本的方格扫雷,并且我们很机智的把逻辑,渲染,UI 组件都分开了。那么实现蜂窝状的扫雷,也可以按照这个思路来。另外,考虑到蜂窝状扫雷除了地图不一样,一些常见操作功能其实和方格扫雷是一样的,所以可以让 Claude 借鉴之前的实现,这样方便我们接入现有的扫雷项目中。
于是第一个提示词就比较简单:
这里扫雷目前是正方形的,我想支持正六边形的扫雷。
帮我参考这里实现一个正六边形的扫雷逻辑部分,然后方便我引入。
当然这里在 Cursor 中提示的时候,也把方格扫雷的代码文件作为引用文件,这样 Claude3.5 就可以参考之前的实现。
上面提示词简简单单,但 Claude3.5 理解的相当不错。回复也切中了重点,六边形网格的主要区别在于相邻单元格的计算方式不同。然后帮我创建了一个新的类 HexMinesweeperGame 来实现六边形蜂窝的逻辑:
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 还经常会漏掉部分实现,后续可能用到的时候,要慢慢补充。
蜂窝扫雷核心逻辑
上面修复完各种小错误后,现在点击切换六边形扫雷,地图看起来是蜂窝了。然后点击后也有翻开格子,不过这里有个比较严重的问题,点击的格子和翻开的格子不一致。比如下图,我点击的红色圆圈内的格子,但翻开的是相隔几个位置的格子。
看起来是扫雷格子定位有问题。这里比方格扫雷定位难了很多,方格的定位,只需要计算坐标位置,然后就能很快知道对应哪个格子。六边形格子的布局和定位相对复杂了不少。
这里一开始的时候,自己也没想清楚六边形怎么布局,然后对一个像素位置,要怎么定位到对应的格子。然后只是在各种追问 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 的坐标反向计算格子坐标,验证反向计算的坐标和原坐标是否一致。测试部分如下图:
这里定位格子的问题解决后,点击格子,翻开格子就正常了。后面又让 Claude 对蜂窝地雷的核心逻辑部分生成了更多测试用例,这样后续改动心里也有底些。
不过这里顺便提下,Claude3.5 有时候会生成一些过于肤浅的测试用例,这里还是需要自己去思考怎么设计有效的测试用例。好在只要想清楚思路,简单提示清楚,基本不用自己写代码,Claude3.5 就能接着生成了。
Claude3.5 使用技巧总结
在完整方格扫雷的基础上,用 Claude3.5 和 Cursor 我最终还是实现了一个蜂窝版本的扫雷。
总体感觉下来,Claude3.5 还是挺依赖具体提示的。只要自己想清楚具体实现思路,然后把问题合理拆分,一步步提示,Claude3.5 生成的代码大部分时候还是符合预期的。
对于复杂的问题,如果自己没想清楚,可以先让 Claude3.5 描述下大致的思路,不要急于写代码,不然容易被错误的代码带偏,浪费不少时间。
最后再分享一个小妙招,如果某个函数的实现总是不符合预期,这时候可以添加详细的测试用例,然后用具体的用例和结果来思考,这样就能很快找到问题所在。