cpp-display-skill
/cpp-display-skill
把一段算法代码做成「逐行执行演示」单页 HTML —— 左侧源码随执行高亮并自动滚动,右侧数组/栈/链表/树/图/DP 表同一帧内同步变化,带播放、暂停、单步、重置、调速、键盘控制和深链接。内置「题型 → 画法」选型手册:递归、DFS、BFS、回溯、各类 DP、双指针、滑动窗口、单调栈/队列、堆、链表、树、图、二分、贪心、位运算各自该画什么图。当用户说"做个演示页 / 动态演示 / 一步步看这段代码怎么跑 / 画个 html 展示流程 / 代码和画面联动 / 像上次那样再做一个 / 我想直观看到执行过程 / 这题该画什么图 / 这题怎么可视化 / 递归怎么演示 / DFS BFS 怎么画"时使用。
- Author
- Yaki-Blake ↗
- Repository
- cpp-display-skill ↗
- Licence
- MIT
- Last repository change
- 11.11.2026
Assessment and security
- Files in this package
- 3
- Scripts
- 0
- HTML comments
- 0
- URLs in SKILL.md
- 0
No signals detected. Repository changed on 11.11.2026, without references to retired models or APIs.
MIT, open: use and modify while retaining author attribution.
What it does and when to use it
把一段算法代码做成「逐行执行演示」单页 HTML —— 左侧源码随执行高亮并自动滚动,右侧数组/栈/链表/树/图/DP 表同一帧内同步变化,带播放、暂停、单步、重置、调速、键盘控制和深链接。内置「题型 → 画法」选型手册:递归、DFS、BFS、回溯、各类 DP、双指针、滑动窗口、单调栈/队列、堆、链表、树、图、二分、贪心、位运算各自该画什么图。当用户说"做个演示页 / 动态演示 / 一步步看这段代码怎么跑 / 画个 html 展示流程 / 代码和画面联动 / 像上次那样再做一个 / 我想直观看到执行过程 / 这题该画什么图 / 这题怎么可视化 / 递归怎么演示 / DFS BFS 怎么画"时使用。
Original author description
把一段算法代码做成「逐行执行演示」单页 HTML —— 左侧源码随执行高亮并自动滚动,右侧数组/栈/链表/树/图/DP 表同一帧内同步变化,带播放、暂停、单步、重置、调速、键盘控制和深链接。内置「题型 → 画法」选型手册:递归、DFS、BFS、回溯、各类 DP、双指针、滑动窗口、单调栈/队列、堆、链表、树、图、二分、贪心、位运算各自该画什么图。当用户说"做个演示页 / 动态演示 / 一步步看这段代码怎么跑 / 画个 html 展示流程 / 代码和画面联动 / 像上次那样再做一个 / 我想直观看到执行过程 / 这题该画什么图 / 这题怎么可视化 / 递归怎么演示 / DFS BFS 怎么画"时使用。
How it works
- 一、先判断该不该做
- 二、题型 → 画法(先选型,再动手)
- 三、七条铁律
- 四、五步流程
- 五、指针与连线
- 六、文件
- 七、TRACE 标记,别删
- 八、规模参考
SKILL.md preview
---
name: cpp-display-skill
description: 把一段算法代码做成「逐行执行演示」单页 HTML —— 左侧源码随执行高亮并自动滚动,右侧数组/栈/链表/树/图/DP 表同一帧内同步变化,带播放、暂停、单步、重置、调速、键盘控制和深链接。内置「题型 → 画法」选型手册:递归、DFS、BFS、回溯、各类 DP、双指针、滑动窗口、单调栈/队列、堆、链表、树、图、二分、贪心、位运算各自该画什么图。当用户说"做个演示页 / 动态演示 / 一步步看这段代码怎么跑 / 画个 html 展示流程 / 代码和画面联动 / 像上次那样再做一个 / 我想直观看到执行过程 / 这题该画什么图 / 这题怎么可视化 / 递归怎么演示 / DFS BFS 怎么画"时使用。
---
# 代码逐行演示页
把一个算法变成能按帧播放的 HTML:左半边是源码,右半边是数据。**同一帧里**代码高亮到某一行,数据就呈现出那一行刚执行完的状态。
这个技能的全部价值在"同一帧"三个字。不是左边贴代码、右边贴答案图,而是**一次刷新同时改两边**,让人能一格一格地看见变量怎么变、指针怎么动、答案怎么攒起来。
## 一、先判断该不该做
**该做**
- "做个演示页 / 动态演示 / 一步步看它怎么跑"
- "代码和画面联动"
- 用户卡在"想不出执行过程",讲了三遍还是晕
- "像上次那个一样,再做一个"
**不该做**
- 只是问概念 —— 先讲,讲不清再提议做页面
- 输入规模巨大(n > 20)—— 演示页装不下,先缩小到 4~8 个元素
- 只要一张流程图 —— 那是 diagram 类工具的事,不需要执行引擎
## 二、题型 → 画法(先选型,再动手)
**动手前必须先回答"这道题该画什么"。** 不确定就别开写——图选错,代码写得再漂亮也是废页。
完整版(含每种题型的识别信号、特有要素、坑)见 `references/problem-shapes.md`,本节是主干索引。
### 三步定图法
**① 看代码骨架,别看题目故事。** 题目讲"小猫跳台阶"还是"股票买卖"不重要,看最外层结构长什么样:
| 骨架 | 本质 | 画什么 |
|---|---|---|
| 一个函数调自己,只调一次 | 线性递归 | **调用栈帧**(不是树!) |
| 一次调两个以上自己 | 分治 / 树形递归 | 递归树 + 返回值上行箭头 |
| 递归 + `pop_back` 撤销 | 回溯 | 决策树 + 路径高亮 + 剪枝打叉 |
| `queue` + 层循环 | BFS | 网格**波纹**(同层同色) |
| 四方向 + `visited` | DFS | 网格(蓝路径 + 灰死路) |
| `dp[i] = f(dp[...])` | 填表 | 表 + **依赖来源箭头** |
| `while` 弹栈顶 | 单调栈 | 横排栈 + 结算名单 |
| `deque` 两端进出 | 单调队列 | 窗口 + 双端队列 |
| `priority_queue` | 堆 | **完全二叉树**(上浮下沉) |
| `while (lo <= hi)` 折半 | 二分 | 行 + `[lo, hi]` 括号收拢 |
| `while (parent[x] != x)` | 并查集 | **森林**(同树同色) |
| 改 `next` 指针 | 链表 | 固定槽位 + 弧线 |
**递归要多问一句 —— 它有五张脸。** 骨架对上"递归"之后,还得挑画法:
| 画法 | 什么时候用 | 模板 |
|---|---|---|
| 调用栈帧 | 只线性调自己,观众只需知道「现在第几层」 | `recursion.html` ① |
| 递归树 | 要看清分叉结构本身,且一屏放得下 | `recursion.html` ② |
| 决策树 + 路径高亮 | 有 `pop_back` 撤销,路径本身就是答案 | `recursion.html` ③ |
| 记忆化砍子树 | 有 `memo` 命中,要看见"整棵子树被砍掉" | `recursion.html` ④ |
| **代码切块** | 难点是「回到谁 / 参数谁给的 / 多层局部值同时在世」,或树宽深到一屏塞不下 | `recursion-blocks.html` |
前四种画的是**结构**,切块画的是**执行流**(纵轴时间、横轴深度,进入接右下方、返回卡接左下方)。"什么时候该切块、什么时候千万别"的判别式见 `references/block-tiling.md` 第一节。
**② 找"单调变化量"。** 算法里一定有个东西只朝一个方向走,那就是用户该盯的东西:窗口伸缩、区间折半、填表推进、路径进退、波前扩散、集合合并。
> **图要画"算法正在改变什么",不是画"数据长什么样"。**
> 数据长什么样看输入就知道了,动画的唯一价值是展示**变化**。
**③ 配辅助视图 —— 先给变量算权重,再决定谁上屏。** 这条最容易做塌。漏掉一个关键变量,学习者的思维链就在那一行断掉:他看见 `row.push_back(cur->val)`,屏幕上却找不到 `row`,只能自己脑补"篮子现在装了啥",一脑补错,后面全错。
**先列全。** 把源码里出现过的每个变量名抄下来,一个不落:入参、局部变量、容器、循环变量、临时结果、判定标志。**这一步先不做取舍。**
**再打分,按「影响面 × 变化频率」。** 别用"重不重要"这种虚的判断,看两个能数出来的东西:
| 影响面 \ 变化频率 | 频繁变化(每步 / 每轮) | 整个函数恒定 |
|---|---|---|
| **决定走向 / 承载答案** | **A 级 → 独立区块** | **C 级 → note 带一句** |
| **只服务当前这一行** | **B 级 → 变量表** | **C 级 → note 带一句** |
落成表:
| 级别 | 典型变量 | 展示方式 |
|---|---|---|
| **A1 答案形态** | `res`、`row`、`path`、`ans`、`dp` 表、`out` | **独立区块,完整展开**,逐项 / 逐层可见 |
| **A2 走向开关** | `cur`、`node`、`lo`/`hi`、栈 / 队列内容、`visited`、双指针 | **独立区块**;画在树 / 图 / 网格上标色也算 |
| **B 过程量** | 循环下标 `i`、计数器、长度、临时比较值 | 变量表,一行一个 |
| **C 可省** | 常量、哨兵、全程不变的句柄(如 `root`、`MOD`、`dummy`) | 不占位,note 里写清它的值即可 |
> **"不变"指的是整个函数执行期间恒定**,不是"这一轮没变"。像层序遍历的 `n`,每轮开头重新赋值一次,它划定了 `for` 的边界,属于 **A2**,不能因为"这轮没动"就降成 C。判错的后果很实在:`n` 一藏,"为什么只处理这一层"就失去了画面依据。
**版面真挤的时候按这个顺序保**:**A1 答案形态 > A2 走向开关 > B 过程量 > C**。先砍 `i`、`count` 这类,绝不砍 `row`、`path`、`res`。
**最后做「逐行投影」自检**(交付前必做,比看截图管用)。从第 1 行走到最后一行,每行执行完问两句:
1. 这一行**改了谁**?它此刻在屏幕上有位置吗?
2. note 里**提到了谁**?那个名字在屏幕上找得到吗?
两句有一个答"没有",就算漏:**关键变量补视图,非关键变量在 note 里写清数值**。宁可少画一个装饰,不能少画一个变量。
**三类变量最容易漏,见到就必须给位置:**
1. **每轮清空重建的累积容器**(`row` / `path` / `curPath` / 当前层)。它恰恰是"一层""一条路径"这些概念的**实体**——层序遍历把 `row` 藏了,"为什么最后是二维数组"就彻底没法看了。
2. **刚被 push / 刚被改写的那一格**。不画出来,读者只能猜"进没进去、进了第几个、覆盖没覆盖"。
3. **决定分支的判定结果**(`cmp`、`ok`、`q.empty()` 的真假)。它决定循环走不走,藏起来整段逻辑就是黑箱。
### 最容易画错的三件事
| 错误 | 为什么错 | 正确做法 |
|---|---|---|
| 递归一律画成树 | 线性递归的递归树是一条**斜线**,画树反而更难懂 | 线性递归用**调用栈帧**竖着堆 |
| DFS 和 BFS 画成一样 | 两者视觉必须能一眼区分,否则白做 | DFS = 一条**线**在爬;BFS = 一圈**波**在扩 |
| 堆 / 并查集画成数组 | 数组看不出"上浮下沉""合并"这些动作 | 堆画**树**;并查集画**森林** |
### 版面分三块,变量可以不止三块
```
┌─────────────────┬──────────────┐
│ 主动图 (60%+) │ A 级容器视图 │
├─────────────────┴──────────────┤
│ B 级变量表 + 当前语句说明 │
└────────────────────────────────┘
```
"三块"限的是**视觉分区**,不是变量个数。A1/A2 级变量有几个就开几个小区块(`res` 一块、`row` 一块、队列一块),并排塞在右上或右下都行,只要不抢主动图的面积。**B 级变量才有资格挤进那一行小格子表**——把 A 级变量往变量表里塞,等于没画。
> **"并排"不是审美偏好,是高度预算逼出来的。** 1440×900 下右栏只有约 800px,主图吃掉一半,剩下一半最多放 **2 个并排的 A 级容器 + 1 行变量表**。竖排堆到第 4 块,必然出屏(见铁律⑦)。
**同一个数据结构不要画两遍**:网格里已经显示 `dist` 了,就别再来一张 dist 表。
### 什么时候不该画
说不出"这一步在动什么",就不要画。纯数学推导、答案只依赖规模、20 行以内模板题、状态超过两屏——都直接讲。
## 三、七条铁律
缺一条这个页面就不成立。
**① 联动必须同帧。** 不许左边先跳到第 20 行、右边还停在第 3 步。所有状态来自同一个 `steps[idx]`。
**② 每步都要 `line` 和 `note`。** `line` 是 1-based 行号;`note` 是人话,**必须带具体数值**。
- 反面:"进入 while 循环"
- 正面:"栈顶是 2 号(值 1),1 < 4 → 成立,2 号的答案确定是 4"
note 是这个页面的灵魂,写得含糊等于没做。
**③ 六个控件一个不能少。** 播放 / 暂停 / 上一步 / 下一步 / 重置 / 速度(至少三档)。再加键盘 `←` `→` `空格`。
**④ 快照必须深拷贝。** `a.slice()`、`[...st]`、`{...V}`。漏了就会出现"所有历史步都显示最终状态"这种一眼假的 bug,而且极难查。
**⑤ 没验证过不给。** 必须跑 `scripts/verify-demo.js`:语法 + 对拍 + 行号 + 截图 + **`--fit` 一屏适配**。**数字不对的演示页比没有更害人**——学生会把错的过程当成对的记进去;**一屏装不下的演示页同样废**——读者滚来滚去,联动看不出,等于白做。
**⑥ 变量不许失踪。** 三个集合必须一层套一层:
```
屏幕上画出来的变量 ⊇ note 里提到的变量 ⊇ 影响控制流的变量
```
少任何一环,学习者就会在那一行卡住。这一条比"版面好看""区块数量少"重要得多——**宁可页面挤一点,也不能让读者自己去脑补某个变量的当前值**。
**⑦ 一屏装得下。能在一屏讲完的,绝不许溢出到第二屏。**
这条和⑥是一对:⑥ 说"不许少画变量",⑦ 说"不许为此把内容顶出屏幕"。两条同时满足才算做完——**做法是压版面,不是删变量、也不是加屏幕**。
判定标准(1440×900,三帧抽查:首帧 / 中帧 / 末帧):
| 指标 | 合格线 | 不合格长什么样 |
|---|---|---|
| 整页 `scrollHeight` | ≤ 视口高 | 页面本身能上下滚,顶栏被滚走 |
| 整页 `scrollWidth` | ≤ 视口宽 | 出现横向滚动条 |
| **右侧数据栏 `scrollHeight`** | **≤ `clientHeight`,1px 都不许超** | 读者要滚右栏才看得到 `total` / 变量表 |
| 右侧数据栏 `scrollWidth` | ≤ `clientWidth` | 有内容挤到右边看不见 |
**右栏一出现滚动条,这一帧就毁了**:读者盯着树看到了变化,却要往下滚一下才知道总数变成了多少,两个信息被拆到两个屏幕位置,每步都要滚一次,联动就断了——这正是"一屏塞不下"最伤的后果。
**四条执行纪律:**
1. **先算高度,再排块。** 右栏可用高 ≈ 视口高 − 顶栏(≈52) − 内边距(≈32)。1440×900 大约剩 **800px**。常见分配:主图 440~520、A 级容器每个 110~140、变量表 50~70、本轮算式 50~70。**先加一遍,超了就不许加块**——不是写完再回头压。
2. **宁可并排,不要竖排到底。** 两个以上 A 级容器用 `grid-template-columns: 1fr 1fr` 并排,别一层层往下堆。第三个块出现时先问"能不能塞进主图旁边的空位",塞不进才考虑缩小。
3. **挤了先压间距,最后才砍 C 级。** 顺序是:`--gap` / `padding` / 字号 → 合并次要说明 → 砍 C 级。**A1 / A2 一个都不能砍**(呼应⑥)。反过来也不许"多塞":明明一屏能讲完,非要加一个装饰性小标题把它顶出去。
4. **主图尺寸跟着可用高度走,不写死 px。** 容器 `flex:1; min-height:0`,树 / 网格 / 表格用 `height:100%` + `viewBox` 自适应。写死 `height:420px` 的树,在 900 高的屏上必溢出,在 1080 上又空一大片。
**别靠肉眼看截图判断。** 缩略图会把 16px 的溢出看丢(真的踩过)。用 `--fit` 量真实 `scrollHeight`:
```bash
node scripts/verify-demo.js <demo.html> --fit
```
它会直接告诉你「第 N 步 右侧数据栏 717 > 701(超出 16px)」,比盯着图猜准得多。
### 排版四件套(模板自带,不要另发明)
这四处是学习者**用眼睛找不到东西**的地方,模板已经内置,复制骨架时**照抄,别自己重写**:
**① 代码区必须是三列:行号 | 代码 | 注释。**
| 列 | 规则 | 为什么 |
|---|---|---|
| 代码 | `white-space: pre`,**永不折行** | 代码折行 = 一行变两行,行号对不上 |
| 注释 | `white-space: pre-wrap`,**自己折行** | 注释是给人读的,折行不影响语义 |
| 注释起点 | 用 `scrollWidth` 量出最长代码行,写进 `--code-col-w`,所有行**共用** | 每行各算各的列宽,注释会参差不齐 |
**必须用 `scrollWidth`**:`getBoundingClientRect().width` 量到的是被列宽压扁后的宽度,会算出一个偏小的列宽,结果代码直接压在注释上,字叠字。
注释列被超长代码行挤到不足 180px 时,自动加 `.cl.flow` **降级成行内跟随**(注释退回代码行尾、代码自己也允许折行)。判断放在 `updateCodeMode()` 里,拖动和 resize 后重算。**不要为"注释对齐"硬撑**——挤成三行高的碎条比不对齐更难读。
**② 代码区宽度可拖。**
`main` 三列 `var(--code-w) 15px minmax(0,1fr)`,中间那条 `.resizer` 拖动改 `--code-w`。钳制规矩:
- 下限 = **代码列宽 + 170**,保证拖到最窄时注释列还在,不会被整块裁掉
- 上限 = 总宽 − 右侧数据区 400
- `resize` 时用 `getBoundingClientRect().width` 实测,**别读 `--code-w`**(它可能是百分比,`parseFloat('46%')` 会得到 46)
- 深链接带宽度:`#step=30&w=700`
**③ 变量区:竖排单列,紧凑胶囊。**
```css
.vars { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.v { display: inline-grid; grid-template-columns: 40px auto; column-gap: 8px; min-width: 132px; }
```
**禁用 `justify-content: space-between`。** 名字贴左、值贴右,中间空一整行,三个变量还并排横跨全宽——眼睛要在面板上左右横跳才能把"谁等于几"连起来。名字和值**紧挨着**,一行一个,扫一眼就到底。
**④ 文字视觉对齐:先对齐,再谈好不好看。**
字没对齐不会报错,但读者会莫名觉得"这页很糙",而且**说不出哪儿不对**——那点不适感最后全变成"看不懂"。
| 症状 | 真实原因 | 修法 |
|---|---|---|
| 数值一变,整列数字左右抖 | `1` 比 `8` 窄,比例字体下宽度不同 | `font-variant-numeric: tabular-nums` + 计数列 `text-align: right` |
| 同一行"标题 + 小字"看着一高一低 | `align-items: center` 对的是**盒心**,字号不同就错开几 px | 换成 `align-items: baseline` |
| 右栏几块内容左边缘差 1~2px | 每块各自设 `padding`,或某块单独加了 `margin` | 父级统一 `padding`,各块 `padding-left: 0` |
| 树节点上下两行一个居中一个偏左 | 两行继承了不同的 `text-align` | 成对的两行用**同一个** `text-align` + 同一行高 |
| `5` 换成 `13` 时整块宽度跳一下 | 格子没定 `min-width` | `min-width: 2.5em`,`—` 占位与数字**同宽** |
| 换个字号/换台机器就散架 | 拿 ` `、空格凑对齐 | 一律 `grid` 列或 `flex` + 固定 `column-gap` |
三条底线:
- **数字必须等宽**。`font-variant-numeric: tabular-nums` 加在容器上,全部子孙生效;计数列再加 `text-align: right`。
- **成对元素用基线对齐**(名字↔数值、标题↔小字、图例点↔文字),**只有图标**才用 `center` + `line-height: 1`。
- **不许用空格排对齐**。空格宽度随字体和系统变,本机齐、换机器歪,而且改一个字就全散。
```css
/* 数值区:等宽数字 + 右对齐,变化时宽度不抖 */
.vals { font-variant-numeric: tabular-nums; }
.vals .num { text-align: right; min-width: 2.5em; }
/* 名字 + 值:左贴左、紧挨、基线对齐 */
.v { display: grid; grid-template-columns: 42px auto; column-gap: 8px; align-items: baseline; }
```
顺便一条:中文和数字**不要敲空格字符**分隔(`共 3 个` 里的空格随字体变宽),要留空就用 CSS `margin` 或全角标点。
> 这四条都属于"不改就算没做":**代码被裁、注释被吞、变量要横跳去找、文字对不齐**,任何一个出现,页面就算做完了也是废页。
## 四、五步流程
### 第 1 步:定行号
把源码原样放进模板字符串再 `.split('\n')`,**行号 = 数组下标 + 1**。
先建一张行号常量表,生成器里只引用它:
```js
const L = { readN: 7, alloc: 8, readA: 9, initAns: 11, loop: 14, cmp: 16, fill: 17 };
```
改代码只改 `L` 一处。**直接在 yield 里写死行号是最常见的翻车原因**——加一行注释,后面全错位,而页面照样能跑,只是高亮错了,很难发现。
### 第 2 步:写生成器 `function* run(input)`
每个 `yield` 就是一次屏幕刷新。粒度规矩:
- **一条 C++ 语句 = 一次 yield**
- 一条语句里有多个可见变化(如 `while` 判断 + 弹栈)→ 拆成多次 yield,`line` 相同、时刻不同
- 读输入的循环 → **每个元素一次 yield**,别一次刷完,用户想看数组一格一格被填满
- 预处理(建表、前缀和)→ 可以合并,也可以逐项,看它是不是理解的关键
快照契约:
| 字段 | 类型 | 必填 | 作用 |
|---|---|---|---|
| `line` | int | 是 | 高亮第几行,1-based |
| `note` | string | 是 | 这步在干什么,带数值 |
| `phase` | string | 否 | 顶部阶段标签,如 `init` / `main` / `done` |
| `vars` | object | 建议 | 变量表,键是显示名 |
| `tag` | object | 否 | 视觉强调,如 `{changed: 2, ptr: 'cur'}` |
| 数据结构副本 | 各自 | 是 | `a.slice()` / `[...st]` / 链表 `nx` 映射表 |
**每个 A 级变量在快照里必须各占一个字段**:`row: row.slice()`、`res: res.map(r => r.slice())`、`q: q.map(x => ({ ...x }))`。
变量失踪多半不是渲染时忘画,而是**快照里根本没存**——生成器写顺手了只存了 `res`,回头想加 `row` 那块就得重跑一遍所有 yield。**先按分级表把字段摆齐,再写渲染。**
变量表用 `null` 表示"此刻不适用",渲染成 `—`。**别增删行**,行数一变整个面板会跳。
### 第 3 步:写渲染
`render()` 是纯函数:`steps[idx]` → DOM。拆成小块,一个函数管一块,好改也好删:
```js
renderArray(s); renderStack(s); renderVars(s); renderAns(s);
```
**A 级变量各占一个 render 函数**(`renderRow` / `renderQueue` / `renderRes`),别合并。合进去的变量最容易被"版面太挤"顺手删掉,独立函数就是它的护身符。
代码高亮就三行:
```js
codeEls.forEach(el => el.classList.toggle('active', +el.dataset.line === s.line));
const act = codeEls.find(el => +el.dataset.line === s.line);
if (act) act.scrollIntoView({ block: 'nearest' });
```
但**构造代码行**不是三行——必须按「行号 / 代码 / 注释」三列拆开,注释才能自己折行不被裁。整段照抄模板里的 `CODE.forEach(...)` + `alignCommentColumn()` + `updateCodeMode()`,别自己简化成 `<span>整行文本</span>`。
配色语义全局统一,四个色贯穿整个页面:
| 颜色 | 含义 |
|---|---|
| 琥珀 | 本步正在改的 |
| 蓝 | 已纳入 / 在结构里等待 |
| 绿 | 已结算 / 完成 |
| 红 | 负值 / 异常 / 被弹出 |
尺寸、字体、配色见 `references/rendering.md`。**只要画面上要出现指针、箭头或连线,先读下一节再动手。**
### 第 4 步:接控制
**播放用 `setTimeout` 递归,不用 `setInterval`** —— 变速时不用清旧定时器。
```js
const tick = () => {
if (!playing) return;
if (idx >= steps.length - 1) { stop(); render(); return; }
idx++; render();
timer = setTimeout(tick, speed);
};
```
**所有单步操作前先 `stop()`**,否则播放中点"下一步"会有两个定时器打架,步进速度翻倍。
播放到末尾自动停。`←` `→` 单步,空格播放暂停,空格要 `e.preventDefault()`(否则页面滚动)。
再挂一个深链接,方便截图和分享:
```js
const m = location.hash.match(/step=(\d+)/);
if (m) idx = Math.min(Number(m[1]), steps.length - 1);
```
### 第 5 步:校验 + 交付
```bash
node scripts/verify-demo.js <demo.html>
```
检查项:JS 语法 → TRACE 块能否提取 → 步数 → 行号越界 → note 空值 → 重复步 → **有 `brute()` 就自动对拍**。
**再量一屏适配**(必做,别跳):
```bash
node scripts/verify-demo.js <demo.html> --fit
```
它把探针注进页面里量真实尺寸,抽查首帧 / 中帧 / 末帧,报三种超屏:整页出屏、**右侧数据栏要滚**、右栏横向挤出。默认按 1440×900 量,超 1px 都算错,会直接打印超出多少 px。顺带它还会**量右栏各块的左边缘**,差超过 3px 就提醒"这几块没对齐"(这是排版四件套④的机器判据)。
再截关键帧看布局:
```bash
node scripts/verify-demo.js <demo.html> --shot 0 --shot 8 --shot last
```
重点看五样:连线有没有被格子盖住、指针标签有没有叠在一起、**有没有哪一行注释被右边裁掉或压住代码**、**中缝拖到最窄时注释列还在不在**、变量区是不是一行一个。
交付前把这张单子过一遍:
- [ ] 第一步列出的**变量清单**还在,每个名字都能在分级表里找到归属
- [ ] 每个 **A 级**变量有独立区块,不是挤在变量表里
- [ ] 每个 **B 级**变量在变量表有一行,且行数全程不变
- [ ] **C 级**变量在 note 里查得到当前值
- [ ] 逐行投影过一遍,没有"改了却没显示"的行
- [ ] **`--fit` 通过**:整页不出屏、右侧数据栏不滚(超 1px 都算错)
- [ ] 1440×900 下**所有注释完整可见**,代码和注释没有叠字
- [ ] 中缝能拖、拖到最窄注释列仍在;`Shift + 滚轮` 能横向滚代码区
- [ ] 变量区竖排单列、名字与数值紧挨(没有 `space-between`)
- [ ] **文字视觉对齐**:右栏各块左边缘共线;数值列 `tabular-nums` + 右对齐;成对文字用 `baseline` 而不是 `center`;全程没有用空格/` ` 凑对齐
最后 `present_files` 打开,并告诉用户:改哪个常量换数据、一共几步、最终结果是什么、你对拍过哪些数据。
## 五、指针与连线
数组和栈用 `div` 就够了。**一旦画面上出现「谁指向谁」,就必须上 SVG** —— 链表、双指针、树、图、并查集,全是这一类。
五条规则,按踩坑的惨烈程度排:
### ① 位置固定,只动箭头
节点从左到右排死在原地,**永远不重排**。
按 `next` 顺序排看着自然,实则两处致命:一是节点会在反转过程中左右乱跳,人眼跟不上;二是"某个节点暂时没人指向"的那一刻,它会直接从遍历序列里掉出去,看起来像凭空消失的 bug。
固定槽位之后,这一切都变成"**那根箭头没了,但位子还在**"。想知道 `#2` 指向谁,就看从它底下出来那根箭头落在哪。某个格子**一根箭头都没有** = 没人指向它,位子不动,只把边框改成红虚线。
### ② 向右走下弧,向左走上弧
方向一分,看图就能立刻判断箭头朝哪边,不用顺着线找半天。
| 方向 | 绕哪边 | 出入点 |
|---|---|---|
| 向右(`to > from`) | 格子**下方** | 源格右下 → 目标格左下 |
| 向左(`to < from`) | 格子**上方** | 源格左上 → 目标格右上 |
```js
`M ${x1} ${botY} C ${x1} ${botY + d}, ${x2} ${botY + d}, ${x2} ${botY + 2}` // 下
`M ${x1} ${topY} C ${x1} ${topY - d}, ${x2} ${topY - d}, ${x2} ${topY - 2}` // 上
```
### ③ 终点落在格子外面 2px
SVG 先画、格子后画。终点写进格子内部会被整个盖住,只剩一根**没头的线** —— 画面上看着像"箭头丢了",其实是被压住了。
同理,任何压在节点上的装饰(标签、光晕、高亮框)都要在节点**之后**画。
### ④ 撞了就加大弧度
同方向的两条弧,如果水平区间重叠、深度又差不到 6px,会叠成一条粗线,分不清谁连谁。
贪心分配:按"水平区间是否重叠 + 深度是否差不到 6px"判断撞车,撞上就加一档(24~26px)再试,最多叠 30 次。完整 `assign()` 见 `references/rendering.md`。
**向上和向下两组必须分开分配。** 它们在画布上下两侧,物理上撞不到;混在一个列表里算会互相挤压、白白加深好几档。先按方向拆成 `ups` / `downs` 两个数组,各自调一次。完整写法见 `references/problem-shapes.md` 附录 D。
另外弧高要跟着跨度走:`d = base + k * max(0, dist - 1)`,否则跨度小的弧只有 20 多像素,像格子上长了个小包。
### ⑤ 指针标签只占一层
`prev` / `cur` / `nxt` 这类标签,**只能占一层高度**。
我试过叠三层(y = 90 / 112 / 134)想让它们互不遮挡,结果向上绕的弧线直接横穿三层标签,比叠在一起还乱。单层 + 每个指针固定横向 `dx` 偏移,是唯一稳的解法:
```js
const ptrs = [
{ name: 'prev', dx: -13, color: '#1d5fa8' },
{ name: 'cur', dx: 0, color: '#b8392f' },
{ name: 'nxt', dx: 13, color: '#2c7a4b' }
];
```
两层东西抢同一块空间时,**永远不要靠"多层"解决,要分层级**:标签占最上面一层,弧线抬到标签之上,两者各占各的高度。
### 坐标基线
```js
const boxW = 74, boxH = 58, gapX = 52;
const spacing = boxW + gapX;
const padX = 36;
const topY = 168, botY = topY + boxH;
const OFF = 24; // 出入点离格子中心的横向偏移
const cx = id => padX + id * spacing + boxW / 2;
const W = padX * 2 + n * spacing + boxW;
```
完整实现(含 SVG 分组、悬空态判定、箭头 marker)见 `references/rendering.md` 第四节。
## 六、文件
| 文件 | 用途 |
|---|---|
| **`references/problem-shapes.md`** | **题型 → 画法。动手前对着它选型:25 种题型(递归 / DFS / BFS / DP / 指针 / 单调 / 堆 / 二分 / 排序贪心 / 位运算)+ 链表 7 / 树 8 / 图 7 变体表,每种的识别信号、主视图、辅助视图、坑** |
| **`templates/recursion.html`** | **递归四形态可跑模板:① 栈帧 ② 递归树 ③ 决策树 ④ 记忆化砍子树。四种完全不同的画法,改成别的递归题只需换 `CODE` + `run()`** |
| **`templates/recursion-blocks.html`** | **递归第 5 种画法:代码切块。一块 = 一次调用,进入接右下方、返回卡接左下方,连成蛇形回路;左栏完整源码逐行高亮,块内只留结果。内置 437 示例** |
| **`templates/search.html`** | **搜索三件套可跑模板:① DFS 网格(蓝路径+灰死路+方向圆点)② BFS 波纹(同层同色+队列)③ 拓扑排序(DAG+入度递减)** |
| `templates/trace-demo.html` | 单场景骨架,内置单调栈示例。复制改名,替换 `CODE` / `INPUT` / `run()` / 三个 `render*` |
| `scripts/verify-demo.js` | 单场景校验 + 截图;**加 `--fit` 量一屏适配(整页出屏 / 右栏滚动)** |
| **`scripts/verify-scenes.js`** | **多场景校验:逐个 tab 跑 `run()`、查行号/note、验深拷贝、真的调一次 `render()`、按 `#scene=x&step=N` 截图** |
| `references/rendering.md` | 数组、栈、链表、树、DP 表的画法;**第四节是指针与箭头全套** |
| **`references/block-tiling.md`** | **代码切块:布局公式 / 相机 / 箭头 / 七条坑。第一节是「切块 vs 右栏罗列数据」的判别式,选型前先读它** |
| `references/pitfalls.md` | 踩过的坑,动手前扫一眼(5~8 条全是箭头相关) |
**多场景模板的额外约定**:`TRACE` 块除了 `INPUT` / `CODE` / `run`(默认场景),还要导出 `allRuns` / `allCodes` / `allScenes` / `expect`,这样 `verify-scenes.js` 能一次验完所有 tab。`expect` 支持 `{path, value}` 和「函数返回 true 或错误说明」两种写法。
## 七、TRACE 标记,别删
模板里这两行是校验脚本的锚点,必须原样保留:
```js
/* ==TRACE-BEGIN== */
const INPUT = ...; const CODE = ...; function* run(...) {...} function brute(...) {...}
/* ==TRACE-END== */
```
块内**只放纯计算,不许碰 DOM**。脚本会把这一块单独抽出来丢进 node 运行,`document` 一出现就崩。
`brute()` 是可选的暴力解法,返回值和最后一步快照里的 `ans`(或 `result`)同构即可。写了就自动对拍,强烈建议写——这是唯一能证明"演示的过程是对的"的手段。
## 八、规模参考
| 内容 | 舒服的步数 |
|---|---|
| 一个循环 + 少量判断 | 30 ~ 60 步 |
| 双指针 / 单调栈 | 40 ~ 80 步 |
| 链表反转 / 递归展开 | 60 ~ 120 步 |
| 代码切块(蛇形回路) | 40 ~ 120 步(一次调用约 4~6 块,树节点别超 9 个) |
超过 200 步就该考虑拆成两页,或者把预处理阶段合并掉。
Files in this package (3)
- LICENSE 1 KB
- README.md 5 KB
- SKILL.md 30 KB
Versions
The database has no other skills named cpp-display-skill.
Database snapshot: 13.11.2026 · package version 5df5ff049dd8 (SHA-256 digest of the entire package)
Downloads in the last 30 days: 0 · ranking
Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.
Similar skills
- 100dsa-graphs-bfs-dfs-topological-sort
Use this skill when traversing network graphs, finding shortest paths in unweighted graphs, detecting cycles, or determining build orders. - 100dsa-trees-binary-search-tree-dfs-bfs
Use this skill when traversing, validating, serializing, or balancing binary trees and binary search trees using recursion and queues. - 100graph-algorithms-interviewer
A mid-level software engineering interviewer specializing in graph algorithms. Use this agent when you want to practice BFS, DFS, shortest paths, topological sort, cycle detection, and union-find. It provides progressive hints, ASCII graph visualizations, and structured feedback… - 86graphify-windows
any input (code, docs, papers, images) → knowledge graph → clustered communities → HTML + JSON + audit report. Use when user asks any question about a codebase, project content, architecture, or file relationships — especially if graphify-out/ exists. Provides persistent graph… - 100graph-algorithms
Reference patterns for graph algorithm problems including BFS, DFS, Dijkstra, topological sort, union-find, MST, Bellman-Ford, and bipartite checking. Provides recognition signals, Python templates, edge cases, and common mistakes for each technique. Use when solving problems… - 100cpp-reading-card
把大型 C++ 工程里的一个函数读成一张 HTML 阅读卡(隐式输入输出 + 写入点 + 阈值 + 所有权 + 自检题)。当用户要「读懂某个函数」「讲给初学者」「画数据流」「对比两条分支」时使用。