主要内容
随机游走
在我们讨论复杂的矢量和基于物理的运动的之前,先想一想简单地在屏幕上移动意味着什么。 让我们从最著名和最简单的运动模拟之一 —— 随机游走开始。
想象你站在平衡木的中间。 每十秒钟,你抛一个硬币。 正面,向前迈出一步。 反面,退后一步。 这是一个随机游走——由一系列随机步骤定义的路径。 掉下平衡木并落到地板上,你可以通过将相同的硬币翻转两次来执行二维随机游走,结果如下:
翻转 1 | 翻转 2 | 结果 |
---|---|---|
朝上 | 朝上 | 前进。 |
朝上 | 朝下 | 往右走。 |
朝下 | 朝上 | 往左走。 |
朝下 | 朝下 | 后退。 |
是的,这似乎是一个特别简单的算法。 尽管如此,随机游走可以用来模拟现实世界中发生的现象,从天然气中的分子运动到在赌场度过一天的赌徒的行为。 至于我们,我们通过研究一个有三个目标的随机游走来开始这个课题。
随机游走对象
让我们首先通过构建一个
Walker
对象来回顾一下面向对象编程(OOP)。这只是一个粗略的复习。如果您以前从未使用过OOP,那么应该先阅读面向对象的JavaScript部分。JavaScript中的 对象 是一种数据类型,它通过其原型附加了属性和功能。我们正在设计一个
Walker
对象,它既可以跟踪数据(它存在于屏幕上的位置),也可以执行某些操作(例如绘制自己或移动一步)。为了创建
Walker
的实例,我们需要定义一个Walker
对象。这个对象就是切曲奇饼的模型,每个新的 Walker
实例就是一个曲奇饼。让我们从定义
Walker
对象类型开始。 Walker
只需要两个数据 —— 一个数字用于其x坐标,一个用于其y坐标。我们将在构造函数中设置它们,将它们设置为画布的中心。var Walker = function() {
this.x = width/2;
this.y = height/2;
};
除了跟踪它的x和y之外,我们的
Walker
对象还将有可以调用它的方法。第一种方法是允许对象将自身显示为黑点。请记住,我们通过将方法附加到对象的prototype
来向JavaScript中的对象添加方法。Walker.prototype.display = function() {
stroke(0, 0, 0);
point(this.x, this.y);
};
第二种方法将指示
Walker
对象采取行动。现在,这是让事情变得更有趣的地方。还记得我们采取随机步骤的地板吗?那么,现在我们可以以相同的方法使用我们的画布。有四个可能的步骤。可以通过递增 x
(x++
)来模拟往右边移动的步骤;通过递减 x
(x--
)向左移动;向前移动一个像素(y++
);向上移动一个像素(y--
)。我们如何从这四种选择中挑选?早些时候我们说可以翻转两个硬币。然而,在ProcessingJS中,当我们想从一个选项列表中随机选择时,可以使用 random()
来选择一个随机数。Walker.prototype.walk = function() {
var choice = floor(random(4));
};
上面的代码行选择0到4之间的随机浮点数,并使用
floor()
将结果转换为整数,结果为0,1,2或3 。从技术上讲,最高的数字永远不会是4.0,而是3.999999999(与小数位数一样多9);因为floor()
返回最小或相等的最接近的整数,我们得到的最高结果是3 。接下来,我们根据选择的随机数采取适当的步骤(左,右,上或下)。Walker.prototype.walk = function() {
var choice = floor(random(4));
if (choice === 0) {
this.x++;
} else if (choice === 1) {
this.x--;
} else if (choice === 2) {
this.y++;
} else {
this.y--;
}
};
现在我们已经编写了这个类,是时候在程序中创建一个实际的
Walker
对象了。假设我们正在寻找单个随机游走的模型,通过使用new运算符调用构造函数来声明,并初始化一个类型为Walker
的全局变量。var w = new Walker();
接下来,为了让walker真正做某事,我们定义
draw()
函数,并告诉walker每次调用时都移动一步并绘制自己:draw = function() {
w.walk();
w.display();
};
由于我们在draw函数中不调用
background()
,可以在画布上看到随机游走的踪迹:改进随机游走
我们可以对随机游走进行一些改进。首先,这个游走者的步骤选择限于四个选项 - 向上,向下,向左和向右。但窗口中的任何给定像素都有八个可能的邻居,第九种可能性是留在同一个地方。
要实现一个可以移动到任何相邻像素(或保持放置)的
Walker
对象,我们可以选择0到8之间的数字(一共九种选择)。但是,编写代码的更有效方法是简单地从x轴移动的方向的三个可能步骤(-1,0或1)和沿y轴方向的三个可能步骤(-1,0或1)中进行选择。Walker.prototype.walk = function() {
var stepx = floor(random(3))-1;
var stepy = floor(random(3))-1;
this.x += stepx;
this.y += stepy;
};
更进一步,我们可以使用十进制代替
x
和y
并根据-1和1之间的任意随机值移动 —— 如果我们编程环境可以显示“2.2”和“2.4”之间的差异:Walker.prototype.walk = function() {
var stepx = random(-1, 1);
var stepy = random(-1, 1);
this.x += stepx;
this.y += stepy;
};
所有这些关于“传统”随机游走的变化都有一个共同点:在任何时刻,
Walker
选择在给定方向上迈出一步(或根本不动)的概率等于 Walker
将做出任何其他给定选择的概率。换句话说,如果有四个可能的步骤,那么Walker
将采取任何给定步骤的概率为1/4(或25%)。有九个可能的步骤,它是1/9(或11.1%)的机会。方便的是,这符合
random()
函数的工作原理。它的随机数发生器产生所谓的“均匀”数字分布。我们可以使用一个程序来测试这个分布,该程序在每次拾取随机数时计算,并将其绘制为矩形的高度:运行几分钟后,这些杆的高度是否相同?可能不。我们的样本量(即我们选择的随机数的数量)相当小,并且偶尔存在一些差异,其中某些数字被更频繁地选取。随着时间的推移,使用一个好的随机数生成器,这类情况只会偶然出现。
我们从
random()
函数得到的随机数并不是真正随机的;因此它们被称为“伪随机”。它们是模拟随机性的数学函数的结果。这个函数随着时间的推移会产生一个模式,但是这个时间段太长了,对我们来说,它和纯随机性一样好!在下一节中,我们将讨论不同的方法,我们可以创建有“倾向”向某些方向的步行者(Walker)。在深入研究之前,有一个挑战等待你来面对!
“自然模拟”系列课程是由 Daniel Shiffman 的 "编程的本质" 衍生而来,基于 知识共享 著名-非商用性 3.0 本地化许可协议。