走进分形曲线和生命游戏探索

这篇文章实际是上个学期的VC面向对象编程课时写的。由于考虑到自己的水平有限,很难写出高水平的文章出来。所以,刚开始出于这种顾虑,这篇文章迟迟没有发表。这个学期已经是大三的下学期了。我要准备考研了。所以,以后可能也没有更多的时间来好好的整理这些东西了。只能暂时发表出来吧。尽管,写的很不完美。再者说,这个博客的初衷就是当成一个电子笔记来写的。所以,写的没那么准确也就算了。自己看,还是可以的。


PS:这学期还有跟老师一个科研的项目,不知道能否做出点成绩,只要有收获就好吧。



老师在上课的过程中,像我们介绍了两个比较具有探索性的课题,一个是分形曲线,一个是元胞自动机。下面是我对这两个课题探讨的一些总结。

关于生命游戏:元胞自动机的示例

生命游戏的网页在:(提供了Java实现方式的源代码)

http://www.bitstorm.org/gameoflife/

我要实现的生命游戏的规则先定义如下:

100*100的方格中,给出初始状态后,按照如下规则演化:

1. 当周围有3个细胞为生时,该细胞为生。

2.当周围有0个或1个细胞为生时,该细胞为死。

3.当周围有4个或更多个细胞为生时,该细胞为死。

4.当周围有2个细胞为生时,该细胞保持原来的状态。

因此,为实现上诉演算规则,先进行了小规模的测试,代码如下:

#include <iostream>
using namespace std;

#define OCCUPIED 1
#define UNOCCUPIED 0

class GameOfLife
{
private :
	char **cell;//用于保存细胞状态的数组
	int numOfRow;//矩阵的规模,行数
	int numOfCol;//矩阵的规模,列数
	int generations;//用于控制向下计算多少代。
public:
	void Read();
	void Display(int genNo);
	void Game();
};

//读取数据函数,用来将行数,列数和代数等控制数据读入。
void GameOfLife::Read()
{
	cin>>numOfRow>>numOfCol>>generations;
	
	cell=new char *[numOfRow];//进行动态申请内存,这是多少行。
	int i,j;
	for(i=0;i<numOfRow;i++)
		cell[i]=new char[numOfCol];//动态申请内存,这是每一行多少列
	for(i=0;i<numOfRow;i++)
	{
		for(j=0;j<numOfCol;j++)
			cell[i][j]=UNOCCUPIED;//进行状态的初始化。
		
	}
	char * line = new char [numOfCol+1];//读入每一行的数据。
	int maxRow=0;
	int maxCol=0;
	
	for(i=0;i<numOfRow&&gets(line)!=NULL;i++)
	{
		for(j=0;j<strlen(line);j++)
		{
			if(line[j]!=' ')
				cell[i][j]=OCCUPIED;//非空字符说明此处有生物。
			if(maxRow<i) maxRow=i;
			if(maxCol<j) maxCol=j;//记录输入中出现的最大的行与列,方便把数据移动到区域的中央部分
			
			
		}
	}
	//为了把数据移动到中央区域所做的工作
	int rowGap=(numOfRow-maxRow)/2;
	int colGap=(numOfCol-maxCol)/2;
	
	for(i=maxRow;i>=0;i--)
	{
		for(j=maxCol;j>=0;j--)
		{
			cell[i+rowGap][j+colGap]=cell[i][j];
		}
		for(j=colGap-1;j>=0;j--)
		{
			cell[i+rowGap][j]=UNOCCUPIED;
		}
		
	}
	for(i=rowGap-1;i>=0;i--)
	{
		for(int j=0;j<numOfCol;j++)
		{
			cell[i][j]=UNOCCUPIED;
		}
	}
	delete []line;
}

//主要在这个函数中,进行演化。注意出现生物全部消失和到达稳定状态就可以停止了。
void GameOfLife::Game()
{
	if(cell==NULL)
	{
		return ;
	}
	Display(0);//描绘一下初始状态。
	
	char **workCopy;//使用一个工作数组,用来保留原来的数据,做对比。进行按规则变换。
	bool existLife = true ;//开始时,假设存在微生物,不存在稳定状态。
	bool isStable = false ;
	
	workCopy= new char*[numOfRow];
	
	int i,j;
	for(i=0;i<numOfRow;i++)
		workCopy[i]=new char[numOfCol];
	//进行演化的规则,计算邻居的数目。
	for(int gen=1;gen<=generations && existLife && !isStable; gen++)
	{
		for(i=0;i<numOfRow;i++)
		{
			for(int j=0;j<numOfRow;j++)
				workCopy[i][j]=cell[i][j];
		}
		existLife=false;//先假设不存在生命了,并且到达平衡状态了。
		isStable=true;
		for(i=0;i<numOfRow;i++)
		{
			for(j=0;j<numOfCol;j++)
			{
				//计算邻居的个数。注意边界的处理情况。
				int top=(i==0)?0:i-1;
				int bottom=(i==numOfRow-1)?numOfRow-1:i+1;
				int left=(j==0)?0:j-1;
				int right=(j==numOfCol-1)?numOfCol-1:j+1;
				
				int neighbores=0;
				for(int u=top;u<=bottom;u++)
				{
					for(int v=left;v<=right;v++)
						if((u!=i||v!=j)&&workCopy[u][v]==OCCUPIED)
							neighbores++;
						
						
				}
				if(workCopy[i][j]==OCCUPIED)
				{
					if(neighbores==2||neighbores==3)
						cell[i][j]=OCCUPIED;
					else
						cell[i][j]=UNOCCUPIED;
				}
				else
				{
					if(neighbores==3)
						cell[i][j]=OCCUPIED;
					else
						cell[i][j]=UNOCCUPIED;
				}
				if(cell[i][j]==OCCUPIED)
					existLife=true;
				if(cell[i][j]!=workCopy[i][j])
					isStable=false;
			}
		}
		if(!existLife)
		{
			cout<<"生物死光了"<<endl;
			break;
		}
		else if(isStable)
		{
			cout<<"系统稳定了"<<endl;
			break;
		}
		else Display(gen);
	}
	for(i=0;i<numOfRow;i++)
		delete []workCopy[i];
	delete[]workCopy;
}

void GameOfLife::Display(int gen)
{
	for(int i=0;i<numOfRow;i++)
	{
		for(int j=0;j<numOfCol;j++)
		{
			if(cell[i][j]==OCCUPIED)
				cout<<" * ";

		}
		cout<<endl;
	}
}

int main()
{
	GameOfLife g ;
	g.Read();
	g.Game();
	
	return 0;
}

尝试一:因为在控制台下实现了对计算下一代元胞自动机生命状态计算的模拟。接下来我们进行第一次的尝试代码。

首先,新建一个单文档的应用程序,然后将无用的菜单项删除,构建自己的菜单项。包含有:

开始游戏,设置代数,设置初始状态。

(现在可以将设置初始状态转化成二级级联菜单,选择初始状态)

设置代数,关联一个对话框窗口,用于接收用户输入的代数。这是循环结束的控制数据。

设置初始状态,关联一个对话框窗口,提供五个点的X,Y坐标的输入,用来表示初始状态的位置。

在开始游戏中,进行计算下一代的生命分布情况,并进行绘图。


首先在CMainFrame类中的OnPreCreateWindow(&CREATESTRUCT &cs)中给窗口指定生成的大小。代码如下:

	//设置窗口的大小
	cs.cx=1000;
	cs.cy=1000;
	//设置窗口的初始位置
	cs.x=500;
	cs.y=100;

在CView类中添加一些需要用到的数据成员:

public:
	int nWidth;
	int nHeight;
	int width ;
	int height ;
	CPoint point1;
	CPoint point2;
	int cell[100][100] ;
	int generation;
	int x1,y1,x2,y2,x3,y3,x4,y4,x5,y5;

在OnDraw()函数中,进行初始化的绘图(目前的作用,只是给窗口设置上背景颜色)

	CDC MemDC ;
	CBitmap MemBitmap;
	CRect rect ;
	GetWindowRect(&rect);
	ScreenToClient(&rect);
	nWidth = rect.Width();
	nHeight = rect.Height ();
	MemDC.CreateCompatibleDC(NULL);
	MemBitmap.CreateCompatibleBitmap (pDC,nWidth,nHeight);
	
	CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
	MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(0,0,0));
	
	width=nWidth/100;
	height=nHeight/100;
	for(int j=0;j<100;j++)
	{
		for(int i=0;i<100;i++)
		{
			
			point1.x=i*width;
			point1.y=j*height;
			point2.x=point1.x+width;
			point2.y=point1.y+height;
			CBrush brush(RGB(51,51,51));
			MemDC.FillRect(CRect(point1,point2),&brush);
		}
	}
	
	
	pDC->BitBlt (0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
	
	MemBitmap.DeleteObject ();
	MemDC.DeleteDC ();

分别添加菜单响应函数如下:

设置代数:

void CCAGameView::OnSetGen() 
{
	// TODO: Add your command handler code here
	CSetGen m_setgen;
	m_setgen.DoModal();
	generation=m_setgen.m_gen ;
	
}
设置初始状态
void CCAGameView::OnSetState() 
{
	// TODO: Add your command handler code here
	CSetState m_setstate;
	m_setstate.DoModal();
	x1=m_setstate.m_x1;
	y1=m_setstate.m_y1;
	x2=m_setstate.m_x2;
	y2=m_setstate.m_y2;
	x3=m_setstate.m_x3;
	y3=m_setstate.m_y3;
	x4=m_setstate.m_x4;
	y4=m_setstate.m_y4;
	x5=m_setstate.m_x5;
	y5=m_setstate.m_y5;
	
	
}

开始游戏的响应代码:

void CCAGameView::OnMenuBegin() 
{
	// TODO: Add your command handler code here
	int i,j;
	for(i=0;i<100;i++)
	{
		for(j=0;j<100;j++)
		{
			cell[i][j]=0;
		}
	}
	cell[this->x1][this->y1]=1;
	cell[this->x2][this->y2]=1;
	cell[this->x3][this->y3]=1;
	cell[this->x4][this->y4]=1;
	cell[this->x5][this->y5]=1;
	CClientDC dc(this);
	for( i=0;i<100;i++)
	{
		for( j=0;j<100;j++)
		{
			
			point1.x=i*width;
			point1.y=j*height;
			point2.x=point1.x+width;
			point2.y=point1.y+height;
			if(cell[i][j]==1)
			{
				CBrush brush(RGB(255,255,0));
				dc.FillRect(CRect(point1,point2),&brush);
			}
			else
			{
				CBrush brush(RGB(51,51,51));
				dc.FillRect(CRect(point1,point2),&brush);
			}
		}
	}
	
	int workCopy[100][100]={0};
	bool existLife=true;
	bool isStable=false;
	
	for(int gen = 1;gen<=generation&&existLife&&!isStable;gen++)
	{
		for(i=0;i<100;i++)
		{
			for(j=0;j<100;j++)
			{
				workCopy[i][j]=cell[i][j];
			}
		}
		existLife=false;
		isStable=true;
		for(i=0;i<100;i++)
		{
			for(j=0;j<100;j++)
			{
				//计算邻居的个数。注意边界的处理情况。
				int top=(i==0)?0:i-1;
				int bottom=(i==99)?99:i+1;
				int left=(j==0)?0:j-1;
				int right=(j==99)?99:j+1;
				
				int neighbores=0;
				for(int u=top;u<=bottom;u++)
				{
					for(int v=left;v<=right;v++)
						if((u!=i||v!=j)&&workCopy[u][v]==1)
							neighbores++;
						
						
				}
				if(workCopy[i][j]==1)
				{
					if(neighbores==2||neighbores==3)
						cell[i][j]=1;
					else
						cell[i][j]=0;
				}
				else
				{
					if(neighbores==3)
						cell[i][j]=1;
					else
						cell[i][j]=0;
				}
				if(cell[i][j]==1)
					existLife=true;
				if(cell[i][j]!=workCopy[i][j])
					isStable=false;
			}
		}
		if(!existLife)
		{
			AfxMessageBox("No Life Exists ");
		}
		else if(isStable)
		{
			
			AfxMessageBox("The System is stable now");
		}
		else 
		{
			CClientDC dc(this);
			for(int i=0;i<100;i++)
			{
				for(int j=0;j<100;j++)
				{
					
					point1.x=i*width;
					point1.y=j*height;
					point2.x=point1.x+width;
					point2.y=point1.y+height;
					if(cell[i][j]==1)
					{
					//	CString str;
					//	str.Format("%d,%d",i,j);
					//	AfxMessageBox(str);
						CBrush brush(RGB(255,255,0));
						dc.FillRect(CRect(point1,point2),&brush);
					}
					else
					{
						CBrush brush(RGB(51,51,51));
						dc.FillRect(CRect(point1,point2),&brush);
					}
				}
			}
		}
	}
	
}



上述,是第一次尝试的过程。基本上可以实现演示生命游戏的过程,但还仍然存在着一些问题,比如,并没有用到双缓冲机制来实现图的重绘(现在仍然不知道应该怎么样才算正确的使用了双缓冲机制来绘图。不知道屏幕上的图的变化,怎么来进行窗口重绘,难道是显示调用吗?但感觉这个和输入文字,显示文字的过程是类似的,只好找例子学习学习了。)

还有一个问题是,初始状态设置的限制太大了,不是很好。现在改用在设置初始状态时,给一些下拉列表,然后选择相应的初始状态。


注意,关于屏幕坐标。最左上角为(0,0)点。X轴是横向的,向右增大。Y轴是纵向的,向下增大。


第二次尝试:由于第一次尝试时,发现了一些更好的组织方式,因此对生命游戏进行了更改,改变了组织方式,可以更好的使用了。

下面的图片是程序的主界面,程序的菜单项带有级联菜单。

下面是代码部分:

public:
	int gen ;
	int count ;
	int generation ;
	int cell[50][50];
	int workCopy[50][50];
	int choose ;
	CPoint point1,point2;
	int width,height;
	CRect rect ;
	bool existLife ;
	bool isStable;

在CView中添加的成员变量

相应的一些初始化工作

CCAGameBView::CCAGameBView()
{
	// TODO: add construction code here
	for(int i=0;i<50;i++)
	{
		for(int j=0;j<50;j++)
		{
			cell[i][j]=0;
			workCopy[i][j]=0;
		}
	}
	choose = 0;
	generation = 0;
	count =-1;
}

这个对话框用于接收要演化的代数


主程序中,由相应的菜单项激发,菜单项的响应代码如下:(注意包含头文件)

void CCAGameBView::OnSetgen() 
{
	// TODO: Add your command handler code here
	CSetgen m_setgen ;
	m_setgen.DoModal();
	this->generation=m_setgen.m_gen ;	
}
另外一个菜单项是级联菜单。可以设置初始状态的情况。

通过选择不同的菜单项,更改相应的choose的值,然后在开始运行时,通过choose的值进行相应的初始化工作。

开始菜单的响应函数如下:

void CCAGameBView::OnBegin() 
{
	// TODO: Add your command handler code here
	count ++;
	for(int i=0;i<50;i++)
	{
		for(int j=0;j<50;j++)
		{
			cell[i][j]=0;
			workCopy[i][j]=0;
		}
	}
	switch (choose)
	{
	case 1:
		cell[25][25]=1;
		cell[26][26]=1;
		cell[24][27]=1;
		cell[25][27]=1;
		cell[26][27]=1;
		break;
	case 2:
		cell[25][25]=1;
		cell[24][26]=1;
		cell[25][26]=1;
		cell[26][26]=1;
		cell[24][27]=1;
		cell[26][27]=1;
		cell[25][28]=1;
		break;
	case 3:
		cell[20][20]=1;
		cell[22][20]=1;
		cell[24][20]=1;
		cell[20][21]=1;
		cell[24][21]=1;
		cell[20][22]=1;
		cell[24][22]=1;
		cell[20][23]=1;
		cell[24][23]=1;
		cell[20][24]=1;
		cell[22][24]=1;
		cell[24][24]=1;
		break;
	case 4:
		cell[20][20]=1;
		cell[21][20]=1;
		cell[22][20]=1;
		cell[23][20]=1;
		cell[24][20]=1;
		cell[25][20]=1;
		cell[26][20]=1;
		cell[27][20]=1;
		cell[28][20]=1;
		cell[29][20]=1;
		break;
	case 5:
		cell[20][20]=1;
		cell[21][20]=1;
		cell[22][20]=1;
		cell[23][20]=1;
		cell[19][21]=1;
		cell[19][23]=1;
		cell[23][21]=1;
		cell[23][22]=1;
		cell[22][23]=1;
		break;
	case 6:
		cell[20][20]=1;
		cell[21][20]=1;
		cell[23][20]=1;
		cell[24][20]=1;
		cell[20][21]=1;
		cell[21][21]=1;
		cell[21][22]=1;
		cell[21][23]=1;
		cell[21][24]=1;
		cell[19][23]=1;
		cell[19][24]=1;
		cell[19][25]=1;
		cell[20][25]=1;
		cell[23][21]=1;
		cell[24][21]=1;
		cell[23][22]=1;
		cell[23][23]=1;
		cell[23][24]=1;
		cell[25][23]=1;
		cell[25][24]=1;
		cell[24][25]=1;
		cell[25][25]=1;
		break;
	}
	Display();
	existLife=true;
	isStable=false;
	
	for( gen = 1;gen<=this->generation && existLife && !isStable;gen++)
	{
		int i,j;
		for(i=0;i<50;i++)
		{
			for(j=0;j<50;j++)
			{
				workCopy[i][j]=cell[i][j];
			}
		}
		existLife=false;
		isStable=true;
		for(i=0;i<50;i++)
		{
			for(j=0;j<50;j++)
			{
				int top=(i==0)?0:i-1;
				int bottom=(i==49)?49:i+1;
				int left=(j==0)?0:j-1;
				int right=(j==49)?49:j+1;
				
				int neighbores=0;
				for(int u=top;u<=bottom;u++)
				{
					for(int v=left;v<=right;v++)
					{
						if((u!=i||v!=j)&&workCopy[u][v]==1)
						{
							neighbores++;
						}
					}
				}
				
				if(workCopy[i][j]==1)
				{
					if(neighbores==2 || neighbores==3)
						cell[i][j]=1;
					else
						cell[i][j]=0;
					
				}
				else
				{
					if(neighbores==3)
						cell[i][j]=1;
					else
						cell[i][j]=0;
				}
				if(cell[i][j]==1)
					existLife=true;
				if(cell[i][j]!=workCopy[i][j])
					isStable=false;
			}
		}
		if(isStable)
		{
			Display();
			AfxMessageBox("The System is stable now!");
		}
		else if(!existLife)
		{
			Display();
			AfxMessageBox("The life is dying out now!");
		}
		else
			Display();
	}
	
}


Display()函数的代码如下:

void CCAGameBView::Display()
{
	CClientDC dc (this);
	for(int i=0;i<50;i++)
	{
		for(int j=0;j<50;j++)
		{
			point1.x=i*width;
			point1.y=j*height;
			point2.x=point1.x+width;
			point2.y=point1.y+height;
			if(cell[i][j]==1)
			{
				CBrush brush(RGB(255,255,0));
				dc.FillRect(CRect(point1,point2),&brush);
			}
			else
			{
				CBrush brush(RGB(50,50,50));
				dc.FillRect(CRect(point1,point2),&brush);
			}
			
		}
	}
}


前面的switch语句就是进行相应的初始化工作的,其后的代码是进行演化的规则。


存在的问题:

本次实验,相比第一次的实验,可以进行更多的初始化的设置了,也更改了实验一时,对Windows程序的x和y坐标的认识,并且设置了初试时屏幕的大小和位置。

但仍然存在的问题是,不能够对窗口大小发生变化时,做出合适的响应。所采用的方法,和在VC实验中采用的方式相同,但这两个问题,存在着一些不一样的地方。

VC实验中,画棋盘是在一次内画完才显示的。为了符合人的习惯逻辑,设计了一个计数变量。第一次,采用菜单命令驱动的方式进行绘图。以后当窗口大小发生变化后,是采用双缓冲机制在OnDraw()的响应函数中进行的重新绘图。

而这里的生命游戏中,由于生命游戏中的小方格的移动并不会触发OnDraw()函数被调用执行,而并且是每一次小方格的移动都要绘画下来,因此不能使用上一次的方式进行绘画。


这里,我感觉可能会用到多线程来解决这个问题。


第三次尝试:VC中的时间控制函数的引入。

上一次的实验,仍然存在的问题是:

1  生命演化的过程太快,并且不能响应窗口变化时的重画。

2 整个代码的逻辑结构,其实仍然是按照面向过程的编程的思想来设计的。是一种伪面向对象的过程。

为了解决问题一,可以借助VC中的时间控制函数来进行。

时间控制函数,有SetTimer()函数,用来设定一个计时器。KillTimer()函数,用来删除一个计时器。OnTimer()函数,捕捉系统发出的WM_TIMER消息,当消息发生是,被调用,做相应的处理。

下面是我搜索到的一些关于VC的计时器的使用的规则。

这篇文章对计时器在哪里进行设置和在哪里进行销毁给出了说明。

http://blog.csdn.net/clever101/article/details/2116112

当我们需要一段时间做一件事情时,就需要使用计时器了,这里正好可以使用计时器来解决上诉窗口变化时,无法重画的问题,也可以调整生命演化过程的快慢。


通过增加一个下拉菜单来调整速度。

并且添加下拉菜单的响应函数

void CCAGameBView::OnFast() 
{
	// TODO: Add your command handler code here
	choosetimer =1;
	
}


void CCAGameBView::OnMiddle() 
{
	// TODO: Add your command handler code here
	choosetimer =2;
}


void CCAGameBView::OnSlow() 
{
	// TODO: Add your command handler code here
	choosetimer =3;
}

在OnDraw中只进行初始化时进行调用绘制背景就可以了。

CDC MemDC ;
	CBitmap MemBitmap ;
	
	GetWindowRect(&rect);
	MemDC.CreateCompatibleDC(NULL);
	MemBitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
	CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
	MemDC.FillSolidRect(0,0,rect.Width(),rect.Height(),RGB(50,50,50));
	
	width=rect.Width()/50;
	height=rect.Height()/50;	
	CBrush brush (RGB(50,50,50));
	
	for(int i=0;i<50;i++)
	{
		for(int j=0;j<50;j++)
		{
			point1.x=i*width;
			point1.y=j*height;
			point2.x=point1.x+width;
			point2.y=point2.y+height;
			MemDC.FillRect(CRect(point1,point2),&brush);
		}
	}	
	pDC->BitBlt(0,0,rect.Width(),rect.Height(),&MemDC,0,0,SRCCOPY);
	MemBitmap.DeleteObject();
	MemDC.DeleteDC();

在OnBegin中只进行初始化状态和设置计时器就可以。

	for(int i=0;i<50;i++)
	{
		for(int j=0;j<50;j++)
		{
			cell[i][j]=0;
			workCopy[i][j]=0;
		}
	}
	switch (choose)
	{
	case 1:
		cell[25][25]=1;
		cell[26][26]=1;
		cell[24][27]=1;
		cell[25][27]=1;
		cell[26][27]=1;
		break;
	case 2:
		cell[25][25]=1;
		cell[24][26]=1;
		cell[25][26]=1;
		cell[26][26]=1;
		cell[24][27]=1;
		cell[26][27]=1;
		cell[25][28]=1;
		break;
	case 3:
		cell[20][20]=1;
		cell[22][20]=1;
		cell[24][20]=1;
		cell[20][21]=1;
		cell[24][21]=1;
		cell[20][22]=1;
		cell[24][22]=1;
		cell[20][23]=1;
		cell[24][23]=1;
		cell[20][24]=1;
		cell[22][24]=1;
		cell[24][24]=1;
		break;
	case 4:
		cell[20][20]=1;
		cell[21][20]=1;
		cell[22][20]=1;
		cell[23][20]=1;
		cell[24][20]=1;
		cell[25][20]=1;
		cell[26][20]=1;
		cell[27][20]=1;
		cell[28][20]=1;
		cell[29][20]=1;
		break;
	case 5:
		cell[20][20]=1;
		cell[21][20]=1;
		cell[22][20]=1;
		cell[23][20]=1;
		cell[19][21]=1;
		cell[19][23]=1;
		cell[23][21]=1;
		cell[23][22]=1;
		cell[22][23]=1;
		break;
	case 6:
		cell[20][20]=1;
		cell[21][20]=1;
		cell[23][20]=1;
		cell[24][20]=1;
		cell[20][21]=1;
		cell[21][21]=1;
		cell[21][22]=1;
		cell[21][23]=1;
		cell[21][24]=1;
		cell[19][23]=1;
		cell[19][24]=1;
		cell[19][25]=1;
		cell[20][25]=1;
		cell[23][21]=1;
		cell[24][21]=1;
		cell[23][22]=1;
		cell[23][23]=1;
		cell[23][24]=1;
		cell[25][23]=1;
		cell[25][24]=1;
		cell[24][25]=1;
		cell[25][25]=1;
		break;
	}
	Display();
	SetTimer(choosetimer,choosetimer*300,NULL);

	existLife=true;
	isStable=false;

将演化规则独立出来,形成一个单独的函数,供时间响应函数进行调用。

并通过gen变量来控制演化的代数。

void CCAGameBView::nextgen()
{
	int i,j;
		for(i=0;i<50;i++)
		{
			for(j=0;j<50;j++)
			{
				workCopy[i][j]=cell[i][j];
			}
		}
		existLife=false;
		isStable=true;
		for(i=0;i<50;i++)
		{
			for(j=0;j<50;j++)
			{
				int top=(i==0)?0:i-1;
				int bottom=(i==49)?49:i+1;
				int left=(j==0)?0:j-1;
				int right=(j==49)?49:j+1;
				
				int neighbores=0;
				for(int u=top;u<=bottom;u++)
				{
					for(int v=left;v<=right;v++)
					{
						if((u!=i||v!=j)&&workCopy[u][v]==1)
						{
							neighbores++;
						}
					}
				}
				
				if(workCopy[i][j]==1)
				{
					if(neighbores==2 || neighbores==3)
						cell[i][j]=1;
					else
						cell[i][j]=0;
					
				}
				else
				{
					if(neighbores==3)
						cell[i][j]=1;
					else
						cell[i][j]=0;
				}
				if(cell[i][j]==1)
					existLife=true;
				if(cell[i][j]!=workCopy[i][j])
					isStable=false;
			}
		}
		if(isStable)
		{
			Display();
			//AfxMessageBox("The System is stable now!");
		}
		else if(!existLife)
		{
			Display();
			//AfxMessageBox("The life is dying out now!");
		}
		else
			Display();
	

}

打开Class Wizard 控制面板,给CView类添加一个WM_TIMER消息的响应函数。

void CCAGameBView::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	
	if(this->gen < this->generation)
	{
		switch(nIDEvent)
		{
		case 1:
			nextgen();
			break;
		case 2:
			nextgen();
			break;
		case 3:
			nextgen();
			break;
		}
		this ->gen ++ ;
	}

	CView::OnTimer(nIDEvent);
}





添加WM_DESTROY消息,当窗口消失时,销毁计时器。

void CCAGameBView::OnDestroy() 
{
	CView::OnDestroy();
	
	// TODO: Add your message handler code here
	KillTimer (choosetimer);
	
}

这样设置取消计时器是不正确的,只能在一次的实验中,进行正确的反映。由于第二次时,计时器还存在,便会干扰实验的进行。

于是改成了下面,但是怎么能在自己的响应函数中,撤销自己呢?好像仍然有问题。

	if(this->gen < this->generation)
	{
		switch(nIDEvent)
		{
		case 1:
			nextgen();
			break;
		case 2:
			nextgen();
			break;
		case 3:
			nextgen();
			break;
		}
		this ->gen ++ ;
	}
	if(this->gen == this->generation)
	{
		KillTimer(choosetimer);
		choosetimer =0 ;
	}


(待解决问题)


总结:这次修改,可以在演化的过程中,窗口变化也可以重画。

但是,当窗口绘画完成后,仍然不能保存下来,响应变化。


还有进行初始化的方法。老师给提出的要求是用鼠标拖过的区域进行初始化,使用WM_MOUSEMOVE消息。

下面是关于WM_MOUSEMOVE的搜索结果。

当使用WM_MOUSEMOVE时,系统会获得当前鼠标的位置,然后根据这个位置,做一个向数组的映射,这样就可以根据鼠标的扫描位置,来进行初始化最初的元胞自动机的生命状态。

关于,使用纯面向对象的思想来构造的算法。

把每一个小方格当成是一个对象,小方格的数据成员是:生死状态,指向邻居的指针。方法是,获得生死状态,演化到下一个状态。

为了方面的使用WM_MOUSEMOVE进行初始化,我控制了进行响应的区域。只在50*50的网格中的(20,20)*(30,30)的中间区域,才能进行用鼠标进行初始化。

下面是程序的代码:

void CCAGameBView::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	CRect rect(width*20,height*20,width*30,height*30);
	if(PtInRect(&rect,point))
	{
		cell[point.x/width][point.y/height]=1;
		Display();

	}
	CView::OnMouseMove(nFlags, point);
}


同时,加上了文字提示:在OnDraw中:

	CString str1,str2,str3;
	switch(choosetimer)
	{
	case 0:
		str1="未设定";
		break;
	case 1:
		str1="快速";
		break;
	case 2:
		str1="中速";
		break;
	case 3:
		str1="慢速";
		break;
	}
	switch(choose)
	{
	case 0:
		str2="自定义";
		break;
	default:
		str2="菜单";
		break;		
	}
	str3.Format("%d",this->gen );

	pDC->SetTextColor(RGB(255,0,0));
	pDC->TextOut(0,0,"速度等级:"+str1);
	pDC->TextOut(200,0,"初始化类型:"+str2);
	pDC->TextOut(390,0,"演化代数:"+str3);

在OnTimer函数中,每到时间就刷新一次,显示发送WM_PAINT消息。

CRect rect(0,0,600,30);
InviladateRect(&rect,TRUE);

存在的问题是,每次进行重绘时,屏幕闪动的厉害。

应该用双缓冲机制解决。

第四次尝试:本次尝试要解决的问题如下:

采用面向对象的思想来编程

采用随机方式决定元胞的生命状态

采用状态栏显示游戏的设置信息


首先:调整窗口的大小和合适的背景颜色。

如下:

	cs.cx=700;
	cs.cy=700;
	cs.x=500;
	cs.y=10;
在CFrm类中控制窗口的大小

::SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)::GetStockObject(GRAY_BRUSH));
在CView中添加WM_CREATE消息响应函数。设置背景。





第五次尝试:

本次实验将平台更改为VS10中,由于现在在行业中新的工具如VS已经盛行起来了,所以有必要掌握新的工具。这次就是在使用新工具进行的探讨。

并且这次实验的目标是,采用面向对象的思想来构造程序,同时将显示信息在状态栏上,并且使用随机生成状态决定每一个元胞的生死状态。


首先在VS中新建基于单文档的MFC应用程序。步骤如下:

打开创建向导时,应用程序类型选择:单文档

项目类型选择:MFC标准

视觉样式/颜色:Windows本机/默认

点击完成就可以了。

通过对比VC中新建的单文档应用程序,发现生成的基本的类仍然是CApp类,CFrm类,CView类,CDoc类。但是类中的函数发生了变化,增加了一些目前我还不知道功能的函数。







实验二 关于分形曲线


上诉关于生命游戏的展示,到这里先告一段落。下面开始探讨分形曲线的绘图方法。

我采用的都是递归的画法:

首先是关于Cantor三分集的绘制:比较简单。

 代码如下:

void CFractalView::Cantor(double ax, double ay, double bx, double by, int k)
{

	CClientDC dc(this);
	if(bx-ax<k)
	{
		dc.MoveTo(ax,ay);
		dc.LineTo(bx,by);
	}
	else
	{
		double cx,cy,dx,dy;
		dc.MoveTo(ax,ay);
		dc.LineTo(bx,by);
		
		cx=ax+(bx-ax)/3;
		cy=ay+20;
		dx=bx-(bx-ax)/3;
		dy=by+20;
		ay=ay+20;
		by=by+20;
		Cantor(ax,ay,cx,cy,k);
		Cantor(dx,dy,bx,by,k);
	}
}

Koch雪花的递归生成:

void CFractalView::Koch(double ax, double ay, double bx, double by, int k)
{
	
	CClientDC dc(this) ;

	double  one_third_x=(2.0*ax+bx)/3.0;					//点(x1,y1)与点(x2,y2)连线的1/3处x轴的坐标
	double  one_third_y=(2.0*ay+by)/3.0;					//点(x1,y1)与点(x2,y2)连线的1/3处y轴的坐标
	double  two_third_x=(2.0*bx+ax)/3.0;					//点(x1,y1)与点(x2,y2)连线的2/3处x轴的坐标
	double  two_third_y=(2.0*by+ay)/3.0;					//点(x1,y1)与点(x2,y2)连线的2/3处y轴的坐标
	double  m_point_x = 0.2887*(double)(ay-by)+(ax+bx)/2.0;	//突凸出的顶点的x轴的坐标
	double  m_point_y = 0.2887*(double)(bx-ax)+(ay+by)/2.0;	//突凸出的顶点的y轴的坐标
	//0.2887=sqrt3/6
	if(k==0)											
	{													 //递归终止
	
		dc.MoveTo(ax,ay);
		dc.LineTo(one_third_x,one_third_y);
		dc.LineTo(m_point_x,m_point_y);
		dc.LineTo(two_third_x,two_third_y);
		dc.LineTo(bx,by);
	
	}
	else												//递归调用
	{
		Koch(ax,ay,one_third_x,one_third_y,k-1);
		Koch(one_third_x,one_third_y,m_point_x,m_point_y,k-1);
		Koch(m_point_x,m_point_y,two_third_x,two_third_y,k-1);
		Koch(two_third_x,two_third_y,bx,by,k-1);
	
	}	

}

Sierpinski三角形的递归生成:

void CFractalView::Sierpinski(POINT a, POINT b, POINT c)
{
	CClientDC dc(this);
	int cc=100;
	if((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y)<cc)
	{
		return ;
	}
	else
	{
		CPoint _a((b.x+a.x)/2,(b.y+a.y)/2);
		CPoint _b((a.x+c.x)/2,(a.y+c.y)/2);
		CPoint _c((b.x+c.x)/2,(b.y+c.y)/2);
		dc.MoveTo(_a.x,_a.y);
		dc.LineTo(_b.x,_b.y);
		dc.LineTo(_c.x,_c.y);
		dc.LineTo(_a.x,_a.y);

		Sierpinski(a,_a,_b);
		Sierpinski(_a,b,_c);
		Sierpinski(_b,_c,c);

	}

}

然后在OnDraw中调用相应的函数,生成相应的图案:

	Cantor(400,100,600,100,10);
	double  point[6]={50,100,350,100,200,360};
	Koch(point[0],point[1],point[4],point[5],5);				//最大三角形得第一条边构造
	Koch(point[4],point[5],point[2],point[3],5);				//第二条边
	Koch(point[2],point[3],point[0],point[1],5);				//第三条边
	CPoint p1(501,428),p2(750,1),p3(1000,428);
	pDC->MoveTo(p1);
	pDC->LineTo(p2);
	pDC->LineTo(p3);
	pDC->LineTo(p1);
	Sierpinski(p1,p2,p3);

这样,美丽的分形图案就绘制出来了。







  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值