Claude3.5 实现完整单机五子棋
五子棋是一个比较简单的棋类游戏,规则比较简单,如果用 Claude3.5 来实现,可能一句话就能实现一个简单版本的五子棋了。不过仔细研究了五子棋的禁手规则后,发现还是有点复杂的。五子棋的规则包含了各种禁手规则,比如三三禁手、四四禁手、长连禁手等。
如果用 Claude3.5 来实现禁手,会有难度吗?对于复杂的规则,当使用 Claude 辅助开发的时候,如何才能保证实现的正确性?本文记录用 Claude 实现单机五子棋的完整过程,一起来体会和 AI 结对编程的乐趣。
可以在这里体验:在线五子棋 游戏,目前只有单机版,后续会支持联网对战和 人、AI 对战。代码全部开源,在 Github 上。
快速实现 UI
对于我这前端新手菜鸟,如果没有 AI,让我实现五子棋,可能需要花一天时间来实现棋盘的展示和棋子布局。然后有了 Claude3.5,这类事情就简单了很多,直接简单提示下,就能拿到一个基本可用的五子棋棋盘了。
这里经过了两三轮提示,主要是补充实现方法以及屏幕适配。核心包括:
帮我画一个五子棋棋盘,要求每行每列都是放 15 个棋子,用react 和tailwind css实现,然后做好屏幕适配。
效果如下:
不过如果仔细观察,这里的棋子在方格里面,其实正规棋盘,棋子不是放到方格里面,而是放到交叉线地方的。想实现也很简单,再描述清楚,然后 Claude 就能快速实现。另外在棋盘边界线周围还需要留一点空白,这样边上的棋子也有足够的位置。
整体体验下来,对于这种 UI 的需求,只要描述清楚需求,Claude 实现的质量还是很不错的。
禁手规则拆分
五子棋的禁手规则比较复杂,直接让 AI 实现完整的禁手规则,有不少错误。其实也尝试让 AI 来解释五子棋禁手,不过 AI 解释有点不太清晰。于是先找了一个比较权威的对于禁手的解释,然后让 AI 实现。
实现这种比较复杂的规则,肯定要拆分子问题了。为了实现三三禁手检测,这里先单独实现一个连续活三的检测。对于一个落点,检查前、后两个点是否是同一方,之后检测两端是否是空白的。比如落点 X,连续活三有下面几种情况:
_00X_
_X00_
_0X0_
这里 _
代表空格,0
代表先手棋子,X
代表先手将要下的位置,任意 1 种都是活三。这里因为模式比较固定,就想着直接让 Claude3.5 根据模式来匹配。AI 给出的代码还是可以的,没有什么错误。跳跃活三稍微复杂些,不过可以用同样的思路解决,只是模式稍微多一些,Claude3.5 也解决的不错。整体代码如下:
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 重修修改这里的逻辑,不单独判断冲四,直接增加一个计算活四或者冲四连接的方法。具体步骤:
- 对于某个先手落子,算出再下一个棋子会五连的所有情况。
- 对于每一种五连,记录用到的当前 4 个棋子的位置,以及需要再下棋子的位置。
- 然后按照用到的 4 连棋子的位置进行聚合,计算出每种情况下,需要下的棋子的位置,如果对于某四个棋子,只有一个位置可以行成五连,就是冲四。有两个位置可以形成五连,就是活四。
- 函数返回两部分,一个是冲四的棋子位置,一个是活四的棋子位置
上面算法其实手写的话,还是挺费劲的,特别是我甚至都不太熟悉 JavaScript 语言。但是我描述给 Claude3.5,它竟然真的懂了,一遍就给出了正确的代码,核心部分如下,完整的在 github 可以看到:
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 很快就给出了各种用例。不过这里为了让用例对人友好,这里故意把整个棋盘写出来,方便一眼看清。比如下面的双活三棋盘:
board: [
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "B", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "B", "X", "B", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "B", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]
],
AI 能快速给出各种测试用例,不过还是免不了人工干预。毕竟有些边界条件和专门的测试用例,靠 AI 来生成还是有点难。好在这里只用改下棋盘中棋子位置,人工来做也很方便了。
测试运行结果:
有测试用例做保证,这里禁手的实现基本没啥问题了。不过如果仔细看专业的禁手规则,发现还有太专业的非禁手情况,等后面有时间精力了再来看看怎么实现吧。
AI 可以写大项目吗?
加禁手的单机五子棋,其实算是复杂了,Claude 实现的还算可以。其实更大的项目,比如这整个站点,全都是在 Claude3.5 和 cursor 的辅助下快速完成的。
最开始想着只是体验下 Claude3.5 的能力,写几个简单的页面就行了。慢慢写着写着,这个演示站点的的页面越来越多,功能也越来越丰富。最近 cursor 编辑器又集成了 Claude3.5,写复杂项目的体验也越来越好。
对于不太需要复杂算法实现的功能,基本上描述清楚需求,Claude3.5 都能很好的实现。有时候实现的会比预期好,毕竟 Claude 大模型其实有很多知识。结合 Cursor 的文件引用,AI 对于整个项目的理解也越来越好,改动一个地方后,AI 甚至会提醒修改其他相关的文件,这点还是很不错的。
体验下来比较欠缺的地方有几个:
- 复杂的逻辑,要描述清楚,claude 才能实现;如果开发者自己都没想明白怎么实现,AI 大概率也是没法实现;
- AI 堆代码的时候,有时候会给出一些冗余代码,比如同样的函数在项目多个文件到处都有。需要开发者自己不断去重构一些实现,保证代码整体的简洁与可维护性。
- 当你提了一些错误的思路,Claude 会尽量去拟合,而不能指出你的错误。有时候错的很离谱,我不小心打开了一个其他页面的代码,然后在 cursor 中,把这个文件当做五子棋的功能来修改,结果 AI 真的就在这里乱改了!
回答问题的答案,AI 完全可以来写大项目,只是需要人不断去提示,去纠错。人在这个过程,更像是监工,去提需求,检查质量,去重构,保证项目质量。