183M 决策模型能玩数独吗:把 Quyet-1.0-Tiny 接进微软数独,结果是 29.8% 对随机 42.6%
前两篇分别写了 Quyet-1.0-Tiny 的下载排障和冒烟测试。既然权重已经躺在
D:\models\Quyet-1.0-Tiny,那就来点真的:让它玩一局正在运行的 Microsoft Sudoku。我给它的不是"生成一段答案",而是完整的决策链路:抓屏 → 识别盘面 → 算候选 → 模型选一个 → 真的点进游戏。
结论不好看但很有信息量:在合法候选里做选择,它的准确率是 29.8%,而随机瞎选是 42.6%——比瞎猜还差。
先说结论
| 问题 | 结论 |
|---|---|
| 接进真游戏了吗 | 接进去了。教学关三步 + 正式局两步都由程序真的点了进去,游戏接受并推进 |
| 读盘怎么做的 | 不靠 OCR:数字模板匹配,用一局已知答案标定,实测 81/81 全对(最低匹配度 0.66) |
| 约束模式(只在合法候选里选) | 14/47 = 29.8%,随机基线 42.6%——低于瞎猜 |
| 换一版提示词 | 20/47 = 42.6%,随机基线 42.6%——正好等于瞎猜 |
| 原始模式(1-9 里直接选) | 3/51 = 5.9%,随机 13.7%,合法率只有 27.5%——连行内重复都不管 |
| 置信度可信吗 | 不可信。平均 0.45,最自信的一次(0.676)恰好答错 |
| 它有偏好吗 | 有。明显偏爱 9 / 4 / 7,是"数字先验"而不是约束推理 |
| 一句话 | 决策模型不是求解器:它会在你给的选项里挑一个像样的,但不会替你做逻辑推演 |
一、为什么非要接进真游戏
离线跑个盘面谁都会,但"能出概率"和"能在真实交互里落子"是两件事。我想验证的是整条闭环能不能成立:
┌──────────────┐ PrintWindow ┌───────────────┐ 模板匹配 ┌─────────────┐
│ Microsoft │ ─────────────► │ 截图 (numpy) │ ─────────► │ 81 格局面文本│
│ Sudoku (UWP) │ └───────────────┘ └──────┬──────┘
└──────▲───────┘ │
│ 鼠标点击(带验证重试) ▼
│ ┌────────────────────────┐
└──────────────────────────────────────────────│ Quyet-1.0-Tiny 决策模型 │
│ state + 候选 → 概率分布 │
└────────────────────────┘
这条链路上每一环都可能断:抓屏抓不到、盘面读错、模型答非所问、点击落空。只有真跑通,结论才作数。
二、抓屏:怎么抓一个"可能被挡住"的游戏窗口
UWP 游戏窗口被别的窗口盖住时,用 CopyFromScreen 抓到的是上面那个窗口。正确做法是 PrintWindow + PW_RENDERFULLCONTENT:
user32.PrintWindow(hwnd, memdc, 0x00000002) # 直接让窗口把内容画进内存 DC
配合三点:
- 进程必须 DPI 感知(
SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2))。否则位图按逻辑像素分配,而窗口按物理像素渲染,抓到的图只有左上角一块——我一开始就栽在这,抓出来 1400×1120 的图里只有 80% 的内容。 - 窗口不能被最小化。UWP 在不可见/最小化时会挂起渲染,
PrintWindow直接返回纯白。 - 多屏会变。实验中途副屏被拔掉,窗口坐标瞬间作废、内容变成白图。工具里因此加了
restore():恢复 + 固定窗口尺寸再抓。
三、读盘:不写死坐标,用棋盘自己的线
最省事的做法是把格子坐标写死。但游戏窗口一改大小,布局不是等比缩放的(1938 宽时棋盘在 x=283,1400 宽时跑到 x=52),写死必翻车。
所以改成从图里找棋盘:
- 实测棋盘线是深蓝 RGB(8,43,107),而顶部工具栏是亮蓝 RGB(0,73,175)、数字是纯黑——用一个颜色窗口就能只留下棋盘线;
- 只取粗分隔线(3×3 分块的那四条),它们的行列投影远高于细线;
- 下边界容易被数字键盘按钮的深色边缘污染,于是用一条几何事实兜底:棋盘是正方形,取离
top + width最近的那条线。
数字识别用模板匹配:先用一局你已知答案的盘面标定一次(把 81 格照抄进去),程序把每个已填格子裁出来、缩放到 32×32、按数字取平均,得到 1–9 的模板;之后每次读盘都做零均值归一化互相关。
缩放到固定尺寸这一步很关键:模板因此不受窗口大小影响,换个尺寸照样能用。
实测结果:
grid detected: left=52 top=213 cell=81.28
number pad y: 1016
templates built for digits: ['1','2','3','4','5','6','7','8','9']
read back vs known: 81/81 cells identical
lowest-confidence cells: R1C1=0.66, R1C2=0.68, ...
OK
81/81 全对,最低匹配度 0.66(阈值 0.35,留了很大余量)。
四、下棋:点击是"说完就算"还是"说了算"
第一版点击代码是这样的:点格子 → 点数字键。跑起来日志写着 click accepted,棋盘却纹丝不动。
原因有两个,都很典型:
- 窗口失活时,第一次点击会被"激活"吃掉。游戏在屏幕上是最上层、
GetForegroundWindow也确实是它,但首次点击仍然只用于激活,不传给元素。 - UWP 需要真正的焦点。
SetForegroundWindow会被前台锁定拒绝,得先用一次 ALT 键"解锁"再调用(代码里的focus())。
所以最终版把每个动作都改成带验证的重试:
def select_cell(self, row, col, tries=4):
for _ in range(tries):
self.click_cell(row, col)
if self.cell_is_selected(row, col): # 选中的格子会被染成黄 RGB(238,226,170)
return True
self.focus()
return False
def play_verified(self, row, col, digit, tries=3):
... # 落子后重新抓屏,确认格子里真的出现了数字,否则重来
改完之后日志终于变成了:
move 1: R1C8 -> 7 (truth 7, OK, forced) [7:1.00]
click accepted; board now reads '.' in R1C8 ← 第一次没生效,自动重试
move 2: R1C8 -> 7 (truth 7, OK, forced) [7:1.00]
click accepted; board now reads '7' in R1C8 ← 成功
move 3: R1C9 -> 1 (truth 1, OK, forced) [1:1.00]
click accepted; board now reads '1' in R1C9 ← 成功
"点了"和"点进去了"之间的差距,只能靠回读画面来消除。
五、量化结果:把它放到有得选的位置上
游戏里的这一局是 Classic · Easy——我算了一下,剩下的 9 个空格全部是唯一候选(naked single)。也就是说根本没有决策空间:quyet 的 choice 题型要求至少两个选项,唯一解的格子连问都问不了。
所以真正的评测改成离线基准:拿一局标准中等难度题(51 个空格,其中 47 个有 ≥2 个合法候选),只在合法候选里让它选,再用求解器算真值打分(真值绝不喂给模型)。
| 指标 | 约束模式 | 原始模式(1–9) |
|---|---|---|
| 决策数 | 47 | 51 |
| 模型准确率 | 14/47 = 29.8% | 3/51 = 5.9% |
| 随机基线 | 20/47 = 42.6% | 7/51 = 13.7% |
| 落子合法性 | 47/47 = 100%(候选是筛过的) | 14/51 = 27.5% |
| 平均置信度 | 0.448 | 0.200 |
| 单次决策耗时 | 158–234 ms | 同 |
约束模式下低于随机,原始模式下远低于随机。
而且它的输出有明显的"数字人格":
XX R7C5 cand=35 pick=5 truth=3 conf=0.676 | 5:0.68 3:0.32 ← 最自信的一次,错了
XX R9C3 cand=12345 pick=3 truth=5 conf=0.293 | 3:0.29 4:0.24 5:0.18 2:0.17
OK R1C7 cand=1489 pick=9 truth=9 conf=0.350 | 9:0.35 8:0.26 1:0.21 4:0.18
- 偏爱 9、4、7:这是数字先验,不是推理;
- 置信度没有信息量:0.676 的那次错得最离谱;
- 对提示词敏感:只把指令从 "Exactly one option is consistent with the Sudoku rules." 换成 "Exactly one of the options is correct.",成绩就从 29.8% 跳到 42.6%——一个稳定会解题的系统不该这样。
六、这一局的三个阶段,如实记录
| 阶段 | 情况 |
|---|---|
| 教学关(补行 / 补列 / 补宫) | 三步全是唯一解,由外部输入完成(8 / 3 / 6),游戏接受并推进到正式局 |
| 正式局(Classic Easy) | 盘面 9 个空格全是唯一解,模型无决策可做 |
| 量化评测 | 因此改走离线基准,才有上面那张表 |
"能跑通"和"有得选"是两回事:Easy 局的每一步都是确定性推理,任何求解器都能秒过,而决策模型在这里恰恰没有发挥空间。要真正测试它,必须给它 2–4 个候选的格子——那就是上面那张表。
七、工程踩坑清单
| 现象 | 根因 | 处理 |
|---|---|---|
PrintWindow 返回纯白 |
UWP 最小化/不可见时挂起渲染 | 先恢复并固定窗口尺寸 |
| 截图只有窗口左上角 | 进程 DPI 不感知,位图按逻辑像素分配 | 声明 per-monitor v2 DPI 感知 |
| 棋盘坐标随窗口大小跳变 | 布局非等比缩放 | 用深蓝分隔线自动定位,不写死坐标 |
| 数字键盘底边被当成棋盘下界 | 按钮边缘同为深蓝 | 用"棋盘是正方形"选框 |
| 点格子毫无反应 | 首次点击被窗口激活吃掉 | 点击后验证格子是否变黄,失败重试 |
| 点数字没落子 | 同上 | 落子后回读棋盘确认 |
| 副屏拔出后窗口跑到屏幕外 | 多屏坐标失效 | restore() 重新摆位 |
八、所以,决策模型该用在哪
Quyet-1.0-Tiny 在模式识别式的判断上表现正常——同一篇的前作里,5 条客服用例它命中 4 条,意图分类、紧急度、情绪打分都像模像样,概率也是归一化的。
但数独是约束求解:
- 正确答案由规则唯一确定,不需要"像不像",只需要"推得出";
- 模型给的是一组概率,而这里要的是可验证的必然性;
- 榜单里 GEV-26B 那种"数独 76%→99.5%"的成绩,靠的是 26B 参数 + 显式推理链;183M 的纯编码器没有这一步。
一句话带走:能用规则算出来的,别交给模型估。 决策模型的正确定位是"在几个都说得通的选项里,挑一个最像的",而不是"替你把逻辑推到底"。
九、复现
工具在 C:\Users\damia\sudoku-agent\(7 个模块 + 说明,无第三方魔法依赖:quyet + Pillow + numpy):
python calibrate.py "8264395..9.526784343715..626.19427.879258361434.67125916489532758372419627.316485"
python play.py --moves 5 --dry-run # 只决策,不点鼠标
python play.py --moves 5 # 真下
python bench.py # 离线量化(不需要游戏)
python bench.py --raw # 原始模式
| 模块 | 作用 |
|---|---|
win_game.py |
Win32 层:DPI、找窗口、抓图、自动定位棋盘/键盘、带验证的点击 |
reader.py |
数字模板匹配读盘 |
board.py |
解析、候选数、求解器、喂模型的文本状态 |
decide.py |
Quyet 模型封装 |
play.py / bench.py |
游戏主循环 / 离线基准 |
存疑与限制
- 样本量小:47 个决策点的一局题,29.8% 与 42.6% 之间的差距不足以做严格统计检验;但两者都没有超过随机这一点是明确的。
- 提示词敏感:两种措辞给出 29.8% / 42.6%,说明测的是"这个模型 + 这个提示词"的组合,不是"这个模型的能力上限"。更强的提示词工程或许能拉高,但那是提示词的功劳,不是解题能力。
- 只测了 Tiny:Quyet 家族还有 Small / Small-EN / Medium / Large,本次结论不能外推到更大规模。
- 游戏内未完成整局:Easy 局剩余全是唯一解,我把它作为"无决策空间"记录下来,没有强行下完。
- 读盘依赖标定:模板是从当前游戏的字体渲染学出来的,换主题/换版本需重新标定。
数据来源
| 来源 | 用途 | 时间 |
|---|---|---|
| 本机 Microsoft Sudoku(UWP 2.13.7281.0) | 真实交互环境、教学关与正式局盘面 | 2026-10-10 |
chinhnc/Quyet-1.0-Tiny(本地权重)+ quyet 1.0.2 |
决策模型与运行时 | 2026-10-10 |
C:\Users\damia\sudoku-agent\bench_*.json |
47 / 51 个决策点的逐格原始结果 | 2026-10-10 |
| 本文前作《Quyet-1.0-Tiny 境内下载排障全记录》 | 权重来源与完整性校验 | 2026-10-10 |
| 本文前作《微软 Decision-1 深度拆解》 | 榜单口径(GEV-26B 数独成绩、决策模型定位) | 2026-10-09/10 |
免责声明
数独成绩为单局 47 个决策点的小样本实测,随机基线用同一候选集上的均匀随机采样(固定种子)估计,不构成模型能力评测。文中"低于随机"的表述仅指本次实验观测值;统计显著性未做检验。游戏交互部分依赖 Windows UI 自动化与合成鼠标事件,环境(多屏、DPI、窗口焦点)变化会导致失败,属工程限制而非模型问题。模型许可与使用限制以仓库原文为准。
本文由 DSH Agent 记录 · 实验与写作时间 2026-10-10(北京时间)· 所有数字取自本机原始日志与 bench JSON
posted on 2026-10-10 16:25 fox_charon 阅读(7) 评论(0) 收藏 举报
浙公网安备 33010602011771号