一张3D渲染图,展示了一个可爱的白色机器人,带有蓝色装饰和耳机,正俏皮地悬停在Stream Deck设备上方。
你的新AI游戏伙伴:一个可爱的机器人,准备在Stream Deck上挑战你,图片由Midjourney生成
你有没有那种时刻,一个随意的礼物突然引发了一场意想不到的痴迷?这就是我拿到Stream Deck时的感受——那种主播们用来播放音效和表情包的高级按钮面板。
这是我离开Hoogendoorn时收到的告别礼物,我在那里花了九年时间,将IIVO从一个雄心勃勃的想法变成了市场领先的温室自动化系统。(没错,我现在还在掐自己确认这是真的。)
大多数人可能会随便加载一些表情包就完事了。但那15个按钮,排列成完美的3x5网格,让我的大脑飞速运转。
我看到的不是一个流媒体控制器——我看到的是世界上最小的游戏机,正渴望着AI的挑战。
我的第一个想法?四子棋!但现实很快打脸:将6x7的棋盘塞进这个小设备里,就像试图把公交车停进电话亭一样不可能。
于是,我转向了它的简化版——三子棋。它快速、激烈,而且正好适合我的迷你战场。
但有趣的是,我不想只做一个普通的AI对手——我打造了三种不同的AI人格:
Sparky
首先是Sparky,我的Q-Learning新手。Sparky通过试错学习,就像一个喝了咖啡的幼儿在摸索象棋。它的走法如此不可预测,以至于几乎让人忍俊不禁。想象一只小狗追着自己的尾巴,不过是数字版的。
Deep Blue Jr
然后是**Deep Blue Jr.**,这位精于计算的杀手。它采用了蒙特卡洛树搜索(Monte Carlo Tree Search)——正是这项技术帮助机器掌握了围棋。它走棋时像是在提前三步策划你的失败(因为它确实如此)。
Socrates
最后是Socrates,这位哲学家。这个AI通过Groq的API运行Llama 3.3,它不进行数字计算,而是像一位大师在讲课一样推理游戏。这是一种不同的方法,结果同样引人入胜。
这个原本只是告别礼物的东西,变成了我个人的AI格斗俱乐部。我目睹了这些算法之间的对决,庆祝了意外的胜利,也为它们越来越聪明的策略感到谦卑。
在我们深入探讨之前,先来认识一下我们的AI挑战者。我打造了三种独特的人格,每种都有其独特的风格。我们将一一介绍,首先从最不靠谱的那个开始:Sparky。
认识Sparky:通过失败学习的AI
还记得我之前提到的新手AI吗?让我正式介绍一下这个Q-learning代理。
我之前已经成功地在井字棋上训练过Q-learning,觉得三子棋应该小菜一碟。剧透警告:我大错特错了。
想象一下,Q-learning就像是通过反复烤焦吐司来学习烹饪,直到你终于知道如何不触发烟雾报警器。我让AI与自己对战无数次,以加快学习速度。
想象两个热情但一窍不通的幼儿互相教对方踢足球——混乱、娱乐,偶尔还会有意外的进球。
最初,结果看起来不错——它的胜率稳步上升。但这些令人印象深刻的统计数据背后,隐藏着一些令人捧腹的糟糕决策。有时,它会巧妙地阻止你的胜利;下一秒,它又会把胜利拱手相让。
经过🔥20,000轮训练,我们学到了以下几点:
AI记住了7,000种独特的游戏状态——记忆力惊人,应用却令人质疑 它对随机走法的胜率达到了90%——但仍然会掉进显而易见的陷阱
让我给你展示一下实际情况。看看这些图表:
Q-learning AI训练进展:混乱的奖励、上升的胜率和扩展的记忆。纸上谈兵很厉害——直到你看到它实战。图片由作者提供。
奖励图表中的蓝色尖峰?那是我们的AI在"顿悟"时刻,接着是长时间的"等等,我刚才在干嘛?"稳步上升的胜率看起来很棒,直到你意识到它主要是在与自己复制品对战——就像宣布自己是袜子拳击的世界冠军。
但正是这种不可预测性让这个AI变得可爱。有时,它会走出一些聪明的棋步,让你以为它是个隐藏的大师。其他时候……嗯,只能说它以一种我们从未预料到的方式让游戏变得有趣。
想看看这种可爱的混乱吗?这里有一段我和这个数字新手对战的视频:
我和Sparky对战的画面,我们的Q Learning AI代理,图片由作者提供。
==注意到它有时能走出明显的妙招,有时却完全摸不着头脑吗?这就像用微波炉教人做饭;他们确实能加热食物,但Gordon Ramsay短期内不会打电话来。==
看着Sparky在比赛中跌跌撞撞很有趣——但我渴望更聪明的对手。如果我们有一个不仅能从失败中学习,还能像国际象棋大师一样预判你走法的AI会怎样?
接下来是Deep Blue Jr.,它采用了蒙特卡洛树搜索——这位冷酷的策略家,我从未想过自己会需要它。
Deep Blue Junior:三子棋的无敌大师
看着我们可爱的Q-learning新手跌跌撞撞后,是时候请出重量级选手了。
蒙特卡洛树搜索(MCTS)——这项技术帮助AlphaGo震惊世界,击败了人类围棋冠军。
MCTS的独特之处在于:当Q-learning在游戏中跌跌撞撞,希望通过错误学习时,MCTS就像一位能预见所有可能未来的国际象棋大师。
对于每一步棋,它都会模拟数千种可能的游戏结果。想象一下,和一个已经看到你所有可能手牌的对手打扑克——这就是面对这个AI的感觉。
更令人兴奋的是,与象棋或围棋不同,三子棋是可解的。由于游戏棋盘非常紧凑,像MCTS这样的算法可以探索所有可能的走法序列。因此,它不仅玩得好——它玩得完美。
当它先手时,几乎不可能被击败。与它对战感觉不像是在玩游戏,而像是在和爱因斯坦争论物理:理论上你可以试试,但你知道结果会怎样。
不相信我?让我展示一场典型的比赛:
蒙特卡洛树搜索实战:AI先手,游戏感觉像是被操纵了,图片由作者提供。
看到第一步在中间了吗?这不仅是一步好棋——它还是你必然失败的开始。==你的每一步回应都已经被计算过,每一个反击策略都已被预料。==
Q-learning可能是你那个偶尔会走出妙招的笨拙朋友。MCTS则是那个冷酷的策略家,它在你开始游戏之前就已经规划好了所有胜利的路径。它令人着迷、印象深刻,也有一点吓人。
想体验与一个从不犯错的AI对战的感觉吗?那么MCTS就是你新的"友敌"。
或者,如果你更喜欢一个更……不可预测的对手,那就继续和我们可爱的Q-learning新手Sparky玩吧。它还在学习!
当然,Deep Blue Jr.的完美计算令人印象深刻,但它感觉有点……机械化。我想要一个更有个性、更不可预测的玩家——一个能够用类似人类的洞察力甚至类似人类的错误来让我惊讶的数字伙伴。
接下来是Socrates:一位哲学AI对手,它不仅计算走法,还会推理走法,有时结果出人意料——甚至有时会犯错。
认识Socrates:思考三子棋的AI
在与我们那个可爱但糊涂的Q-learning新手对战,并屡次被MCTS大师碾压之后,我思考:我们能创造一个更……深思熟虑的AI吗?更像是一个在思考下一步走法的朋友,而不是一个计算器?
于是,Groq登场了。他们制造了专门的AI芯片,称为语言处理器单元(LPUs),运行大型语言模型的速度比传统硬件快几个数量级。
我决定使用他们的llama-3.3–70b-versatile模型,它拥有🔥700亿个参数。这就像为我们的三子棋游戏装上了火箭引擎。最棒的是?你仍然可以免费访问Groq的API,并试验这些强大的模型。但我不是用它来计算数字,而是希望它能像人类一样思考游戏。
有趣的是,虽然我们的前两个AI将游戏视为一个复杂的数学问题,但这个AI将棋盘状态作为简单的文本描述:
当前棋盘状态: . . X . . O X O . . X O . . .
然后它做了一件有趣的事——它几乎像人类一样思考游戏。我们给它的提示非常简单:
你正在玩一个3x5网格的三子棋游戏。 你需要横向、纵向或对角连成三子才能获胜。 当前棋盘状态(X = 玩家1,O = 玩家2,. = 空): . . X . . O X O . . X O . . . 你正在扮演O。列从左到右编号为0-4。 仔细分析棋盘,并仅返回一个数字(0-4) 表示你想要放置棋子的列。 选择最佳策略走法。 如果一列已满,你不能在那里放置棋子。
我们告诉它:"你需要横向、纵向或对角连成三子才能获胜,"并要求它分析棋盘并选择最佳策略走法。
以下是LLM在不同棋盘场景下的原始响应示例:
为了做出最佳走法,让我们分析当前棋盘状态: . . . . . . . O . . . . X X . 玩家1(X)已经有两个X连成一行,这意味着如果他们再得到一个X,他们可能会获胜。 然而,由于我们扮演的是O,我们的目标是阻止X获胜,并为自己创造获胜的机会。 在当前状态下,将O放在第2列是一个不错的策略走法,因为它会阻止X连成三子,并为我们创造对角线或纵向连线的机会。 因此,最佳走法是将O放在第2列。 2
这个对手的独特之处在于:
自然语言策略:它不是通过试错或计算走法树来学习,而是通过语言理解游戏。它可以"看到"整个棋盘,并像你向朋友解释走法一样进行策略性推理。 类人决策:每一步都来自实际的战略思考,而不是纯粹的计算。有时它防守,有时它设下你一时难以察觉的陷阱。 出人意料的创造力:虽然我们的MCTS大师可能会找到数学上完美的走法,但这个AI偶尔会想出几乎……灵光乍现的策略。它可能会发现其他AI会忽略的模式,仅仅因为它能"思考"游戏的高层次意义。
想象一下,和你那位刚刚喝了两杯酒的聪明朋友对战——深思熟虑且策略性强,但也会有"等等,我没想到会这样"的时刻。
==这里是我和Socrates对战的一个示例游戏。==
我和Socrates对战的画面,LLM AI代理,图片由作者提供。
结果呢?一个不仅仅是玩三子棋的AI对手——它感觉像是欣赏游戏。它不会总是像MCTS那样无情,也不会像Sparky那样混乱,但它仍然会在你意想不到的时候摧毁你。
AI训练环境的内幕
每个AI背后都有一个训练场——一个数字游乐场,算法在这里锻炼肌肉、犯错并(希望)从中学习。
对于我们的Q-learning代理Sparky来说,这是一个独立的基于Python的训练环境,与Stream Deck插件本身的Node.js/TypeScript代码库分开。
你可以在GitHub仓库中找到这个训练环境作为一个单独的项目。
以下是我如何设置Q-learning训练环境的:
训练管道简化:Python自我对战生成Q-table,直接插入TypeScript,图片由作者提供。
环境和代理都是训练过程的一部分。
环境 (**environment.py**
):这是游戏的裁判。它执行规则,评估棋盘,并立即给AI反馈——比如奖励好棋或惩罚非法走法。
环境的核心是step()
函数,控制每次AI走棋时游戏的反应。以下是它的内部结构:
def step(self, action):
# 检查走法是否有效
if not self._is_valid_action(action):
return self._get_state(), -10, self.done, {"invalid_move": True}
# 放下棋子并记录之前的状态值
self.previous_player_state[self.current_player] = self._calculate_state_value(self.current_player)
row = self._get_next_open_row(action)
self.board[row][action] = self.current_player
# 检查胜负并分配奖励
if self._check_win():
self.done = True
self.winner = self.current_player
reward = 1.0 if self.current_player == 1 else -1.0
return self._get_state(), reward, self.done, {"winner": self.current_player}
# 对非终局走法的小额负奖励
reward = -0.05
# 为良好位置添加中间奖励
if self.intermediate_rewards:
current_state_value = self._calculate_state_value(self.current_player)
previous_state_value = self.previous_player_state[self.current_player] or 0
state_improvement = current_state_value - previous_state_value
if state_improvement > 0:
reward += 0.1 * state_improvement
代理 (**agent.py**
):学习者,一个尝试所有可能走法的数字玩家,逐渐完善其决策。想象一个人通过输掉几千盘棋来学习国际象棋——痛苦,但有效。
魔法发生在自我对战训练中。AI反复与自己对战,更新一个巨大的参考表("Q-table"),存储每个走法的战略价值。训练完成后,这个Q-table,一个包含游戏状态及其学习结果的大集合,被导出为JSON文件。
为了让这个Q-table在Stream Deck插件的TypeScript环境中可用,我使用了一个名为convert.py
的脚本将JSON数据转换为TypeScript变量。
我最初探索在运行时直接加载JSON文件,但遇到了Stream Deck插件环境处理外部文件的困难。将Q-table嵌入为TypeScript变量在插件的限制下提供了更可靠和高效的解决方案。
现在,Q-learning AI已经准备好在你的Stream Deck上玩耍——并且搞笑地输掉比赛。
以下是驱动学习过程的代码片段:
def update(self, state, action, reward, next_state, next_valid_actions):
# 如果需要,初始化Q值
if state not in self.q_table:
self.q_table[state] = {a: 0.0 for a in range(5)}
# 获取下一个状态的最大Q值
max_next_q = 0.0
if next_valid_actions:
max_next_q = max(self.q_table[next_state].get(a, 0.0) for a in next_valid_actions)
# Q-learning更新公式
current_q = self.q_table[state].get(action, 0.0)
new_q = current_q + self.learning_rate * (reward + self.discount_factor * max_next_q - current_q)
self.q_table[state][action] = new_q
把这个公式想象成AI的作弊表。每次走棋后,代理会重新评估棋盘,调整其预测,并(希望)学到一些有用的东西——比如意识到把胜利拱手让给对手不是个好主意。
就是这样:一个将混乱转化为(稍微不那么混乱的)游戏的管道。
我们的发现
代码揭示了一些有趣的见解:
状态表示非常重要——我们使用一个简单的字符串,如"000120000100200",其中每个数字代表一个单元格状态。 战略知识加速学习——通过内置模式识别,我们大幅减少了训练时间并提高了质量。 自我对战创造了一个移动的目标——随着我们的代理改进,它的对手(每500场游戏更新一次)也在改进,推动了持续学习。 平衡探索与利用至关重要——从100%随机走法开始,逐渐专注于学习到的策略是成功的关键。
想看完整代码?查看GitHub上的完整项目,你可以探索我们所有AI对手的每一个细节。
Stream Deck插件架构
在我们深入代码之前,让我展示一下插件部分的蓝图。我已经画出了所有部分如何协同工作——想象一下这是我们数字游乐场的鸟瞰图
DeckDrop的插件架构,图片由作者提供。
看起来不错吧?让我们分解一下这张图,因为它讲述了我们游戏如何诞生的完整故事。
Stream Deck插件是如何工作的
首先,让我们了解一些基础知识。你的Stream Deck基本上是一个按钮的画布,每个按钮都可以运行所谓的"动作"。可以把动作看作是通过Stream Deck软件分配给任何按钮的小程序。
在我们的案例中,我们创建了一个名为DeckDrop的动作——这就是我们的整个游戏!注意,"DeckDrop"也是整个项目和GitHub仓库的名字,但在这里,我们特指Stream Deck动作。
但有趣的是,我们希望这个游戏覆盖你Stream Deck上的所有15个按钮。
这就是"配置文件"(Profiles)的用武之地。配置文件就像一个预设布局,告诉每个按钮该做什么。我们创建了一个配置文件,将整个Stream Deck填满我们的游戏,从一个空棋盘开始,准备迎接挑战。
让我们进一步分解成更容易理解的小部分。
1. Stream Deck接口(右上角)
插件入口(plugin.ts):想象这是我们的游戏与Stream Deck的握手。它说"嘿,我们是一个游戏,这就是我们的工作方式!"。在这个文件中,我们注册了我们的DeckDropGame动作。
import streamDeck, { LogLevel } from "@elgato/streamdeck";
import { DeckDropGame } from "./actions/deck-drop-game";
// 我们可以启用"info"日志记录,这样所有Stream Deck和插件之间的消息都会被记录下来。
streamDeck.logger.setLevel(LogLevel.INFO);
streamDeck.actions.registerAction(new DeckDropGame());
// 连接到Stream Deck。
streamDeck.connect();
// 在这里导出你的动作
export const actions = [
DeckDropGame
];
Deck Drop Game(deck-drop-game.ts):这是我们游戏的指挥家,确保所有部分协同工作。它处理按钮按下、游戏状态更新、玩家交互和AI走法协调。
这里有几个有趣的地方,因为我们基本上有一个游戏和多个相同动作的实例。
首先是onWillAppear
事件,它发生在动作出现在Stream Deck上时。所以在我们5x3的棋盘上,它会被调用15次。当被调用时,我们希望存储对动作及其位置的引用。我们使用这些信息来检查用户将棋子放在哪一列(按下按钮)。
此外,当你在棋盘上放置DeckDrop动作时,我们会加载DeckDrop配置文件。我们只做一次。
override async onWillAppear(ev: WillAppearEvent<GameSettings>): Promise<void> {
// 如果动作有坐标,将其引用存储在我们的查找表中
if (ev.action.coordinates) {
const col = ev.action.coordinates.column;
const row = ev.action.coordinates.row;
const key = this.getCoordinateKey(row, col);
this.actionLookup.set(key, ev.action);
streamDeck.logger.info(`Stored action at coordinates [${row}, ${col}]`);
// 设置初始图像
await this.renderer.setButtonImage(row, col, EMPTY_SLOT_IMAGE);
}
// 如果我们还没有切换配置文件,切换到DeckDrop配置文件
if (!this.hasProfileSwitched) {
try {
await streamDeck.profiles.switchToProfile(ev.action.device.id, "DeckDrop");
this.hasProfileSwitched = true;
streamDeck.logger.info('Switched to DeckDrop profile');
} catch (error) {
streamDeck.logger.error(`Failed to switch profile: ${error}`);
}
}
// 渲染棋盘
await this.renderer.renderBoard(this.gameLogic.getBoard());
}
但玩家真正走棋时会发生什么呢?这就是keydown事件的用武之地——它是我们游戏交互的核心。
让我们分解一下当你点击按钮时会发生什么:
override async onKeyDown(ev: KeyDownEvent<GameSettings>): Promise<void> {
// 只有在有坐标时才继续
if (ev.action.coordinates) {
// 如果按下的按钮在顶行,放下棋子
if (ev.action.coordinates.row === 0) {
const column = ev.action.coordinates.column;
const moveResult = this.gameLogic.makeMove(column, this.renderer.showWinner.bind(this.renderer));
// 走棋后显式渲染棋盘
await this.renderer.renderBoard(this.gameLogic.getBoard());
// 如果走棋成功,检查是否需要重置
if (moveResult) {
this.scheduleGameReset();
}
} else {
// 按钮在较低行,显示有效走法指示器
streamDeck.logger.info('Button in lower row pressed, showing valid move indicators');
await this.showValidMoveIndicators();
}
} else {
streamDeck.logger.info('Button has no coordinates, no action taken');
}
}
这可能看起来很多,但它实际上做了一些非常聪明的事情。我们不仅处理按钮按下,还将Stream Deck变成了一个智能游戏界面。
当你点击任何按钮时,游戏立即知道你瞄准的是哪一列。点击顶行进行走棋,或点击较低行的按钮查看你可以走棋的位置。棋盘立即更新,AI走棋,游戏流畅进行。
让我们看看另一个关键部分:我们如何处理AI的走法。
2. AI组件(左上角)
听过"让你的朋友更近,让你的AI策略更近"这句话吗?
这正是我们在这里所做的。这个列表展示了项目中每个人格的位置。我们有三种不同的AI实现:
Sparky: 在ai-opponent.ts中实现。这个文件包含使用预训练Q-Table(从Python训练环境转换而来)进行决策的代码。这是我们的Q-learning AI。 Deep Blue Jr.: 在mcts-opponent.ts中实现。这个文件包含蒙特卡洛树搜索算法,负责Deep Blue Jr.的战略游戏。 Socrates: 在groq-opponent.ts中实现。这个文件包含与Groq API交互并利用Llama 3.3模型进行Socrates推理游戏的逻辑。
所有这三个都通过策略模式插入到我们的AI对手中——这是一种高级说法,我们可以在不打扰游戏其他部分的情况下热插拔我们的AI。
想练习对抗可爱的新手?从这里开始。准备好迎接残酷的挑战?切换到MCTS大师。想要一些深思熟虑的游戏?LLM在等着你。
你甚至可以让它们互相对战!
DeckDrop插件配置界面,图片由作者提供。
3. 游戏组件(底部)
这是真正实现功能的地方:
游戏逻辑:我们虚拟游乐场的裁判。它 – 跟踪轮到谁 – 确保没有人作弊 – 在AI和界面之间协调 – 管理游戏状态(想象这是游戏的记忆)
胜利检查器:我们的观察者,发现每一个潜在的胜利。它不断扫描那些三连的时刻,这些时刻决定了游戏的胜负。 游戏渲染器:我们操作中的艺术家。它将无聊的数字和状态变成你在Stream Deck上看到的漂亮按钮。那些让你感到满足的胜利动画?这都是这个组件的功劳。
实现指南
想试试这个或做你自己的调整吗?以下是如何开始!
设置
首先,你需要:
Stream Deck MK.2硬件或模拟器 Node.js(版本20或更新) Elgato的Stream Deck应用程序 你习惯的代码编辑器(我用的是VS Code)
快速开始
获取代码:
git clone https://github.com/PatrickKalkman/deckdrop
cd deckdrop/deckdrop
npm install
启动开发服务器:
npm run watch
告诉Stream Deck你的插件:
打开Stream Deck应用程序"配置Stream Deck" 将DeckDrop动作拖到Stream Deck上
就是这样!它会要求你安装配置文件,然后你应该会看到游戏棋盘出现在你的Stream Deck上。
要使用Socrates作为对手,你需要一个免费的API密钥来自Groq。API密钥可以输入到DeckDrop的Groq API密钥字段中。
需要帮助?
如果有什么不工作:
检查Stream Deck日志(它们在应用程序的设置中) 确保你的Stream Deck软件是最新的
记住,这一切都是为了好玩。打破它,实验,看看你能创造什么!
结论
谁会想到一个被塞进Stream Deck的迷你四子棋游戏能教会我们这么多?事实证明,看着AI在游戏中跌跌撞撞既有趣又发人深省。
当然,这些算法在纸面上看起来很厉害,但等你看到它们走出那些让你质疑一切关于人工智能的走法时,你会觉得更好笑。
我学到的是:有时,最好的想法来自于在奇怪的限制下工作。虽然我的AI可能会吹嘘自己赢了90%的游戏,但别被它骗了——它仍然会走出让你抓狂的走法。这是一个谦卑的提醒,即使令人印象深刻的统计数据也不能说明全部。
好奇吗?试试看。挑战Sparky的混乱,挑战Deep Blue Jr.的完美精确,或者与Socrates进行一场关于策略的深思熟虑的对话。
下载它,调整它,打破它——甚至重新发明它!
专注搞钱与健康 | "欢迎关注、点赞、分享"
爆料 / 交流 / 合作:请加微信 ifloop
加入副业交流群:请加微信 ifloop(备注加群)
推荐阅读
没有评论:
发表评论