夜雨聆风学习资料网

ARTICLE · 1083381

AI slop一则:TOPPEN(登山家)调查报告

AI slop一则:TOPPEN(登山家)调查报告

Salicylic:

其实这里本来应该是我自己手工总结一下和AI一起探究的结果的,但实在是有些懒了,所以就直接堆叠结果。

简单来说,我最近老是在淘宝上看到这个游戏的商品,但是心里总怀疑它可能不耐玩。所以我干脆让AI模拟了一下,看看它 会不会是井字棋那种决策深度很低的游戏。从结果上说,虽然没有预期的那么糟糕,但如果太用力思考的话应该会很快变无聊吧,结论是适合拿来作为派对游戏那样玩而不是像《茂林源记》或者《‌展翅翱翔‌》那样狠狠玩。

TOPPEN 调查报告

2026 年 9 月 25 到 26 日,我研究了日本桌游 TOPPEN(トッペン),目标是把它的规则整理成能跑在电脑上的程序,再用程序回答几个关于这个游戏的问题。这份报告是整个过程的事后整理:规则怎么考据的,代码写了什么,提出过哪些猜想,哪些验证了,哪些被我自己的数据推翻。

配套文件有两个。toppen_solver.py 是全部代码,单文件 Python,838 行。输出日志.md 按时间顺序收录了每一次运行的完整终端输出。所有随机实验都固定了种子,数字可以复现。

一、游戏本身

TOPPEN,原版叫《バベルの塔》(巴别塔),设计师新澤大樹,2012 年作为同人游戏颁布,2021 年由游戏店コロコロ堂重制发行。两人对弈,十枚木瓦,两种花纹各五枚,一局十分钟。BGG 上 49 人评分 5.7,权重 1.5,登记为抽象策略游戏。

玩法:十枚瓦片背面朝上洗乱,后手把它们摆成一个边相连的形状,然后翻面。此后轮流行动,每次移动一枚己方颜色的瓦片,必须是它所在塔的顶子,走正交的一格,叠到相邻格子的塔顶上。目的地不能是空格。移动之后,所有瓦片必须仍然连成一片,不许把场地撕成两块。没棋可走就跳过,对手连走。十枚叠成一座塔的时候,塔顶那枚的颜色获胜。

这套规则看起来简单,但有三处当时的资料互相打架,都靠一手证据才定下来。

二、规则考据,以及三个坑

第一个坑是胜利条件。BGG 的搜索摘要写着"用四枚棋子连成直线",来源是一篇实测博客。把博客正文完整抓下来之后发现,"连直线"是同一篇文章里下一款游戏 Flow 的介绍,摘要拼接时错位到了 TOPPEN 段落。五个独立来源(出版社、BGG 词条、两个日本资料站、博客本身)在"叠成一座塔,顶子颜色获胜"上完全一致。此案了结。

第二个坑最要命:能不能把瓦片移进空格。实测博客说可以,BGG 词条明确列在三条限制里说不行。证据一度两比一倾向"可以",我差点按它写引擎。最后是 BGG 图库里一张 2013 年上传的组件照片救了场:照片里摊着原版说明书,分辨率不够,我把它裁成四块放大,读出了关键句"タイルが移動先に1枚以上あれば可能です",移动的前提是目的地至少有一枚瓦片。结论反转,不可移入空格。回头看,博客作者的下一句"场地会逐渐缩小"本来就和自己的说法矛盾,是我当时没有较真。

第三个坑是骏河屋商品页那句"隣り合う他のタイルの横に移動させるか、上に重ねていきます"。我最初把"横に移動"读成"移到旁边的空格",和坑二连起来看差点造成误判。正确读法是"横向移动一格、叠上去",一句话描述移动的两个方面。

考据还留下一条没能完全读出的规则:说明书提到了千日手(局面反复导致游戏无法推进),但裁定条文被照片里的瓦片挡住了,只能确认存在,内容存疑。其余遗留的小项(谁先手、斜向是否允许等)在代码里全部做成了参数,默认值在报告第五节会交代。

三、求解器

代码是一个 int64 状态编码加三套算法。状态编码:十格塔高各占 4 位,颜色序列占 10 位,加行动方,一个整数就是一个局面。着法生成按规则逐条翻译,每次移动后断言不变量(总数 10、两色各 5、占格连通)。局面图用广度优先从初盘枚举,胜负用三值回归式求解:能强制登顶的标胜,任何着法都逃不掉的标负,其余标记为和局。和局采用循环语义,即无限重复等于和局,这正是说明书千日手条款的自然读法。

写回归式的时候踩了一个坑。所有后继都是"对手成塔"的残局,比如一座单枚对一座九枚塔,这种状态的胜负必须在预扫描阶段就裁定,否则它永远进不了传播队列,会被误判成和局。修掉之后,代码通过了换色对称性校验、和局循环的逐步合法性校验,以及十几个随机种子的不变量断言。

可行性的账在开工前就算过:对局树有大约 10 的 20 次方条路径,但不同局面(图上的节点)每盘初盘只有 10 万到 100 万个,全部地形加起来上界约 8200 万。指数的树被线性的图压掉了,这是整个项目能做的根本原因。Python 试点实测每秒 5.5 万状态,全量普查要一周,不可行;Node.js 加类型数组估计几小时、2GB 内存,可行。不过后来的发现让全量方案根本没必要启动,这是后话。

四、采样给的三次教训

第一课:小样本会撒谎。每种地形只试 4 种配色时,直线地形 4 比 0 全是先手胜,十字地形 4 比 0 全是后手胜。加到每种 30 个配色,直线掉到 57%,十字掉到 43%。所谓"全胜地形"不存在,存在的是偏置强度不同的地形。

第二课:筛选不算数。为了找后手优势地形,我快速筛了 40 个随机生成的地形,其中一个筛出 62.5% 后手胜。对它做全 252 种配色的普查,真实数字是 42.5%。先筛后验的做法里,筛选阶段的极端值会被均值回归打回来。这之后我只报告验证过的数字。

第三课:错了要留痕。我自己提过一个"补色配对可以把普查省一半"的省钱思路,推演时发现角色对换的同构会把先手映射成后手,而普查固定的是先手视角,所以补色不提供任何节省。这个想法死在了写代码之前。另一次,我给"分支越多和局越多"下过一个粗结论,后来双十字的数据(分支全谱最多,和局率却低于十字)把它修正成了"排布比计数重要"。这两处修正都写进了猜想的账本,没有抹掉。

五、两个定理

项目从"做实验"升级为"有结构",靠的是两个定理。

图同构不变性。 四种形状完全不同的路径地形(直线、蛇形、L 形、阶梯),252 种配色的普查结果逐位相同,都是先手 122 胜、后手 114 胜、16 和局。两个长得毫不相干的随机地形后来也被发现是同构图,普查同样逐位相同。原因说穿了很朴素:规则只引用"相邻"和"连通",全部通过地形图的抽象结构表达,网格上怎么拐弯从不进入规则。推论是 4655 种十格多联骨牌里,所有路径形坍缩成同一局博弈,全量普查的真实规模是图的同构类数,远小于表面数字。

节奏奇偶定理。 每一局对局恰好发生 9 次合并(塔数从 10 降到 1),第 9 次合并的人把己方瓦片放上唯一的塔顶,所以第 9 合并者获胜。倒腾和被迫跳过都不改变塔数,它们是"浪费着"。设双方浪费着数为 a 和 b,总手数就是 9+a+b,先手走第 1 手,所以先手胜当且仅当 a+b 是偶数。整个博弈归结为一场浪费着的奇偶战争:谁能在需要翻转奇偶的时刻拿得出浪费着,谁就赢。

遗留参数的默认值也在这里交代:非摆放方先手;只允许正交移动;三次重复局面判和;设置时后手选形、颜色随机;重制版规则与原版一致。这些参数在代码里都可以切换。

六、数据

十几个地形的全 252 种配色精确普查结果如下。表里 A 是先手胜率,B 是后手胜率,D 是和局率。

地形(图类型)
A
B
D
路径形(直线=蛇形=L形=阶梯,4 种嵌入)
48.4%
45.2%
6.3%
T5x5(树,1 个度3点)
45.6%
45.2%
9.1%
双十字‡(两个度4中心)
49.2%
41.3%
9.5%
rect2x5(梯子图)
83.3%
10.3%
6.3%
蜘蛛(6,1,1,1)
45.6%
45.2%
9.1%
蜘蛛(5,2,1,1)
45.2%
44.4%
10.3%
蜘蛛(4,3,1,1)
42.5%
45.6%
11.9%
蜘蛛(4,2,2,1)
54.4%
40.1%
5.6%
蜘蛛(3,3,2,1)
48.4%
40.9%
10.7%
十字(3,2,2,2)
40.1%
47.2%
12.7%
纯环 C10
99.2%
0%
0.8%
环8+尾2(ring8+2)
86.5%
5.6%
7.9%
2x3块+尾4
71.4%
22.2%
6.3%
环4+尾6
55.2%
36.1%
8.7%
环4+双尾(随机地形 hunt8)
48.4%
42.5%
9.1%

抽样汇总:前后三轮共 348 个抽样根局面加约 2500 个普查根局面,总体先手胜率 63% 到 64%,后手 29% 到 30%,和局 6% 到 10%。

策略深度的精确统计(573 万个已解状态):平均分支因子 2.6,最大 14;30.3% 的局面只有唯一合法手;14.2% 的局面有一枚好棋但陷阱可选;30.6% 的局面存在会改变胜负的错误选择。随机对局长度均值约 50 步,95 分位 84 步,最长 235 步,一万七千多局没有一局失控。

七、和局长什么样

日志第十节有两则完整的和局记录,逐手打印了棋盘,这里只说结构。

纯环的和局配色是环上颜色交替,全 252 种配色里只有 2 个。终局剩三座塔,塔顶全是后手的颜色,先手的瓦片全被埋住,只能被迫跳过;后手握着唯一机动的瓦片,在两格之间来回倒腾。后手为什么不直接合并?因为每座塔的次层都埋着先手的瓦片,后手一削塔,埋着的先手瓦片就在第 9 合并前复活,反偷塔顶。求解器验证过后手所有破环尝试,全部通向先手胜,于是拖和成了双方的最优解。

环8+尾2 的和局终局是两座相邻高塔之间的贸易舞。瓦片在两塔间来回换位,塔高沿 4比6、5比5、6比4 循环振荡,谁都不敢把塔削到单枚,因为塔底埋着的颜色一旦暴露,对面的合并立刻按奇偶定理兑现成胜负。循环里双方各有一次被迫跳过,每一手浪费都翻转"若立即连续合并第 9 手归属"的预测列,而预测列永远追不到头。

这些记录还定格了一个反直觉细节:被迫跳过同样是奇偶武器。浪费着不分敌我,只看奇偶。被埋的一方靠被迫跳过维持对自己有利的奇偶性,和主动倒腾在定理眼里没有区别。

八、词汇与猜想的账本

过程中自然长出了一批只有我们俩懂的词。地形指初盘连通形状;路径形、环族、蜘蛛族、双十字是地形的图结构分类;普查指 252 种配色全部解掉,轨道约化是利用自同构群把 252 压到几十个代表元;浪费着包括倒腾和被迫跳过;奇偶战争、资源剥夺、贸易舞、削塔陷阱描述机制;强制手、唯一好棋、可犯错节点是局面选择自由度的三档;47.2% 这个数字被我们叫做后手天花板;先手优势谱指的是从纯环的 99% 到路径的 48% 这条连续谱。

猜想各有各的命运。图同构不变性和节奏奇偶定理从猜想升级成了定理,前者三次独立验证,后者有严格证明加记录可视化。环长越长先手优势越大,在环族内单调成立。腿长越均匀后手越强、和局越多,在蜘蛛族内成立。存在全胜地形的猜想被加密采样推翻。分支越多和局越多被双十字修正。资源剥夺机制与全部梯度一致,但严格归因还欠一步。后手胜率越过 50% 的地形,已测的七十多个里没有出现,但它是不是真的不存在,以现有普查覆盖面还不能下结论。

九、没解决的问题

三件事挂在墙上。第一,后手胜率超过 50% 的地形是否存在于未普查的图类里,要回答它需要按图同构去重之后的全量普查,估计还有几千个图类。第二,资源剥夺机制的严格归因,需要提取主变例、逐局统计双方的浪费着收支,目前只有梯度证据。第三,也是最贴近真实规则的一个:后手有权挑选地形,那么在"后手选形"的博弈下,这游戏的真实先手胜率是多少。现在地图已经足够密,这一步随时可以做。

十、文件清单与复现

toppen_solver.py,838 行,依赖只有 Python 标准库。运行环境是 Windows 11 下的 conda 环境 datasci_env(纯 Python,任何 3.8+ 都能跑)。用法:

python toppen_solver.py smoke                     # 单地形烟囱测试python toppen_solver.py batch 4                   # 10 地形 x 4 配色抽样python toppen_solver.py dense 30                  # 10 地形 x 30 配色加密采样python toppen_solver.py random_shapes 12 4        # 12 个随机地形 x 4 配色python toppen_solver.py exact252 line10,snake10   # 指定地形 252 配色暴力普查python toppen_solver.py census rect2x5,ring8+2    # 轨道约化精确普查(推荐)python toppen_solver.py hunt 40 8                 # 随机地形筛选+验证python toppen_solver.py drawrecord C10ring        # 提取和局对局记录

输出日志.md 按运行顺序收录了十次调用的完整输出,每次开头注明命令行。日志是当时终端输出的誊录,中间的调试轮次(两次 bug 修复、一次语法修正)没有收录,过程在报告第三、四节有交代。

数据快照日期 2026 年 9 月 19 日。引用的外部资料:出版社 korokorodou.com 商品页、BGG 词条 253095 与图库照片 pic5579735、Hiew 的实测博客(2025 年 7 月)、骏河屋原版商品页、JELLY JELLY CAFE 游戏介绍页。

TOPPEN 调查:运行输出日志

按时间顺序收录 toppen_solver.py 各次运行的完整终端输出,每次开头注明命令行。这些是各命令最终一次运行的誊录;中间的调试轮次(两次求解器 bug 修复、一次语法修正、一次几何认知修正)没有单独收录,过程见报告第三、四节。运行环境:Windows 11,conda 环境 datasci_env,纯 Python。日期 2026-09-18 至 09-19。


1. smoke(烟囱测试,rect2x5 交替配色)

python toppen_solver.py smoke
states=339391  enumerate=5.04s  solve=0.50sroot value: DRAWmean_branch=2.787  frac_L1=0.2554  frac_O1(L>1)=0.2014  frac_blunderable=0.4208L_hist={1: 86668, 2: 79173, 3: 74640, 4: 52987, 5: 24913, 6: 12068, 7: 5477, 8: 2213, 9: 762, 10: 378, 11: 69, 12: 30, 13: 13}layer_hist={2: 15210, 3: 50644, 4: 92506, 5: 99928, 6: 63763, 7: 15966, 8: 1360, 9: 13, 10: 1}symmetry: root=D  swapped-root=D  match=Truedraw_states=130083/339391draw cycle length: 8  legal=True    (1, 2):BBA (1, 3):AAB (1, 4):BBAA  mover=A    (1, 2):BBA (1, 3):AABA (1, 4):BBA  mover=B    (1, 2):BBA (1, 3):AABA (1, 4):BBA  mover=A    (1, 2):BBA (1, 3):AAB (1, 4):BBAA  mover=B    (1, 2):BBA (1, 3):AA (1, 4):BBAAB  mover=A    (1, 2):BBA (1, 3):A (1, 4):BBAABA  mover=B    (1, 2):BBA (1, 3):A (1, 4):BBAABA  mover=A    (1, 2):BBA (1, 3):AA (1, 4):BBAAB  mover=Bno-shuffle control: root=D  states=480playouts n=2000 mean_len=46.5 max=229 runaway=0

注:no-shuffle 对照组是一个被禁用倒腾着的人工变体,存在"冻结态"伪影,只作 smoke 参考,未进入任何结论。

2. batch(10 地形 × 4 配色抽样)

python toppen_solver.py batch 4
[line10] values over 4 colorings: ['A', 'A', 'A', 'A'][rect2x5] values over 4 colorings: ['A', 'A', 'A', 'A'][blob3x3+1] values over 4 colorings: ['A', 'D', 'A', 'A'][cross10] values over 4 colorings: ['B', 'B', 'B', 'B'][snake10] values over 4 colorings: ['B', 'D', 'B', 'A'][L6x4] values over 4 colorings: ['A', 'A', 'A', 'B'][T5x5] values over 4 colorings: ['B', 'A', 'B', 'A'][H10] values over 4 colorings: ['A', 'A', 'A', 'A'][stair10] values over 4 colorings: ['A', 'A', 'A', 'D'][ring8+2] values over 4 colorings: ['A', 'A', 'A', 'A']=== batch summary ===roots=40  values: {'A': 28, 'D': 3, 'B': 9}total_states=5727520  max_root_states=411302  wall=102.9spooled mean_branch=2.607  frac_L1=0.3031  frac_O1(L>1)=0.1423  frac_blunderable=0.3059pooled L_hist: L1:1735732, L2:1378588, L3:1151773, L4:809526, L5:353378, L6:183463, L7:70256, L8:27656, L9:10505, L10:4943, L11:1072, L12:441, L13:153, L14:34draw roots: [('blob3x3+1', 1), ('snake10', 1), ('stair10', 3)]playouts n=15000 mean=49.7 p50=47 p95=84 max=235

3. dense(10 地形 × 30 配色加密采样)

python toppen_solver.py dense 30
[line10] K=30  A=17  B=11  D=2  A%=57%  (17s)[rect2x5] K=30  A=27  B=0  D=3  A%=90%  (158s)[blob3x3+1] K=30  A=21  B=6  D=3  A%=70%  (221s)[cross10] K=30  A=13  B=13  D=4  A%=43%  (61s)[snake10] K=30  A=17  B=12  D=1  A%=57%  (18s)[L6x4] K=30  A=15  B=15  D=0  A%=50%  (19s)[T5x5] K=30  A=12  B=15  D=3  A%=40%  (27s)[H10] K=30  A=21  B=8  D=1  A%=70%  (111s)[stair10] K=30  A=18  B=11  D=1  A%=60%  (18s)[ring8+2] K=30  A=28  B=1  D=1  A%=93%  (52s)TOTAL K=300  A=189  B=92  D=19  A%=63%

4. random_shapes(12 个随机地形 × 4 配色)

python toppen_solver.py random_shapes 12 4
=== rand1  A=3 B=1 D=0  (first coloring shown) ===AABABBAB...A...B=== rand2  A=2 B=2 D=0  (first coloring shown) ===BABBB.AB...A..AA=== rand3  A=2 B=2 D=0  (first coloring shown) ===B.AABAA..ABB..B.=== rand4  A=2 B=2 D=0  (first coloring shown) ===.BA.BBAAB.B.A.A.=== rand5  A=1 B=2 D=1  (DRAW coloring shown) ===.A....A....B...BABAA...B....B.=== rand6  A=3 B=0 D=1  (DRAW coloring shown) ===AB..ABAA..A.BBB.=== rand7  A=4 B=0 D=0  (first coloring shown) ===..A..ABBBBAB.AA.=== rand8  A=3 B=1 D=0  (first coloring shown) ===.AAB..AB...AABBB=== rand9  A=4 B=0 D=0  (first coloring shown) ===BBA..BABBA..A.A=== rand10  A=4 B=0 D=0  (first coloring shown) ===B.BABBAAB.AA=== rand11  A=4 B=0 D=0  (first coloring shown) ===..ABBABABA..AB.=== rand12  A=2 B=1 D=1  (DRAW coloring shown) ===BABB.A..BABA.A.TOTAL roots=48  A=34  B=11  D=3

5. exact252(6 个均衡地形的 252 配色暴力普查)

python toppen_solver.py exact252 line10,snake10,L6x4,stair10,T5x5,cross10
  [line10] 36/252 ... A=28 B=4 D=4  [line10] 72/252 ... A=54 B=13 D=5  [line10] 108/252 ... A=73 B=27 D=8  [line10] 144/252 ... A=89 B=47 D=8  [line10] 180/252 ... A=104 B=65 D=11  [line10] 216/252 ... A=116 B=88 D=12  [line10] 252/252 ... A=122 B=114 D=16[line10] EXACT n=252  A=122 (48.4%)  B=114 (45.2%)  D=16 (6.3%)  (146s)  [snake10] 36/252 ... A=28 B=4 D=4  [snake10] 72/252 ... A=54 B=13 D=5  [snake10] 108/252 ... A=73 B=27 D=8  [snake10] 144/252 ... A=89 B=47 D=8  [snake10] 180/252 ... A=104 B=65 D=11  [snake10] 216/252 ... A=116 B=88 D=12  [snake10] 252/252 ... A=122 B=114 D=16[snake10] EXACT n=252  A=122 (48.4%)  B=114 (45.2%)  D=16 (6.3%)  (143s)  [L6x4] 36/252 ... A=1 B=35 D=0  [L6x4] 72/252 ... A=8 B=62 D=2  [L6x4] 108/252 ... A=24 B=76 D=8  [L6x4] 144/252 ... A=45 B=91 D=8  [L6x4] 180/252 ... A=60 B=106 D=14  [L6x4] 216/252 ... A=87 B=113 D=16  [L6x4] 252/252 ... A=122 B=114 D=16[L6x4] EXACT n=252  A=122 (48.4%)  B=114 (45.2%)  D=16 (6.3%)  (146s)  [stair10] 36/252 ... A=28 B=4 D=4  [stair10] 72/252 ... A=54 B=13 D=5  [stair10] 108/252 ... A=73 B=27 D=8  [stair10] 144/252 ... A=89 B=47 D=8  [stair10] 180/252 ... A=104 B=65 D=11  [stair10] 216/252 ... A=116 B=88 D=12  [stair10] 252/252 ... A=122 B=114 D=16[stair10] EXACT n=252  A=122 (48.4%)  B=114 (45.2%)  D=16 (6.3%)  (148s)  [T5x5] 36/252 ... A=24 B=9 D=3  [T5x5] 72/252 ... A=45 B=21 D=6  [T5x5] 108/252 ... A=62 B=35 D=11  [T5x5] 144/252 ... A=80 B=53 D=11  [T5x5] 180/252 ... A=94 B=71 D=15  [T5x5] 216/252 ... A=106 B=91 D=19  [T5x5] 252/252 ... A=115 B=114 D=23[T5x5] EXACT n=252  A=115 (45.6%)  B=114 (45.2%)  D=23 (9.1%)  (226s)  [cross10] 36/252 ... A=0 B=36 D=0  [cross10] 72/252 ... A=6 B=66 D=0  [cross10] 108/252 ... A=14 B=87 D=7  [cross10] 144/252 ... A=22 B=103 D=19  [cross10] 180/252 ... A=32 B=119 D=29  [cross10] 216/252 ... A=65 B=119 D=32  [cross10] 252/252 ... A=101 B=119 D=32[cross10] EXACT n=252  A=101 (40.1%)  B=119 (47.2%)  D=32 (12.7%)  (469s)

6. hunt(40 个随机地形筛选 + 3 个候选验证)

python toppen_solver.py hunt 40 8
hunt1: A=7 B=1 D=0  cost=6.49s/roothunt2: A=5 B=3 D=0  cost=4.14s/roothunt3: A=5 B=1 D=2  cost=3.56s/roothunt4: A=7 B=1 D=0  cost=5.82s/roothunt5: A=8 B=0 D=0  cost=5.06s/roothunt6: A=1 B=4 D=3  cost=0.99s/roothunt7: A=7 B=0 D=1  cost=3.73s/roothunt8: A=2 B=5 D=1  cost=1.12s/roothunt9: A=5 B=3 D=0  cost=2.86s/roothunt10: A=3 B=4 D=1  cost=1.17s/roothunt11: A=7 B=0 D=1  cost=5.18s/roothunt12: A=3 B=4 D=1  cost=2.31s/roothunt13: A=7 B=1 D=0  cost=2.58s/roothunt14: A=8 B=0 D=0  cost=3.34s/roothunt15: A=5 B=2 D=1  cost=4.70s/roothunt16: A=3 B=4 D=1  cost=1.99s/roothunt17: A=5 B=3 D=0  cost=3.81s/roothunt18: A=8 B=0 D=0  cost=1.24s/roothunt19: A=4 B=3 D=1  cost=2.89s/roothunt20: A=3 B=4 D=1  cost=1.08s/roothunt21: A=5 B=3 D=0  cost=1.09s/roothunt22: A=3 B=4 D=1  cost=2.22s/roothunt23: A=6 B=2 D=0  cost=4.17s/roothunt24: A=6 B=1 D=1  cost=0.83s/roothunt25: A=5 B=2 D=1  cost=1.02s/roothunt26: A=4 B=2 D=2  cost=2.91s/roothunt27: A=7 B=1 D=0  cost=4.89s/roothunt28: A=5 B=3 D=0  cost=2.15s/roothunt29: A=6 B=2 D=0  cost=4.13s/roothunt30: A=4 B=4 D=0  cost=1.38s/roothunt31: A=6 B=2 D=0  cost=3.41s/roothunt32: A=5 B=3 D=0  cost=3.25s/roothunt33: A=6 B=1 D=1  cost=4.06s/roothunt34: A=5 B=1 D=2  cost=1.49s/roothunt35: A=6 B=1 D=1  cost=3.24s/roothunt36: A=7 B=1 D=0  cost=4.17s/roothunt37: A=3 B=2 D=3  cost=2.12s/roothunt38: A=6 B=1 D=1  cost=1.16s/roothunt39: A=7 B=1 D=0  cost=2.35s/roothunt40: A=5 B=3 D=0  cost=0.62s/root=== tier2 verification (3 promoted) ===--- hunt8 [B-lean]  verify n=252 (EXACT census): A=122 (48.4%)  B=107 (42.5%)  D=23 (9.1%)  (288s)###.#...####.##.--- hunt30 [BAL]  verify n=252 (EXACT census): A=117 (46.4%)  B=105 (41.7%)  D=30 (11.9%)  (366s)...#.####..#.##..##.--- hunt10 [BAL]  verify n=252 (EXACT census): A=122 (48.4%)  B=107 (42.5%)  D=23 (9.1%)  (287s)###..##..#.#.###

7. 自同构群阶校验

python -c "import toppen_solver as t; [print(n, len(t.find_automorphisms(t.SHAPES[n].adj))) for n in [...]]"
rect2x5 4ring8+2 2C10ring 20C6P4 1C4P6 2spider6111 6spider5211 2spider4311 2spider4221 2spider3321 2

注:C6P4 的 |Aut|=1 暴露了一个几何错误,2x3 矩形边界不是纯 6 环(带弦),网格上不存在纯 C6。该地形改名为 blk23P4 后照常使用。

8. census(环族 + rect2x5,轨道约化精确普查)

python toppen_solver.py census rect2x5,ring8+2,C10ring,blk23P4,C4P6
[rect2x5] |Aut|=4  orbits=66  EXACT: A=210 (83.3%)  B=26 (10.3%)  D=16 (6.3%)  (339s)[ring8+2] |Aut|=2  orbits=138  EXACT: A=218 (86.5%)  B=14 (5.6%)  D=20 (7.9%)  (231s)[C10ring] |Aut|=20  orbits=16  EXACT: A=250 (99.2%)  B=0 (0.0%)  D=2 (0.8%)  (20s)[blk23P4] |Aut|=1  orbits=252  EXACT: A=180 (71.4%)  B=56 (22.2%)  D=16 (6.3%)  (484s)[C4P6] |Aut|=2  orbits=182  EXACT: A=139 (55.2%)  B=91 (36.1%)  D=22 (8.7%)  (158s)

9. census(蜘蛛族)

python toppen_solver.py census spider6111,spider5211,spider4311,spider4221,spider3321
[spider6111] |Aut|=6  orbits=112  EXACT: A=115 (45.6%)  B=114 (45.2%)  D=23 (9.1%)  (96s)[spider5211] |Aut|=2  orbits=182  EXACT: A=114 (45.2%)  B=112 (44.4%)  D=26 (10.3%)  (205s)[spider4311] |Aut|=2  orbits=182  EXACT: A=107 (42.5%)  B=115 (45.6%)  D=30 (11.9%)  (245s)[spider4221] |Aut|=2  orbits=152  EXACT: A=137 (54.4%)  B=101 (40.1%)  D=14 (5.6%)  (232s)[spider3321] |Aut|=2  orbits=138  EXACT: A=122 (48.4%)  B=103 (40.9%)  D=27 (10.7%)  (225s)

10. census(双十字)

python toppen_solver.py census ddagger,ddaggerH
[ddagger] |Aut|=72  orbits=26  EXACT: A=124 (49.2%)  B=104 (41.3%)  D=24 (9.5%)  (37s)[ddaggerH] |Aut|=72  orbits=26  EXACT: A=124 (49.2%)  B=104 (41.3%)  D=24 (9.5%)  (37s)

注:竖连与横连双十字是同一个抽象树的不同嵌入,普查逐位相同,图同构不变性第三次自证。

11. drawrecord(和局对局记录提取)

python toppen_solver.py drawrecord C10ring,ring8+2
##### C10ring: draw orbits=1/16  draw colorings=2/252  |Aut|=20draw coloring (A=先手色/B=后手色):ABABB..AABAB--- 从开局到进入循环共 28 手; 循环从第 25 手起, 长 4 手 ---  1. A 合并#1       (0, 0)->(1, 0) [浪费A=0,B=0 | 若此后再无浪费,第9合并归A]  2. B 合并#2       (0, 1)->(0, 2) [浪费A=0,B=0 | 若此后再无浪费,第9合并归A]  3. A 倒腾(浪费)     (1, 0)->(2, 0) [浪费A=1,B=0 | 若此后再无浪费,第9合并归B]  4. B 合并#3       (1, 0)->(2, 0) [浪费A=1,B=0 | 若此后再无浪费,第9合并归B]  5. A 跳过(浪费)     —              [浪费A=2,B=0 | 若此后再无浪费,第9合并归A]  6. B 倒腾(浪费)     (0, 2)->(0, 3) [浪费A=2,B=1 | 若此后再无浪费,第9合并归B]  7. A 合并#4       (0, 2)->(0, 3) [浪费A=2,B=1 | 若此后再无浪费,第9合并归B]  8. B 倒腾(浪费)     (2, 0)->(2, 1) [浪费A=2,B=2 | 若此后再无浪费,第9合并归A]  9. A 倒腾(浪费)     (0, 3)->(1, 3) [浪费A=3,B=2 | 若此后再无浪费,第9合并归B] 10. B 倒腾(浪费)     (0, 3)->(1, 3) [浪费A=3,B=3 | 若此后再无浪费,第9合并归A] 11. A 倒腾(浪费)     (2, 0)->(2, 1) [浪费A=4,B=3 | 若此后再无浪费,第9合并归B] 12. B 合并#5       (0, 3)->(1, 3) [浪费A=4,B=3 | 若此后再无浪费,第9合并归B] 13. A 合并#6       (2, 0)->(2, 1) [浪费A=4,B=3 | 若此后再无浪费,第9合并归B] 14. B 倒腾(浪费)     (1, 3)->(2, 3) [浪费A=4,B=4 | 若此后再无浪费,第9合并归A] 15. A 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=5,B=4 | 若此后再无浪费,第9合并归B] 16. B 倒腾(浪费)     (2, 3)->(1, 3) [浪费A=5,B=5 | 若此后再无浪费,第9合并归A] 17. A 倒腾(浪费)     (2, 2)->(2, 3) [浪费A=6,B=5 | 若此后再无浪费,第9合并归B] 18. B 倒腾(浪费)     (1, 3)->(2, 3) [浪费A=6,B=6 | 若此后再无浪费,第9合并归A] 19. A 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=7,B=6 | 若此后再无浪费,第9合并归B] 20. B 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=7,B=7 | 若此后再无浪费,第9合并归A] 21. A 跳过(浪费)     —              [浪费A=8,B=7 | 若此后再无浪费,第9合并归B] 22. B 合并#7       (2, 1)->(2, 2) [浪费A=8,B=7 | 若此后再无浪费,第9合并归B] 23. A 跳过(浪费)     —              [浪费A=9,B=7 | 若此后再无浪费,第9合并归A] 24. B 倒腾(浪费)     (2, 2)->(2, 3) [浪费A=9,B=8 | 若此后再无浪费,第9合并归B] 25. A 跳过(浪费)     —              [浪费A=10,B=8 | 若此后再无浪费,第9合并归A] 26. B 倒腾(浪费)     (2, 3)->(1, 3) [浪费A=10,B=9 | 若此后再无浪费,第9合并归B] 27. A 跳过(浪费)     —              [浪费A=11,B=9 | 若此后再无浪费,第9合并归A] 28. B 倒腾(浪费)     (1, 3)->(2, 3) [浪费A=11,B=10 | 若此后再无浪费,第9合并归B]--- 循环段逐手棋盘(局面在循环首重现, 可无限持续) ---循环第1手  A (跳过 None->).    .    .    .   .    .    .    AAB .    .    AAB  BABB循环第2手  B (倒腾 (2, 3)->(1, 3)).    .    .    .   .    .    .    AAB .    .    AAB  BABB循环第3手  A (跳过 None->).    .    .    .   .    .    .    AABB.    .    AAB  BAB 循环第4手  B (倒腾 (1, 3)->(2, 3)).    .    .    .   .    .    .    AABB.    .    AAB  BAB ##### ring8+2: draw orbits=12/138  draw colorings=20/252  |Aut|=2draw coloring (A=先手色/B=后手色):AAAB.BAAB.B..B.--- 从开局到进入循环共 33 手; 循环从第 24 手起, 长 10 手 ---  1. A 合并#1       (0, 0)->(0, 1) [浪费A=0,B=0 | 若此后再无浪费,第9合并归A]  2. B 合并#2       (4, 1)->(3, 1) [浪费A=0,B=0 | 若此后再无浪费,第9合并归A]  3. A 倒腾(浪费)     (0, 1)->(0, 2) [浪费A=1,B=0 | 若此后再无浪费,第9合并归B]  4. B 倒腾(浪费)     (3, 1)->(2, 1) [浪费A=1,B=1 | 若此后再无浪费,第9合并归A]  5. A 倒腾(浪费)     (0, 2)->(1, 2) [浪费A=2,B=1 | 若此后再无浪费,第9合并归B]  6. B 合并#3       (3, 1)->(2, 1) [浪费A=2,B=1 | 若此后再无浪费,第9合并归B]  7. A 合并#4       (0, 1)->(0, 2) [浪费A=2,B=1 | 若此后再无浪费,第9合并归B]  8. B 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=2,B=2 | 若此后再无浪费,第9合并归A]  9. A 倒腾(浪费)     (1, 2)->(2, 2) [浪费A=3,B=2 | 若此后再无浪费,第9合并归B] 10. B 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=3,B=3 | 若此后再无浪费,第9合并归A] 11. A 倒腾(浪费)     (0, 2)->(1, 2) [浪费A=4,B=3 | 若此后再无浪费,第9合并归B] 12. B 倒腾(浪费)     (2, 2)->(2, 1) [浪费A=4,B=4 | 若此后再无浪费,第9合并归A] 13. A 合并#5       (0, 2)->(1, 2) [浪费A=4,B=4 | 若此后再无浪费,第9合并归A] 14. B 合并#6       (1, 0)->(2, 0) [浪费A=4,B=4 | 若此后再无浪费,第9合并归A] 15. A 倒腾(浪费)     (1, 2)->(2, 2) [浪费A=5,B=4 | 若此后再无浪费,第9合并归B] 16. B 倒腾(浪费)     (2, 1)->(2, 0) [浪费A=5,B=5 | 若此后再无浪费,第9合并归A] 17. A 倒腾(浪费)     (1, 2)->(2, 2) [浪费A=6,B=5 | 若此后再无浪费,第9合并归B] 18. B 合并#7       (1, 2)->(2, 2) [浪费A=6,B=5 | 若此后再无浪费,第9合并归B] 19. A 跳过(浪费)     —              [浪费A=7,B=5 | 若此后再无浪费,第9合并归A] 20. B 倒腾(浪费)     (2, 0)->(2, 1) [浪费A=7,B=6 | 若此后再无浪费,第9合并归B] 21. A 跳过(浪费)     —              [浪费A=8,B=6 | 若此后再无浪费,第9合并归A] 22. B 倒腾(浪费)     (2, 0)->(2, 1) [浪费A=8,B=7 | 若此后再无浪费,第9合并归B] 23. A 合并#8       (2, 0)->(2, 1) [浪费A=8,B=7 | 若此后再无浪费,第9合并归B] 24. B 倒腾(浪费)     (2, 2)->(2, 1) [浪费A=8,B=8 | 若此后再无浪费,第9合并归A] 25. A 倒腾(浪费)     (2, 2)->(2, 1) [浪费A=9,B=8 | 若此后再无浪费,第9合并归B] 26. B 跳过(浪费)     —              [浪费A=9,B=9 | 若此后再无浪费,第9合并归A] 27. A 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=10,B=9 | 若此后再无浪费,第9合并归B] 28. B 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=10,B=10 | 若此后再无浪费,第9合并归A] 29. A 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=11,B=10 | 若此后再无浪费,第9合并归B] 30. B 倒腾(浪费)     (2, 1)->(2, 2) [浪费A=11,B=11 | 若此后再无浪费,第9合并归A] 31. A 跳过(浪费)     —              [浪费A=12,B=11 | 若此后再无浪费,第9合并归B] 32. B 倒腾(浪费)     (2, 2)->(2, 1) [浪费A=12,B=12 | 若此后再无浪费,第9合并归A] 33. A 倒腾(浪费)     (2, 2)->(2, 1) [浪费A=13,B=12 | 若此后再无浪费,第9合并归B]--- 循环段逐手棋盘(局面在循环首重现, 可无限持续) ---循环第1手  B (倒腾 (2, 2)->(2, 1)).      .      .     .      .      .     .      ABBA   BBAAAB.      .      .     .      .      .     循环第2手  A (倒腾 (2, 2)->(2, 1)).     .     .    .     .     .    .     ABBAB BBAAA.     .     .    .     .     .    循环第3手  B (跳过 None->).      .      .     .      .      .     .      ABBABA BBAA  .      .      .     .      .      .     循环第4手  A (倒腾 (2, 1)->(2, 2)).      .      .     .      .      .     .      ABBABA BBAA  .      .      .     .      .      .     循环第5手  B (倒腾 (2, 1)->(2, 2)).     .     .    .     .     .    .     ABBAB BBAAA.     .     .    .     .     .    循环第6手  A (倒腾 (2, 1)->(2, 2)).      .      .     .      .      .     .      ABBA   BBAAAB.      .      .     .      .      .     循环第7手  B (倒腾 (2, 1)->(2, 2)).       .       .      .       .       .      .       ABB     BBAAABA.       .       .      .       .       .      循环第8手  A (跳过 None->).        .        .       .        .        .       .        AB       BBAAABAB.        .        .       .        .        .       循环第9手  B (倒腾 (2, 2)->(2, 1)).        .        .       .        .        .       .        AB       BBAAABAB.        .        .       .        .        .       循环第10手  A (倒腾 (2, 2)->(2, 1)).       .       .      .       .       .      .       ABB     BBAAABA.       .       .      .       .       .      

注:循环段棋盘的列宽随塔高变化,相邻两手之间列位置会有漂移,读图时以格点坐标为准。格内字母左到右为自底向顶。

相关学习资料