ARTICLE DETAIL

资讯详情

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

Codex 100个真实案例 - 用AI写俄罗斯方块(触屏手机也能玩)

Codex 100个真实案例 - 用AI写俄罗斯方块(触屏手机也能玩) 1. 为什么我决定用 Codex 写一个触屏能玩的俄罗斯方块俄罗斯方块这个游戏几乎每个写前端的人都动过手。但真正把它写到「手机上能顺畅玩」的程度坑比想象中多得多。我试过直接让 AI 生成一版结果在电脑上跑得好好的一放到手机浏览器就出问题手指滑动没反应、按钮点一下触发两次、方块下落速度跟屏幕刷新率绑死导致越玩越快。这次我换了个思路用 Codex 从零开始把「触屏手机也能玩」当成第一优先级来写。核心要解决三件事HTML5 Canvas 的渲染要适配不同 DPR 的屏幕、触摸手势要区分「点击旋转」和「下滑硬降」、游戏循环要用时间戳驱动而不是帧数驱动。这三个点搞定移动端可玩性基本就稳了。这篇文章会给出完整的 Codex 提示词、Canvas 初始化配置、触屏事件绑定代码以及我在手机浏览器实测旋转、下落、消行的过程。适合已经会一点 JavaScript、想用 AI 快速做出可玩小游戏的人。你不需要懂游戏引擎只要跟着步骤把代码贴进去就能在手机上玩到自己写的俄罗斯方块。先说清楚 Codex 在这里扮演什么角色。它不是帮你「一键生成整个游戏」的魔法按钮而是一个能理解具体技术术语的结对伙伴。你描述得越精确——比如「10列×20行、每格30px、用 SRS 旋转系统」——它生成的代码就越接近能直接用的状态。模糊地说「做个俄罗斯方块」出来的东西大概率要返工。2. 前置准备TaoToken 接入 Codex 的配置与 API Key 获取在开始写游戏之前得先把 Codex 跑起来。我用的方式是走 TaoToken 的 API 接入这样在命令行里就能直接调用不用来回切网页。整个流程分三步拿 Key、配环境变量、验证连通。2.1 获取 API Key打开 TaoToken 控制台进入 API Keys 页面创建一个新的 Key。创建时建议给它起个能认出来的名字比如codex-tetris方便后面区分不同项目的用量。Key 只在创建时完整显示一次复制后先存到安全的地方。拿到 Key 之后不要直接写死在代码里。我用的是环境变量方式在~/.zshrc或~/.bashrc里加一行export TAOTOKEN_API_KEYsk-你的key然后source ~/.zshrc让它生效。这样做的原因是后面 Codex 的配置文件里只需要引用变量名不会把明文 Key 提交到 git 里。2.2 配置 Codex 的 auth.jsonCodex 的认证信息放在~/.codex/auth.json。如果你之前没配过这个文件可能不存在手动创建即可。内容结构如下{ OPENAI_API_KEY: sk-你的key, OPENAI_BASE_URL: https://taotoken.net/api }注意 Base URL 这里填的是https://taotoken.net/api不要带多余的路径。Model ID 在调用时指定常用的编码模型填gpt-5-codex或你账号下可用的对应模型名。这三个要素——Base URL、Key、Model ID——缺一不可后面排查 401 错误时也是先查这三项。2.3 验证连通性配好之后用一条最简单的请求确认能通。在终端里执行curl https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY如果返回一个包含模型列表的 JSON说明 Key 和网络都没问题。如果返回 401先检查 Key 有没有复制完整、有没有多余空格如果返回连接超时检查 Base URL 是不是写成了带/v1的完整路径导致重复。这一步看起来简单但很多人卡在这里。我踩过的坑是把 Key 写进了auth.json但忘了export环境变量结果 Codex 读的是空值。两个地方都要对缺一个都不行。3. 可复制配置Canvas 初始化与触屏事件绑定这一节是整篇文章的核心。我会把 Codex 生成的配置拆成三块Canvas 的 DPR 适配、游戏循环的时间戳驱动、触摸手势的判定逻辑。每一块都给出可直接复制的代码。3.1 Canvas 的 DPR 适配配置移动端 Canvas 最常见的问题是「画出来模糊」。原因是 CSS 像素和物理像素不是 1:1尤其在 Retina 屏上。解决办法是根据devicePixelRatio放大 Canvas 的实际分辨率再用 CSS 把它缩回视觉尺寸。function setupCanvas(canvas, cssWidth, cssHeight) { const dpr window.devicePixelRatio || 1; // 实际分辨率 CSS 尺寸 × DPR canvas.width cssWidth * dpr; canvas.height cssHeight * dpr; // CSS 尺寸保持视觉大小 canvas.style.width cssWidth px; canvas.style.height cssHeight px; const ctx canvas.getContext(2d); // 缩放上下文让后续绘制按 CSS 像素坐标走 ctx.scale(dpr, dpr); return ctx; } // 主画布10列 × 20行每格30px const gameCtx setupCanvas( document.getElementById(game-canvas), 300, 600 );这段配置的关键在ctx.scale(dpr, dpr)。调用之后你后面所有fillRect(x, y, 30, 30)里的坐标都按 CSS 像素算不用自己乘 DPR。Codex 生成这段时我特意在提示词里写了「用 devicePixelRatio 适配高分屏」它就把 scale 这步带上了。3.2 游戏循环的时间戳驱动很多教程用setInterval或者按帧计数来控制下落这在 60Hz 和 120Hz 屏幕上表现完全不同。正确做法是用requestAnimationFrame拿到时间戳自己算距离上次下落过了多久。let lastDropTime 0; let dropInterval 800; // 初始下落间隔毫秒 function gameLoop(timestamp) { if (gameOver || paused) return; // 距离上次自动下落是否超过间隔 if (timestamp - lastDropTime dropInterval) { moveDown(); lastDropTime timestamp; } render(); requestAnimationFrame(gameLoop); } // 启动 requestAnimationFrame((ts) { lastDropTime ts; gameLoop(ts); });dropInterval随等级递减公式用经典的Math.pow(0.8 - (level - 1) * 0.007, level - 1) * 1000最低不低于 50ms。这样无论屏幕刷新率多少下落速度都是一致的。3.3 触摸手势的判定逻辑触屏操作要区分三种意图短按旋转、快速下滑硬降、水平滑动左右移。判定依据是位移距离和持续时间。let touchStartX 0; let touchStartY 0; let touchStartTime 0; gameCanvas.addEventListener(touchstart, (e) { e.preventDefault(); const touch e.touches[0]; touchStartX touch.clientX; touchStartY touch.clientY; touchStartTime Date.now(); }, { passive: false }); gameCanvas.addEventListener(touchend, (e) { e.preventDefault(); const touch e.changedTouches[0]; const dx touch.clientX - touchStartX; const dy touch.clientY - touchStartY; const dt Date.now() - touchStartTime; const absDx Math.abs(dx); const absDy Math.abs(dy); if (absDx 10 absDy 10 dt 300) { // 短按旋转 rotatePiece(1); } else if (absDy absDx dy 50) { // 快速下滑硬降 hardDrop(); } else if (absDx absDy absDx 30) { // 水平滑动左右移 moveHorizontal(dx 0 ? 1 : -1); } }, { passive: false });这里{ passive: false }很重要。移动端浏览器默认把 touch 事件当被动监听preventDefault()会失效导致页面跟着滚动。加上这个选项才能阻止默认行为。3.4 虚拟按钮的绑定除了手势底部还放了一排虚拟按钮方便不习惯滑动的用户。绑定用touchstart而不是click响应更快。const btnMap { btn-left: () moveHorizontal(-1), btn-right: () moveHorizontal(1), btn-rotate: () rotatePiece(1), btn-down: () softDrop(), btn-drop: () hardDrop(), btn-hold: () holdCurrentPiece(), }; Object.entries(btnMap).forEach(([id, handler]) { const btn document.getElementById(id); btn.addEventListener(touchstart, (e) { e.preventDefault(); handler(); }, { passive: false }); });按钮的 CSS 里要加touch-action: manipulation和-webkit-tap-highlight-color: transparent去掉点击高亮和 300ms 延迟。4. 验证请求手机浏览器实测旋转、下落与消行代码写完得在真机上验证。我用的是手机 Chrome通过局域网访问电脑起的静态服务。这一步的目的是确认三件事方块能正常旋转、下落速度稳定、消行逻辑正确。4.1 起一个本地服务在项目目录下执行python3 -m http.server 8080然后在手机浏览器输入电脑的局域网 IP 加端口比如http://192.168.1.100:8080。注意手机和电脑要在同一个 Wi-Fi 下。4.2 验证旋转进入游戏后点一下屏幕中央方块应该顺时针转 90 度。连续点四次应该回到初始朝向。如果转不动检查rotatePiece里的isValidPosition判断——可能是旋转后的形状越界了SRS 的踢墙数据没生效。我在实测时遇到过一次「贴墙旋转失败」原因是踢墙偏移的 y 轴方向搞反了。SRS 数据里 dy 为正表示向上代码里要用y - dy而不是y dy。改过来之后贴左墙和贴右墙都能正常旋转。4.3 验证下落与消行让方块自然下落观察速度是否均匀。然后手动堆几行故意留一行快满等一个长条方块下来消掉。消行时应该看到闪烁动画然后上面的行整体下移。验证消行的关键是看clearLines函数。它从底部往上扫遇到满行就splice删掉然后在顶部unshift补空行。如果消行后出现错位多半是splice的索引没处理好——删一行之后后面的行号会变所以要从下往上删。4.4 验证触屏手势在画布上快速下滑方块应该直接落到底部并锁定。水平滑动方块应该左右移动一格。短按方块旋转。三种手势互不干扰。如果下滑被识别成了旋转检查dy 50这个阈值。手指滑动距离太短会被当成点击。反过来如果水平滑动没反应可能是absDx 30设得太高调低到 20 试试。5. 本篇常见错误排查401、local proxy failed 与 OAuth 报错配置和运行过程中最容易卡住的是认证和网络问题。这一节把几个高频报错和对应解法列出来。5.1 401 Unauthorized这是最常见的。原因通常是三个Key 不对、Base URL 不对、Model ID 不对。先确认auth.json里的OPENAI_API_KEY和OPENAI_BASE_URL都填了。Base URL 必须是https://taotoken.net/api不要带/v1。然后确认调用时指定的 Model ID 是你账号下可用的。三者都对还报 401就去控制台重新生成一个 Key 试试。5.2 local proxy failed这个报错通常出现在 Codex 尝试走本地代理但连不上时。检查你的环境变量里有没有残留的HTTP_PROXY或HTTPS_PROXY设置。如果有先unset掉再重试。另外确认auth.json里的 Base URL 是完整的https://taotoken.net/api不要写成相对路径。5.3 reading choices 报错这个错误一般出现在响应格式不符合预期时。Codex 期望返回的 JSON 里有choices字段如果返回的是错误信息或者空对象就会报这个。先确认请求的 endpoint 是/v1/chat/completions或对应的编码接口再确认 Model ID 拼写正确。5.4 OAuth 相关报错如果你用的是需要 OAuth 的接入方式报错时先检查 token 有没有过期。重新走一遍授权流程拿到新的 token 后更新到配置文件里。注意 OAuth token 和 API Key 是两套东西不要混用。5.5 触屏相关的问题如果手机上按钮点不动先检查有没有加{ passive: false }。如果页面跟着手指滚动在touchstart和touchmove里都调preventDefault()。如果按钮有 300ms 延迟加touch-action: manipulation。6. 从单机到联机用 Codex 继续扩展你的俄罗斯方块单机版跑通之后如果想继续加功能Codex 也能接着帮你写。我试过让它加 Hold 暂存、下一方块预览、甚至 WebSocket 联机对战都能生成可用的代码。6.1 加 Hold 暂存Hold 的逻辑是按 C 键把当前方块存起来如果之前存过就交换。关键是每次锁定前只能用一次用一个canHold标志控制。function holdCurrentPiece() { if (!currentPiece || !canHold) return; if (holdPiece) { const temp holdPiece; holdPiece { type: currentPiece.type }; currentPiece createTetromino(temp.type); } else { holdPiece { type: currentPiece.type }; currentPiece getNextPiece(); } canHold false; updatePreview(); }锁定新方块时把canHold重置为true。6.2 加下一方块预览用一个小的 Canvas 画下一个方块的形状。Codex 生成的drawPreview函数会自动居中绘制不用自己算偏移。6.3 加 WebSocket 联机联机部分需要一个 Node.js 服务端。核心逻辑是两个玩家匹配到同一个房间一方消行时给对手发「垃圾行」一方游戏结束另一方获胜。// 服务端转发垃圾行 case lines_cleared: const garbageLines msg.count 4 ? 4 : msg.count - 1; if (garbageLines 0) { forwardToOpponent(ws, { type: garbage, count: garbageLines }); } break;客户端收到garbage消息后在棋盘底部加一行灰色方块随机留一个缺口。6.4 长期编码建议如果你打算持续用 Codex 做这类项目可以考虑用 Coding Plan 的方式管理调用。它的好处是额度更稳定适合需要反复迭代的场景。配置方式跟单次调用一样只是计费模式不同。6.5 验证模型输出写完一段逻辑不确定对不对可以把代码贴到模型对话里让它 review。比如问「这段消行逻辑有没有边界问题」它通常能指出splice索引或者空行补充的疏漏。整个项目从零到能玩我用了一个下午。大部分时间花在调触屏手势的阈值和 SRS 踢墙数据的方向上。Codex 省掉的是查文档和写样板代码的时间但逻辑对不对、手感好不好还是得自己在真机上试。建议你先把单机版跑通再逐步加功能每加一个就在手机上验证一次这样出问题容易定位。
返回列表