ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用AI写五子棋App实战:从0到1的Android开发避坑指南

用AI写五子棋App实战:从0到1的Android开发避坑指南 用 AI 写一个五子棋 App现在已经不是能不能做的问题了而是怎么少踩坑的问题。我最近用 open claw其实就是 Claude中文圈习惯这么叫它从零写了一个简易版五子棋 Android 应用整个过程走下来最大的感受是AI 能帮你把骨架在十分钟内搭好但真正让它变成一个能玩、不闪退、判定准确的项目还是需要你自己懂一点棋类逻辑和 Android 布局的基本功。这篇文章就把完整的实现思路、提示词写法、代码细节以及我踩过的一堆坑整理出来给想用 AI 辅助写小游戏 App 的朋友做个参考。这个项目适合谁主要有三类人一是想学 Android 开发但对纯代码有点发怵的新手通过 AI 辅助可以更快理解界面和交互是怎么串起来的二是想快速做个课程设计、毕业设计或者面试 Demo 的同学五子棋是一个足够经典又不过度复杂的选题三是想摸清AI 编程到底靠不靠谱的开发者这篇实战记录能给你一个相对客观的判断。我会把项目核心逻辑、提示词写法、胜负判定算法、触摸交互实现、常见报错排查全部过一遍内容比较长但每一步都是可以直接照着操作的。1. 先理清楚这个五子棋 App 到底要做什么1.1 简易版五子棋的功能边界很多人一上来就想做一个人机对战 联机对战 悔棋 计时 音效的全功能版本结果搭了两周骨架还没搭明白。我的建议是既然是简易版就先把核心闭环跑通一个棋盘、两种颜色的棋子、双方轮流落子、五子连珠时判断胜负、游戏结束后可以重新开始。这五个功能点齐全了这个 App 就已经是一个能拿得出手的五子棋应用了。我最初给 open claw 的需求清单就三行15x15 标准棋盘点击交叉点落子黑白双方轮流下。什么禁手规则、AI 难度、联机对战这些统统先砍掉。你可能觉得 15 路棋盘对初学者来说计算量大但其实稍微拆解一下棋盘只是一个二维数组加一张背景图并没有想象中复杂。之所以选择 15 路而不是 9 路或 13 路是因为标准五子棋规则下 15 路是通用配置后续如果想扩展 AI 或者其它功能也不用回头重构棋盘尺寸。用 open claw 写这件事的本质是把 open claw 当作一个能快速输出代码块的结对编程伙伴而不是什么都不懂的工具人。你需要告诉它你选用的技术栈、目标平台、棋盘尺寸、交互方式甚至让它帮你考虑坐标换算这种细节它才能给出真正能跑的代码。如果只丢一句帮我写个五子棋那大概率只能得到一堆零散的片段。后面我会专门展示我实际用的提示词模板。1.2 技术选型原生 Android 自定义 View关于技术方案我对比过两条路一条是 H5/uni-app 之类的跨端方案另一条是 Android 原生 自定义 View。考虑到五子棋本身对性能要求不高棋盘绘制和落子交互都极其简单用跨端方案其实也能做。但我在实际项目里选择的是原生方案理由是简单的自定义 View 绘制反而比跨端方案更直接你不需要处理 WebView 和原生层的通信也不用在搞不懂的配置文件里找问题核心逻辑全部集中在两三个 Java 文件里非常利于学习和排查。具体技术栈是Android Studio Java使用自定义 View 绘制棋盘和棋子。为什么不用 Kotlin其实 Kotlin 现在已经是 Android 开发的主流语言但如果你去查五子棋相关的开源项目大量旧项目还是 Java 写的加上我这次以演示和快速实现为主Java 的代码更直白对新手更友好。如果你自己有 Kotlin 基础让 open claw 输出 Kotlin 版本也完全没问题本质逻辑一模一样。整个 App 的结构就两层一个 MainActivity 负责承载界面一个自定义的 GobangView 负责绘制棋盘、处理触摸、存储棋子数据、判定胜负。这种单界面 自定义画布的模式是棋类小游戏最经典的结构后面所有代码都围绕这两个文件展开。2. 五子棋核心逻辑拆解AI 能帮你写但你必须懂2.1 棋盘建模数组是核心别只想着画线五子棋最核心的数据结构就一句话用一个二维数组保存棋盘状态。一个 15x15 的 int 数组0 表示空位1 表示黑子2 表示白子。所有界面绘制、落子判断、胜负判定全部围绕这个数组展开。这可以说是整个 App 的地基也是我重点让 open claw 处理的部分。为什么一定要用数组而不是直接用坐标点列表因为数组天然支持按位置查状态这个操作。比如你判断某个交叉点能不能落子只需要检查board[row][col]是否为 0判断胜负时你需要从某个落子点出发朝四个方向扫描数组下标操作是最快的。如果用一个已落子坐标集合来存储每次判断都要遍历集合写着写着就会发现代码绕来绕去。棋盘的马里奥里还有一行类定义棋盘的起始坐标和格子间距。棋盘大小固定的情况下这些值基本都是常量// 棋盘相关常量 private static final int BOARD_SIZE 15; // 15x15 棋盘 private static final int PANEL_SIZE 600; // 棋盘总绘制宽度像素 private static final int MARGIN 30; // 棋盘边距给棋子留空间 private int cellSize; // 每个格子的边长 private int[][] board new int[BOARD_SIZE][BOARD_SIZE];cellSize的计算是(PANEL_SIZE - 2 * MARGIN) / (BOARD_SIZE - 1)这里不直接除以BOARD_SIZE是因为 15 路棋盘交叉点的间距是 14 份而不是 15 份。这个细节特别容易忽略我第一次实现时就直接除以了 15结果棋子对不齐交叉点整体偏了一格。这类细节 open claw 有时候也会忽略所以你自己心里得有数。2.2 坐标换算点击位置到棋盘交叉点的精确映射这个部分是很多新手写五子棋卡壳的第一个地方也是 AI 生成的代码最容易出问题的地方。用户在屏幕上点击的是一个像素坐标但你要知道这个像素离哪个交叉点最近才能确定棋子落在哪。换算逻辑分三步。第一步把点击的 x 坐标减去棋盘左边距MARGIN得到相对于棋盘左上角的偏移量第二步除以格子大小cellSize得到浮点数的格数第三步用四舍五入取最近的整数下标。换算完还要做边界检查防止用户在棋盘外点击时计算出越界下标导致崩溃。int col Math.round((event.getX() - MARGIN) / cellSize); int row Math.round((event.getY() - MARGIN) / cellSize); // 越界保护 if (col 0 || col BOARD_SIZE || row 0 || row BOARD_SIZE) { return; }还有一个容易忽略的细节触摸事件的坐标可能出现在棋盘边缘之外比如你点在棋盘左上角外侧col或row变成负数数组就会越界。这个保护判断必须加。我在实际调试时发现open claw 生成的代码里这个判断有时会被漏掉需要你主动补充。2.3 胜负判定四方向扫描法一段让人头大的代码胜负判定是整个五子棋程序的灵魂也是 open claw 生成代码时最容易看起来对、实际有 bug的地方。我见过很多 AI 写的判定逻辑有的只判断了横向和纵向忘了斜向有的把胜负判定写成了从棋盘左上角遍历到右下角每走一步都要扫一遍虽然功能没错但效率差。我给 open claw 的提示词里专门强调胜负判定必须在当前落子位置向四个方向扫描每个方向分别向两边累计相同棋子数量只要某一方向相同棋子数大于等于 5就判定获胜。这种做法不只是效率高更重要的是避免了一个隐藏 bug如果每次重新遍历整个棋盘你可能永远找不到最新一步连成五子的准确位置尤其在双方连续落子后逻辑会混乱。四方向的具体实现是对当前落子的 (row, col)依次检查水平、垂直、主对角线左上到右下、副对角线右上到左下四个方向。每个方向需要做两次扫描正方向和反方向然后相加减一当前棋子被算了两次。private boolean checkWin(int row, int col) { // 四个方向水平、垂直、主对角线、副对角线 int[][] directions {{0,1}, {1,0}, {1,1}, {1,-1}}; for (int[] dir : directions) { int count 1; // 正方向扫描 for (int i 1; i 5; i) { int nr row dir[0] * i; int nc col dir[1] * i; if (nr 0 || nr BOARD_SIZE || nc 0 || nc BOARD_SIZE) break; if (board[nr][nc] currentPlayer) count; else break; } // 反方向扫描 for (int i 1; i 5; i) { int nr row - dir[0] * i; int nc col - dir[1] * i; if (nr 0 || nr BOARD_SIZE || nc 0 || nc BOARD_SIZE) break; if (board[nr][nc] currentPlayer) count; else break; } if (count 5) return true; } return false; }这段代码是我让 open claw 生成后自己又手动优化过的版本。原始版本把count 5写成了count 5这在某些情况下会出问题。举个具体例子如果某一行已经形成了五连再加一个同色子变成六连count 5就判定不了胜负玩家会陷入明明已经很长的连子却赢不了的 bug。正确的判断必须是5并且把连子超过了五颗视为获胜。这个细节非常重要后面排查问题部分我还会再提。2.4 重启与交替落子状态管理的几个隐藏坑除了数组和判定状态管理也容易出问题。游戏进行中需要一个currentPlayer字段记录当前轮到谁每落一子就切换一次玩家。常用的切换方式是currentPlayer 3 - currentPlayer当currentPlayer为 1 时变成 2为 2 时变回 1非常简洁。但注意不要用currentPlayer 2这种硬编码不然下两子之后逻辑就乱了。游戏结束时要锁定棋盘不能再响应落子事件。我用的方式是加一个isGameOver布尔变量在onTouchEvent里开始时直接判断如果游戏结束直接返回如果棋盘满了还没分出胜负则提示平局并且结束游戏。棋盘判满的逻辑也很简单每落一子都自增一个moveCount当moveCount BOARD_SIZE * BOARD_SIZE时说明棋盘满了。Override public boolean onTouchEvent(MotionEvent event) { if (isGameOver) return false; if (event.getAction() MotionEvent.ACTION_DOWN) { // 坐标换算、落子、判定胜负、刷新 } return true; }这里有一个我建议新手注意的坑不要用ACTION_UP作为落子触发事件因为手指在按下后如果轻微滑动最终弹起的位置和按下位置可能不一样导致棋子落在你不想放的位置。使用ACTION_DOWN会更稳定。这个细节尤其在手机屏幕上表现明显我用 open claw 生成的第一版代码就是ACTION_UP触发的实测移动时点按容易偏移后来改成了ACTION_DOWN世界清净了。3. 实操记录从提示词到能跑的 App3.1 我给 open claw 的完整提示词模板open claw 的实际输出质量非常依赖提示词的清晰度。很多朋友反馈AI 写的代码不能跑大概率是提示词里缺了平台、语言、结构和约束条件。我在第一版对话里就一次性给出了完整项目背景后续基本没遇到牛头不对马嘴的回复。我的提示词大致是这样写的你可以直接参考请帮我写一个 Android 简易五子棋 App使用 Java 语言编译环境 Android Studio。 需求如下 1. 棋盘为 15x15 标准五子棋棋盘使用自定义 View 绘制网格和棋子。 2. 支持黑白双方交替落子黑子先行。 3. 点击棋盘交叉点落子需要做坐标换算并处理边缘点击。 4. 落子后立即判断胜负任一方向连成 5 子即提示胜利并结束游戏。 5. 棋盘底部或顶部有一个重新开始按钮点击后清空棋盘。 6. 代码结构尽量精简一个 Activity 加一个自定义 View 类即可。 7. 关键地方请加中文注释。这里有个小技巧提示词里明确给出一个 Activity 加一个自定义 View 类的结构约束AI 就不会生成一堆乱七八糟的文件结构。很多新手让 AI 写代码不限定结构结果生成十几个文件每个文件互相引用反而把自己绕晕了。3.2 生成结果第一版代码的骨架体验open claw 生成的代码骨架整体是可用的。MainActivity 里放了一个布局布局里面是一个 GobangView 加上一个 Button。GobangView 类里负责初始化画笔、计算格子大小、onDraw绘制棋盘和棋子、onTouchEvent处理落子。这和我预期结构一致。不过生成的第一版有几个地方我没有直接用而是手动改了。一是坐标换算那块AI 当时写的是(int) ((event.getX() - MARGIN) / cellSize)这种强制转换是向下取整意味着你点击交叉点左半边时棋子会落到左边那个交叉点体感上不太精确。我改成了Math.round(...)四舍五入手感好了很多。二是胜负判定里count 5的问题前面已经提过。三是棋盘背景的绘制AI 默认只画了线没有画星位天元和星位的小点虽然不影响功能但看着不够专业后面我手动补上了。这说明一个事实AI 生成代码的价值在于快速搭骨架但细节的打磨还是得靠你自己的理解。这可能也是 AI 编程目前最真实的状态。3.3 界面布局与代码整合MainActivity 的布局我用的是最简单的 LinearLayout垂直方向上面是棋盘下面是按钮。为了让棋盘尽量接近正方形我在onMeasure里做了处理让 View 的宽高继承自父布局同时在绘制时计算cellSize时使用的是min(width, height)来保证棋盘不会因为屏幕比例变形。LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical com.example.gobang.GobangView android:idid/gobangView android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / Button android:idid/btnRestart android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter_horizontal android:text重新开始 / /LinearLayout这里有个实战细节如果你直接把 GobangView 的宽度设为match_parent在onDraw里用getWidth()作为棋盘总宽度那么当你把手机横过来时棋盘会自动变宽棋子的间距也会跟着变看起来会有些奇怪。简易版可以不处理横竖屏锁定最省事的方式是在 AndroidManifest 里给 Activity 加上android:screenOrientationportrait锁定竖屏这样整个项目就简化为单尺寸问题。专业五子棋 App 会做自适应但简易版真的不用纠结。3.4 棋盘绘制细节怎么画才专业棋盘绘制本身不复杂但有几个细节值得讲究。一个是线宽我用的是 2 像素的灰色线棋盘外框线可以适度加粗到 4 像素这样视觉上更有层次。另一个是棋子为了做出立体感我在画棋子时使用了径向渐变黑子从深灰渐变到纯黑白子从白色渐变到浅灰。如果不加渐变黑白两色棋子的边缘会显得很生硬。第三个细节是棋子要画在交叉点上而不是格子里。落子时的坐标换算已经保证了数据层面的交叉点下标但绘制时别忘了要把下标换算回像素坐标float cx MARGIN col * cellSize; float cy MARGIN row * cellSize; canvas.drawCircle(cx, cy, radius, paint);半径通常取cellSize * 0.4左右如果取 0.5 的话相邻棋子会紧挨着视觉上太过拥挤。我试过 0.38 和 0.420.4 是综合看起来最舒服的。关于星位的绘制标准 15 路棋盘上有五个星位分别在 (3,3), (3,11), (7,7), (11,3), (11,11) 五个交叉点用小实心圆表示。虽然简易版不加也不影响功能但加了以后棋盘一眼看上去就专业很多。这个小改进我给 open claw 提了需求它几秒钟就帮我补好了。4. 我踩过的坑与排查经验4.1 看起来对实际上赢不了胜负判定边界条件这个坑前面提过一部分这里展开说。我刚把第一版代码跑起来时测试了三连和四连判定都正常。但玩到第六手时发现一个诡异现象黑子在一条斜线上连成了 6 个系统居然没有提示胜利。排查过程是这样的。我先在关键位置加了 Log 输出打印count的值。结果发现当第六子落下时count值确实是 6但判断条件是count 5所以直接跳过了胜利分支。这个 bug 其实在代码审查阶段就应该发现但由于是 AI 生成的代码我下意识觉得它写的肯定是对的结果反而忽略了最基本的边界条件。这个问题给了我一个很重要的教训用 AI 写代码一定要对条件判断中的等号和大小比较多留一个心眼。AI 特别容易在和之间做自以为正确的选择。与其事后 Debug不如在提示词里直接写清楚连成 5 子及以上都算获胜这样能省很多事。4.2 触摸偏移为什么我点的位置和棋子落点差了半个格子第二版代码上线后我在手机上实测发现棋子总是偏向点击位置的右下方。后来定位到是坐标换算时用了强制类型转换而不是四舍五入。(int) ((x - MARGIN) / cellSize)这种写法在 Java 里是向下取整。比如点击位置经过计算是 3.7 格向下取整变 3于是棋子在左边一格落下给人的感觉是偏了。这个偏差在格子尺寸比较大的平板上尤其明显。修复方式就是使用Math.round()四舍五入同时在边界处还要防止负数舍入到 -0 的情况统一做Math.max(0, ...)处理虽然越界保护已经做了但多一层保险总没坏处。另外我还发现onTouchEvent返回值的处理也很关键。如果你在ACTION_DOWN中处理逻辑但返回了false后续的触摸事件就不会继续传给这个 View导致第二次落子失效。这里应该始终返回true表示这个触摸事件已经被消费掉了。4.3 中文显示和 Android 版本适配的常见报错还有一个在调试时常见的问题模拟器或旧手机上中文注释显示乱码甚至编译报编码 GBK 的不可映射字符。这个问题其实和五子棋逻辑无关但我实在忍不住提醒一下。解决办法是在build.gradle里加上compileOptions { encoding UTF-8 }或者在 Android Studio 的 File - Settings - Editor - File Encodings 里把 Project Encoding 和 Default Encoding 都设置为 UTF-8。open claw 生成的代码注释默认是中文如果不设置编码在某些环境下一编译就报错。另一个常见问题是如果你在MainActivity里用setContentView(R.layout.activity_main)加载了布局但布局里通过findViewById找到的 GobangView 类名写错了包路径运行时会直接抛ClassNotFoundException。这个倒不是 AI 的锅而是你在复制代码时手滑改坏了。排查方法就是看 Logcat 里的完整异常栈定位到具体的类名和行号。4.4 问题速查表现象大概率原因解决方法棋子落点偏移坐标换算用了强转而非四舍五入改用Math.round()连 5 子不判胜条件写成count 5改成count 5点第二下没反应onTouchEvent返回了false统一返回true编译报中文注释乱码文件编码不是 UTF-8配置全局 UTF-8 编码棋盘显示不完整高度不够或cellSize计算错误使用min(width, height)并检查边距点击棋盘边缘崩溃缺少越界保护添加col/row范围判断这些坑说实话都不深但每个都让我多花了半小时到一小时。整理出来就是希望后来的朋友能直接绕开。5. 从简易版到进阶版还能怎么扩展五子棋项目做到这个程度已经是一个完整的能跑的应用了。如果你想继续提升有几个扩展方向比较推荐。第一个是加入简单的 AI 对战。简易版的 AI 不用做什么深度学习用最简单的评分表策略就行。遍历棋盘每一个空位分别计算该位置对自己形成连子和阻挡对方连子的贡献度然后取最高分落子。这个逻辑其实 50 行代码就能写完让 open claw 帮你写一版效果比随机落子好很多已经能应对完全不会玩五子棋的普通人了。第二个是悔棋功能。实现方式是维护一个落子历史栈每次落子把坐标压栈点悔棋时弹出栈顶对应位置的数组值清零然后刷新棋盘。这个功能在数据结构上只需要一个Stackint[]实现非常简单但是对用户体验提升很明显。第三个是加一个记分板累计双方胜局。这个需要引入SharedPreferences做本地存储代码量也不大但能让你练习数据持久化的基本用法。从项目复盘的角度看这个简易五子棋 App 最大的收获不是你完成了一个小游戏而是通过 AI 辅助你完整走了一遍需求定义 — 逻辑拆解 — 代码生成 — 联调排查的软件开发闭环。你会明显感受到AI 可以帮你省去大量基础编码的时间但前提是你自己能把需求想清楚、把逻辑边界想清楚。这种能力在任何项目里都是核心竞争力。最后再分享一个小技巧如果你手头有 open claw 或者类似的 AI 编程工具在让它生成代码之前自己先在纸上或 Notion 里把数据结构和核心函数签名写出来哪怕只是草稿级别的。然后把这个草稿粘贴到提示词里让 AI 按照你的设计去实现而不是让 AI 从零设计。这样得到的代码会更符合你的心意后续调试成本也会低很多。我这次五子棋项目第二次重构时就是按这个方法来效率至少翻了一倍。
返回列表