基于AIUI开发最小智能体,装进Rokid眼镜抛硬币
一、前言
我这个人有点选择困难,中午吃面还是吃米饭都会纠结的一个人。对于我这种选择困难症的人来说,抛硬币来抉择就是一个很好的选择,并且还带些许的趣味性。不过现如今快节奏的生活,能够随身携带硬币的人已经不多了,不过我想将抛硬币这个简单的动作放到Rokid眼镜上,因为AIUI平台的强大完全可以实现这么一个简单的功能。
所以我在Rokid AIUI Studio上写了"抛硬币"这个智能体:调起页面,圆环里一个64像素的大字,正面或者反面,下面记着本次打开的正反次数,小小的智能体满载趣味性和随机性,这就是我要做的“抛硬币”。
二、背景与选题
AIUI Studio的“抛硬币”智能体打开后由用户点「抛一次」或按确认键才随机出正反,「清零」把计数归零;第三句定行为边界,结果只显示在屏幕上,不语音播报,不写入存储;选抛硬币,是因为它是"随机需求"的最简形态:一句话调起,两个结果,看一眼就结束。题小,逻辑也少,但不缺乏趣味性,每一行都能讲清楚来历。
三、需求取舍
动手前我砍了一晚上功能,砍到最后剩两个按钮。不播报——抛硬币往往是两个人僵持的时候,环境安静,眼镜突然出声很尴尬,而且一加播报就得要TTS,链条越拉越长。不存历史——抛硬币要的是当下一个数,翻历史只会让人想"上次连出三个正,这次该反了吧",那就违背抛硬币了。不接文本参数——最重要的一条,一个"抛硬币智能体"如果声称能预判结果,那它就坏了,随机这件事页面说了算,宿主不该插嘴。
剩下的就是:抛一次,清零,顺带一个本次打开期间的计数,每行都能验证,我心里舒服。
四、界面与状态
界面是黑底荧光绿。中央一个圆环,132乘132像素,荧光绿描边,圆心一个字。页面顶部写"抛硬币",右上角一行小字"本机随机",圆环下面依次是结果行、计数行、两个按钮「抛一次」和「清零」、最底下一条提示。
整个页面一共四个状态。刚打开,圆里是个问号,结果行写着"按确认或点「抛一次」",计数行是"正 0 · 反 0 · 共 0",默认焦点落在「抛一次」上:

连续抛了几次,然后又抛出来是正面,圆里换成"正"字,结果行"正面",计数加一,比分"正 7 · 反 7 · 共 14":

切到「清零」,这时候清零的按钮是高亮状态,点击后问号回到圆里,结果行换成"已清零,再抛一次",计数归零,清零后的屏幕和第一屏一模一样。

点击清零后再抛一次出反面,"反"字上圆,计数变成"正 0 · 反 1 · 共 1":

样式数字全是写死的:荧光绿#40FF5E当主色;圆环3像素描边、66像素圆角,内圈垫一圈6像素深绿内阴影,做出"硬币嵌在屏幕里"的层次;圆心大字64像素加粗,结果行20像素,计数行15像素用亮一档的浅绿,注释和底部提示12像素用暗一档的深绿。两个按钮各219乘40像素。整个页面就三档绿色,信息层级全靠字号和颜色深浅撑。页面data一共六个字段,按钮列表是每次渲染时根据焦点位置现算的,焦点框加不加、按钮字亮不亮,都在渲染函数里拼好再一次性设回页面。
五、交互设计
触屏和键盘各走各的路,但都通向同一个焦点系统。触屏点按钮,先落焦点、再触发动作,任何一次点按之后屏幕上都有个焦点位置。键盘这边,确认键在焦点按钮上就触发它,方向键换焦点。提示行写的是"Enter 抛一次 · 上下键切换焦点",这里我老实交代一个出入:提示只提了上下键,代码里其实四个方向键都认。写提示的时候保守了,做功能的时候放宽了,宁可这样,也别反过来。
两条路最后汇进同一个分发口,代码原样抄在这:
onActionTap(event) {
const index = Number(event && event.currentTarget && event.currentTarget.dataset && event.currentTarget.dataset.index);
if (!Number.isInteger(index) || index < 0 || index >= this.data.actions.length) return;
this._focus = index;
this.render();
this.activate();
},
activate() {
if (this._focus === 0) this.tossOnce();
else this.clearTally();
}
onActionTap第一行那一串取值是在防事件对象残缺:dataset不存在就得到NaN,Number.isInteger一筛,这次点按直接作废,页面不炸。activate只认一个事实:焦点是不是落在第一个按钮上,是就抛,不是就清零。分发口两边接的是两个小函数,也是全部动作:
tossOnce() {
const result = toss();
this._face = result.face;
this._label = result.label;
this._tally = countToss(this._tally, result.side);
this.render();
},
clearTally() {
this._face = EMPTY_FACE;
this._label = CLEAR_LABEL;
this._tally = emptyTally();
this.render();
}
抛一次:拿结果、存三个私有字段、刷新;清零:三个字段回到初始常量、刷新。没有第三个动作。还有两个小决定:按键长按连发忽略掉,按住Enter不会噼里啪啦连抛;没有"正在抛"的中间态,随机本来就是一瞬间的事,加个动画停顿纯粹是演戏,我不演。
六、核心逻辑
coin.js满打满算三十九行。常量五个:正反两个词冻结在SIDES里;EMPTY_FACE是个问号;EMPTY_LABEL"按确认或点「抛一次」";CLEAR_LABEL"已清零,再抛一次";COIN_NOTE"本机随机 · 不出声 · 不保存"。页面代码里不出现一个裸写的文案字符串,要改口径只动这一处。
核心是toss,原文抄在这:
export function toss(random = Math.random) {
const value = Number(random());
const side = Number.isFinite(value) && value < 0.5 ? 'heads' : 'tails';
return {
side,
face: side === 'heads' ? '正' : '反',
label: SIDES[side]
};
}
随机源是参数:默认拿全局的Math.random,但可以把任意函数注进去——就为这一行参数,测试省了大劲。注意头两行的保守写法:先把返回值包一层Number,再用Number.isFinite筛一遍,小于0.5记正面,其余一律记反面,这不是多余,后面验证那节有针对性实测。side只可能是heads或tails,词表在常量里就冻死了,结构上不存在出现第三个字的出口。
计数函数也贴原文:
export function countToss(tally, side) {
const next = {
heads: Number(tally && tally.heads) || 0,
tails: Number(tally && tally.tails) || 0
};
if (side === 'heads') next.heads += 1;
else if (side === 'tails') next.tails += 1;
return next;
}
不修改传进来的旧对象,每次新建一个;开头两行做归一,就算传进来的计数是残缺的甚至空的,Number转换加兜底会先生成一份干净的0再往上加。计数对象只有正、反两个字段,"共N"是tallyText每次现场把两个数加出来的,一份数据一处真相。
最后是app.json里那道红线,加上空schema、空permissions,三面一起上:宿主无话可传、无权可调、且被明令禁止。随机只从Math.random一处来,这是这个项目的全部严肃性所在。
七、验证情况
我能自己跑的验证有四层。第一层,语法和加载,模块和页面脚本用Node直接加载跑通。第二层,注入随机源做边界测试:手造八个返回值喂给toss,0、0.1、0.49、0.4999999四发全是正面,0.5、0.5000001、0.9、1四发全是反面;又加喂了返回NaN的和返回字符串的,都稳稳落在反面——Number包装加isFinite守卫起作用了,随机源再离谱,页面拿到的也是一个确定的结果。第三层,页面状态:四张状态截图都来自AIUI Studio平台的模拟验证阶段,该平台提供完整的从开发到调试再到发布的一套流程,即便是不懂代码的小白,也可以自己提需求让AIUI生成智能体,就像跟豆包对话一样的交互就能做出自己想要的效果,这也太惊艳了,真心呼召感兴趣的小伙伴一定要亲自尝试一下用AIUI开发智能体的爽感...
八、结语
通过这次借助AIUI平台开发智能体的过程,体验真的很不错,做出了属于自己的智能体并且能够通过审核并成功上架,成就感满满。不过我的“抛硬币”还有些许的不足:如没有震动反馈;图标是个简笔画圆环,不出错但也谈不上好看,如果近似于硬币的形象进行展示的话会更加具有可玩性。
抛硬币是我写的特别小的智能体,小到几十行代码就能讲完。写完之后我反而更确定一件事:这个项目真正要守住的不是功能,是"不可预测"本身,随机只从一处来,抛出去的正反谁说了都不算。感兴趣的朋友快来体验吧~
更多推荐



所有评论(0)