
最近闲着没事突然想这用AI写点代码。作为一名要把“简单需求复杂化”刻在 DNA 里的老前端我决定拿最简单的“剪刀石头布”开刀。在这个 AI 满天飞的时代还在用Math.random()写对手逻辑未免太没追求了。于是我基于最新的技术栈Next.js 16 React 19接入了 OpenAI等一众大模型搞出了一个会“读心”、会嘲讽甚至懂博弈论的猜拳游戏。为什么是剪刀石头布别笑剪刀石头布其实是一个极佳的博弈论模型。新手完全随机Chaos。普通人赢了保持输了变招Win-Stay, Lose-Shift。高手预判你的预判。我的目标是构建一个能看穿你心理的 AI并且用目前最前沿的前端技术栈把它跑起来。技术选型这就叫“杀鸡用牛刀”为了配得上这个“高智商”AI我在技术栈上直接拉满全部采用了目前2025-2026的最新稳定版Next.js 16.1 (App Router)服务端组件RSC处理核心逻辑隐藏 AI 的 Prompt保证你没法通过 F12 偷看答案。React 19享受最新的 Hooks 和并发特性。Tailwind CSS v4对就是那个不用配置构建工具、性能起飞的 v4 版本。关键是AI喜欢用这个SQLite LibSQL轻量级数据库用来记仇——啊不记录你的胜负数据。核心玩法AI 是怎么“读心”的这个项目的核心不在于 UI 有多炫虽然 Tailwind 4 确实很润而在于/lib/ai-service.ts里的那段逻辑。传统的游戏 AI 往往是预设好的if-else。但在我的设计里每一轮游戏我都会把你在这个 Session 里的所有历史记录打包像讲故事一样发给 LLM大语言模型// lib/ai-service.ts // 构建游戏历史描述 const historyDescription history.length 0 ? history.map((h) 第${h.round}轮: 玩家出${translateChoice(h.player_choice)}, AI出${translateChoice(h.ai_choice)}, 结果: ${translateResult(h.result)} ).join(\n) : 这是第一轮没有历史记录。; // ...发送给 LLM const response await client.chat.completions.create({ messages: [ { role: system, content: systemPrompt }, { role: user, content: userPrompt }, // 这里包含了 historyDescription ], // ... });System: 你是一个猜拳高手你的对手是一个普通人类。User: 前几轮战况如下第1轮玩家出剪刀你出布输第2轮玩家出石头你出布赢。现在是第3轮请分析玩家的心理并给出你的出拳。不仅如此我给 AI 设定了两种模式策略模式Strategy降低模型的temperature随机性让它进行严密的逻辑推理。比如它会分析“玩家上一把输了根据心理学这把他大概率会出克制我上一把的招数所以我预判他……”混沌模式Chaos拉高temperature让 AI 彻底放飞自我主打一个乱拳打死老师傅。优雅降级当 GPT 脑干缺失时作为老全栈必须要考虑一种情况如果 API 挂了或者响应超时了怎么办难道让用户干等着转圈圈绝对不行。我在后端实现了一套基于传统统计学的本地算法作为“备胎”。如果 LLM 在规定时间内没有响应系统会无缝切换到本地逻辑。这个本地逻辑一点也不弱它内置了经典的策略库代码在lib/game.ts// lib/game.ts 里的心理学博弈逻辑 // 1. 如果玩家上轮输了倾向于出能克制AI上一招的选项 (Win-Stay, Lose-Shift的变种) if (lastResult ai_win) { const lastAIChoice lastRound.ai_choice as Choice; // 预测玩家会出克制我不上一把的牌 const predictedPlayerChoice whatBeatsAI[lastAIChoice]; // 那我就预判你的预判 return counterMoves[predictedPlayerChoice]; } // 2. 如果玩家上轮赢了可能继续用同一招 if (lastResult player_win) { // 玩家可能继续用同一招直接克制它 return counterMoves[lastPlayerChoice]; }这些策略包括频率分析如果你一直出石头它就会疯狂出布。反制连胜如果你赢了它会假设你会继续出一样的直接克制你。在代码实现上这只是一个简单的try-catch降级但对用户体验来说是质的飞跃。用户根本感觉不到 AI 掉线了只会感觉“这家伙怎么变风格了”全栈体验Next.js App Router 的丝滑在 Next.js 16 中前后端的边界变得非常模糊褒义。本项目使用了 App Router 的 Route Handlers 来处理游戏逻辑。前端组件调用后端接口就像调用本地函数一样自然// src/app/game/[id]/page.tsx const playRound useCallback(async (choice: Choice | null, timeout: boolean false) { // ... const res await fetch(/api/game/play, { method: POST, body: JSON.stringify({ /*...*/ }), }); // ... }, []);而在服务端 (src/app/api/game/play/route.ts)我们完成了完整的业务闭环// src/app/api/game/play/route.ts export async function POST(request: NextRequest) { // 1. 身份校验与数据库读取 const { sessionId, playerChoice } await request.json(); const baseSession await db.execute(/*...*/); // 2. 调用 AI (带超时降级) // 如果 API 响应太慢这里会自动切换到本地逻辑 const aiChoiceResult await Promise.race([ getAIChoiceFromAPI(aiConfig, history, difficulty), timeoutPromise // 设定的超时时间 ]); // 3. 判定胜负 写入数据库 const result determineWinner(playerChoice, aiChoiceResult.choice); // ... return NextResponse.json({ /*...*/ }); }这一套流程行云流水类型安全虽然不如 Server Actions 极致但通过共享类型定义Shared Types依然能保证前后端的一致性。不用写繁琐的 Swagger不用搞复杂的 Redux一把梭。实际上手由于太会嘲讽导致不想玩了为了增加趣味性我让 AI 不仅输出“石头/剪刀/布”还要输出一段Reasoning推理过程和Comment赛后嘲讽。当你输掉比赛时你可能会看到这样的结算语“我看你第一把犹豫了很久出了剪刀我就知道你是个保守的人。下一把别这么明显了人类。”说实话代码写完后我自己测试了几把胜率居然只有 40% 左右。看着屏幕上 AI 的嘲讽我即使作为开发者也不禁怀疑这玩意儿是不是真有意识体验地址虽说代码没什么核心科技但带来的博弈体验确实很有趣。我已经把项目部署上去了欢迎来挑战或者被虐在线体验优先地址https://rps.anhejin.cn有条件的https://rps-eta-ten.vercel.app总结这个项目证明了一件事技术是冰冷的但通过简单的创意组合可以创造出有温度甚至有点烫手的交互体验。Next.js 16 和 React 19 的组合让全栈开发的门槛进一步降低让我们有更多精力去关注“玩法”本身而不是被构建配置折磨。