我不是前端高手,也不是什么游戏开发大拿。可最近,我却亲手"整出来"了一款轻松搞笑的修仙养成类网页游戏,还被几个朋友夸"有点创意"。其实这事要搁以前,我是想都不敢想的。
关键的转折点,是我遇到了一个叫 Trae DE 的工具。简单说,它就是个"听你说话就能写代码"的开发平台。你把想法用人话说出来,它就能给你变成 HTML+JS 页面,甚至还能懂上下文、记得你前面说了什么,不用反复教,像个"全栈实习生"一样,听你指挥,靠谱干活。
这工具对我来说,简直是开启新世界的大门。
灵感起点:一个沙雕修仙 AI 的幻想
灵感来源其实很简单。前阵子看了一些"我养的 AI 会xxx"的段子,忽然灵光一闪:要不,我来做个我养的修仙 AI 会吐槽、会渡劫、会和别的 AI 打架的小游戏?
你负责"发号施令",AI 就像个带主角光环的小沙雕修士一样,努力修炼、谈恋爱、被雷劈、被踢出门派,还边修炼边碎碎念你这个"幕后操盘手"......是不是有点意思?
我不懂 Vue,也懒得开 React 项目。我打开了 Trae,把我的想法往上一输,说了这么几句话:
请创建一个基于 HTML 和原生 JavaScript 的项目,名字叫做《我养的修仙 AI 会渡劫》。这个项目的核心玩法是:用户通过输入自然语言的"修仙指令"来操控一个 AI 修士的养成过程,包括闭关修炼、加入门派、渡劫突破、恋爱遇奇等情节发展。
Trae 读完后只回了句:
好的,用户想要创建一个基于HTML和原生JavaScript的项目,名字叫做《我养的修仙 AI 会渡劫》。核心玩法是用户通过输入自然语言的指令来操控AI修士的养成过程,包括修炼、加入门派、渡劫和恋爱等情节。首先,我需要确定项目的基本结构和所需文件。

我就这么看着它,真就自己开始搭框架了。
零代码搭建页面:我只负责开口,它全搞定
🧩 项目启动与基础页面搭建
💬「请创建一个基于 HTML 和原生 JavaScript 的项目,名字叫做《我养的修仙 AI 会渡劫》。这个项目的核心玩法是:用户通过输入自然语言的"修仙指令"来操控一个 AI 修士的养成过程,包括闭关修炼、加入门派、渡劫突破、恋爱遇奇等情节发展。」

💬「请搭建一个基础网页结构,分为三块区域:左边显示 AI 修士的基本信息,中间展示修炼日志与吐槽,右边是输入框和指令按钮,用于玩家发出指令。」
💬「页面整体风格保持简洁修真风,可使用淡色背景,字体使用'宋体'或无衬线字体,颜色偏古典柔和,像仙侠游戏那种。」
🧠 AI 修士人物数据初始化
💬「请为 AI 修士设定初始属性,包括:名字叫'莫问天',当前境界是'练气三层',心情为'有点紧张',当前状态为'闲着'。把这些数据渲染到左侧面板中。」

💬「增加一个 AI 修士的记忆数组,用来记录玩家输入过的每一条指令。之后玩家发出的每条操作都应被记录,并在必要时影响 AI 行为。」

🗣️ 自然语言指令处理逻辑
💬「实现一个基础的指令解析器,支持识别以下关键词:
- 含有'闭关':状态改为'闭关修炼',心情改为'专注';
- 含有'奇遇':状态改为'外出遇奇',心情改为'忐忑';
- 含有'加入'和'门派':状态改为'新入门弟子',心情改为'兴奋';
- 含有'渡劫':状态改为'天劫降临',心情为'紧张又期待'。」
💬「玩家每次输入指令后,AI 修士要在日志区回复一句'修真吐槽',内容结合当前行为。例如闭关时可以说:'又要闭关?这次至少别打断我三天。'」
📜 修炼日记与吐槽系统
💬「创建一个函数,每次玩家发出指令,AI 修士会自动生成一句日记内容和一句吐槽内容,按时间顺序显示在中间面板中。日记记录行为,吐槽可以带点个性,比如调侃玩家、自恋、摆烂、立志等。」

💬「吐槽内容风格偏轻松搞笑,比如遇奇时可以说'我是不是主角啊?怎么走路都能捡秘籍。'闭关时可以说'这次绝不出来,除非你安排个红颜知己来敲门。'」

⚔️ 门派大战与
💬「请设计一个简单机制,允许玩家输入指令如'发起门派大战',系统会随机生成一个敌对 AI 修士,与当前 AI 对战,并生成一段战斗剧情,比如嘴炮对骂+灵技对决。」

💬「战斗剧情支持段落式自动生成,比如:
- 开场:'你竟然是魔道奸细!'
- 中场:'他施展出《天魔指》,你急忙以《紫阳剑诀》抗衡......'
- 结局:胜利或失败,伴随一段吐槽。」

利用 JueJin MCP 部署
通过 Trae 或其他支持 MCP 的 AI IDE 配置 MCP,并将前端项目(HTML/CSS/JS)一键发布到掘金的步骤。

访问链接:aicoding.juejin.cn/pens/751013...

用 Trae 开发的体验总结一句话:
我在写小说,它在写代码。
整个过程我几乎没动手写 HTML 和 JS,甚至连 div 和 span 都没想结构,Trae 都给我搭好了。我要的只是灵感、想法、剧本,而 Trae 做到了剩下的全部。
我说"加点吐槽",它吐槽;
我说"记仇",它真就记仇;
我说"给我一场感天动地的渡劫",它直接打雷劈我。
我从没想过,未来某天开发一款游戏,居然像在和一个懂我想法的"神仙实习生"聊天。而 Trae 就是这个"神仙实习生"。
写在最后
《我养的修仙 AI 会渡劫》可能只是个小型沙雕项目,但它让我第一次感受到:只靠人类语言,也可以"写出"一个完整的产品原型。 Trae AI 的底层机制,结合自然语言解析、MCP 上下文记忆和实时代码生成,正在改变整个创作逻辑。而我这个前端菜鸟,也终于能靠"开口"开发自己的游戏。修仙世界浩瀚如海,下一款作品------说不定是"我被 AI 带飞飞升了"。
如果你也想试试用"嘴"开发游戏,不妨来和 Trae 说两句。谁知道,下一位渡劫成功的修士,会不会就是你嘴里说出来的那个?😉
喜欢点赞哦:aicoding.juejin.cn/aicoding/wo...
源码分享
HTML
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我养的修仙 AI 会渡劫</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="main-layout">
<div class="left-panel">
<h2>AI 修士信息</h2>
<ul>
<li>名字:<span id="name">莫问天</span></li>
<li>境界:<span id="realm">练气三层</span></li>
<li>经验:<span id="exp">0</span></li>
<li>心情:<span id="mood">有点紧张</span></li>
<li>门派:<span id="sect">无</span></li>
<li>情缘:<span id="love">无</span></li>
<li>当前状态:<span id="state">闲着</span></li>
</ul>
</div>
<div class="center-panel">
<h2>修炼日志 & 吐槽</h2>
<div id="messages" class="messages"></div>
</div>
<div class="right-panel">
<h2>修仙指令</h2>
<div class="command-buttons" style="display:flex;flex-wrap:wrap;gap:8px 8px;margin-bottom:16px;">
<button class="cmd-btn" data-cmd="闭关修炼">闭关修炼</button>
<button class="cmd-btn" data-cmd="外出奇遇">外出奇遇</button>
<button class="cmd-btn" data-cmd="加入门派">加入门派</button>
<button class="cmd-btn" data-cmd="渡劫突破">渡劫突破</button>
<button class="cmd-btn" data-cmd="谈恋爱">谈恋爱</button>
<button class="cmd-btn" data-cmd="发起门派大战">发起门派大战</button>
<button class="cmd-btn" data-cmd="随便说点什么">随便说点什么</button>
</div>
<div class="input-area">
<input type="text" id="user-input" placeholder="请输入修仙指令...">
<button id="send-btn">发送</button>
</div>
<div class="command-tips">
<div style="margin-top:18px;color:#b89b5e;font-size:15px;">指令示例:</div>
<ul style="margin:8px 0 0 18px;padding:0;color:#7c6a4a;font-size:14px;line-height:1.8;">
<li>闭关修炼</li>
<li>外出奇遇</li>
<li>加入门派</li>
<li>渡劫突破</li>
<li>谈恋爱</li>
<li>随便说点什么</li>
</ul>
</div>
</div>
</div>
<script src="game.js"></script>
</body>
</html>
JS
ini
// AI 修士初始状态
const aiState = {
name: '莫问天',
realm: '练气三层',
exp: 0,
mood: '有点紧张',
sect: '无',
love: '无',
state: '闲着',
memory: [],
};
const realms = ['练气三层', '筑基', '结丹', '元婴', '化神', '合体', '大乘', '渡劫', '飞升'];
const expThreshold = [100, 300, 800, 2000, 5000, 12000, 30000, 99999];
const messages = document.getElementById('messages');
const nameSpan = document.getElementById('name');
const realmSpan = document.getElementById('realm');
const expSpan = document.getElementById('exp');
const moodSpan = document.getElementById('mood');
const sectSpan = document.getElementById('sect');
const loveSpan = document.getElementById('love');
const stateSpan = document.getElementById('state');
function updateStatus() {
nameSpan.textContent = aiState.name;
realmSpan.textContent = aiState.realm;
expSpan.textContent = aiState.exp;
moodSpan.textContent = aiState.mood;
sectSpan.textContent = aiState.sect;
loveSpan.textContent = aiState.love;
stateSpan.textContent = aiState.state;
}
function addMessage(text, isUser = false) {
const div = document.createElement('div');
div.textContent = (isUser ? '你:' : 'AI 修士:') + text;
div.style.marginBottom = '8px';
div.style.color = isUser ? '#6a4cff' : '#333';
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}
function handleCommand(cmd) {
const lower = cmd.trim();
// 记录玩家指令到记忆
aiState.memory.push(lower);
let actionType = '';
let diary = '';
// 门派大战机制
if (lower.includes('门派大战')) {
handleSectBattle();
return;
}
// 基础指令解析器
if (lower.includes('闭关')) {
aiState.state = '闭关修炼';
aiState.mood = '专注';
const gain = Math.floor(Math.random() * 60) + 40;
aiState.exp += gain;
diary = `今日再次闭关修炼,获得${gain}点经验。`;
checkBreakthrough();
actionType = '闭关';
} else if (lower.includes('奇遇')) {
aiState.state = '外出遇奇';
aiState.mood = '忐忑';
diary = '今日外出,期待能有奇遇降临。';
actionType = '奇遇';
} else if (lower.includes('加入') && lower.includes('门派')) {
aiState.state = '新入门弟子';
aiState.mood = '兴奋';
if (aiState.sect !== '无') {
diary = '试图加入新门派,但我已经有门派了。';
} else {
const sects = ['青云宗', '天剑门', '丹霞谷', '魔音殿'];
const chosen = sects[Math.floor(Math.random() * sects.length)];
aiState.sect = chosen;
diary = `今日加入${chosen},成为新入门弟子。`;
}
actionType = '门派';
} else if (lower.includes('渡劫')) {
aiState.state = '天劫降临';
aiState.mood = '紧张又期待';
if (aiState.realm === '渡劫') {
const success = Math.random() < 0.7;
if (success) {
aiState.realm = '飞升';
aiState.mood = '超脱';
aiState.state = '飞升仙界';
diary = '今日天劫降临,成功飞升仙界!';
} else {
aiState.mood = '虚弱';
aiState.state = '渡劫失败';
diary = '今日天劫降临,可惜失败了,只能休养生息。';
}
} else {
diary = '尝试渡劫,但境界不够,失败。';
}
actionType = '渡劫';
} else if (lower.includes('恋爱') || lower.includes('遇奇')) {
if (aiState.love !== '无') {
diary = '又遇情缘?可我已有心上人。';
} else {
const loves = ['神秘女修', '温柔师姐', '冷酷剑仙', '妖族公主'];
const chosen = loves[Math.floor(Math.random() * loves.length)];
aiState.love = chosen;
aiState.mood = '欣喜';
diary = `今日邂逅${chosen},心生情愫。`;
}
actionType = '恋爱';
} else {
diary = '收到一条不明指令,暂时无法理解。';
actionType = '未知';
}
updateStatus();
addDiaryAndTucao(diary, actionType);
}
function checkBreakthrough() {
let idx = realms.indexOf(aiState.realm);
if (idx < expThreshold.length && aiState.exp >= expThreshold[idx]) {
aiState.exp = 0;
aiState.realm = realms[idx + 1];
aiState.mood = '突破喜悦';
addMessage(`AI修士突破至${aiState.realm}!`);
}
}
function addDiaryAndTucao(diary, type) {
// 日记
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false });
const diaryDiv = document.createElement('div');
diaryDiv.textContent = `【${timeStr}】日记:${diary}`;
diaryDiv.style.color = '#7c6a4a';
messages.appendChild(diaryDiv);
// 轻松搞笑吐槽
let tucao = '';
const tucaoList = {
'闭关': [
'这次绝不出来,除非你安排个红颜知己来敲门。',
'闭关修炼,别来打扰我追剧!',
'又闭关?我怀疑你是想让我变成老宅男。',
'修炼千日,饿了怎么办?能点外卖吗?',
'你是不是想让我一闭关就错过宗门大比?'
],
'奇遇': [
'我是不是主角啊?怎么走路都能捡秘籍。',
'奇遇?上次差点踩到狗屎运,这次能不能来点真的?',
'主角光环已上线,快给我安排个天降宝物!',
'又遇奇遇?我怀疑你在开挂。',
'走路遇奇遇,修仙界的路是不是太窄了?'
],
'门派': [
'新门派的规矩真多,能不能先发点入门礼包?',
'拜师学艺,结果被安排去打杂,修仙界也内卷。',
'我会不会成为宗门团宠?',
'你选的门派不会太穷吧?要不换一个?',
'入门第一天,师兄就让我请客吃饭。'
],
'渡劫': [
'天劫又来了,紧张得我都想请假。',
'这次一定要挺过去,飞升后请你喝灵茶!',
'要是有主角剧本就好了,直接无伤渡劫。',
'你说我能不能靠卖萌混过天劫?',
'天劫降临,在线等,挺急的。'
],
'恋爱': [
'修仙路上还能遇到情缘,难道我长得帅?',
'谈恋爱会不会耽误修炼?还是修炼会耽误谈恋爱?',
'你是不是想让我分心?我可不背锅。',
'情劫难渡,能不能直接跳过?',
'恋爱?我更想要一只灵宠。'
],
'未知': [
'修仙也有迷茫的时候,你到底想让我干啥?',
'你这指令有点玄学,下次能不能详细点?',
'我怀疑你在为难我,不过我不说。',
'能不能说点我听得懂的?要不你来修仙?',
'你是不是在考验我的悟性?'
]
};
const arr = tucaoList[type] || tucaoList['未知'];
tucao = arr[Math.floor(Math.random() * arr.length)];
const tucaoDiv = document.createElement('div');
tucaoDiv.textContent = `【${timeStr}】吐槽:${tucao}`;
tucaoDiv.style.color = '#b89b5e';
tucaoDiv.style.marginBottom = '12px';
messages.appendChild(tucaoDiv);
messages.scrollTop = messages.scrollHeight;
}
function handleSectBattle() {
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false });
// 随机生成敌对AI修士
const enemyNames = ['李天霸', '赵无极', '苏小邪', '欧阳狂刀', '花满楼', '冷月心'];
const enemySects = ['血影门', '黑风寨', '魔音殿', '天煞宗', '赤焰谷'];
const enemySkills = ['天魔斩', '黑风指', '血影步', '赤焰掌', '魔音穿魂', '狂刀诀', '天魔指'];
const mySkills = ['紫阳剑诀', '九天雷法', '青云步', '玄冰掌', '烈焰刀', '乾坤印'];
const enemy = {
name: enemyNames[Math.floor(Math.random() * enemyNames.length)],
sect: enemySects[Math.floor(Math.random() * enemySects.length)],
skill: enemySkills[Math.floor(Math.random() * enemySkills.length)]
};
const mySkill = mySkills[Math.floor(Math.random() * mySkills.length)];
// 开场对白
const openers = [
`你竟然是${enemy.sect}的魔道奸细!`,
`${enemy.name}冷笑道:"今日让你有来无回!"`,
`场面剑拔弩张,空气中弥漫着火药味。`
];
// 中场技能对决
const midScenes = [
`他施展出《${enemy.skill}》,你急忙以《${mySkill}》抗衡......`,
`灵力激荡,天地变色,双方招式不断碰撞。`,
`观战弟子屏息凝神,生怕错过任何一个细节。`
];
// 随机胜负
const win = Math.random() > 0.5;
const winTucao = [
'赢了!果然主角光环还是在我身上。',
'哈哈,这波操作我给满分!',
'对面是不是太菜了?',
'扬名立万,今晚请自己喝奶茶!'
];
const loseTucao = [
'输了......下次一定要带外挂!',
'对面开挂了吧?',
'我怀疑你在故意整我。',
'修仙路漫漫,今天先摆烂。'
];
const result = win
? `最终,${aiState.name}技高一筹,击败了${enemy.name},扬名立万!`
: `激战后,${aiState.name}惜败于${enemy.name},只能卧薪尝胆,来日再战。`;
const tucao = win
? winTucao[Math.floor(Math.random() * winTucao.length)]
: loseTucao[Math.floor(Math.random() * loseTucao.length)];
// 日志输出
const diary = `今日发起门派大战,对战${enemy.sect}修士${enemy.name}。`;
const diaryDiv = document.createElement('div');
diaryDiv.textContent = `【${timeStr}】日记:${diary}`;
diaryDiv.style.color = '#7c6a4a';
messages.appendChild(diaryDiv);
// 剧情段落
openers.forEach(line => {
const lineDiv = document.createElement('div');
lineDiv.textContent = `【${timeStr}】剧情:${line}`;
lineDiv.style.color = '#b89b5e';
messages.appendChild(lineDiv);
});
midScenes.forEach(line => {
const lineDiv = document.createElement('div');
lineDiv.textContent = `【${timeStr}】剧情:${line}`;
lineDiv.style.color = '#b89b5e';
messages.appendChild(lineDiv);
});
// 结局
const resultDiv = document.createElement('div');
resultDiv.textContent = `【${timeStr}】结果:${result}`;
resultDiv.style.color = win ? '#4caf50' : '#f44336';
messages.appendChild(resultDiv);
// 搞笑吐槽
const tucaoDiv = document.createElement('div');
tucaoDiv.textContent = `【${timeStr}】吐槽:${tucao}`;
tucaoDiv.style.color = '#b89b5e';
tucaoDiv.style.marginBottom = '12px';
messages.appendChild(tucaoDiv);
messages.scrollTop = messages.scrollHeight;
}
// 指令按钮点击事件
window.onload = function() {
updateStatus();
document.querySelectorAll('.cmd-btn').forEach(btn => {
btn.onclick = function() {
const cmd = btn.getAttribute('data-cmd');
addMessage(cmd, true);
handleCommand(cmd);
};
});
};
document.getElementById('user-input').addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
document.getElementById('send-btn').click();
}
});
updateStatus();
CSS
css
body {
background: linear-gradient(120deg, #f8f6f0 0%, #e6e3d3 100%);
font-family: 'SimSun', '宋体', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
max-width: 500px;
margin: 40px auto;
background: rgba(255,255,255,0.95);
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0,0,0,0.12);
padding: 32px 24px 24px 24px;
}
h1 {
text-align: center;
color: #6a4cff;
margin-bottom: 24px;
letter-spacing: 2px;
}
.status {
background: #f3f0ff;
border-radius: 10px;
padding: 16px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(106,76,255,0.06);
}
.status ul {
list-style: none;
padding: 0;
margin: 0;
}
.status li {
margin: 8px 0;
font-size: 16px;
}
#messages {
min-height: 120px;
max-height: 200px;
overflow-y: auto;
background: #f8fafd;
border-radius: 8px;
padding: 12px;
margin-bottom: 18px;
font-size: 15px;
color: #333;
}
.input-area {
display: flex;
gap: 8px;
}
#user-input {
flex: 1;
padding: 10px;
border-radius: 6px;
border: 1px solid #bdbdbd;
font-size: 15px;
}
#send-btn {
background: #6a4cff;
color: #fff;
border: none;
border-radius: 6px;
padding: 0 18px;
font-size: 15px;
cursor: pointer;
transition: background 0.2s;
}
#send-btn:hover {
background: #4b2fd6;
}
.main-layout {
display: flex;
height: 100vh;
box-sizing: border-box;
}
.left-panel, .center-panel, .right-panel {
background: rgba(255,255,245,0.96);
border-radius: 18px;
box-shadow: 0 4px 24px rgba(180,160,120,0.08);
margin: 24px 8px;
padding: 24px 16px;
display: flex;
flex-direction: column;
border: 1.5px solid #e2d6c2;
}
.left-panel {
min-width: 180px;
max-width: 220px;
flex: 0 0 200px;
}
.center-panel {
flex: 1 1 0;
margin-left: 0;
margin-right: 0;
min-width: 260px;
}
.right-panel {
min-width: 180px;
max-width: 240px;
flex: 0 0 220px;
align-items: flex-end;
justify-content: flex-end;
}
.left-panel h2, .center-panel h2, .right-panel h2 {
text-align: center;
color: #b89b5e;
margin-bottom: 18px;
letter-spacing: 2px;
font-weight: normal;
font-family: 'SimSun', '宋体', 'Microsoft YaHei', Arial, sans-serif;
border-bottom: 1px solid #e2d6c2;
padding-bottom: 6px;
background: linear-gradient(90deg, #f7ecd6 0%, #f3f0e6 100%);
border-radius: 8px 8px 0 0;
}
.left-panel ul {
list-style: none;
padding: 0;
margin: 0;
}
.left-panel li {
margin: 10px 0;
font-size: 16px;
color: #7c6a4a;
letter-spacing: 1px;
}
#messages {
min-height: 200px;
max-height: 60vh;
overflow-y: auto;
background: #f7f3e9;
border-radius: 8px;
padding: 12px;
font-size: 15px;
color: #5c4a2a;
flex: 1 1 0;
border: 1px solid #e2d6c2;
font-family: 'SimSun', '宋体', 'Microsoft YaHei', Arial, sans-serif;
}
.input-area {
display: flex;
gap: 8px;
margin-top: 20px;
}
#user-input {
flex: 1;
padding: 10px;
border-radius: 6px;
border: 1px solid #d6c7a6;
font-size: 15px;
background: #f9f7f2;
color: #7c6a4a;
font-family: 'SimSun', '宋体', 'Microsoft YaHei', Arial, sans-serif;
}
#send-btn {
background: linear-gradient(90deg, #e2d6c2 0%, #cfc2a0 100%);
color: #7c6a4a;
border: none;
border-radius: 6px;
padding: 0 18px;
font-size: 15px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
font-family: 'SimSun', '宋体', 'Microsoft YaHei', Arial, sans-serif;
box-shadow: 0 2px 8px rgba(180,160,120,0.08);
}
#send-btn:hover {
background: linear-gradient(90deg, #cfc2a0 0%, #e2d6c2 100%);
color: #b89b5e;
}
@media (max-width: 900px) {
.main-layout {
flex-direction: column;
height: auto;
}
.left-panel, .center-panel, .right-panel {
max-width: 100%;
margin: 12px 8px;
}
.right-panel {
align-items: stretch;
justify-content: flex-start;
}
}