细胞自动机! 用OpenGL动态展示兰顿蚂蚁

兰顿蚂蚁算法

因为作业要用OpenGL画几条线, 所以我打算画一个简单的细胞自动机. 我们选择最简单的兰顿蚂蚁自动机. 这个自动机的核心算法非常简单:

  • 若蚂蚁在黑格,右转90度,将该格改为白格,向前移一步;
  • 若蚂蚁在白格,左转90度,将该格改为黑格,向前移一步.

首先下载一个glut包, 然后简单配置一下VS2013, 让他可以工作. 然后我们就开始工作.

 

绘图函数

首先我们要开始画格子

#define SCREEN_X 800
#define SCREEN_Y 800
#define MAP_MAX_X 30
#define MAP_MAX_Y 30

void drawLine(GLint a, GLint b, GLint c, GLint d) {
	glBegin(GL_LINES);
	glVertex2f((float)a / SCREEN_X, (float)b / SCREEN_Y);
	glVertex2f((float)c / SCREEN_X, (float)d / SCREEN_Y);
	glEnd();
}

void drawMap() {
	int step_x = SCREEN_X / MAP_MAX_X * 2;
	int step_y = SCREEN_Y / MAP_MAX_Y * 2;
	for (int i = -SCREEN_X; i <= SCREEN_X; i += step_x) {
		drawLine(i, -SCREEN_Y, i, SCREEN_Y);
	}
	for (int i = -SCREEN_Y; i <= SCREEN_Y; i += step_y) {
		drawLine(-SCREEN_X, i, SCREEN_X, i);
	}
}

 

效果如下2

 

现在我们要写两个函数, 用格子的坐标就能给格子填充黑色或者白色. 或者说可以对指定格子设定生死.

void setAlife(int x, int y) {
	int step_x = SCREEN_X / MAP_MAX_X * 2;
	int step_y = SCREEN_Y / MAP_MAX_Y * 2;
	int xa = (x + 1)*step_x - SCREEN_X + 1;
	int xb = xa + step_x - 2;
	int ya = SCREEN_Y - ((y + 1) * step_y);
	int yb = ya - step_y + 2;
	glColor3f(0.0f, 0.7f, 0.7f);
	glRectf((float)xa / SCREEN_X, (float)ya / SCREEN_Y, (float)xb / SCREEN_X, (float)yb / SCREEN_Y);
}

void setDead(int x, int y) {
	int step_x = SCREEN_X / MAP_MAX_X * 2;
	int step_y = SCREEN_Y / MAP_MAX_Y * 2;
	int xa = (x + 1)*step_x - SCREEN_X + 1;
	int xb = xa + step_x - 2;
	int ya = SCREEN_Y - ((y + 1) * step_y);
	int yb = ya - step_y + 2;
	glColor3f(1.0f, 1.0f, 1.0f);
	glRectf((float)xa / SCREEN_X, (float)ya / SCREEN_Y, (float)xb / SCREEN_X, (float)yb / SCREEN_Y);
}

这样我们简单的通过 setAlive(2, 12) 就能将第3行第13个格子涂成黑色(表示有生命存在); 用setDead(2, 13) 就成将他重新变成白色(没有生命存在的格子)

 

编程表示蚂蚁

主要有两个任务

  1. 建立一个地图数组, 表示地图当中某个格子是否有生命, True代表有, False代表没有. 同时设定蚂蚁的坐标和方向;
  2. 使用glutTimerFunc做出动画效果.

这两个任务的代码如下:

int antdirect = 0;		// 0, 1, 2, 3 表示四个方向 up right down left
int antx = MAP_MAX_X / 2;
int anty = MAP_MAX_Y / 2;
bool lifemap[MAP_MAX_X][MAP_MAX_Y];

void autoFunc(int v) {
	if (lifemap[antx][anty]) {
		setDead(antx, anty);
		antdirect++;
	} else {
		setAlife(antx, anty);
		antdirect--;
	}

	antdirect = (antdirect + 4) % 4;
	if (antdirect == 0) {
		antx--;
	} else if (antdirect == 1) {
		anty++;
	} else if (antdirect == 2) {
		antx++;
	} else if (antdirect == 3) {
		anty--;
	}

	if (antx >= MAP_MAX_X) antx = 0;
	else if (antx < 0) antx = MAP_MAX_X - 1;

	if (anty >= MAP_MAX_Y) anty = 0;
	else if (anty < 0) anty = MAP_MAX_Y - 1;
	glFlush();
	glutTimerFunc(200, &autoFunc, 0);	// 回调函数
}

void Cellular_Automaton(void) {
	glClear(GL_COLOR_BUFFER_BIT);                         
	glColor3f(0.3, 0.3, 0.3);                             
	drawMap();
	glFlush();                                           
	glutTimerFunc(200, &autoFunc, 0);	// 动画开始
}

 

运行!

3

完整代码如下:

#include <iostream>
#include <gl/glut.h>

using namespace std;

#define SCREEN_X 800
#define SCREEN_Y 800
#define MAP_MAX_X 100
#define MAP_MAX_Y 100

#define SPEED 1

int antdirect = 0;		// 0, 1, 2, 3 表示四个方向 up right down left
int antx = MAP_MAX_X / 2;
int anty = MAP_MAX_Y / 2;
bool lifemap[MAP_MAX_X][MAP_MAX_Y];

void drawLine(GLint a, GLint b, GLint c, GLint d) {
	glBegin(GL_LINES);
	glVertex2f((float)a / SCREEN_X, (float)b / SCREEN_Y);
	glVertex2f((float)c / SCREEN_X, (float)d / SCREEN_Y);
	glEnd();
}

void drawMap() {
	glColor3f(0.0f, 0.0f, 0.0f);
	int step_x = SCREEN_X / MAP_MAX_X * 2;
	int step_y = SCREEN_Y / MAP_MAX_Y * 2;
	for (int i = -SCREEN_X + step_x; i < SCREEN_X + step_x; i += step_x) {
		drawLine(i, -SCREEN_Y + step_y - 10, i, SCREEN_Y - step_y);
	}
	for (int i = SCREEN_Y; i > -SCREEN_Y; i -= step_y) {
		drawLine(-SCREEN_X + step_x, i, SCREEN_X - step_x + 10, i);
	}
}

/*
	将一个格子变成黑的.
	0 <= x < MAP_MAX_X
	0 <= y < MAP_MAX_Y
	所以左上角第一个格子的坐标是(0, 0)
*/
void setAlife(int x, int y) {
	int step_x = SCREEN_X / MAP_MAX_X * 2;
	int step_y = SCREEN_Y / MAP_MAX_Y * 2;
	int xa = (x + 1)*step_x - SCREEN_X + 1;
	int xb = xa + step_x - 2;
	int ya = SCREEN_Y - ((y + 1) * step_y);
	int yb = ya - step_y + 2;
	glColor3f(0.0f, 0.7f, 0.7f);
	glRectf((float)xa / SCREEN_X, (float)ya / SCREEN_Y, (float)xb / SCREEN_X, (float)yb / SCREEN_Y);
	lifemap[x][y] = 1;
}

void setDead(int x, int y) {
	int step_x = SCREEN_X / MAP_MAX_X * 2;
	int step_y = SCREEN_Y / MAP_MAX_Y * 2;
	int xa = (x + 1)*step_x - SCREEN_X + 1;
	int xb = xa + step_x - 2;
	int ya = SCREEN_Y - ((y + 1) * step_y);
	int yb = ya - step_y + 2;
	glColor3f(1.0f, 1.0f, 1.0f);
	glRectf((float)xa / SCREEN_X, (float)ya / SCREEN_Y, (float)xb / SCREEN_X, (float)yb / SCREEN_Y);
	lifemap[x][y] = 0;
}

void autoFunc(int v) {
	if (lifemap[antx][anty]) {
		setDead(antx, anty);
		antdirect++;
	} else {
		setAlife(antx, anty);
		antdirect--;
	}

	antdirect = (antdirect + 4) % 4;
	if (antdirect == 0) {
		antx--;
	} else if (antdirect == 1) {
		anty++;
	} else if (antdirect == 2) {
		antx++;
	} else if (antdirect == 3) {
		anty--;
	}

	if (antx >= MAP_MAX_X) antx = 0;
	else if (antx < 0) antx = MAP_MAX_X - 1;

	if (anty >= MAP_MAX_Y) anty = 0;
	else if (anty < 0) anty = MAP_MAX_Y - 1;
	glFlush();
	glutTimerFunc(SPEED, &autoFunc, 0);	// 回调函数
}

void Cellular_Automaton(void) {
	glClear(GL_COLOR_BUFFER_BIT);                         
	glColor3f(0.3, 0.3, 0.3);                             
	drawMap();
	glFlush();                                           
	glutTimerFunc(0, &autoFunc, 0);	// 动画开始
}

int main(int argc, char* *argv) {
	glutInit(&argc, argv);                                
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
	glutInitWindowSize(SCREEN_X, SCREEN_Y);
	glutInitWindowPosition(200, 100);
	glutCreateWindow("Jecvay 的细胞自动机");                  
	glClearColor(1, 1, 1, 0);
	glutDisplayFunc(&Cellular_Automaton);
	cout << "Start" << endl;
	glutMainLoop();
	cout << "The end." << endl;
	return 0;
}