用 Claude3.5 从零写扫雷游戏-基本功能篇
扫雷是个太经典的游戏了,以前在 Windows 电脑上玩过,现在换了 Mac,好久没玩了。刚好最近在重度体验 Cursor 和 Claude3.5,也用它们写了不少代码。于是想着用 Claude3.5 从零写个扫雷游戏,看看效果如何。
先展示下最后的成果吧 在线扫雷游戏:
当然,我用 Claude 写的扫雷可不是一个简单的玩具,这次实现的扫雷功能十分齐全:
- 支持各种游戏难度,包括自定义扫雷界面长度和宽度,以及雷的个数;
- 支持传统的方格游戏棋盘,也支持蜂窝状游戏棋盘;
- 支持自动标记雷,双击自动翻开周边格子;
- 支持多种颜色主题,可以随时切换自己喜欢的扫雷颜色,比如黄色,绿色,暗黑色;
接下来我用几篇文章,来记录下自己用 Claude 开发扫雷游戏的一些体会。本篇是第一篇:用 Claude3.5 从零写扫雷游戏-基本功能篇。
Claude 快速生成扫雷游戏
扫雷游戏还是有不少逻辑的,为了避免 AI 写错一个地方不好排查,所以肯定要加测试用例的。所以在写提示词的时候,我就着重提醒 Claude 把 ui 和实现逻辑分开,这样方便后续测试和代码维护。
然后虽然 Claude 可能知道扫雷游戏的规则,不过开始的提示词还是把规则简单说下了,这样更能约束好 Claude 的行为。完整提示词如下:
帮我实现扫雷游戏,用 react 和 tailwind css 实现,逻辑和 ui 要分开,方便测试逻辑部分。
一个扫雷盘面由许多方格(cell)组成,方格中随机分布着一定数量的雷(mine),一个格子中至多只有1雷。胜利条件是打开所有安全格(非雷格,safe cell),失败条件是打开了一个雷格(踩雷)。
扫雷中有三种基础操作方式:
左键点击一个未打开的格子可以将其打开。
右键点击一个未打开的格子可以将其标雷/取消标雷。
双击(左右键同时点击)一个已打开的数字,如果该数字周围标的雷数量等于该数字,那么双击操作会同时打开该数字周围剩余所有格子。
踩雷时,游戏失败,踩到的雷会红色高亮,标错的雷(插在安全格上的旗子)粉色高亮,未标出的雷会显示出来。胜利时,游戏会自动标上所有未标的雷。游戏界面的左上角显示的是剩余雷数(总雷数-标雷数)。游戏界面右上角显示的是计时器,以本局游戏第一次操作为0.001秒开始计时。
因为扫雷这游戏太经典了,网上到处都是现成的代码,所以 Claude 训练的时候肯定学到很多。对于这种比较常规的需求,Claude 写的代码还是很不错的。Claude 实现了一个逻辑类 MinesweeperGame,所有的游戏逻辑完全封装在该类中。
class MinesweeperGame {
constructor(rows = 9, cols = 9, mines = 10) {
this.rows = rows;
this.cols = cols;
this.mines = mines;
this.board = [];
this.revealed = [];
...
此外还有一个单独的游戏组件,UI 组件使用 React 和 Tailwind CSS 实现,使用 React hooks 管理游戏状态和计时器。第一个版本已经相当完善了,包含了下面功能:
- 左键点击揭示格子
- 右键点击标记地雷
- 计时器功能
- 剩余地雷计数
- 游戏胜利和失败的判定
其实这里生成的逻辑处理部分相当完善,代码也写的比较清晰易懂。包括抽象出了各种函数逻辑,比如游戏初始化,单个翻开格子等。代码质量已经不弱于大部分普通开发写的扫雷游戏代码了。
调整扫雷游戏界面
接着为了把这个游戏很好的融合到我现有的项目中,我需要调整下扫雷游戏的界面。于是接着提示:
这里整个界面分为两部分,游戏和设置部分。
大屏幕下游戏在左侧 4/5,设置部分在右侧 1/5 ;小屏幕下游戏在上面,下面是设置部分。
本项目中的绝大部分页面都是遵循这个布局规则,不过自己并没系统学过 css 布局和 tailwind 的样式。好在 Claude 能很准确的根据简单的提示,就修改现有代码,实现我的布局需求。
当然中间可能有些小偏差,一般再追问几轮,基本能很快解决。自己看的多了,对 tailwind 的一些布局也有了一些了解,不少小错误,肉眼到了后也能很快 fix 掉。
优化扫雷界面渲染
到目前为止,已经实现了一个玩具扫雷游戏了,不过界面还是有点丑,于是想着继续优化。这里我想让 ui 界面尽量模仿传统的扫雷样式,最好也能支持不同的主题。
此外,我直接对渲染棋盘用的技术也做了说明,直接让它用 canvas 实现每个方格。这里补充一些自己的观点,其实在使用 AI 的过程中,开发者还是要有一定的背景知识。这样能更精确的给出一些提示,少走弯路。
之前在实现布隆过滤器的可视化的时候,Claude 绘制大量格子直接用 grid 组件实现,导致性能十分低下。后来多次提问,才最终学到可以用 canvas 来实现。如果不知道 canvas,不提示 AI 的话,可能在一个错误的方案上浪费很多时间。
最后,简单的提示词如下:
这里整个扫雷的界面,要考虑特别多地雷的场景,这里用 canvas 实现每个方格。
然后ui 界面尽量模仿传统的扫雷样式,最好也能支持不同的主题。
然后 Claude 很快就给出了非常不错的实现。这次设计了一个传统风格的扫雷界面,使用 Canvas 来渲染游戏格子,并支持主题切换。
class CanvasRenderer {
constructor(canvas, theme) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.theme = theme;
this.cellSize = 24; // 默认格子大小
this.borderSize = 3; // 边框大小
}
...
}
这里 Claude 说自己新的实现模仿了经典 Windows 扫雷界面,有 3D 凸起效果的格子,LED 风格的计数器显示,经典的灰色背景和边框效果。此外,还优化了地雷、数字和旗子的绘制等。
不过事实上它并没有实现 3D 凸起效果。然后我这里继续提示:
这里现在待探索的方格目前都连在一起,应该每个方格有边界和突出,让看起来明显些
这次 Claude 通过修改 CanvasRenderer 类来增加方格的 3D 效果,使其看起来更像经典的 Windows 扫雷游戏。改动的部分如下:
不得不说,这里超出了我的预期。本来我自己也不太知道如何才能实现 3D 效果,也没有很详细的提示词来描述实现过程。好在 Claude3.5 足够聪明,直接给出了完整的 canvas 绘制代码,还简单描述了下改进的方法:左上方为亮边,右下方为暗边,中间为格子表面,这样看上去就是 3D 效果了。
如果没有 AI,要我来从零实现这 3D 效果,估计要花不少时间。
解决各种小 bug
到此为止,大的框架基本完成了,接下来就是十分烦人的阶段了。Claude3.5 生成的代码整体质量可以,大部分逻辑也没问题,但是细节上还是有不少小 bug。比如上面改动完之后,加载页面就报错:
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading '0')
Source
src/app/[lang]/games/minesweeper/content.js (29:42) @ col
27 | col,
28 | {
> 29 | revealed: game.revealed[row][col],
| ^
30 | flagged: game.flagged[row][col],
31 | exploded: game.gameOver && game.board[row][col] === -1 && game.revealed[row][col],
32 | },
因为自己不太会前端,遇见这种错误,基本也是直接复制错误信息给 Cursor ,然后 Claude3.5 根据错误信息,给出修改建议。
大部分时候,Claude3.5 会解释这里错误的原因,并给出修改方案。在 Cursor 下,可以很方便的看到修改的 diff 部分,然后再结合自己的判断来修改代码。
当然,有时候修了一个错误可能会有另一个,需要耐心的去修复。这里扫雷的实现中,因为前面一下子实现了很长的代码,在长上下文下,claude3.5 出错的概率还是挺大的。
好在这里分离了逻辑,渲染和 ui 部分,在 Claude 的帮助下,还是能很容易定位到错误原因的。
总结
在 Claude3.5 和 Cursor 的帮助下,终于从零实现了一个完整的扫雷游戏。中间遇到的问题还是不少的,不过经过不断的追问,最终也都一一解决。当然如果自己的前端基础好一点,估计能走不少弯路。就算是强如 Claude3.5,如果是完全小白,没有编程经验的话,想从头实现完整的扫雷,其实还是有点难度的。
比如遇到不符合预期的地方,没有编程经验,可能不知道怎么添加日志,纯粹靠 AI,有时候也不能解决问题。比如有相当的调试经验,并且能看到这里代码的基本含义,定位到蛛丝马迹后,才能更好的提示 Claude 去修改代码。
接下来,我会接着写几篇文章,记录用 Claude3.5 完善扫雷游戏的过程。