当猜数字游戏遇见飞书机器人
一个人的时候,有时会想写点小东西——不是为了有用,只是为了有趣。昨天晚上,我就在飞书上让 Hermes Agent 给我写了一个猜数字游戏。
玩法
系统随机生成一个四位不重复数字(0-9),你来猜。每次猜测,系统会告诉你两个数字:
- A = 数字正确且位置正确
- B = 数字正确但位置不对
比如秘密数是 1284,你猜 1234,结果是 2A2B——两个数字(1 和 4)位置正确,两个数字(2 和 3)数字对了但位置不对。
直到猜出 4A0B,游戏结束。
从终端到飞书
一开始它写的是一个终端交互版——用 input() 接收猜测,在终端里 print() 结果。这当然没问题,但我想在飞书聊天里直接玩,不需要 SSH 进去。
所以改了一版:把游戏逻辑模块化,用 JSON 文件持久化状态(秘密数和尝试次数)。这样每次你在飞书发一条消息,Hermes 就调一次 Python 脚本,读状态、做判断、写回状态、回复结果。整个过程对玩家来说是无感的,就像在和一个真人裁判对话。
源码
完整的游戏脚本,不到 60 行:
#!/usr/bin/env python3
"""猜数字游戏 — 飞书/聊天交互版(文件持久化)"""
import json
import os
import random
STATE_FILE = "/tmp/guess_number_state.json"
def _load_state() -> dict:
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
return json.load(f)
return {"secret": None, "attempts": 0}
def _save_state(state: dict):
with open(STATE_FILE, "w") as f:
json.dump(state, f)
def is_valid(guess: str) -> bool:
return len(guess) == 4 and guess.isdigit() and len(set(guess)) == 4
def start() -> str:
secret = "".join(random.sample("0123456789", 4))
_save_state({"secret": secret, "attempts": 0})
return (
"🎯 猜数字游戏开始!\n"
"规则:我已生成一个四位不重复数字(0-9)。\n"
"- A = 数字对且位置对\n"
"- B = 数字对但位置不对\n\n"
"直接输入你的猜测(4位数字)即可开始!"
)
def guess(guess_str: str) -> str:
state = _load_state()
secret = state.get("secret")
if secret is None:
return "游戏还没开始!请说「开始游戏」"
if not is_valid(guess_str):
return "输入错误!请输入4位不重复数字(0-9)。例如:1234"
a = sum(g == s for g, s in zip(guess_str, secret))
b = sum(g in secret for g in guess_str) - a
state["attempts"] += 1
if a == 4:
msg = (
f"🎉 恭喜!猜对了!\n"
f"答案:{secret}\n"
f"总共猜了 {state['attempts']} 次!\n"
f"说「再来一局」重新开始"
)
_save_state({"secret": None, "attempts": 0})
return msg
_save_state(state)
return f"结果:{a}A{b}B(第 {state['attempts']} 次)"
核心逻辑其实只有 evaluate 那一小段——sum(g == s ...) 算 A,sum(g in secret ...) - a 算 B。干净利落。
实战:1284 的四次对峙
游戏开始了。我并不知道秘密数是什么,只能靠每一次的反馈去逼近。
第一猜:4637
0A1B
四个数字只有一个存在,而且位置也不对。信息量很少,但至少排除了三个数字。
第二猜:1289
3A0B
这一下子从迷雾中走了出来。三个数字完全正确,且位置也对。唯一的遗憾是最后一位猜错了——但这也说明,那三个数字(1、2、8)已经锁定了。只是最后一位不是 9。
接下来就是一场耐心的排除。剩下那位,只可能从 {0,3,4,5,6,7} 里选。
第三猜:1287
3A0B
不是 7。
第四猜:1283
3A0B
不是 3。
第五猜:1284
🎉 恭喜!猜对了!答案:1284,总共猜了 5 次!
那一刻的感觉,就像终于拨开最后一层迷雾,看到了山顶的风景。五次,不多不少。从第二次猜中前三位开始,后面三次不过是在逐一验证那最后一个数字——用最小的代价,完成了最后的确认。
一点感想
猜数字的魅力就在于,它既需要逻辑推理,又保留了一点运气成分。你永远无法一步到位,但每一步都在缩小答案的可能空间。这种「越猜越近」的感觉,和调试代码时的二分查找、逐步缩小 bug 范围,本质上是同一种思维方式。
而把它搬到飞书机器人上之后,又多了一层趣味——像是多了一个随时陪你玩游戏的朋友。
如果你也想玩,说一句「开始游戏」就行。😄