- 数据分析
- CLI
- 数据可视化
【免费下载链接】visidata
A terminal spreadsheet multitool for discovering and arranging data
导读
Noah's Tapestry(诺亚的挂毯)是 VisiData 仓库experimental目录下附带的一个完整交互式数据解谜游戏(interactive data game):玩家在终端里跟随一段家族故事,通过 SQLite 数据库中的顾客、订单、商品数据逐关追踪一块传奇地毯的下落,每一关的答案都是一个电话号码或密码。本文以该游戏的开场关卡 puzzle0.md 为切入点,完整讲解第一关的目标、线索与答案推导,并从 tapestry.py 源码出发,拆解题目加载、线索注入、答案校验与烛台动画的底层实现,最后给出 noahs.sqlite 的数据库结构与全部 9 个关卡的玩法概览。读完本文,你既能亲手通关第一关,也能理解如何用 VisiData 的 Sheet、Canvas、命令注册等机制搭建一个可交互的"数据侦探"式游戏。
一、Puzzle 0:一段关于旧地毯的家族悬案
1.1 剧情背景
visidata/experimental/noahs_tapestry/puzzle0.md 是游戏的序幕(标题为Noah's Market),它用一段完整的叙事交代了整场游戏的任务:
- 你的叔祖父 Noah 在纽约经营着一家"什么都卖"的老式杂货店 Noah's Market,商品超过一千种,顾客遍布全美,数据库还是 2017 年初由表哥 Alex 搭建的。
- 婶婶 Sarah 向你求助:Noah 惦记一块花纹精美的旧地毯(边缘织着"嗡嗡作响的蜂巢"图案),几年前 Sarah 把地毯送去干洗店,却一直忘了取回。干洗店的取件单上写着"所有物品必须在 90 天内取走",等她再回去时,干洗店只剩下了另一半单据,上面写着
'2017 spec {cleanerinits}'——店员的解释是,这是 2017 年由某位承包商经手的"特别项目"。 - 地毯上还发生过一连串怪事:蜗牛留下的黏液痕迹、"逼真的蜘蛛"、猫、以及后面每一关陆续出现的各种动物图案。
- Sarah 把一枚标着"Noah's Market Database Backup"的 U 盘交给你,并提示:备份是加密的 zip 压缩包,密码就是 Alex 搭建数据库那一年(2017 年)对应的希伯来历年份。
第一关的问题就落在文末一行加粗的提问上:
What's the password to open the .zip files on the USB drive?
1.2 关键线索与答案推导
要解开这一关,需要把文档中的叙事线索翻译成数据线索:
- "2017 spec {cleanerinits}"中的占位符
{cleanerinits}不是笔误,而是游戏引擎的线索插值标记。查看 clues.json,可以看到它的取值:"cleanerinits": "DS"—— 即 2017 年那位承包商的姓名缩写是DS(这个线索在后续 Puzzle 2 中用来在数据库里定位承包商)。- 其余字段(
p3_sign: Libra、p3_chinesezodiac: Goat、clue5a: in the Bronx等)也都是为后续关卡准备的随机化线索。
- 密码 = 希伯来历年份:Alex 于2017 年初搭建数据库,2017 年对应的希伯来历年份为5777 年(希伯来历 5777 年大致覆盖 2016 年秋至 2017 年秋)。
- 答案校验:打开 solutions.json,可以看到
"p0": "NTc3Nw==",这是对字符串5777的 Base64 编码。也就是说,游戏内置的正确答案正是5777,与剧情线索完全吻合。
结论:Puzzle 0 的密码答案是
5777。在游戏中按下Shift+A输入5777(或把光标移到包含该值的单元格后按Shift+Y),即可点亮第一支蜡烛。
二、游戏是如何运转的:从题目文本到烛台动画
Noah's Tapestry 不是一段静态 Markdown,而是一个完整的、可交互的 VisiData 模块。其全部实现集中在 visidata/experimental/noahs_tapestry/ 目录下:
visidata/experimental/noahs_tapestry/ ├── __init__.py ├── clues.json # 各关卡的线索插值(如 cleanerinits=DS) ├── solutions.json # 各关卡的 Base64 编码答案 ├── noahs.sqlite # 游戏数据库(顾客/订单/商品) ├── puzzle0.md … puzzle8.md # 9 个关卡的剧情文本 ├── menorah.ddw / tapestry.ddw / flame.ddw # 烛台、挂毯、火焰动画 ├── tapestry.py # 游戏主逻辑(Sheet、Canvas、命令注册)2.1 题目如何被渲染成终端里的"剧本"
NoahsPuzzle是一个继承自Sheet的表格类(tapestry.py),它的核心逻辑在iterload中:
def iterload(self): clues = json.loads(vd.getNoahsPath('clues.json').read_text()) source = vd.getNoahsPath(f'puzzle{self.puznum}.md') winWidth = 78 formatted_text = source.open(encoding='utf-8').read().format(**clues) for startingLine, text in enumerate(formatted_text.splitlines()): text = text.strip() if text: for i, (L, _) in enumerate(wraptext(str(text), width=winWidth)): yield [startingLine+i+1, L] else: yield [startingLine+1, text]要点如下:
- 每个
.md题目文件在加载时会被当作 Python 字符串模板,用clues.json中的键值对做一次str.format(**clues)插值。这就是为什么 puzzle0.md 里写'2017 spec {cleanerinits}',玩家在终端里实际看到的是'2017 spec DS'——同一份题目文本,可以配合不同取值生成不同关卡变体。 - 每一行题目文本会被
wraptext按宽度 78 重新折行,作为表格的一行(rowdef: [linenum, text])展示,行号列linenum保留原始行号,文本列text宽度 80、使用displayer='full'不做截断。 - puzzle0.md 中出现的
[:bold]...[/]、[:onclick open-noahs-database]USB drive[/]是 VisiData 的内联标记语法:前者加粗关键信息,后者把文本变成可点击(可触发open-noahs-database命令)的超链接——这也是文档里 U 盘字样可以直接点击打开数据库的原因。
2.2 答案校验:Base64 与"点亮蜡烛"
solve_puzzle(tapestry.py)负责校验玩家答案:
@VisiData.api def solve_puzzle(vd, answer): puznum = vd.noahsCurrentPuznum if b64encode(answer.encode()).decode() != vd.noahsSolutions[f'p{puznum}']: vd.fail("Hmmm, that doesn't seem right. Try again?") vd.noahsTapestry.solved.add(puznum) vd.status('Correct! The candle is now lit.') vd.push(vd.noahsTapestry)机制设计值得留意:
- 答案以 Base64 形式存放在 solutions.json,而非明文。例如
"p0": "NTc3Nw=="解码后为5777,"p1": "NzY3LTM2NS03MjY5"解码后为767-365-7269。这样可以防止玩家直接翻配置文件"抄答案",同时配合import base64一行就能自校验。 - 校验通过后,当前关卡编号会被加入
Tapestry.solved集合,并在状态栏提示Correct! The candle is now lit.,随后自动切回 Tapestry 画布。
2.3 烛台与挂毯动画:Tapestry Canvas
Tapestry类继承自Canvas(tapestry.py),是游戏的"主界面":
- 加载时通过
openNoahsText读取menorah.ddw、tapestry.ddw、flame.ddw三个 DDW(Draw Data With,VisiData 的逐帧绘图脚本格式)动画资源; draw方法会根据solved集合点亮烛台对应位置的火焰,火焰动画每帧用time.time()加随机相位模拟摇曳效果;- 游戏为每次启动准备了一面完整烛台(九支蜡烛对应 9 个关卡),全部点亮即通关;同时
solvedays标签还会驱动挂毯动画逐块"显影",与剧情中"每次清理出一块动物图案"的设定呼应; - 终端尺寸小于 120x50 时会显示醒目的
WARNING: TERMINAL TOO SMALL提示(tapestry.py),所以游玩时建议开一个足够大的终端窗口。
2.4 命令注册表:整个游戏的"遥控器"
游戏的所有交互都通过 VisiData 的命令系统注册(tapestry.py):
| 按键 | 命令 | 作用 |
|---|---|---|
Shift+B | open-noahs-database | 打开 Noah's Market 数据库(noahs.sqlite) |
Shift+V | open-noahs-tapestry | 打开 Noah's Tapestry 主画布 |
Shift+N | open-puzzle-next | 打开下一个未解关卡 |
Shift+A | solve-puzzle-input | 弹出输入框,提交当前关卡答案 |
Shift+Y | solve-puzzle-cell | 把光标所在单元格的值当作答案提交 |
0–8 | open-puzzle-0…open-puzzle-8 | 直接跳转到指定关卡 |
Alt+0–Alt+8 | solve-puzzle-force-0… | (调试用)直接强制点亮某支蜡烛 |
其中Shift+Y是把"表格查询结果即答案"的思路发挥到极致:你可以在数据库中查出某个电话号码,把光标移到该单元格,按Shift+Y直接提交,无需手工抄写。
三、数据库:所有答案都藏在 noahs.sqlite 里
整个游戏的核心数据源是 visidata/experimental/noahs_tapestry/noahs.sqlite,一个 SQLite 数据库,包含 4 张表:
CREATE TABLE products ( sku text, desc text, wholesale_cost decimal(10,2), dims_cm array ); CREATE TABLE customers ( customerid integer, name text, address text, citystatezip text, birthdate text, phone text, timezone text, lat decimal(10,5), long decimal(10,5) ); CREATE TABLE orders ( orderid text, customerid text, ordered timestamp, shipped timestamp, total decimal(10,2), items array ); CREATE TABLE orders_items ( orderid integer, sku text, qty integer, unit_price decimal(10,2) );customers表记录每位顾客的姓名、住址、出生日期、电话、时区与经纬度,是绝大多数关卡"找电话号码"的答案来源;orders/orders_items/products构成完整的订单链路,用于按购买行为(如"经常光顾 Noah's"、"只买特价商品")反查人物;- 表中时间戳从 2017-01-31 开始,与"2017 年初 Alex 搭建数据库"的剧情一致;
tapestry.py通过vd.open_sqlite(vd.getNoahsPath('noahs.sqlite'))把它接入 VisiData,玩家在游戏中按Shift+B即可进入该数据库,直接使用 VisiData 的筛选、正则搜索、聚合等能力来"查案"。
实际数据形态举例(摘自customers表前两行):
1001 | Daniel Davenport | 434A Lewis Ave | Brooklyn, NY 11233 | 1984-10-04 | 716-830-1680 | America/New_York | 40.68096 | -73.93566 1002 | Cheryl Stewart | 26 E 53rd St | Manhattan, NY 10022 | 1974-01-28 | 315-592-2559 | America/New_York | 40.75878 | -73.96524四、从 Puzzle 0 到 Puzzle 8:九个关卡的任务链
Puzzle 0 是序幕,之后的 puzzle1.md 到 puzzle8.md 构成一条完整的人物链,地毯在九个人之间辗转,每一关都要在前一关人物的描述中提炼线索、回到数据库里反查下一个人的电话号码:
| 关卡 | 目标 | 关键线索 |
|---|---|---|
| Puzzle 0 | zip 压缩包密码 | 希伯来历年份(答案5777) |
| Puzzle 1 | 私家侦探的电话 | 姓名可拼成电话号码(电话键盘字母映射) |
| Puzzle 2 | 干洗承包商的电话 | 2017 spec {cleanerinits}→ 缩写DS |
| Puzzle 3 | 邻居的电话 | 星座 Libra、属相 Goat、"蜘蛛图案的帽子"、乘坐地铁、提 Noah's 购物袋 |
| Puzzle 4 | 自行车修理师的电话 | 5am 起床、抢购 Noah's 第一批糕点 |
| Puzzle 5 | Freecycle 上取毯女士的电话 | 住在 Bronx(clue5a)、猫毛、10/11 只老猫 |
| Puzzle 6 | 堂姐妹(cousin)的电话 | 名叫 Deborah(clue6a)、住 Bronx、极度节俭、地铁涨价后不再来访 |
| Puzzle 7 | 前男友的电话 | 住在 Astoria(clue7a)、与玩家在店里偶遇换购商品 |
| Puzzle 8 | 收藏家的电话 | 妹妹住 Manhattan(clue8a)、拥有整套 Noah's 收藏品 |
可以看到,clues.json 中的clue5a/clue6a/clue6b/clue7a/clue8a等字段正是为这些关卡注入的地区与姓名线索;而 solutions.json 中p1–p8的 Base64 解码结果均为形如767-365-7269的标准电话号码,印证了每一关的最终交付物。
五、如何亲手游玩
- 加载游戏:该模块位于 VisiData 的
experimental实验性目录下。以插件/模块方式加载 visidata/experimental/noahs_tapestry/tapestry.py(其顶部导入了visidata的 Sheet、Canvas、Animation、Path、asyncthread 等基础设施,底部通过vd.addCommand注册全部游戏命令),之后:- 按
Shift+V打开 Tapestry 主画布; - 在画布上按
Shift+N打开第一个关卡(Puzzle 0); - 阅读题目后按
Shift+B进入 Noah's Market 数据库开始查案; - 得出答案后按
Shift+A输入,或把光标停在答案单元格上按Shift+Y提交。
- 按
- 通关验证:正确提交后状态栏会出现
Correct! The candle is now lit.,画布上对应的蜡烛被点亮;九关全通即可点亮整面烛台并看完全部挂毯图案。 - 答题辅助:每个题目的
guide都会显示当前关卡号与快捷键提示(见 tapestry.py);若只想体验剧情,也可用Alt+数字调试命令直接点亮对应蜡烛。
六、从"玩"到"做":这个游戏教了你什么
Noah's Tapestry 表面上是一个解谜游戏,实际上是一套完整的 VisiData 技能训练场:
- 数据探查:在
customers/orders中做筛选、正则搜索、排序、聚合,把剧情线索(星座、属相、住址、购买习惯、时区)翻译成 SQL 语义或 VisiData 列操作; - 跨表关联:
orders_items↔products↔customers的 join 能力是打通"谁在什么时候买了什么"的关键; - 终端内交互:
[:onclick ...]内联超链接、Sheet的iterload动态生成、Canvas动画与Animation(ddw)脚本,展示了 VisiData 作为"可编程数据终端"的扩展能力; - 代码即游戏引擎:
clues.json + str.format的模板插值、solutions.json + Base64的答案校验、按solved集合驱动动画状态,这些模式完全可以复刻到你自己的"数据闯关"教学或演示场景中。
如果你对实现细节感兴趣,可以从三个文件入手精读:tapestry.py(游戏主逻辑)、clues.json(线索模板)、solutions.json(答案表),再对照 noahs.sqlite 的 4 张表动手验证每一关的推理链条。
- 数据分析
- CLI
- 数据可视化
【免费下载链接】visidata
A terminal spreadsheet multitool for discovering and arranging data
相关推荐
用 VisiData 追踪清洗承包商:Noah's Tapestry Puzzle 2 数据解谜实战
用 VisiData 追踪清洗承包商:Noah's Tapestry Puzzle 2 数据解谜实战 本篇技术指南以 VisiData 仓库实验项目 Noah'
数据分析CLI数据可视化用 VisiData 与 SQL 解开 Noah's Tapestry 谜题 5:追踪 Freecycle 取毯人的电话号码
用 VisiData 与 SQL 解开 Noah's Tapestry 谜题 5:追踪 Freecycle 取毯人的电话号码 导读:本文以 VisiData 仓
数据分析CLI数据可视化VisiData 数据解谜实战:Noah's Tapestry 第 1 关——用 T9 号码映射找出私人侦探的电话
VisiData 数据解谜实战:Noah's Tapestry 第 1 关——用 T9 号码映射找出私人侦探的电话 本篇指南围绕 VisiData 仓库中的交互
数据分析CLI数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考