程序源码下载链接点这里
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
设置三个绘图区,表示三桌子(程序中用桌子table表示汉诺塔中的柱子),在每个绘图区中放置盘子(用椭圆代替),可以通过程序设置汉诺塔的层数,利用鼠标拖动的方式进行盘子的移动,对非法操作(如大盘放在小盘之上或者移动不存在的圆盘)进行报错并终止运行,移动完成后进行提示,并结束运行。可以对移动中的相关属性进行设置。
每一个圆盘用一个结构体表示,结构体中的成员变量为盘子水平方向上与边沿的距离(盘子从下到上不断变小)、盘子底部的坐标。代码如下:
struct element
{
int pan_w; //盘子水平方向上与边沿的距离
int pan_bottom; //盘子底部的位置
};
每个table用一个类表示,类成员包括table的名称(A,B,C)、table上盘子信息(用vector对盘子信息进行管理)、绘图区域和每个盘子的高度(汉诺塔的层数不一样,盘子的平均厚度就不一样)。成员函数有清除圆盘和绘图。代码如下:
class hannuo //这个类用于记录每个table上的圆盘信息并在相应的位置绘图
{
public:
hannuo(void);
~hannuo(void);
public:
void draw(); //在指定的位置绘制圆盘
void clear(); //清除该柱子上的所有数据
public:
char name;
vectorpan; //记录每个盘子的信息
CRect Rect; //绘图区域
int h; //每个盘子的厚度
};
对于清除圆盘,只需要使用vector中的clear函数即可,即:
void hannuo::clear()
{
pan.clear();
}
对于绘图函数来说,是从别的类中在主窗口中绘图(创建了一个基于对话框的MFC程序),因此,首先要获取主窗口的指针,用AfxGetApp()->GetMainWnd()函数。绘图函数代码如下:
void hannuo::draw()
{
CClientDC dc(AfxGetApp()->GetMainWnd()); //获得主窗口的指针
dc.Rectangle(Rect); //重绘绘图区,清除之前的图像
int i,x1,y1,x2,y2;
for(i=0;i
程序中声明了三个hannuo类型的变量table_A、table_B和table_C表示三个柱子。并赋予其初始化参数,主要代码如下:
CRect rect;
GetDlgItem(IDC_A)->GetWindowRect(&rect); //获取绘图区A的坐标
ScreenToClient(rect);
table_A.Rect=rect;
table_A.name='A';
table_A.clear();
//table_B和table_C的初始化一样的
设置一个可编辑文本框,用于输入汉诺塔层数,利用一个按钮获取这个值,根据这个值计算每个盘子的大小。主要代码如下:
int height=table_A.Rect.Height(); //获取绘图区域的高度
int width=table_A.Rect.right-table_A.Rect.left; //获取绘图区的宽度
int w_max=width-10; //盘子宽度的最大值
int w_min=w_max/10; //盘子宽度的最小值
int h_ave=(int)(height*1.0/(m_num*1.0)); //盘子的平均厚度
int w_ave=(int)((w_max-w_min)/2)*1.0/(m_num*1.0); //每个盘子之间的宽度差
将柱子的信息分别赋值给三个柱子变量,如table_A.h=h_ave;其他两个用同样的方法。
最后向柱子上添加盘子,初始化条件下只有A柱子上面有盘子,B和C都没有的,因此,只需要对A进行操作。主要代码如下:
int i;
element elem; //中间变量
for(i=0;i
盘子移动利用一个函数move来实现,主要代码及分析如下:
void ChntDlg::move(hannuo &A,hannuo &B) //将盘子从A移动到B,使用传引用的方式传递参数
{
element temp;
int n=A.pan.size();
if(!n)
{
str.Format("%c已经没有可移动之元素",A.name); //试图从空的柱子上移动盘子
MessageBox(str);
OnPaint();
return;
}
temp=A.pan[n-1]; //获取待移动柱子最上面的一个盘子
n=B.pan.size();
if(!n);
else if(temp.pan_w
捕获鼠标左键按下及抬起信息,设置两个变量from和to记录鼠标按下及抬起时所处的位置。主要代码如下:
//arae_A、area_B和area_C分别表示三个绘图区域的绝对坐标。
if(point.x>=area_A.left && point.x<=area_A.right && point.y>=area_A.top && point.y<=area_A.bottom)
from='A';
else if(point.x>=area_B.left && point.x<=area_B.right && point.y>=area_B.top && point.y<=area_B.bottom)
from='B';
else if(point.x>=area_C.left && point.x<=area_C.right && point.y>=area_C.top && point.y<=area_C.bottom)
from='C';
else from=' '
按键抬起后,即获得了from和to的信息,根据二者的不同组合,分别执行不同的move函数,实现盘子的移动。
需要显示拖动的轨迹,捕获鼠标移动的消息OnMouseMove,以从A柱子移动盘子为例,代码如下:
CClientDC dc(this);
int x1,y1,x2,y2;
if(from=='A')
{
if(!table_A.pan.size()); //A里面没有元素了,就不需要绘制了
else
{
//以下是取A柱子最上面盘子的大小信息
x1=point.x-85+table_A.pan[table_A.pan.size()-1].pan_w;
y1=point.y-table_A.h/2;
x2=point.x+85-table_A.pan[table_A.pan.size()-1].pan_w;
y2=point.y+table_A.h/2;
//绘制一个和A柱子最上面一样的盘子
dc.Ellipse(x1,y1,x2,y2);
}
}
最后,需要实现轨迹的属性设置,设置一个按钮,用于设置轨迹颜色,如下所示:
CColorDialog dlg;
if(dlg.DoModal()==IDOK)
m_color=dlg.m_cc.rgbResult; //获取颜色值
在OnMouseMove函数中创建一个CPen,用于设置绘图属性,如下所示:
CClientDC dc(this);
CPen pen(PS_SOLID,1,m_color);
CPen* pOldPen=dc.SelectObject(&pen);
//绘图
dc.SelectObject(pOldPen);
程序运行后,默认层数为5,已经在A柱子上绘制好了原始的图形,轨迹颜色为红色,等待移动。
鼠标左键从A划至B,移动圆盘
两个盘子移动完成了,移动第三个盘子
非法操作,显示警告
非法操作,显示警告
移动完成