高尔顿钉板:随机过程与确定性播放器
小球从顶部落下,每撞一排钉子就左右各半概率偏一格,穿过 排后落入底部某个槽。大量小球累积出二项分布,随排数增大逼近正态曲线。
它看上去是一台需要碰撞检测的物理玩具,实际不必。落点只取决于「这一路向左还是向右」的一串抉择;抉择一旦抽定,整条下落轨迹就是一条确定的折线。随机性被收进开局那一次抽样,播放本身仍是时间到值的纯函数——本系列反复用到的正是这条主线。
1 · 连续投放与统计涌现
持续向钉板倾泻小球,底部直方图实时累积。偏置 让左右概率不再对半时,分布随之偏斜,均值移到 ;叠加的细线为理论二项分布。
每排是一次独立的伯努利试验,落点是 次之和,故服从 ,均值 、方差 ;由中心极限定理, 增大时逼近正态。直方图的钟形不是任何单个球的属性,而是大量独立抉择串的统计涌现。
2 · 单球可回退
把镜头收到一个球上。它的整条轨迹由开局那串抉择唯一决定,位置只是 currentTime 的函数,因此 seek 与 reverse 都零成本——不需要记录历史,也不需要反向积分。
注 · 折线的段内插值分了两个方向:横向走 easeInOut,纵向走线性再叠一个
的小跳。这不是物理,是补上视觉缺口——纯线性折线在拐点处会显出尖角,而横向缓动让球在每颗钉子附近减速、小跳让它看起来是被弹开而非贴着线滑行。真实的自由落体纵向应该是加速的,本页刻意没做:
排匀速下落才能让整块板上的球看起来节奏一致。
3 · 一个球就是一个 Animation
每个球是一个 Animation:keyframes 是那条预抽的路径,onUpdate 沿折线采样位置,sink 换成 canvas 就能同时画几十个球,投放本身用交错的思路错开起点。
import { Animation } from '@vega/anim';
function drop(dirs) { // dirs: 开局抽好的左右序列
const pts = pathFromDirs(dirs); // 落点折线 = 一条确定的路径
const bin = dirs.filter((d) => d > 0).length;
return new Animation({
keyframes: [0, 1], duration: 1800, ease: 'linear',
onUpdate: (p) => { // p: 0→1 线性时钟
const s = sampleAlong(pts, p); // 沿折线取点 + 逐段 easeInOut 摆动
ball.x = s.x; ball.y = s.y; // sink: 画到 <canvas>
},
onComplete: () => { bins[bin]++; }, // 落定 → 直方图 +1
});
}
同属「非补间的信号源」的还有噪声与三角函数,只是那里的信号是连续的周期或噪声,本页则是一串离散的随机抉择。播放器为何能在纯函数之上免费得到 seek 与反向,见播放控制。