7.1 细胞自动机
CA。...
在前面一章我们展示了在不知不觉间实现涌现复杂性是多么简单。使用细胞自动机网格,我们将学习一种更加结构化的观察此种现象的方法。在1970年,计算机科学界沉迷于细胞自动机问题。超级计算机被部署用于运行John Conway的生命游戏(详见7.1.2部分)达数周之长。如今,那个时代的超级计算机的计算能力在我们的手机上都已经拥有了,因此我们可以轻易的在Processing的速写本上模拟细胞自动机。
下图是一个多细胞网格每一格均有两种状态:开或者闭,黑或者白,活或者死。每一个细胞格子对周围环境都只有有限的关于局部区域的认知,它仅能看到与其直接相邻接的八个细胞。在一系列回合中,每一个细胞基于周围细胞的状态决定自身的下一种状态。
好了,要上代码了:
//listing 7.1 CA framework
Cell[][] _cellArray;
int _cellSize = 10;
int _numX, _numY;
/*设置屏幕大小及计算横纵各有多少行、列 */
void setup(){
size(500,300);
_numX = floor(width/_cellSize);
_numY = floor(height/_cellSize);
restart();
}
/*构造细胞表格*/
void restart(){
_cellArray = new Cell[_numX][_numY];
for(int x = 0; x < _numX; x++){
for(int y = 0; y < _numY; y++){
Cell newCell = new Cell(x,y);
_cellArray[x][y] = newCell;
}
}
//循环告知每一个对象它的邻居
for(int x =0; x < _numX; x++){
for(int y = 0; y < _numY; y++){
//以当前的细胞位置为中心,确定上、下、左、右的索引
int above = y - 1;
int below = y + 1;
int left = x - 1;
int right = x + 1;
//位于网格边角的细胞位置判断
//定义最上方的一行与最下方的一行相邻
if(above < 0){
above = _numY - 1;
}
if(below == _numY){
below = 0;
}
//定义最左边的一列与最右边的一列相邻
if(left < 0){
left = _numX - 1;
}
if(right == _numX){
right = 0;
}
//将相邻的细胞添加到二维数组
_cellArray[x][y].addNeighbor(_cellArray[left][above]);
_cellArray[x][y].addNeighbor(_cellArray[left][y]);
_cellArray[x][y].addNeighbor(_cellArray[left][below]);
_cellArray[x][y].addNeighbor(_cellArray[x][above]);
_cellArray[x][y].addNeighbor(_cellArray[x][below]);
_cellArray[x][y].addNeighbor(_cellArray[right][above]);
_cellArray[x][y].addNeighbor(_cellArray[right][y]);
_cellArray[x][y].addNeighbor(_cellArray[right][below]);
}
}
}
void draw(){
background(200);
for(int x = 0; x < _numX; x++){
for(int y = 0; y < _numY; y++){
_cellArray[x][y].calNextState();
}
}
translate(_cellSize/2, _cellSize/2);
for(int x = 0; x < _numX; x++){
for(int y = 0; y < _numY; y++){
_cellArray[x][y].drawMe();
}
}
}
void mousePressed(){
restart();
}
//======================对象
class Cell{
float x, y;
boolean state;
boolean nextState;
Cell[] neighbors;
Cell(float ex, float why){
x = ex * _cellSize;
y = why * _cellSize;
if(random(2) > 1){
nextState = true;
}
else{
nextState = false;
}
state = nextState;
neighbors = new Cell[0];
}
void addNeighbor(Cell cell){
neighbors = (Cell[])append(neighbors,cell);
}
void calNextState(){
//TBD
}
void drawMe(){
state = nextState;
stroke(0);
if(state==true){
fill(0);
}
else{
fill(255);
}
ellipse(x,y,_cellSize, _cellSize);
}
}
想下载下来自己运行的可以从云盘拿到这个代码自己研究:
https://yunpan.cn/cRtQVMs7i9XjI (提取码:68f8)
或者点击阅读原文跳转。
这个例子的代码有些长,但是知识点都是已经说过的,并不新颖。在最下方的代码,我们定义了一个对象,即一个细胞(Cell),它有x,y位置信息以及一个状态:开或者关。它也有一个变量用于纪录下一个状态:nextState,它将在下一次调用drawMe函数时计入这个状态。
在setup( )函数中, 我们根据设定的细胞的宽度与高度以及画布的尺寸计算出细胞的行数与列数。然后,调用restart( )函数填充绘制细胞网格需要的二维数组的数据。当网格创建好以后,我们需要告知每一个细胞它的周围的八个邻居是谁(通过上、下、左、右的索引定位它们)。
draw( )函数两次循环遍历数组。第一次遍历是触发每一个细胞计算自己的下一个状态,第二次遍历是触发每一个细胞计算自己的移动位置并绘制在屏幕上。注意到这需要在两个阶段进行,因此每一个细胞对象需要有一个静态的相邻细胞集合,这也为计算下一个状态提供依据。
现在,我们已经可以绘制一个细胞网格了(如下图所示),我们也可以给这些细胞一些简单的的行为。最著名的CA游戏是John Conway的生命游戏,而这正是我们接下来的起始案例。运行效果图示:
代码中的逻辑这里不再一一去理,只在多说一些关于多维数组的Tip:
多维数组
通常一维数组就像我们在第六章见到的那样,是一种用于存储一系列元素的数据结构,定义如下所示:
int[] numberArray = {1,2,3};
但是,如果你想存储的数据不止是一维的数组,那么你可以选择使用二维或者以上的方式进行存储。一个典型的二维数组初始化如下:
int twoDimArray = {{1,2,3}, {4,5,6}, {7,8,9}};
本质上,我们定义的启示是一个存储一维数组的数组。在7.1的代码中,我们就这么使用了。
如果你想要更多维的数组,也是可以的。任意多维度的数组均可以在Processing中定义,比如三维:
int [][][] threeDimArray;
四维:
int [][][][] fourDimArray;
甚至更高维:
...
但是,一般最多常用的也就是二维,三维,更高的维度几乎用不到了。
关注 Processing学习部落
微信扫一扫关注公众号