---
title: 用 Claude3.5 从零写扫雷游戏-基本功能篇
date: '2024-12-16 22:00:00'
tags: ['claude']
keywords: ['Claude3.5 辅助前端', '扫雷游戏开发', '在线扫雷游戏']
description: 用 Claude3.5 写一个经典的扫雷游戏，支持多种难度，多种主题，多种蜂窝状游戏棋盘。作为一个前端小白，虽然遇到不少问题，但借助 Claude3.5 和 Cursor 的强大功能，最终还是实现了。本文记录实现经典扫雷部分，希望对想借助 AI 写代码的朋友有所帮助。
---

扫雷是个太经典的游戏了，以前在 Windows 电脑上玩过，现在换了 Mac，好久没玩了。刚好最近在重度体验 Cursor 和 Claude3.5，也用它们写了不少代码。于是想着用 Claude3.5 从零写个扫雷游戏，看看效果如何。

先展示下最后的成果吧 [在线扫雷游戏](https://gallery.selfboot.cn/zh/games/minesweeper)：

![Claude 实现的完整扫雷游戏](https://slefboot-1251736664.file.myqcloud.com/20241216_ai_gallery_blog_cover.png)

当然，我用 Claude 写的扫雷可不是一个简单的玩具，这次实现的扫雷功能十分齐全：

1. 支持各种游戏难度，包括自定义扫雷界面长度和宽度，以及雷的个数；
2. 支持传统的方格游戏棋盘，也支持蜂窝状游戏棋盘；
3. 支持自动标记雷，双击自动翻开周边格子；
4. 支持多种颜色主题，可以随时切换自己喜欢的扫雷颜色，比如黄色，绿色，暗黑色；

接下来我用几篇文章，来记录下自己用 Claude 开发扫雷游戏的一些体会。本篇是第一篇：用 Claude3.5 从零写扫雷游戏-基本功能篇。

## Claude 快速生成扫雷游戏

扫雷游戏还是有不少逻辑的，为了避免 AI 写错一个地方不好排查，所以肯定要加测试用例的。所以在写提示词的时候，我就着重**提醒 Claude 把 ui 和实现逻辑分开**，这样方便后续测试和代码维护。

然后虽然 Claude 可能知道扫雷游戏的规则，不过开始的提示词还是把规则简单说下了，这样更能约束好 Claude 的行为。完整提示词如下：

```
帮我实现扫雷游戏，用 react 和 tailwind css 实现，逻辑和 ui 要分开，方便测试逻辑部分。

一个扫雷盘面由许多方格（cell）组成，方格中随机分布着一定数量的雷（mine），一个格子中至多只有1雷。胜利条件是打开所有安全格（非雷格，safe cell），失败条件是打开了一个雷格（踩雷）。

扫雷中有三种基础操作方式：

左键点击一个未打开的格子可以将其打开。
右键点击一个未打开的格子可以将其标雷/取消标雷。
双击（左右键同时点击）一个已打开的数字，如果该数字周围标的雷数量等于该数字，那么双击操作会同时打开该数字周围剩余所有格子。

踩雷时，游戏失败，踩到的雷会红色高亮，标错的雷（插在安全格上的旗子）粉色高亮，未标出的雷会显示出来。胜利时，游戏会自动标上所有未标的雷。游戏界面的左上角显示的是剩余雷数（总雷数-标雷数）。游戏界面右上角显示的是计时器，以本局游戏第一次操作为0.001秒开始计时。
```

因为扫雷这游戏太经典了，网上到处都是现成的代码，所以 Claude 训练的时候肯定学到很多。**对于这种比较常规的需求，Claude 写的代码还是很不错的**。Claude 实现了一个逻辑类 MinesweeperGame，所有的游戏逻辑完全封装在该类中。

```javascript
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 管理游戏状态和计时器。第一个版本已经相当完善了，包含了下面功能：

1. 左键点击揭示格子
2. 右键点击标记地雷
3. 计时器功能
4. 剩余地雷计数
5. 游戏胜利和失败的判定

其实这里**生成的逻辑处理部分相当完善，代码也写的比较清晰易懂**。包括抽象出了各种函数逻辑，比如游戏初始化，单个翻开格子等。代码质量已经不弱于大部分普通开发写的扫雷游戏代码了。

## 调整扫雷游戏界面

接着为了把这个游戏很好的融合到我现有的项目中，我需要调整下扫雷游戏的界面。于是接着提示：

```
这里整个界面分为两部分，游戏和设置部分。
大屏幕下游戏在左侧 4/5，设置部分在右侧 1/5 ；小屏幕下游戏在上面，下面是设置部分。
```

本项目中的绝大部分页面都是遵循这个布局规则，不过自己并没系统学过 css 布局和 tailwind 的样式。好在 Claude 能很准确的根据简单的提示，就修改现有代码，实现我的布局需求。

当然中间可能有些小偏差，一般再追问几轮，基本能很快解决。自己看的多了，对 tailwind 的一些布局也有了一些了解，不少小错误，肉眼到了后也能很快 fix 掉。

## 优化扫雷界面渲染

到目前为止，已经实现了一个玩具扫雷游戏了，不过界面还是有点丑，于是想着继续优化。这里我想让 ui 界面**尽量模仿传统的扫雷样式，最好也能支持不同的主题**。

此外，我直接对渲染棋盘用的技术也做了说明，直接让**它用 canvas 实现每个方格**。这里补充一些自己的观点，其实**在使用 AI 的过程中，开发者还是要有一定的背景知识**。这样能更精确的给出一些提示，少走弯路。

之前在实现[布隆过滤器的可视化](https://gallery.selfboot.cn/zh/algorithms/bloomfilter)的时候，Claude 绘制大量格子直接用 grid 组件实现，导致性能十分低下。后来多次提问，才最终学到可以用 canvas 来实现。如果不知道 canvas，不提示 AI 的话，可能在一个错误的方案上浪费很多时间。

最后，简单的提示词如下：

```
这里整个扫雷的界面，要考虑特别多地雷的场景，这里用 canvas 实现每个方格。

然后ui 界面尽量模仿传统的扫雷样式，最好也能支持不同的主题。
```

然后 Claude 很快就给出了非常不错的实现。这次设计了一个传统风格的扫雷界面，使用 Canvas 来渲染游戏格子，并支持主题切换。

```javascript
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 效果的扫雷方格](https://slefboot-1251736664.file.myqcloud.com/20241216_ai_gallery_blog_3dcell.png)

不得不说，这里超出了我的预期。本来我自己也不太知道如何才能实现 3D 效果，也没有很详细的提示词来描述实现过程。好在 Claude3.5 足够聪明，直接给出了完整的 canvas 绘制代码，还简单描述了下改进的方法：**左上方为亮边，右下方为暗边，中间为格子表面，这样看上去就是 3D 效果**了。

如果没有 AI，要我来从零实现这 3D 效果，估计要花不少时间。

## 解决各种小 bug

到此为止，**大的框架基本完成了，接下来就是十分烦人的阶段了**。Claude3.5 生成的代码整体质量可以，大部分逻辑也没问题，但是细节上还是有不少小 bug。比如上面改动完之后，加载页面就报错：

```javascript
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 完善扫雷游戏的过程。