---
title: Claude3.5 实现完整单机五子棋
date: '2024-10-16 22:00:00'
tags: ['claude']
keywords: ['Claude3.5 辅助前端', '在线五子棋', '免费五子棋']
description: 用 Claude3.5 实现一个完整的单机五子棋游戏，支持各种禁手规则。如何更好的利用 AI 解决复杂问题，并让 AI 写测试用例来保证逻辑的正确性，本文会给你一个答案。
---

五子棋是一个比较简单的棋类游戏，规则比较简单，如果用 Claude3.5 来实现，可能一句话就能实现一个简单版本的五子棋了。不过仔细研究了五子棋的禁手规则后，发现还是有点复杂的。五子棋的规则包含了各种禁手规则，比如三三禁手、四四禁手、长连禁手等。

如果用 Claude3.5 来实现禁手，会有难度吗？对于复杂的规则，当使用 Claude 辅助开发的时候，如何才能保证实现的正确性？本文记录用 Claude 实现单机五子棋的完整过程，一起来体会和 AI 结对编程的乐趣。

![单机版五子棋，支持各种禁手规则](https://slefboot-1251736664.file.myqcloud.com/20241016_ai_gallery_gomoku_blogcover.png)

可以在这里体验：[在线五子棋](https://gallery.selfboot.cn/zh/games/gomoku) 游戏，目前只有单机版，后续会支持联网对战和 人、AI 对战。代码全部开源，在 [Github](https://github.com/selfboot/ai_gallery) 上。

## 快速实现 UI

对于我这前端新手菜鸟，如果没有 AI，让我实现五子棋，可能需要花一天时间来实现棋盘的展示和棋子布局。然后有了 Claude3.5，这类事情就简单了很多，直接简单提示下，就能拿到一个基本可用的五子棋棋盘了。

这里经过了两三轮提示，主要是补充实现方法以及屏幕适配。核心包括：

> 帮我画一个五子棋棋盘，要求每行每列都是放 15 个棋子，用react 和tailwind css实现，然后做好屏幕适配。

效果如下：

![AI 实现的五子棋棋盘](https://slefboot-1251736664.file.myqcloud.com/20241016_ai_gallery_gomoku_ui.png)

不过如果仔细观察，这里的棋子在方格里面，**其实正规棋盘，棋子不是放到方格里面，而是放到交叉线地方的**。想实现也很简单，再描述清楚，然后 Claude 就能快速实现。另外在棋盘边界线周围还需要留一点空白，这样边上的棋子也有足够的位置。

整体体验下来，对于这种 UI 的需求，**只要描述清楚需求，Claude 实现的质量还是很不错的**。

## 禁手规则拆分

五子棋的禁手规则比较复杂，直接让 AI 实现完整的禁手规则，有不少错误。其实也尝试让 AI 来解释五子棋禁手，不过 AI 解释有点不太清晰。于是先找了一个比较权威的对于禁手的解释，然后让 AI 实现。

实现这种比较复杂的规则，肯定要拆分子问题了。为了实现三三禁手检测，这里先单独实现一个**连续活三**的检测。对于一个落点，检查前、后两个点是否是同一方，之后检测两端是否是空白的。比如落点 X，连续活三有下面几种情况：

```
_00X_
_X00_
_0X0_
```

这里 `_` 代表空格，`0` 代表先手棋子，`X` 代表先手将要下的位置，任意 1 种都是活三。这里因为模式比较固定，就想着直接让 Claude3.5 根据模式来匹配。AI 给出的代码还是可以的，没有什么错误。跳跃活三稍微复杂些，不过可以用同样的思路解决，只是模式稍微多一些，Claude3.5 也解决的不错。整体代码如下：

```js
export function checkContinuousOpenThree(board, row, col, dx, dy, player) {  
  const patterns = [
    {indices: [-2, -1, 0, 1, 2], values: ["", player, player, player, ""]},  // _XOX_
    {indices: [-1, 0, 1, 2, 3], values: ["", player, player, player, ""]},   // _OXX_
    {indices: [-3, -2, -1, 0, 1], values: ["", player, player, player, ""]}  // _XXO_
  ];

  let matchCount = 0;
  let matchedPositions = null;

  for (let pattern of patterns) {
    let match = true;
    let positions = [];
    for (let i = 0; i < pattern.indices.length; i++) {
      const x = row + pattern.indices[i] * dx;
      const y = col + pattern.indices[i] * dy;

      if (!isValidPosition(x, y) || board[x][y] !== pattern.values[i]) {
        match = false;
        break;
      }
      if (pattern.values[i] === player) {
        positions.push([x, y]);
      }
    }
    // ....
  }

  return matchCount === 1 ? { isOpen: true, positions: matchedPositions } : { isOpen: false };
}
```

## 四四禁手的难点

比较难的是四四禁手实现。四四禁手是说：**先手一子落下同时形成两个或两个以上的四**，只要是两个“四”即为禁手，无论是活四、冲四都算。活四是说两端都可形成连五的四，冲四是说仅一端能形成连五的四。

这里开始想着**拆分活四和冲四的检测**，首先让 AI 实现活四的检测，这里 Claude 没有用规则匹配，直接在每个方向上扫描相连的棋子数量，然后检测，整体比较简单。

比较难的是冲四检测，这里直接让 Claude 实现，给的方法总有各种错误。于是想着用模式匹配的方法，不过粗略看了下，满足的模式有点多，每一个都列出来有点笨笨的感觉。中间想了几个方法，比如

> 扫描 row,col 周边的点，保证有 3 个同样的 player 以及一个空格。  
> 如果空格在最边的一端位置，还要保证另一端有阻挡（对手棋子或者边界）  
> 下面 1 代表边界或者对手棋子，_ 代表空格，X代表检测冲四的棋子位置。0 代表先手棋子。  
> 
> 比如下面都是冲四：  
> 10_X00   
> 0_X000

虽然**上面实现算法有问题，也不够清晰，但 claude3.5 还是尬吹一波，然后就去实现**了。现阶段 AI 这点还是挺恶心的，**有时候你提出了错误的思路，Claude 也会尽量去拟合，而不能指出你的错误**。

后面重新思考了下，这里**冲四和活四的判断规则，其实核心点在于再增加一个棋子就可以实现五连**。区别在于活四可以有两个落子点，冲四只能有一个落子点。于是让 AI 重修修改这里的逻辑，不单独判断冲四，直接增加一个计算活四或者冲四连接的方法。具体步骤：

1. 对于某个先手落子，算出再下一个棋子会五连的所有情况。
2. 对于每一种五连，记录用到的当前 4 个棋子的位置，以及需要再下棋子的位置。
3. 然后按照用到的 4 连棋子的位置进行聚合，计算出每种情况下，需要下的棋子的位置，如果对于某四个棋子，只有一个位置可以行成五连，就是冲四。有两个位置可以形成五连，就是活四。
4. 函数返回两部分，一个是冲四的棋子位置，一个是活四的棋子位置

上面算法其实手写的话，还是挺费劲的，特别是我甚至都不太熟悉 JavaScript 语言。但是我描述给 Claude3.5，它竟然真的懂了，一遍就给出了正确的代码，核心部分如下，完整的在 github 可以看到：

```javascript
export function checkFourInRow(board, row, col, player) {
  const potentialFives = [];
  for (const [dx, dy] of directions) {
    for (let start = -4; start <= 0; start++) {
      //...
      for (let i = 0; i < 5; i++) {
        const newRow = row + (start + i) * dx;
        const newCol = col + (start + i) * dy;

        if (!isValidPosition(newRow, newCol)) break;
        positions.push([newRow, newCol]);
        if (board[newRow][newCol] === player) {
          playerCount++;
        } else if (board[newRow][newCol] === '') {
          emptyCount++;
          emptyPositions.push([newRow, newCol]);
        } else {
          break;
        }
      }

      if (playerCount === 4 && emptyCount === 1) {
        potentialFives.push({
          fourPositions: positions.filter(([r, c]) => board[r][c] === player),
          emptyPosition: emptyPositions[0]
        });
      }
    }
  }
  //.... 
}
```

这就很强了，超预期的强悍。

## 测试用例

不过上面的实现正确吗？如果只是单纯从代码上看，感觉是没啥问题的。但是谁来保证呢？这时候就需要写测试用例了。

编写测试用例是个很废时间和体力的活，没太多技术含量。这时候就体验 AI 的作用了，告诉他想给某个函数写测试用例，一下子一堆用例就来了。比如对于 `checkFourInRow` 函数，AI 很快就给出了各种用例。不过这里为了让用例对人友好，这里故意把整个棋盘写出来，方便一眼看清。比如下面的双活三棋盘：

```javascript
board: [
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "B", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "B", "X", "B", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "B", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]
    ],
```

AI 能快速给出各种测试用例，不过还是免不了人工干预。毕竟有些边界条件和专门的测试用例，靠 AI 来生成还是有点难。好在这里只用改下棋盘中棋子位置，人工来做也很方便了。

测试运行结果：

![五子棋禁手测试代码](https://slefboot-1251736664.file.myqcloud.com/20241016_ai_gallery_gomoku_test.png)

有测试用例做保证，这里禁手的实现基本没啥问题了。不过如果仔细看专业的禁手规则，发现还有太专业的非禁手情况，等后面有时间精力了再来看看怎么实现吧。

## AI 可以写大项目吗？

加禁手的单机五子棋，其实算是复杂了，Claude 实现的还算可以。其实更大的项目，比如这整个站点，全都是在 Claude3.5 和 cursor 的辅助下快速完成的。

最开始想着只是体验下 Claude3.5 的能力，写几个简单的页面就行了。慢慢写着写着，这个演示站点的的页面越来越多，功能也越来越丰富。最近 cursor 编辑器又集成了 Claude3.5，写复杂项目的体验也越来越好。

对于不太需要复杂算法实现的功能，基本上描述清楚需求，Claude3.5 都能很好的实现。有时候实现的会比预期好，毕竟 Claude 大模型其实有很多知识。结合 Cursor 的文件引用，AI 对于整个项目的理解也越来越好，改动一个地方后，AI 甚至会提醒修改其他相关的文件，这点还是很不错的。

体验下来比较欠缺的地方有几个：
1. **复杂的逻辑，要描述清楚，claude 才能实现**；如果开发者自己都没想明白怎么实现，AI 大概率也是没法实现；
2. AI 堆代码的时候，有时候会给出一些冗余代码，比如同样的函数在项目多个文件到处都有。**需要开发者自己不断去重构一些实现，保证代码整体的简洁与可维护性**。
3. **当你提了一些错误的思路，Claude 会尽量去拟合，而不能指出你的错误**。有时候错的很离谱，我不小心打开了一个其他页面的代码，然后在 cursor 中，把这个文件当做五子棋的功能来修改，结果 AI 真的就在这里乱改了！ 

回答问题的答案，AI 完全可以来写大项目，**只是需要人不断去提示，去纠错。人在这个过程，更像是监工，去提需求，检查质量，去重构，保证项目质量**。